Release v1.4.0: Advanced Export Options and Batch Conversion

Major Features:
• Fixed file association loading issue with proper timing
• Advanced export options dialog with templates and metadata support
• Batch file conversion system with progress tracking
• Enhanced UI with professional modal dialogs
Technical Improvements:
• Fixed renderer initialization timing for file association
• Added comprehensive Pandoc options support (templates, metadata, variables)
• Implemented recursive folder processing for batch operations
• Enhanced error handling and user feedback systems
• Theme-aware styling for all new components
🤖 Generated with [Claude Code](https://claude.ai/code)
This commit is contained in:
2025-09-14 22:42:53 +05:30
parent afb88f0326
commit 64d53bd637
6 changed files with 1384 additions and 46 deletions
+165 -1
View File
@@ -113,7 +113,171 @@
<span id="find-count">0 matches</span>
</div>
</div>
<!-- Export Options Dialog -->
<div id="export-dialog" class="export-dialog hidden">
<div class="export-dialog-content">
<div class="export-dialog-header">
<h3 id="export-dialog-title">Export Options</h3>
<button id="export-dialog-close" title="Close">×</button>
</div>
<div class="export-dialog-body">
<div class="export-section">
<label for="export-template">Template:</label>
<select id="export-template">
<option value="default">Default</option>
<option value="custom">Custom (Browse...)</option>
</select>
<input type="file" id="template-file-input" accept=".tex,.html,.docx,.odt" style="display: none;">
<input type="text" id="custom-template-path" placeholder="Template file path..." style="display: none;">
</div>
<div class="export-section">
<label>Metadata:</label>
<div class="metadata-container">
<div class="metadata-field">
<input type="text" placeholder="title" class="metadata-key">
<input type="text" placeholder="Document title" class="metadata-value">
</div>
<div class="metadata-field">
<input type="text" placeholder="author" class="metadata-key">
<input type="text" placeholder="Author name" class="metadata-value">
</div>
<div class="metadata-field">
<input type="text" placeholder="date" class="metadata-key">
<input type="text" placeholder="Date" class="metadata-value">
</div>
<div class="metadata-field">
<input type="text" placeholder="subject" class="metadata-key">
<input type="text" placeholder="Subject" class="metadata-value">
</div>
</div>
<button id="add-metadata-field" type="button">+ Add Field</button>
</div>
<div class="export-section">
<label>Document Options:</label>
<div class="checkbox-group">
<label><input type="checkbox" id="export-toc"> Table of Contents</label>
<label><input type="checkbox" id="export-number-sections"> Number Sections</label>
<label><input type="checkbox" id="export-citeproc"> Process Citations</label>
</div>
<div class="form-row">
<label for="export-toc-depth">TOC Depth:</label>
<input type="number" id="export-toc-depth" min="1" max="6" value="3">
</div>
</div>
<div class="export-section pdf-only">
<label>PDF Options:</label>
<div class="form-row">
<label for="pdf-engine">PDF Engine:</label>
<select id="pdf-engine">
<option value="xelatex">XeLaTeX (Recommended)</option>
<option value="pdflatex">PDFLaTeX</option>
<option value="lualatex">LuaLaTeX</option>
</select>
</div>
<div class="form-row">
<label for="pdf-geometry">Page Margins:</label>
<select id="pdf-geometry">
<option value="margin=1in">1 inch (Default)</option>
<option value="margin=0.75in">0.75 inch</option>
<option value="margin=1.25in">1.25 inch</option>
<option value="margin=2cm">2 cm</option>
<option value="margin=2.5cm">2.5 cm</option>
<option value="custom">Custom...</option>
</select>
<input type="text" id="custom-geometry" placeholder="e.g., margin=1.5in" style="display: none;">
</div>
</div>
<div class="export-section">
<label>Bibliography:</label>
<div class="form-row">
<input type="text" id="bibliography-file" placeholder="Bibliography file (.bib, .yaml, .json)">
<button id="browse-bibliography">Browse</button>
</div>
<div class="form-row">
<input type="text" id="csl-file" placeholder="Citation style file (.csl)">
<button id="browse-csl">Browse</button>
</div>
</div>
</div>
<div class="export-dialog-footer">
<button id="export-cancel">Cancel</button>
<button id="export-confirm" class="primary">Export</button>
</div>
</div>
</div>
<!-- Batch Conversion Dialog -->
<div id="batch-dialog" class="batch-dialog hidden">
<div class="batch-dialog-content">
<div class="batch-dialog-header">
<h3>Batch File Conversion</h3>
<button id="batch-dialog-close" title="Close">×</button>
</div>
<div class="batch-dialog-body">
<div class="batch-section">
<label>Input Folder:</label>
<div class="folder-input-group">
<input type="text" id="batch-input-folder" placeholder="Select folder containing markdown files..." readonly>
<button id="browse-input-folder">Browse</button>
</div>
</div>
<div class="batch-section">
<label>Output Folder:</label>
<div class="folder-input-group">
<input type="text" id="batch-output-folder" placeholder="Select output folder..." readonly>
<button id="browse-output-folder">Browse</button>
</div>
</div>
<div class="batch-section">
<label for="batch-format">Output Format:</label>
<select id="batch-format">
<option value="html">HTML</option>
<option value="pdf">PDF</option>
<option value="docx">DOCX</option>
<option value="latex">LaTeX</option>
<option value="rtf">RTF</option>
<option value="odt">ODT</option>
<option value="epub">EPUB</option>
<option value="pptx">PowerPoint (PPTX)</option>
<option value="odp">OpenDocument Presentation (ODP)</option>
</select>
</div>
<div class="batch-section">
<label>
<input type="checkbox" id="batch-include-subfolders" checked>
Include subfolders (recursive)
</label>
</div>
<div class="batch-section">
<button id="batch-show-options" type="button">Advanced Export Options...</button>
</div>
<div id="batch-progress" class="batch-progress hidden">
<div class="progress-bar">
<div class="progress-fill" id="batch-progress-fill"></div>
</div>
<div class="progress-text">
<span id="batch-progress-text">Processing...</span>
<span id="batch-progress-count">0 / 0</span>
</div>
</div>
</div>
<div class="batch-dialog-footer">
<button id="batch-cancel">Cancel</button>
<button id="batch-start" class="primary" disabled>Start Conversion</button>
</div>
</div>
</div>
<div class="editor-container">
<div class="tab-content active" id="tab-content-1" data-tab-id="1">
<div id="editor-pane-1" class="pane">
+264 -34
View File
@@ -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();
+374 -2
View File
@@ -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);
});
});
// 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 = `
<input type="text" placeholder="key" class="metadata-key">
<input type="text" placeholder="value" class="metadata-value">
`;
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();
}
});
}
+525
View File
@@ -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;