Fix export functionality and improve Windows build

- Fixed export function issues with comprehensive pandoc detection
- Added built-in HTML and PDF export fallbacks (no pandoc required)
- Improved error handling with clear user messages
- Enhanced code quality with optional chaining and better error handling
- Fixed NSIS installer script issues for Windows build
- Added comprehensive logging and debugging for export process
- Created test files and documentation for export functionality
- Successfully builds Windows release with proper XLSX support
Resolves export failures and provides robust fallback mechanisms.
This commit is contained in:
2025-09-21 22:56:23 +05:30
parent 24576df3b0
commit 7c8e206764
7 changed files with 495 additions and 163 deletions
+303 -151
View File
@@ -29,6 +29,22 @@ const store = {
let mainWindow;
let currentFile = null; // This will now represent the active tab's file
let pandocAvailable = null; // Cache pandoc availability check
// Check if pandoc is available
function checkPandocAvailability() {
return new Promise((resolve) => {
if (pandocAvailable !== null) {
resolve(pandocAvailable);
return;
}
exec('pandoc --version', (error, stdout, stderr) => {
pandocAvailable = !error;
resolve(pandocAvailable);
});
});
}
function createWindow() {
mainWindow = new BrowserWindow({
@@ -253,163 +269,59 @@ function exportFile(format) {
]
});
if (outputFile) {
if (!outputFile) return; // User cancelled
console.log(`Attempting to export ${format} to:`, outputFile);
// Check pandoc availability first
checkPandocAvailability().then((hasPandoc) => {
console.log('Pandoc available:', hasPandoc);
if (!hasPandoc) {
// Handle formats that don't require pandoc
if (format === 'html') {
console.log('Using built-in HTML export');
exportToHTML(outputFile);
return;
} else if (format === 'pdf') {
console.log('Using built-in PDF export');
exportToPDFElectron(outputFile);
return;
} else {
dialog.showErrorBox('Export Error',
`Pandoc is required for ${format.toUpperCase()} export but is not installed or not found in PATH.\n\n` +
`Please install Pandoc from: https://pandoc.org/installing.html\n\n` +
`Alternatively, you can export to HTML or PDF using the built-in converters.`
);
return;
}
}
// Use pandoc for export
console.log('Using Pandoc for export');
let pandocCmd = `pandoc "${currentFile}" -o "${outputFile}"`;
// Add specific options for PDF export to ensure proper generation
// Add specific options for different formats
if (format === 'pdf') {
pandocCmd = `pandoc "${currentFile}" --pdf-engine=xelatex -V geometry:margin=1in -o "${outputFile}"`;
// Try with different PDF engines if xelatex fails
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, async (finalError) => {
if (finalError) {
// Ultimate fallback: Use Electron's built-in PDF export
try {
const marked = require('marked');
const fs = require('fs');
// Read markdown file
const markdownContent = fs.readFileSync(currentFile, 'utf8');
// Convert markdown to HTML
const htmlContent = marked.parse(markdownContent);
// Create full HTML document with styling
const fullHtml = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
line-height: 1.6;
color: #333;
max-width: 800px;
margin: 0 auto;
padding: 40px;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1.5em;
margin-bottom: 0.5em;
}
code {
background: #f4f4f4;
padding: 2px 4px;
border-radius: 3px;
font-family: Consolas, Monaco, 'Courier New', monospace;
}
pre {
background: #f4f4f4;
padding: 1em;
border-radius: 5px;
overflow-x: auto;
}
pre code {
background: transparent;
padding: 0;
}
blockquote {
border-left: 4px solid #ddd;
margin-left: 0;
padding-left: 1em;
color: #666;
}
table {
border-collapse: collapse;
width: 100%;
margin: 1em 0;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #f4f4f4;
}
a {
color: #0066cc;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
img {
max-width: 100%;
height: auto;
}
</style>
</head>
<body>
${htmlContent}
</body>
</html>
`;
// Create a hidden window to render and export PDF
const { BrowserWindow } = require('electron');
const pdfWindow = new BrowserWindow({
show: false,
webPreferences: {
nodeIntegration: true,
contextIsolation: false
}
});
await pdfWindow.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(fullHtml)}`);
const pdfData = await pdfWindow.webContents.printToPDF({
marginsType: 1, // Use default margins
pageSize: 'A4',
printBackground: true,
printSelectionOnly: false,
landscape: false
});
fs.writeFileSync(outputFile, pdfData);
pdfWindow.close();
showExportSuccess(outputFile);
} catch (electronPdfError) {
dialog.showErrorBox('PDF Export Error',
`Failed to export PDF. The built-in PDF export encountered an error.\n\n` +
`For better PDF export, please install one of the following:\n` +
`• XeLaTeX (recommended)\n` +
`• PDFLaTeX\n` +
`• wkhtmltopdf\n\n` +
`Error: ${electronPdfError.message}`
);
}
} else {
showExportSuccess(outputFile);
}
});
} else {
showExportSuccess(outputFile);
}
});
} else {
showExportSuccess(outputFile);
}
});
exportWithPandocPDF(pandocCmd, outputFile);
} else if (format === 'docx') {
pandocCmd = `pandoc "${currentFile}" -t docx -o "${outputFile}"`;
exportWithPandoc(pandocCmd, outputFile, format);
} else if (format === 'html') {
pandocCmd = `pandoc "${currentFile}" -t html5 --standalone -o "${outputFile}"`;
exportWithPandoc(pandocCmd, outputFile, format);
} else if (format === 'latex') {
pandocCmd = `pandoc "${currentFile}" -t latex -o "${outputFile}"`;
exportWithPandoc(pandocCmd, outputFile, format);
} else {
exec(pandocCmd, (error, stdout, stderr) => {
if (error) {
dialog.showErrorBox('Export Error', `Failed to export: ${error.message}\n\nMake sure Pandoc is installed.`);
} else {
showExportSuccess(outputFile);
}
});
// Generic export for other formats
exportWithPandoc(pandocCmd, outputFile, format);
}
}
}).catch((error) => {
console.error('Error checking pandoc availability:', error);
dialog.showErrorBox('Export Error', `Error checking system requirements: ${error.message}`);
});
}
function showExportSuccess(outputFile) {
@@ -421,6 +333,246 @@ function showExportSuccess(outputFile) {
});
}
// Helper function to export with pandoc (general)
function exportWithPandoc(pandocCmd, outputFile, format) {
exec(pandocCmd, (error, stdout, stderr) => {
if (error) {
console.error(`Pandoc error for ${format}:`, error);
dialog.showErrorBox('Export Error',
`Failed to export to ${format.toUpperCase()}:\n${error.message}\n\n` +
`Command used: ${pandocCmd}\n\n` +
`Please ensure Pandoc is properly installed and accessible.`
);
} else {
console.log(`Successfully exported to ${format}:`, outputFile);
showExportSuccess(outputFile);
}
});
}
// Helper function to export PDF with pandoc (with fallbacks)
function exportWithPandocPDF(pandocCmd, outputFile) {
exec(pandocCmd, (error, stdout, stderr) => {
if (error) {
console.log('XeLaTeX failed, trying PDFLaTeX...');
// Fallback to pdflatex
const fallbackCmd = pandocCmd.replace('--pdf-engine=xelatex', '--pdf-engine=pdflatex');
exec(fallbackCmd, (fallbackError, fallbackStdout, fallbackStderr) => {
if (fallbackError) {
console.log('PDFLaTeX failed, trying Electron PDF...');
// Final fallback to Electron PDF
exportToPDFElectron(outputFile);
} else {
console.log('Successfully exported PDF with PDFLaTeX');
showExportSuccess(outputFile);
}
});
} else {
console.log('Successfully exported PDF with XeLaTeX');
showExportSuccess(outputFile);
}
});
}
// Export to HTML using marked (no pandoc required)
function exportToHTML(outputFile) {
try {
const marked = require('marked');
const markdownContent = fs.readFileSync(currentFile, 'utf8');
const htmlContent = marked.parse(markdownContent);
const fullHtml = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Exported Document</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
line-height: 1.6;
color: #333;
max-width: 800px;
margin: 0 auto;
padding: 40px;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1.5em;
margin-bottom: 0.5em;
}
code {
background: #f4f4f4;
padding: 2px 4px;
border-radius: 3px;
font-family: Consolas, Monaco, 'Courier New', monospace;
}
pre {
background: #f4f4f4;
padding: 1em;
border-radius: 5px;
overflow-x: auto;
}
pre code {
background: transparent;
padding: 0;
}
blockquote {
border-left: 4px solid #ddd;
margin-left: 0;
padding-left: 1em;
color: #666;
}
table {
border-collapse: collapse;
width: 100%;
margin: 1em 0;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #f4f4f4;
}
a {
color: #0066cc;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
img {
max-width: 100%;
height: auto;
}
</style>
</head>
<body>
${htmlContent}
</body>
</html>`;
fs.writeFileSync(outputFile, fullHtml, 'utf8');
console.log('Successfully exported HTML');
showExportSuccess(outputFile);
} catch (error) {
console.error('HTML export error:', error);
dialog.showErrorBox('HTML Export Error', `Failed to export HTML: ${error.message}`);
}
}
// Export to PDF using Electron (no pandoc required)
function exportToPDFElectron(outputFile) {
try {
const marked = require('marked');
const markdownContent = fs.readFileSync(currentFile, 'utf8');
const htmlContent = marked.parse(markdownContent);
const fullHtml = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PDF Export</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
line-height: 1.6;
color: #333;
max-width: 800px;
margin: 0 auto;
padding: 40px;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1.5em;
margin-bottom: 0.5em;
}
code {
background: #f4f4f4;
padding: 2px 4px;
border-radius: 3px;
font-family: Consolas, Monaco, 'Courier New', monospace;
}
pre {
background: #f4f4f4;
padding: 1em;
border-radius: 5px;
overflow-x: auto;
}
pre code {
background: transparent;
padding: 0;
}
blockquote {
border-left: 4px solid #ddd;
margin-left: 0;
padding-left: 1em;
color: #666;
}
table {
border-collapse: collapse;
width: 100%;
margin: 1em 0;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #f4f4f4;
}
img {
max-width: 100%;
height: auto;
}
@media print {
body { padding: 20px; }
}
</style>
</head>
<body>
${htmlContent}
</body>
</html>`;
// Create a hidden window to render and export PDF
const pdfWindow = new BrowserWindow({
show: false,
webPreferences: {
nodeIntegration: true,
contextIsolation: false
}
});
pdfWindow.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(fullHtml)}`).then(() => {
return pdfWindow.webContents.printToPDF({
marginsType: 1, // Use default margins
pageSize: 'A4',
printBackground: true,
printSelectionOnly: false,
landscape: false
});
}).then((pdfData) => {
fs.writeFileSync(outputFile, pdfData);
pdfWindow.close();
console.log('Successfully exported PDF with Electron');
showExportSuccess(outputFile);
}).catch((error) => {
pdfWindow.close();
console.error('Electron PDF export error:', error);
dialog.showErrorBox('PDF Export Error',
`Failed to export PDF using built-in engine: ${error.message}\n\n` +
`For better PDF export, please install Pandoc with LaTeX support.`
);
});
} catch (error) {
console.error('PDF export setup error:', error);
dialog.showErrorBox('PDF Export Error', `Failed to setup PDF export: ${error.message}`);
}
}
function exportSpreadsheet(format) {
if (!currentFile) {
dialog.showErrorBox('Error', 'Please save the file first');
+12 -10
View File
@@ -9,7 +9,10 @@ marked.setOptions({
if (lang && hljs.getLanguage(lang)) {
try {
return hljs.highlight(code, { language: lang }).value;
} catch (err) {}
} catch (err) {
// Fallback to auto highlighting if language-specific highlighting fails
console.warn('Syntax highlighting failed for language:', lang, err.message);
}
}
return hljs.highlightAuto(code).value;
},
@@ -71,6 +74,7 @@ class TabManager {
if (e.ctrlKey || e.metaKey) {
switch (e.key) {
case 'n':
case 't':
e.preventDefault();
this.createNewTab();
break;
@@ -80,10 +84,6 @@ class TabManager {
this.closeTab(this.activeTabId);
}
break;
case 't':
e.preventDefault();
this.createNewTab();
break;
case 'Tab':
if (this.tabs.size > 1) {
e.preventDefault();
@@ -152,7 +152,7 @@ class TabManager {
// Notify main process about current file for exports
const tab = this.tabs.get(tabId);
if (tab && tab.filePath) {
if (tab?.filePath) {
ipcRenderer.send('set-current-file', tab.filePath);
}
}
@@ -169,14 +169,16 @@ class TabManager {
const tab = this.tabs.get(tabId);
if (tab.isDirty) {
// TODO: Show confirmation dialog
// Show confirmation dialog for unsaved changes
const result = confirm('You have unsaved changes. Do you want to close this tab without saving?');
if (!result) return;
}
// Remove tab elements
const tabElement = document.querySelector(`[data-tab-id="${tabId}"]`);
const tabContent = document.getElementById(`tab-content-${tabId}`);
if (tabElement && tabElement.classList.contains('tab')) {
if (tabElement?.classList.contains('tab')) {
tabElement.remove();
}
if (tabContent) {
@@ -258,7 +260,6 @@ class TabManager {
if (!tab) return;
const editor = document.getElementById(`editor-${tabId}`);
const preview = document.getElementById(`preview-${tabId}`);
if (editor) {
editor.value = tab.content;
@@ -285,7 +286,8 @@ class TabManager {
const sanitizedHtml = DOMPurify.sanitize(html);
preview.innerHTML = sanitizedHtml;
} catch (error) {
preview.innerHTML = '<p>Error rendering preview</p>';
console.error('Error rendering preview:', error);
preview.innerHTML = '<p class="error">Error rendering preview. Please check your markdown syntax.</p>';
}
}