diff --git a/src/main.js b/src/main.js
index c479620..004f3c8 100644
--- a/src/main.js
+++ b/src/main.js
@@ -49,13 +49,7 @@ function createWindow() {
mainWindow = null;
});
- // Handle pending file from file association
- if (app.pendingFile) {
- mainWindow.webContents.once('dom-ready', () => {
- openFileFromPath(app.pendingFile);
- app.pendingFile = null;
- });
- }
+ // Handle pending file from file association will be handled by renderer-ready event
}
function createMenu() {
@@ -182,6 +176,15 @@ function createMenu() {
{ label: 'Reset Zoom', accelerator: 'CmdOrCtrl+0', role: 'resetZoom' }
]
},
+ {
+ label: 'Batch',
+ submenu: [
+ {
+ label: 'Convert Folder...',
+ click: () => showBatchConversionDialog()
+ }
+ ]
+ },
{
label: 'Help',
submenu: [
@@ -192,7 +195,7 @@ function createMenu() {
type: 'info',
title: 'About PanConverter',
message: 'PanConverter',
- detail: 'A cross-platform Markdown editor and converter using Pandoc.\n\nVersion: 1.3.7\nAuthor: Amit Haridas\nEmail: amit.wh@gmail.com\nLicense: MIT\n\nFeatures:\n• Tabbed interface for multiple files\n• Advanced markdown editing with live preview\n• Enhanced PDF export with LaTeX engines\n• File association support for .md files\n• Improved preview typography and spacing\n• Adjustable font sizes via menu (Ctrl+Shift+Plus/Minus)\n• Complete theme support including Monokai fixes\n• Find & replace with match highlighting\n• Line numbers and auto-indentation\n• Export to multiple formats via Pandoc\n• PowerPoint & presentation export\n• Export tables to Excel/ODS spreadsheets\n• Document import & conversion\n• Table creation helper\n• Multiple themes support\n• Undo/redo functionality',
+ detail: 'A cross-platform Markdown editor and converter using Pandoc.\n\nVersion: 1.4.0\nAuthor: Amit Haridas\nEmail: amit.wh@gmail.com\nLicense: MIT\n\nFeatures:\n• Tabbed interface for multiple files\n• Advanced markdown editing with live preview\n• Enhanced PDF export with LaTeX engines\n• File association support for .md files (Fixed in v1.4.0)\n• Advanced export options with templates and metadata\n• Batch file conversion with progress tracking\n• Improved preview typography and spacing\n• Adjustable font sizes via menu (Ctrl+Shift+Plus/Minus)\n• Complete theme support including Monokai fixes\n• Find & replace with match highlighting\n• Line numbers and auto-indentation\n• Export to multiple formats via Pandoc\n• PowerPoint & presentation export\n• Export tables to Excel/ODS spreadsheets\n• Document import & conversion\n• Table creation helper\n• Multiple themes support\n• Undo/redo functionality',
buttons: ['OK']
});
}
@@ -246,6 +249,19 @@ function exportFile(format) {
return;
}
+ // Show export options dialog
+ showExportOptionsDialog(format);
+}
+
+function showExportOptionsDialog(format) {
+ mainWindow.webContents.send('show-export-dialog', format);
+}
+
+function showBatchConversionDialog() {
+ mainWindow.webContents.send('show-batch-dialog');
+}
+
+function performExportWithOptions(format, options) {
const outputFile = dialog.showSaveDialogSync(mainWindow, {
defaultPath: currentFile.replace(/\.[^/.]+$/, `.${format}`),
filters: [
@@ -255,36 +271,50 @@ function exportFile(format) {
if (outputFile) {
let pandocCmd = `pandoc "${currentFile}" -o "${outputFile}"`;
-
+
+ // Add template if specified
+ if (options.template && options.template !== 'default') {
+ pandocCmd += ` --template="${options.template}"`;
+ }
+
+ // Add metadata
+ if (options.metadata) {
+ for (const [key, value] of Object.entries(options.metadata)) {
+ if (value.trim()) {
+ pandocCmd += ` -M ${key}="${value.replace(/"/g, '\\"')}"`;
+ }
+ }
+ }
+
+ // Add variables
+ if (options.variables) {
+ for (const [key, value] of Object.entries(options.variables)) {
+ if (value.trim()) {
+ pandocCmd += ` -V ${key}="${value.replace(/"/g, '\\"')}"`;
+ }
+ }
+ }
+
+ // Add other options
+ if (options.toc) pandocCmd += ' --toc';
+ if (options.tocDepth) pandocCmd += ` --toc-depth=${options.tocDepth}`;
+ if (options.numberSections) pandocCmd += ' --number-sections';
+ if (options.citeproc) pandocCmd += ' --citeproc';
+ if (options.bibliography) pandocCmd += ` --bibliography="${options.bibliography}"`;
+ if (options.csl) pandocCmd += ` --csl="${options.csl}"`;
+
// Add specific options for PDF export to ensure proper generation
if (format === 'pdf') {
- pandocCmd = `pandoc "${currentFile}" --pdf-engine=xelatex -V geometry:margin=1in -o "${outputFile}"`;
- // Try with different PDF engines if xelatex fails
+ const pdfEngine = options.pdfEngine || 'xelatex';
+ const geometry = options.geometry || 'margin=1in';
+ pandocCmd += ` --pdf-engine=${pdfEngine} -V geometry:${geometry}`;
+
+ // Try with specified PDF engine
exec(pandocCmd, (error, stdout, stderr) => {
if (error) {
- // Fallback to pdflatex
- const fallbackCmd = `pandoc "${currentFile}" --pdf-engine=pdflatex -V geometry:margin=1in -o "${outputFile}"`;
- exec(fallbackCmd, (fallbackError, fallbackStdout, fallbackStderr) => {
- if (fallbackError) {
- // Final fallback to wkhtmltopdf
- const htmlToPdfCmd = `pandoc "${currentFile}" -t html5 | wkhtmltopdf - "${outputFile}"`;
- exec(htmlToPdfCmd, (finalError) => {
- if (finalError) {
- dialog.showErrorBox('PDF Export Error',
- `Failed to export PDF. Please ensure you have one of the following installed:\n` +
- `• XeLaTeX (recommended): sudo apt-get install texlive-xetex\n` +
- `• PDFLaTeX: sudo apt-get install texlive-latex-base\n` +
- `• wkhtmltopdf: sudo apt-get install wkhtmltopdf\n\n` +
- `Error: ${finalError.message}`
- );
- } else {
- showExportSuccess(outputFile);
- }
- });
- } else {
- showExportSuccess(outputFile);
- }
- });
+ // Try fallback engines if the specified one fails
+ const fallbackEngines = ['pdflatex', 'lualatex'];
+ tryPdfFallback(currentFile, outputFile, fallbackEngines, 0, options, error);
} else {
showExportSuccess(outputFile);
}
@@ -301,6 +331,44 @@ function exportFile(format) {
}
}
+function tryPdfFallback(inputFile, outputFile, engines, index, options, lastError) {
+ if (index >= engines.length) {
+ dialog.showErrorBox('PDF Export Error',
+ `Failed to export PDF with all available engines. Please ensure you have one of the following installed:\n` +
+ `• XeLaTeX (recommended): sudo apt-get install texlive-xetex\n` +
+ `• PDFLaTeX: sudo apt-get install texlive-latex-base\n` +
+ `• LuaLaTeX: sudo apt-get install texlive-luatex\n\n` +
+ `Last error: ${lastError.message}`
+ );
+ return;
+ }
+
+ const engine = engines[index];
+ const geometry = options.geometry || 'margin=1in';
+ let pandocCmd = `pandoc "${inputFile}" --pdf-engine=${engine} -V geometry:${geometry} -o "${outputFile}"`;
+
+ // Add all other options
+ if (options.template && options.template !== 'default') {
+ pandocCmd += ` --template="${options.template}"`;
+ }
+
+ if (options.metadata) {
+ for (const [key, value] of Object.entries(options.metadata)) {
+ if (value.trim()) {
+ pandocCmd += ` -M ${key}="${value.replace(/"/g, '\\"')}"`;
+ }
+ }
+ }
+
+ exec(pandocCmd, (error, stdout, stderr) => {
+ if (error) {
+ tryPdfFallback(inputFile, outputFile, engines, index + 1, options, error);
+ } else {
+ showExportSuccess(outputFile);
+ }
+ });
+}
+
function showExportSuccess(outputFile) {
dialog.showMessageBox(mainWindow, {
type: 'info',
@@ -387,6 +455,35 @@ ipcMain.on('set-current-file', (event, filePath) => {
currentFile = filePath;
});
+// Handle renderer ready for file association
+ipcMain.on('renderer-ready', (event) => {
+ if (app.pendingFile) {
+ openFileFromPath(app.pendingFile);
+ app.pendingFile = null;
+ }
+});
+
+// Handle export with options
+ipcMain.on('export-with-options', (event, { format, options }) => {
+ performExportWithOptions(format, options);
+});
+
+// Handle batch conversion
+ipcMain.on('batch-convert', (event, { inputFolder, outputFolder, format, options }) => {
+ performBatchConversion(inputFolder, outputFolder, format, options);
+});
+
+// Handle folder selection for batch conversion
+ipcMain.on('select-folder', (event, type) => {
+ const folder = dialog.showOpenDialogSync(mainWindow, {
+ properties: ['openDirectory']
+ });
+
+ if (folder && folder[0]) {
+ event.reply('folder-selected', { type, path: folder[0] });
+ }
+});
+
ipcMain.on('export-spreadsheet', (event, { content, format }) => {
const outputFile = dialog.showSaveDialogSync(mainWindow, {
defaultPath: currentFile.replace(/\.[^/.]+$/, `.${format}`),
@@ -470,6 +567,139 @@ function extractTablesFromMarkdown(markdown) {
return tables;
}
+function performBatchConversion(inputFolder, outputFolder, format, options) {
+ if (!fs.existsSync(inputFolder)) {
+ dialog.showErrorBox('Error', 'Input folder does not exist');
+ return;
+ }
+
+ // Create output folder if it doesn't exist
+ if (!fs.existsSync(outputFolder)) {
+ try {
+ fs.mkdirSync(outputFolder, { recursive: true });
+ } catch (error) {
+ dialog.showErrorBox('Error', `Failed to create output folder: ${error.message}`);
+ return;
+ }
+ }
+
+ // Find all markdown files in input folder
+ const markdownFiles = [];
+
+ function findMarkdownFiles(dir) {
+ const files = fs.readdirSync(dir);
+
+ for (const file of files) {
+ const fullPath = path.join(dir, file);
+ const stat = fs.statSync(fullPath);
+
+ if (stat.isDirectory()) {
+ findMarkdownFiles(fullPath); // Recursive search
+ } else if (file.match(/\.(md|markdown)$/i)) {
+ markdownFiles.push(fullPath);
+ }
+ }
+ }
+
+ findMarkdownFiles(inputFolder);
+
+ if (markdownFiles.length === 0) {
+ dialog.showErrorBox('No Files Found', 'No markdown files found in the selected folder');
+ return;
+ }
+
+ // Show progress dialog
+ let completedCount = 0;
+ const totalCount = markdownFiles.length;
+
+ // Process each file
+ const processNextFile = (index) => {
+ if (index >= markdownFiles.length) {
+ // All files processed
+ dialog.showMessageBox(mainWindow, {
+ type: 'info',
+ title: 'Batch Conversion Complete',
+ message: `Successfully converted ${completedCount} out of ${totalCount} files to ${format.toUpperCase()}.`,
+ buttons: ['OK']
+ });
+ return;
+ }
+
+ const inputFile = markdownFiles[index];
+ const relativePath = path.relative(inputFolder, inputFile);
+ const baseName = path.basename(relativePath, path.extname(relativePath));
+ const outputFile = path.join(outputFolder, relativePath.replace(/\.(md|markdown)$/i, `.${format}`));
+
+ // Create subdirectories in output folder if needed
+ const outputDir = path.dirname(outputFile);
+ if (!fs.existsSync(outputDir)) {
+ fs.mkdirSync(outputDir, { recursive: true });
+ }
+
+ // Build pandoc command
+ let pandocCmd = `pandoc "${inputFile}" -o "${outputFile}"`;
+
+ // Add template if specified
+ if (options.template && options.template !== 'default') {
+ pandocCmd += ` --template="${options.template}"`;
+ }
+
+ // Add metadata
+ if (options.metadata) {
+ for (const [key, value] of Object.entries(options.metadata)) {
+ if (value.trim()) {
+ pandocCmd += ` -M ${key}="${value.replace(/"/g, '\\"')}"`;
+ }
+ }
+ }
+
+ // Add variables
+ if (options.variables) {
+ for (const [key, value] of Object.entries(options.variables)) {
+ if (value.trim()) {
+ pandocCmd += ` -V ${key}="${value.replace(/"/g, '\\"')}"`;
+ }
+ }
+ }
+
+ // Add other options
+ if (options.toc) pandocCmd += ' --toc';
+ if (options.tocDepth) pandocCmd += ` --toc-depth=${options.tocDepth}`;
+ if (options.numberSections) pandocCmd += ' --number-sections';
+ if (options.citeproc) pandocCmd += ' --citeproc';
+ if (options.bibliography) pandocCmd += ` --bibliography="${options.bibliography}"`;
+ if (options.csl) pandocCmd += ` --csl="${options.csl}"`;
+
+ // Add PDF-specific options
+ if (format === 'pdf') {
+ const pdfEngine = options.pdfEngine || 'xelatex';
+ const geometry = options.geometry || 'margin=1in';
+ pandocCmd += ` --pdf-engine=${pdfEngine} -V geometry:${geometry}`;
+ }
+
+ // Execute conversion
+ exec(pandocCmd, (error, stdout, stderr) => {
+ if (!error) {
+ completedCount++;
+ }
+
+ // Update progress (you could send this to renderer for a progress bar)
+ mainWindow.webContents.send('batch-progress', {
+ completed: index + 1,
+ total: totalCount,
+ currentFile: path.basename(inputFile),
+ success: !error
+ });
+
+ // Process next file
+ processNextFile(index + 1);
+ });
+ };
+
+ // Start processing
+ processNextFile(0);
+}
+
app.whenReady().then(() => {
createWindow();
diff --git a/src/renderer.js b/src/renderer.js
index 6d853b0..83bd8dc 100644
--- a/src/renderer.js
+++ b/src/renderer.js
@@ -478,9 +478,12 @@ let tabManager;
document.addEventListener('DOMContentLoaded', () => {
tabManager = new TabManager();
-
+
// Request current theme
ipcRenderer.send('get-theme');
+
+ // Signal that renderer is ready for file operations
+ ipcRenderer.send('renderer-ready');
// Set up auto-save interval
setInterval(() => {
@@ -569,4 +572,373 @@ ipcRenderer.on('adjust-font-size', (event, action) => {
currentFontSize = 15;
}
updateFontSizes(currentFontSize);
-});
\ No newline at end of file
+});
+
+// Export Dialog functionality
+let currentExportFormat = null;
+
+ipcRenderer.on('show-export-dialog', (event, format) => {
+ currentExportFormat = format;
+ showExportDialog(format);
+});
+
+function showExportDialog(format) {
+ const dialog = document.getElementById('export-dialog');
+ const title = document.getElementById('export-dialog-title');
+
+ title.textContent = `Export as ${format.toUpperCase()}`;
+ dialog.setAttribute('data-format', format);
+ dialog.classList.remove('hidden');
+
+ // Initialize form values
+ initializeExportForm(format);
+}
+
+function hideExportDialog() {
+ const dialog = document.getElementById('export-dialog');
+ dialog.classList.add('hidden');
+ currentExportFormat = null;
+}
+
+function initializeExportForm(format) {
+ // Reset form to defaults
+ document.getElementById('export-template').value = 'default';
+ document.getElementById('custom-template-path').style.display = 'none';
+
+ // Clear metadata fields
+ const metadataFields = document.querySelectorAll('.metadata-field');
+ metadataFields.forEach((field, index) => {
+ if (index < 4) { // Keep first 4 default fields
+ field.querySelector('.metadata-key').value = ['title', 'author', 'date', 'subject'][index] || '';
+ field.querySelector('.metadata-value').value = '';
+ } else {
+ field.remove(); // Remove additional fields
+ }
+ });
+
+ // Reset checkboxes and other fields
+ document.getElementById('export-toc').checked = false;
+ document.getElementById('export-number-sections').checked = false;
+ document.getElementById('export-citeproc').checked = false;
+ document.getElementById('export-toc-depth').value = 3;
+
+ // PDF-specific fields
+ if (format === 'pdf') {
+ document.getElementById('pdf-engine').value = 'xelatex';
+ document.getElementById('pdf-geometry').value = 'margin=1in';
+ document.getElementById('custom-geometry').style.display = 'none';
+ }
+
+ // Clear bibliography fields
+ document.getElementById('bibliography-file').value = '';
+ document.getElementById('csl-file').value = '';
+}
+
+function collectExportOptions() {
+ const options = {
+ template: document.getElementById('export-template').value,
+ metadata: {},
+ variables: {},
+ toc: document.getElementById('export-toc').checked,
+ tocDepth: document.getElementById('export-toc-depth').value,
+ numberSections: document.getElementById('export-number-sections').checked,
+ citeproc: document.getElementById('export-citeproc').checked
+ };
+
+ // Collect custom template path
+ if (options.template === 'custom') {
+ options.template = document.getElementById('custom-template-path').value.trim();
+ }
+
+ // Collect metadata
+ const metadataFields = document.querySelectorAll('.metadata-field');
+ metadataFields.forEach(field => {
+ const key = field.querySelector('.metadata-key').value.trim();
+ const value = field.querySelector('.metadata-value').value.trim();
+ if (key && value) {
+ options.metadata[key] = value;
+ }
+ });
+
+ // PDF-specific options
+ if (currentExportFormat === 'pdf') {
+ options.pdfEngine = document.getElementById('pdf-engine').value;
+ const geometrySelect = document.getElementById('pdf-geometry');
+ if (geometrySelect.value === 'custom') {
+ options.geometry = document.getElementById('custom-geometry').value.trim() || 'margin=1in';
+ } else {
+ options.geometry = geometrySelect.value;
+ }
+ }
+
+ // Bibliography
+ const bibFile = document.getElementById('bibliography-file').value.trim();
+ const cslFile = document.getElementById('csl-file').value.trim();
+ if (bibFile) options.bibliography = bibFile;
+ if (cslFile) options.csl = cslFile;
+
+ return options;
+}
+
+// Event listeners for export dialog
+document.addEventListener('DOMContentLoaded', () => {
+ // Template selection
+ document.getElementById('export-template').addEventListener('change', (e) => {
+ const customPath = document.getElementById('custom-template-path');
+ const fileInput = document.getElementById('template-file-input');
+
+ if (e.target.value === 'custom') {
+ customPath.style.display = 'block';
+ fileInput.style.display = 'block';
+ } else {
+ customPath.style.display = 'none';
+ fileInput.style.display = 'none';
+ customPath.value = '';
+ }
+ });
+
+ // Template file input
+ document.getElementById('template-file-input').addEventListener('change', (e) => {
+ const file = e.target.files[0];
+ if (file) {
+ document.getElementById('custom-template-path').value = file.path;
+ }
+ });
+
+ // PDF geometry selection
+ document.getElementById('pdf-geometry').addEventListener('change', (e) => {
+ const customGeometry = document.getElementById('custom-geometry');
+ if (e.target.value === 'custom') {
+ customGeometry.style.display = 'block';
+ } else {
+ customGeometry.style.display = 'none';
+ }
+ });
+
+ // Add metadata field
+ document.getElementById('add-metadata-field').addEventListener('click', () => {
+ const container = document.querySelector('.metadata-container');
+ const newField = document.createElement('div');
+ newField.className = 'metadata-field';
+ newField.innerHTML = `
+
+
+ `;
+ container.appendChild(newField);
+ });
+
+ // Browse bibliography
+ document.getElementById('browse-bibliography').addEventListener('click', () => {
+ const input = document.createElement('input');
+ input.type = 'file';
+ input.accept = '.bib,.yaml,.yml,.json';
+ input.onchange = (e) => {
+ const file = e.target.files[0];
+ if (file) {
+ document.getElementById('bibliography-file').value = file.path;
+ }
+ };
+ input.click();
+ });
+
+ // Browse CSL
+ document.getElementById('browse-csl').addEventListener('click', () => {
+ const input = document.createElement('input');
+ input.type = 'file';
+ input.accept = '.csl';
+ input.onchange = (e) => {
+ const file = e.target.files[0];
+ if (file) {
+ document.getElementById('csl-file').value = file.path;
+ }
+ };
+ input.click();
+ });
+
+ // Dialog close buttons
+ document.getElementById('export-dialog-close').addEventListener('click', hideExportDialog);
+ document.getElementById('export-cancel').addEventListener('click', hideExportDialog);
+
+ // Export confirm
+ document.getElementById('export-confirm').addEventListener('click', () => {
+ const options = collectExportOptions();
+ ipcRenderer.send('export-with-options', {
+ format: currentExportFormat,
+ options: options
+ });
+ hideExportDialog();
+ });
+
+ // Close on backdrop click
+ document.getElementById('export-dialog').addEventListener('click', (e) => {
+ if (e.target === document.getElementById('export-dialog')) {
+ hideExportDialog();
+ }
+ });
+
+ // Close on Escape key
+ document.addEventListener('keydown', (e) => {
+ if (e.key === 'Escape' && !document.getElementById('export-dialog').classList.contains('hidden')) {
+ hideExportDialog();
+ }
+ });
+});
+
+// Batch Conversion Dialog functionality
+let currentBatchOptions = {};
+
+ipcRenderer.on('show-batch-dialog', () => {
+ showBatchDialog();
+});
+
+ipcRenderer.on('batch-progress', (event, progress) => {
+ updateBatchProgress(progress);
+});
+
+ipcRenderer.on('folder-selected', (event, { type, path }) => {
+ if (type === 'input') {
+ document.getElementById('batch-input-folder').value = path;
+ } else if (type === 'output') {
+ document.getElementById('batch-output-folder').value = path;
+ }
+ validateBatchForm();
+});
+
+function showBatchDialog() {
+ const dialog = document.getElementById('batch-dialog');
+ dialog.classList.remove('hidden');
+
+ // Reset form
+ document.getElementById('batch-input-folder').value = '';
+ document.getElementById('batch-output-folder').value = '';
+ document.getElementById('batch-format').value = 'html';
+ document.getElementById('batch-include-subfolders').checked = true;
+ document.getElementById('batch-progress').classList.add('hidden');
+ document.getElementById('batch-start').disabled = true;
+
+ currentBatchOptions = {
+ template: 'default',
+ metadata: {},
+ variables: {},
+ toc: false,
+ tocDepth: 3,
+ numberSections: false,
+ citeproc: false
+ };
+}
+
+function hideBatchDialog() {
+ const dialog = document.getElementById('batch-dialog');
+ dialog.classList.add('hidden');
+}
+
+function updateBatchProgress(progress) {
+ const progressSection = document.getElementById('batch-progress');
+ const progressFill = document.getElementById('batch-progress-fill');
+ const progressText = document.getElementById('batch-progress-text');
+ const progressCount = document.getElementById('batch-progress-count');
+
+ progressSection.classList.remove('hidden');
+
+ const percentage = Math.round((progress.completed / progress.total) * 100);
+ progressFill.style.width = `${percentage}%`;
+
+ if (progress.completed === progress.total) {
+ progressText.textContent = 'Conversion complete!';
+ } else {
+ progressText.textContent = `Processing: ${progress.currentFile}`;
+ }
+
+ progressCount.textContent = `${progress.completed} / ${progress.total}`;
+}
+
+function validateBatchForm() {
+ const inputFolder = document.getElementById('batch-input-folder').value.trim();
+ const outputFolder = document.getElementById('batch-output-folder').value.trim();
+ const startButton = document.getElementById('batch-start');
+
+ startButton.disabled = !inputFolder || !outputFolder;
+}
+
+// Event listeners for batch dialog
+document.addEventListener('DOMContentLoaded', () => {
+ // Browse input folder
+ document.getElementById('browse-input-folder').addEventListener('click', () => {
+ ipcRenderer.send('select-folder', 'input');
+ });
+
+ // Browse output folder
+ document.getElementById('browse-output-folder').addEventListener('click', () => {
+ ipcRenderer.send('select-folder', 'output');
+ });
+
+ // Show advanced options
+ document.getElementById('batch-show-options').addEventListener('click', () => {
+ const format = document.getElementById('batch-format').value;
+ currentExportFormat = format;
+ showExportDialog(format);
+ });
+
+ // Dialog close buttons
+ document.getElementById('batch-dialog-close').addEventListener('click', hideBatchDialog);
+ document.getElementById('batch-cancel').addEventListener('click', hideBatchDialog);
+
+ // Start batch conversion
+ document.getElementById('batch-start').addEventListener('click', () => {
+ const inputFolder = document.getElementById('batch-input-folder').value.trim();
+ const outputFolder = document.getElementById('batch-output-folder').value.trim();
+ const format = document.getElementById('batch-format').value;
+
+ if (!inputFolder || !outputFolder) {
+ return;
+ }
+
+ // Use current export options from advanced dialog if they were set
+ const options = currentBatchOptions;
+
+ // Start batch conversion
+ ipcRenderer.send('batch-convert', {
+ inputFolder,
+ outputFolder,
+ format,
+ options
+ });
+
+ // Show progress
+ document.getElementById('batch-progress').classList.remove('hidden');
+ document.getElementById('batch-start').disabled = true;
+ });
+
+ // Close on backdrop click
+ document.getElementById('batch-dialog').addEventListener('click', (e) => {
+ if (e.target === document.getElementById('batch-dialog')) {
+ hideBatchDialog();
+ }
+ });
+
+ // Close on Escape key (modified to handle both dialogs)
+ document.addEventListener('keydown', (e) => {
+ if (e.key === 'Escape') {
+ if (!document.getElementById('export-dialog').classList.contains('hidden')) {
+ hideExportDialog();
+ } else if (!document.getElementById('batch-dialog').classList.contains('hidden')) {
+ hideBatchDialog();
+ }
+ }
+ });
+
+ // Input validation
+ document.getElementById('batch-input-folder').addEventListener('input', validateBatchForm);
+ document.getElementById('batch-output-folder').addEventListener('input', validateBatchForm);
+});
+
+// Override the export dialog confirm to also save batch options
+const originalExportConfirm = document.getElementById('export-confirm');
+if (originalExportConfirm) {
+ originalExportConfirm.addEventListener('click', () => {
+ // If batch dialog is open, save options for batch conversion
+ if (!document.getElementById('batch-dialog').classList.contains('hidden')) {
+ currentBatchOptions = collectExportOptions();
+ }
+ });
+}
\ No newline at end of file
diff --git a/src/styles.css b/src/styles.css
index ade98f4..16b20fe 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -835,6 +835,531 @@ body.theme-github .status-bar {
flex: 1;
}
+/* Export Options Dialog */
+.export-dialog {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+}
+
+.export-dialog.hidden {
+ display: none;
+}
+
+.export-dialog-content {
+ background: #fff;
+ border-radius: 8px;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
+ width: 90%;
+ max-width: 600px;
+ max-height: 80vh;
+ overflow-y: auto;
+}
+
+.export-dialog-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 16px 20px;
+ border-bottom: 1px solid #eee;
+}
+
+.export-dialog-header h3 {
+ margin: 0;
+ font-size: 18px;
+ font-weight: 600;
+}
+
+.export-dialog-body {
+ padding: 20px;
+}
+
+.export-section {
+ margin-bottom: 20px;
+}
+
+.export-section label {
+ display: block;
+ font-weight: 600;
+ margin-bottom: 8px;
+ color: #333;
+}
+
+.export-section select,
+.export-section input[type="text"],
+.export-section input[type="number"] {
+ width: 100%;
+ padding: 8px 12px;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ font-size: 14px;
+}
+
+.export-section select:focus,
+.export-section input:focus {
+ outline: none;
+ border-color: #007acc;
+ box-shadow: 0 0 0 2px rgba(0, 122, 204, 0.1);
+}
+
+.metadata-container {
+ border: 1px solid #eee;
+ border-radius: 4px;
+ padding: 12px;
+ margin-bottom: 8px;
+}
+
+.metadata-field {
+ display: flex;
+ gap: 8px;
+ margin-bottom: 8px;
+}
+
+.metadata-field:last-child {
+ margin-bottom: 0;
+}
+
+.metadata-key {
+ flex: 0 0 120px;
+ font-weight: 600;
+}
+
+.metadata-value {
+ flex: 1;
+}
+
+.checkbox-group {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ margin-bottom: 12px;
+}
+
+.checkbox-group label {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-weight: normal;
+ margin-bottom: 0;
+}
+
+.checkbox-group input[type="checkbox"] {
+ margin: 0;
+}
+
+.form-row {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 8px;
+}
+
+.form-row label {
+ flex: 0 0 120px;
+ margin-bottom: 0;
+}
+
+.form-row input,
+.form-row select {
+ flex: 1;
+}
+
+.form-row button {
+ flex: 0 0 auto;
+ padding: 8px 16px;
+ background: #f5f5f5;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 14px;
+}
+
+.form-row button:hover {
+ background: #e8e8e8;
+}
+
+.pdf-only {
+ display: none;
+}
+
+.export-dialog[data-format="pdf"] .pdf-only {
+ display: block;
+}
+
+.export-dialog-footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: 12px;
+ padding: 16px 20px;
+ border-top: 1px solid #eee;
+ background: #f8f9fa;
+ border-radius: 0 0 8px 8px;
+}
+
+.export-dialog-footer button {
+ padding: 10px 20px;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 14px;
+ font-weight: 500;
+}
+
+.export-dialog-footer button.primary {
+ background: #007acc;
+ color: white;
+ border-color: #007acc;
+}
+
+.export-dialog-footer button.primary:hover {
+ background: #005fa3;
+ border-color: #005fa3;
+}
+
+.export-dialog-footer button:not(.primary) {
+ background: #f5f5f5;
+}
+
+.export-dialog-footer button:not(.primary):hover {
+ background: #e8e8e8;
+}
+
+#export-dialog-close {
+ background: none;
+ border: none;
+ font-size: 20px;
+ cursor: pointer;
+ padding: 0;
+ width: 24px;
+ height: 24px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 4px;
+}
+
+#export-dialog-close:hover {
+ background: #f0f0f0;
+}
+
+#add-metadata-field {
+ padding: 6px 12px;
+ background: #f8f9fa;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 13px;
+ color: #666;
+}
+
+#add-metadata-field:hover {
+ background: #e9ecef;
+}
+
+/* Theme support for export dialog */
+body.theme-dark .export-dialog-content {
+ background: #2d2d2d;
+ color: #f0f0f0;
+}
+
+body.theme-dark .export-dialog-header {
+ border-color: #444;
+}
+
+body.theme-dark .export-dialog-footer {
+ background: #333;
+ border-color: #444;
+}
+
+body.theme-dark .export-section label {
+ color: #f0f0f0;
+}
+
+body.theme-dark .export-section select,
+body.theme-dark .export-section input {
+ background: #1a1a1a;
+ border-color: #555;
+ color: #f0f0f0;
+}
+
+body.theme-dark .metadata-container {
+ border-color: #444;
+ background: #1a1a1a;
+}
+
+body.theme-dark .form-row button,
+body.theme-dark #add-metadata-field {
+ background: #1a1a1a;
+ border-color: #555;
+ color: #f0f0f0;
+}
+
+body.theme-dark .form-row button:hover,
+body.theme-dark #add-metadata-field:hover {
+ background: #333;
+}
+
+body.theme-dark #export-dialog-close:hover {
+ background: #444;
+}
+
+/* Batch Conversion Dialog */
+.batch-dialog {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+}
+
+.batch-dialog.hidden {
+ display: none;
+}
+
+.batch-dialog-content {
+ background: #fff;
+ border-radius: 8px;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
+ width: 90%;
+ max-width: 500px;
+ max-height: 80vh;
+ overflow-y: auto;
+}
+
+.batch-dialog-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 16px 20px;
+ border-bottom: 1px solid #eee;
+}
+
+.batch-dialog-header h3 {
+ margin: 0;
+ font-size: 18px;
+ font-weight: 600;
+}
+
+.batch-dialog-body {
+ padding: 20px;
+}
+
+.batch-section {
+ margin-bottom: 20px;
+}
+
+.batch-section label {
+ display: block;
+ font-weight: 600;
+ margin-bottom: 8px;
+ color: #333;
+}
+
+.batch-section select,
+.batch-section input[type="text"] {
+ width: 100%;
+ padding: 8px 12px;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ font-size: 14px;
+}
+
+.folder-input-group {
+ display: flex;
+ gap: 8px;
+}
+
+.folder-input-group input {
+ flex: 1;
+}
+
+.folder-input-group button {
+ padding: 8px 16px;
+ background: #f5f5f5;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 14px;
+ white-space: nowrap;
+}
+
+.folder-input-group button:hover {
+ background: #e8e8e8;
+}
+
+.batch-section label input[type="checkbox"] {
+ margin-right: 8px;
+}
+
+#batch-show-options {
+ padding: 8px 16px;
+ background: #f8f9fa;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 14px;
+ color: #666;
+}
+
+#batch-show-options:hover {
+ background: #e9ecef;
+}
+
+.batch-progress {
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ padding: 16px;
+ background: #f8f9fa;
+}
+
+.progress-bar {
+ width: 100%;
+ height: 8px;
+ background: #e0e0e0;
+ border-radius: 4px;
+ overflow: hidden;
+ margin-bottom: 8px;
+}
+
+.progress-fill {
+ height: 100%;
+ background: #007acc;
+ transition: width 0.3s ease;
+ width: 0%;
+}
+
+.progress-text {
+ display: flex;
+ justify-content: space-between;
+ font-size: 14px;
+ color: #666;
+}
+
+.batch-dialog-footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: 12px;
+ padding: 16px 20px;
+ border-top: 1px solid #eee;
+ background: #f8f9fa;
+ border-radius: 0 0 8px 8px;
+}
+
+.batch-dialog-footer button {
+ padding: 10px 20px;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 14px;
+ font-weight: 500;
+}
+
+.batch-dialog-footer button.primary {
+ background: #007acc;
+ color: white;
+ border-color: #007acc;
+}
+
+.batch-dialog-footer button.primary:hover:not(:disabled) {
+ background: #005fa3;
+ border-color: #005fa3;
+}
+
+.batch-dialog-footer button.primary:disabled {
+ background: #ccc;
+ border-color: #ccc;
+ cursor: not-allowed;
+}
+
+.batch-dialog-footer button:not(.primary) {
+ background: #f5f5f5;
+}
+
+.batch-dialog-footer button:not(.primary):hover {
+ background: #e8e8e8;
+}
+
+#batch-dialog-close {
+ background: none;
+ border: none;
+ font-size: 20px;
+ cursor: pointer;
+ padding: 0;
+ width: 24px;
+ height: 24px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 4px;
+}
+
+#batch-dialog-close:hover {
+ background: #f0f0f0;
+}
+
+/* Theme support for batch dialog */
+body.theme-dark .batch-dialog-content {
+ background: #2d2d2d;
+ color: #f0f0f0;
+}
+
+body.theme-dark .batch-dialog-header {
+ border-color: #444;
+}
+
+body.theme-dark .batch-dialog-footer {
+ background: #333;
+ border-color: #444;
+}
+
+body.theme-dark .batch-section label {
+ color: #f0f0f0;
+}
+
+body.theme-dark .batch-section select,
+body.theme-dark .batch-section input {
+ background: #1a1a1a;
+ border-color: #555;
+ color: #f0f0f0;
+}
+
+body.theme-dark .folder-input-group button,
+body.theme-dark #batch-show-options {
+ background: #1a1a1a;
+ border-color: #555;
+ color: #f0f0f0;
+}
+
+body.theme-dark .folder-input-group button:hover,
+body.theme-dark #batch-show-options:hover {
+ background: #333;
+}
+
+body.theme-dark .batch-progress {
+ background: #1a1a1a;
+ border-color: #444;
+}
+
+body.theme-dark .progress-text {
+ color: #ccc;
+}
+
+body.theme-dark #batch-dialog-close:hover {
+ background: #444;
+}
+
/* Theme support for find dialog */
body.theme-dark .find-dialog {
background: #2d2d2d;