mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-02 18:10:18 +05:30
Release v1.5.0: Enhanced Features and Open Source Compatibility
Major improvements including optional advanced export options, removed proprietary dependencies for better open-source compatibility, and comprehensive new features for enhanced user experience. 🔧 Export Functions & Advanced Options: • Fixed export function issues after advanced options integration • Added optional advanced export checkbox (unchecked by default) • Clean UI separation between simple and advanced export workflows 🏗️ Open Source Compatibility: • Removed bundled Pandoc binaries - requires system installation • Replaced proprietary XLSX with open-source CSV export • Eliminated licensing concerns with bundled dependencies ✨ Advanced User Experience Features: • Auto-save system with visual indicators every 30 seconds • Enhanced document statistics (lines, paragraphs, sentences, reading time) • Recent files menu with persistent storage (last 10 files) • Mathematical expression support with KaTeX rendering • Improved export dialog with better user experience 📚 Updated documentation with new dependency requirements and features 🤖 Generated with [Claude Code](https://claude.ai/code)
This commit is contained in:
+24
-2
@@ -122,8 +122,20 @@
|
||||
<button id="export-dialog-close" title="Close">×</button>
|
||||
</div>
|
||||
<div class="export-dialog-body">
|
||||
<div class="export-section">
|
||||
<label for="export-template">Template:</label>
|
||||
<!-- Simple/Advanced Export Toggle -->
|
||||
<div class="export-section export-mode-toggle">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="advanced-export-toggle">
|
||||
<span class="checkmark"></span>
|
||||
Show Advanced Options
|
||||
</label>
|
||||
<small class="export-help">Enable for templates, metadata, bibliography, and advanced PDF settings</small>
|
||||
</div>
|
||||
|
||||
<!-- Advanced Export Options (hidden by default) -->
|
||||
<div id="advanced-export-options" class="advanced-options hidden">
|
||||
<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>
|
||||
@@ -203,6 +215,16 @@
|
||||
<button id="browse-csl">Browse</button>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- End advanced-export-options -->
|
||||
|
||||
<!-- Basic Export Options (always visible) -->
|
||||
<div class="export-section basic-options">
|
||||
<label>Quick Export Settings:</label>
|
||||
<div class="checkbox-group">
|
||||
<label><input type="checkbox" id="basic-toc"> Include Table of Contents</label>
|
||||
<label><input type="checkbox" id="basic-number-sections"> Number Sections</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="export-dialog-footer">
|
||||
<button id="export-cancel">Cancel</button>
|
||||
|
||||
+116
-19
@@ -2,7 +2,21 @@ const { app, BrowserWindow, Menu, dialog, ipcMain, shell } = require('electron')
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { exec } = require('child_process');
|
||||
const XLSX = require('xlsx');
|
||||
|
||||
// Get the system Pandoc path
|
||||
function getPandocPath() {
|
||||
// Always use system pandoc - no bundled binaries
|
||||
return 'pandoc';
|
||||
}
|
||||
|
||||
// Check if Pandoc is available
|
||||
function checkPandocAvailable() {
|
||||
return new Promise((resolve) => {
|
||||
exec('pandoc --version', (error) => {
|
||||
resolve(!error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Simple storage implementation to replace electron-store
|
||||
const settingsPath = path.join(app.getPath('userData'), 'settings.json');
|
||||
@@ -52,6 +66,53 @@ function createWindow() {
|
||||
// Handle pending file from file association will be handled by renderer-ready event
|
||||
}
|
||||
|
||||
function buildRecentFilesMenu() {
|
||||
const recentFiles = getRecentFiles();
|
||||
|
||||
if (recentFiles.length === 0) {
|
||||
return [
|
||||
{
|
||||
label: 'No recent files',
|
||||
enabled: false
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
const recentFileItems = recentFiles.map(filePath => ({
|
||||
label: filePath.split(/[\\/]/).pop(), // Get filename only
|
||||
click: () => {
|
||||
if (fs.existsSync(filePath)) {
|
||||
currentFile = filePath;
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
mainWindow.webContents.send('file-opened', { path: filePath, content });
|
||||
} else {
|
||||
dialog.showErrorBox('File Not Found', `The file "${filePath}" could not be found.`);
|
||||
}
|
||||
},
|
||||
toolTip: filePath // Show full path in tooltip
|
||||
}));
|
||||
|
||||
return [
|
||||
...recentFileItems,
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Clear Recent Files',
|
||||
click: () => {
|
||||
mainWindow.webContents.send('clear-recent-files');
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
function getRecentFiles() {
|
||||
try {
|
||||
const recentFiles = JSON.parse(fs.readFileSync(path.join(app.getPath('userData'), 'recent-files.json'), 'utf-8'));
|
||||
return recentFiles.filter(file => fs.existsSync(file));
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function createMenu() {
|
||||
const template = [
|
||||
{
|
||||
@@ -78,6 +139,11 @@ function createMenu() {
|
||||
click: saveAsFile
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Recent Files',
|
||||
submenu: buildRecentFilesMenu()
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Import Document...',
|
||||
accelerator: 'CmdOrCtrl+I',
|
||||
@@ -97,9 +163,7 @@ function createMenu() {
|
||||
{ label: 'PowerPoint (PPTX)', click: () => exportFile('pptx') },
|
||||
{ label: 'OpenDocument Presentation (ODP)', click: () => exportFile('odp') },
|
||||
{ type: 'separator' },
|
||||
{ label: 'Excel (XLSX)', click: () => exportSpreadsheet('xlsx') },
|
||||
{ label: 'Excel Legacy (XLS)', click: () => exportSpreadsheet('xls') },
|
||||
{ label: 'OpenDocument Spreadsheet (ODS)', click: () => exportSpreadsheet('ods') }
|
||||
{ label: 'CSV (Tables)', click: () => exportSpreadsheet('csv') }
|
||||
]
|
||||
},
|
||||
{ type: 'separator' },
|
||||
@@ -195,7 +259,7 @@ function createMenu() {
|
||||
type: 'info',
|
||||
title: 'About PanConverter',
|
||||
message: 'PanConverter',
|
||||
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',
|
||||
detail: 'A cross-platform Markdown editor and converter using Pandoc.\n\nVersion: 1.5.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• Optional advanced export options\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 CSV format\n• Document import & conversion\n• Table creation helper\n• Multiple themes support\n• Undo/redo functionality\n• Live word count and statistics',
|
||||
buttons: ['OK']
|
||||
});
|
||||
}
|
||||
@@ -270,7 +334,7 @@ function performExportWithOptions(format, options) {
|
||||
});
|
||||
|
||||
if (outputFile) {
|
||||
let pandocCmd = `pandoc "${currentFile}" -o "${outputFile}"`;
|
||||
let pandocCmd = `${getPandocPath()} "${currentFile}" -o "${outputFile}"`;
|
||||
|
||||
// Add template if specified
|
||||
if (options.template && options.template !== 'default') {
|
||||
@@ -345,7 +409,7 @@ function tryPdfFallback(inputFile, outputFile, engines, index, options, lastErro
|
||||
|
||||
const engine = engines[index];
|
||||
const geometry = options.geometry || 'margin=1in';
|
||||
let pandocCmd = `pandoc "${inputFile}" --pdf-engine=${engine} -V geometry:${geometry} -o "${outputFile}"`;
|
||||
let pandocCmd = `${getPandocPath()} "${inputFile}" --pdf-engine=${engine} -V geometry:${geometry} -o "${outputFile}"`;
|
||||
|
||||
// Add all other options
|
||||
if (options.template && options.template !== 'default') {
|
||||
@@ -403,7 +467,7 @@ function importDocument() {
|
||||
const outputFile = inputFile.replace(/\.[^/.]+$/, '.md');
|
||||
|
||||
// Convert to markdown using pandoc
|
||||
const pandocCmd = `pandoc "${inputFile}" -t markdown -o "${outputFile}"`;
|
||||
const pandocCmd = `${getPandocPath()} "${inputFile}" -t markdown -o "${outputFile}"`;
|
||||
|
||||
exec(pandocCmd, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
@@ -496,27 +560,36 @@ ipcMain.on('export-spreadsheet', (event, { content, format }) => {
|
||||
try {
|
||||
// Parse markdown content to extract tables
|
||||
const tables = extractTablesFromMarkdown(content);
|
||||
|
||||
|
||||
if (tables.length === 0) {
|
||||
dialog.showErrorBox('Export Error', 'No tables found in the markdown content');
|
||||
return;
|
||||
}
|
||||
|
||||
// Create workbook
|
||||
const wb = XLSX.utils.book_new();
|
||||
|
||||
// Convert tables to CSV format
|
||||
let csvContent = '';
|
||||
tables.forEach((table, index) => {
|
||||
const ws = XLSX.utils.aoa_to_sheet(table);
|
||||
XLSX.utils.book_append_sheet(wb, ws, `Table ${index + 1}`);
|
||||
if (index > 0) csvContent += '\n\n'; // Separate multiple tables
|
||||
if (tables.length > 1) csvContent += `"Table ${index + 1}"\n`;
|
||||
|
||||
table.forEach(row => {
|
||||
const csvRow = row.map(cell => {
|
||||
// Escape quotes and wrap in quotes if necessary
|
||||
const cleanCell = cell.replace(/"/g, '""');
|
||||
return cleanCell.includes(',') || cleanCell.includes('"') || cleanCell.includes('\n')
|
||||
? `"${cleanCell}"` : cleanCell;
|
||||
}).join(',');
|
||||
csvContent += csvRow + '\n';
|
||||
});
|
||||
});
|
||||
|
||||
// Write file
|
||||
XLSX.writeFile(wb, outputFile);
|
||||
|
||||
// Write CSV file
|
||||
fs.writeFileSync(outputFile, csvContent, 'utf-8');
|
||||
|
||||
dialog.showMessageBox(mainWindow, {
|
||||
type: 'info',
|
||||
title: 'Export Complete',
|
||||
message: `Spreadsheet exported successfully to ${outputFile}`,
|
||||
message: `CSV exported successfully to ${outputFile}`,
|
||||
buttons: ['OK']
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -637,7 +710,7 @@ function performBatchConversion(inputFolder, outputFolder, format, options) {
|
||||
}
|
||||
|
||||
// Build pandoc command
|
||||
let pandocCmd = `pandoc "${inputFile}" -o "${outputFile}"`;
|
||||
let pandocCmd = `${getPandocPath()} "${inputFile}" -o "${outputFile}"`;
|
||||
|
||||
// Add template if specified
|
||||
if (options.template && options.template !== 'default') {
|
||||
@@ -727,6 +800,30 @@ app.on('activate', () => {
|
||||
}
|
||||
});
|
||||
|
||||
// IPC handlers for recent files
|
||||
ipcMain.on('save-recent-files', (event, recentFiles) => {
|
||||
try {
|
||||
const userDataPath = app.getPath('userData');
|
||||
const recentFilesPath = path.join(userDataPath, 'recent-files.json');
|
||||
fs.writeFileSync(recentFilesPath, JSON.stringify(recentFiles, null, 2));
|
||||
} catch (error) {
|
||||
console.error('Error saving recent files:', error);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('clear-recent-files', (event) => {
|
||||
try {
|
||||
const userDataPath = app.getPath('userData');
|
||||
const recentFilesPath = path.join(userDataPath, 'recent-files.json');
|
||||
fs.writeFileSync(recentFilesPath, JSON.stringify([], null, 2));
|
||||
// Rebuild menu to reflect changes
|
||||
createMenu();
|
||||
event.reply('recent-files-cleared');
|
||||
} catch (error) {
|
||||
console.error('Error clearing recent files:', error);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle file opening on macOS
|
||||
app.on('open-file', (event, filePath) => {
|
||||
event.preventDefault();
|
||||
|
||||
+242
-40
@@ -25,6 +25,9 @@ class TabManager {
|
||||
this.nextTabId = 2;
|
||||
this.isPreviewVisible = true;
|
||||
this.showLineNumbers = false;
|
||||
this.autoSaveInterval = null;
|
||||
this.autoSaveDelay = 30000; // 30 seconds
|
||||
this.recentFiles = JSON.parse(localStorage.getItem('recentFiles') || '[]');
|
||||
|
||||
// Initialize first tab
|
||||
this.tabs.set(1, {
|
||||
@@ -112,6 +115,7 @@ class TabManager {
|
||||
this.tabs.set(newTabId, tab);
|
||||
this.createTabElements(tab);
|
||||
this.switchToTab(newTabId);
|
||||
this.startAutoSave();
|
||||
this.updateTabBar();
|
||||
}
|
||||
|
||||
@@ -284,6 +288,22 @@ class TabManager {
|
||||
const html = marked.parse(tab.content);
|
||||
const sanitizedHtml = DOMPurify.sanitize(html);
|
||||
preview.innerHTML = sanitizedHtml;
|
||||
|
||||
// Render math expressions if KaTeX is available
|
||||
if (window.katex && window.renderMathInElement) {
|
||||
try {
|
||||
window.renderMathInElement(preview, {
|
||||
delimiters: [
|
||||
{left: '$$', right: '$$', display: true},
|
||||
{left: '$', right: '$', display: false},
|
||||
{left: '\\[', right: '\\]', display: true},
|
||||
{left: '\\(', right: '\\)', display: false}
|
||||
]
|
||||
});
|
||||
} catch (mathError) {
|
||||
console.warn('Math rendering error:', mathError);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
preview.innerHTML = '<p>Error rendering preview</p>';
|
||||
}
|
||||
@@ -324,10 +344,33 @@ class TabManager {
|
||||
updateWordCount() {
|
||||
const tab = this.tabs.get(this.activeTabId);
|
||||
if (!tab) return;
|
||||
|
||||
const words = tab.content.trim().split(/\\s+/).filter(word => word.length > 0).length;
|
||||
const chars = tab.content.length;
|
||||
document.getElementById('word-count').textContent = `Words: ${words} | Characters: ${chars}`;
|
||||
|
||||
const content = tab.content;
|
||||
const words = content.trim() ? content.trim().split(/\s+/).filter(word => word.length > 0).length : 0;
|
||||
const chars = content.length;
|
||||
const charsNoSpaces = content.replace(/\s/g, '').length;
|
||||
|
||||
// Enhanced statistics
|
||||
const lines = content.split('\n').length;
|
||||
const paragraphs = content.split(/\n\s*\n/).filter(p => p.trim()).length;
|
||||
const readingTime = Math.ceil(words / 200); // Average reading speed: 200 words/minute
|
||||
const sentences = content.split(/[.!?]+/).filter(s => s.trim()).length;
|
||||
|
||||
// Update the word count display with enhanced stats
|
||||
const basicStats = `Words: ${words} | Characters: ${chars} (${charsNoSpaces} no spaces)`;
|
||||
const enhancedStats = `Lines: ${lines} | Paragraphs: ${paragraphs} | Sentences: ${sentences} | Reading time: ${readingTime} min`;
|
||||
|
||||
document.getElementById('word-count').textContent = basicStats;
|
||||
|
||||
// Add enhanced stats to a separate element
|
||||
let enhancedEl = document.getElementById('enhanced-stats');
|
||||
if (!enhancedEl) {
|
||||
enhancedEl = document.createElement('div');
|
||||
enhancedEl.id = 'enhanced-stats';
|
||||
enhancedEl.className = 'enhanced-stats';
|
||||
document.querySelector('.status-bar').appendChild(enhancedEl);
|
||||
}
|
||||
enhancedEl.textContent = enhancedStats;
|
||||
}
|
||||
|
||||
setupEditorEvents() {
|
||||
@@ -408,7 +451,84 @@ class TabManager {
|
||||
this.updateWordCount();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Auto-save functionality
|
||||
startAutoSave() {
|
||||
if (this.autoSaveInterval) {
|
||||
clearInterval(this.autoSaveInterval);
|
||||
}
|
||||
|
||||
this.autoSaveInterval = setInterval(() => {
|
||||
this.performAutoSave();
|
||||
}, this.autoSaveDelay);
|
||||
}
|
||||
|
||||
stopAutoSave() {
|
||||
if (this.autoSaveInterval) {
|
||||
clearInterval(this.autoSaveInterval);
|
||||
this.autoSaveInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
performAutoSave() {
|
||||
const tab = this.tabs.get(this.activeTabId);
|
||||
if (!tab || !tab.filePath || !tab.content) return;
|
||||
|
||||
// Only auto-save if content has changed since last save
|
||||
if (tab.lastSavedContent !== tab.content) {
|
||||
ipcRenderer.send('save-file', { path: tab.filePath, content: tab.content });
|
||||
tab.lastSavedContent = tab.content;
|
||||
|
||||
// Show brief auto-save indicator
|
||||
this.showAutoSaveIndicator();
|
||||
}
|
||||
}
|
||||
|
||||
showAutoSaveIndicator() {
|
||||
const indicator = document.createElement('div');
|
||||
indicator.textContent = 'Auto-saved';
|
||||
indicator.className = 'auto-save-indicator';
|
||||
document.body.appendChild(indicator);
|
||||
|
||||
setTimeout(() => {
|
||||
indicator.classList.add('fade-out');
|
||||
setTimeout(() => {
|
||||
if (indicator.parentNode) {
|
||||
indicator.parentNode.removeChild(indicator);
|
||||
}
|
||||
}, 300);
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
// Recent files functionality
|
||||
addToRecentFiles(filePath) {
|
||||
if (!filePath) return;
|
||||
|
||||
// Remove if already exists
|
||||
this.recentFiles = this.recentFiles.filter(f => f !== filePath);
|
||||
|
||||
// Add to beginning
|
||||
this.recentFiles.unshift(filePath);
|
||||
|
||||
// Keep only last 10 files
|
||||
this.recentFiles = this.recentFiles.slice(0, 10);
|
||||
|
||||
// Save to localStorage and sync with main process
|
||||
localStorage.setItem('recentFiles', JSON.stringify(this.recentFiles));
|
||||
window.electronAPI.send('save-recent-files', this.recentFiles);
|
||||
}
|
||||
|
||||
getRecentFiles() {
|
||||
return this.recentFiles.filter(file => {
|
||||
// Check if file still exists (basic check by trying to access it)
|
||||
try {
|
||||
return file && file.length > 0;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setupToolbarEvents() {
|
||||
// Existing toolbar setup...
|
||||
document.getElementById('btn-preview-toggle').addEventListener('click', () => {
|
||||
@@ -459,6 +579,8 @@ class TabManager {
|
||||
}
|
||||
|
||||
this.restoreTabState(this.activeTabId);
|
||||
this.startAutoSave();
|
||||
this.addToRecentFiles(filePath);
|
||||
this.updateTabBar();
|
||||
}
|
||||
|
||||
@@ -601,6 +723,13 @@ function hideExportDialog() {
|
||||
}
|
||||
|
||||
function initializeExportForm(format) {
|
||||
// Reset advanced export toggle to unchecked
|
||||
const advancedToggle = document.getElementById('advanced-export-toggle');
|
||||
const advancedOptions = document.getElementById('advanced-export-options');
|
||||
|
||||
advancedToggle.checked = false;
|
||||
advancedOptions.classList.add('hidden');
|
||||
|
||||
// Reset form to defaults
|
||||
document.getElementById('export-template').value = 'default';
|
||||
document.getElementById('custom-template-path').style.display = 'none';
|
||||
@@ -635,48 +764,69 @@ function initializeExportForm(format) {
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
const advancedMode = document.getElementById('advanced-export-toggle').checked;
|
||||
const options = {};
|
||||
|
||||
// Collect custom template path
|
||||
if (options.template === 'custom') {
|
||||
options.template = document.getElementById('custom-template-path').value.trim();
|
||||
if (advancedMode) {
|
||||
// Collect advanced options
|
||||
options.template = document.getElementById('export-template').value;
|
||||
options.metadata = {};
|
||||
options.variables = {};
|
||||
options.toc = document.getElementById('export-toc').checked;
|
||||
options.tocDepth = document.getElementById('export-toc-depth').value;
|
||||
options.numberSections = document.getElementById('export-number-sections').checked;
|
||||
options.citeproc = document.getElementById('export-citeproc').checked;
|
||||
} else {
|
||||
// Collect basic options only
|
||||
options.template = 'default';
|
||||
options.metadata = {};
|
||||
options.variables = {};
|
||||
options.toc = document.getElementById('basic-toc').checked;
|
||||
options.tocDepth = 3;
|
||||
options.numberSections = document.getElementById('basic-number-sections').checked;
|
||||
options.citeproc = false;
|
||||
}
|
||||
|
||||
// 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;
|
||||
if (advancedMode) {
|
||||
// Collect custom template path
|
||||
if (options.template === 'custom') {
|
||||
options.template = document.getElementById('custom-template-path').value.trim();
|
||||
}
|
||||
});
|
||||
|
||||
// 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;
|
||||
// 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;
|
||||
} else {
|
||||
// Basic mode - set default PDF options if needed
|
||||
if (currentExportFormat === 'pdf') {
|
||||
options.pdfEngine = 'xelatex';
|
||||
options.geometry = 'margin=1in';
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -697,6 +847,16 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
});
|
||||
|
||||
// Advanced export toggle
|
||||
document.getElementById('advanced-export-toggle').addEventListener('change', (e) => {
|
||||
const advancedOptions = document.getElementById('advanced-export-options');
|
||||
if (e.target.checked) {
|
||||
advancedOptions.classList.remove('hidden');
|
||||
} else {
|
||||
advancedOptions.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
// Template file input
|
||||
document.getElementById('template-file-input').addEventListener('change', (e) => {
|
||||
const file = e.target.files[0];
|
||||
@@ -941,4 +1101,46 @@ if (originalExportConfirm) {
|
||||
currentBatchOptions = collectExportOptions();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// IPC event listeners for recent files functionality
|
||||
if (window.electronAPI) {
|
||||
window.electronAPI.on('recent-files-cleared', () => {
|
||||
tabManager.recentFiles = [];
|
||||
localStorage.setItem('recentFiles', JSON.stringify([]));
|
||||
console.log('Recent files cleared');
|
||||
});
|
||||
}
|
||||
|
||||
// Add math rendering support using KaTeX for enhanced preview
|
||||
function initMathSupport() {
|
||||
// Add KaTeX CSS
|
||||
const katexCSS = document.createElement('link');
|
||||
katexCSS.rel = 'stylesheet';
|
||||
katexCSS.href = 'https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.css';
|
||||
katexCSS.crossOrigin = 'anonymous';
|
||||
document.head.appendChild(katexCSS);
|
||||
|
||||
// Add KaTeX JS
|
||||
const katexJS = document.createElement('script');
|
||||
katexJS.src = 'https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.js';
|
||||
katexJS.crossOrigin = 'anonymous';
|
||||
katexJS.onload = () => {
|
||||
// Add auto-render extension
|
||||
const autoRenderJS = document.createElement('script');
|
||||
autoRenderJS.src = 'https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/contrib/auto-render.min.js';
|
||||
autoRenderJS.crossOrigin = 'anonymous';
|
||||
autoRenderJS.onload = () => {
|
||||
console.log('Math support (KaTeX) initialized');
|
||||
// Re-render current preview to include math
|
||||
if (tabManager) {
|
||||
tabManager.updatePreview();
|
||||
}
|
||||
};
|
||||
document.head.appendChild(autoRenderJS);
|
||||
};
|
||||
document.head.appendChild(katexJS);
|
||||
}
|
||||
|
||||
// Initialize math support on load
|
||||
initMathSupport();
|
||||
+177
@@ -1406,4 +1406,181 @@ body.theme-github .line-numbers {
|
||||
background: #fafbfc;
|
||||
border-right-color: #e1e4e8;
|
||||
color: #586069;
|
||||
}
|
||||
|
||||
/* Advanced Export Toggle Styles */
|
||||
.export-mode-toggle {
|
||||
border-bottom: 1px solid #ddd;
|
||||
padding-bottom: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.checkbox-label input[type="checkbox"] {
|
||||
margin-right: 10px;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.export-help {
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
font-style: italic;
|
||||
margin-left: 25px;
|
||||
}
|
||||
|
||||
.advanced-options {
|
||||
transition: all 0.3s ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.advanced-options.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.basic-options {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.basic-options label {
|
||||
font-weight: normal;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* Theme support for advanced export toggle */
|
||||
body.theme-dark .export-mode-toggle {
|
||||
border-color: #444;
|
||||
}
|
||||
|
||||
body.theme-dark .export-help {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
body.theme-dark .basic-options {
|
||||
background: #1a1a1a;
|
||||
border-color: #444;
|
||||
}
|
||||
|
||||
body.theme-dark .checkbox-label {
|
||||
color: #f0f0f0;
|
||||
}
|
||||
|
||||
body.theme-solarized .export-mode-toggle {
|
||||
border-color: #eee8d5;
|
||||
}
|
||||
|
||||
body.theme-solarized .export-help {
|
||||
color: #586e75;
|
||||
}
|
||||
|
||||
body.theme-solarized .basic-options {
|
||||
background: #fdf6e3;
|
||||
border-color: #eee8d5;
|
||||
}
|
||||
|
||||
body.theme-monokai .export-mode-toggle {
|
||||
border-color: #49483e;
|
||||
}
|
||||
|
||||
body.theme-monokai .export-help {
|
||||
color: #75715e;
|
||||
}
|
||||
|
||||
body.theme-monokai .basic-options {
|
||||
background: #2f2f2f;
|
||||
border-color: #49483e;
|
||||
}
|
||||
|
||||
body.theme-github .export-mode-toggle {
|
||||
border-color: #e1e4e8;
|
||||
}
|
||||
|
||||
body.theme-github .export-help {
|
||||
color: #586069;
|
||||
}
|
||||
|
||||
body.theme-github .basic-options {
|
||||
background: #f6f8fa;
|
||||
border-color: #e1e4e8;
|
||||
}
|
||||
|
||||
/* Enhanced Statistics Styles */
|
||||
.enhanced-stats {
|
||||
font-size: 11px;
|
||||
color: #666;
|
||||
padding: 2px 0;
|
||||
border-top: 1px solid #ddd;
|
||||
margin-top: 2px;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
/* Theme support for enhanced stats */
|
||||
body.theme-dark .enhanced-stats {
|
||||
color: #999;
|
||||
border-color: #444;
|
||||
}
|
||||
|
||||
body.theme-solarized .enhanced-stats {
|
||||
color: #586e75;
|
||||
border-color: #eee8d5;
|
||||
}
|
||||
|
||||
body.theme-monokai .enhanced-stats {
|
||||
color: #75715e;
|
||||
border-color: #49483e;
|
||||
}
|
||||
|
||||
body.theme-github .enhanced-stats {
|
||||
color: #586069;
|
||||
border-color: #e1e4e8;
|
||||
}
|
||||
|
||||
/* Auto-save indicator */
|
||||
.auto-save-indicator {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: #28a745;
|
||||
color: white;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
z-index: 10000;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
||||
animation: slideInRight 0.3s ease-out;
|
||||
}
|
||||
|
||||
.auto-save-indicator.fade-out {
|
||||
animation: fadeOut 0.3s ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes slideInRight {
|
||||
from {
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeOut {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user