Major v1.3.0 update: Fix PDF export, file associations, remove converter menu, and implement tabbed interface

- Enhanced PDF export with multiple LaTeX engine fallbacks
- Fixed file association and direct file opening from OS
- Removed redundant converter menu, moved import to File menu
- Implemented comprehensive tabbed interface for multiple files
- Added tab management with keyboard shortcuts (Ctrl+N, Ctrl+W, Ctrl+Tab)
- Enhanced UI with tab bar and improved navigation
- Updated to version 1.3.0 with new features
- Improved main process and renderer architecture for multi-file support
This commit is contained in:
2025-09-01 20:53:17 +05:30
parent 1b10a39b15
commit 328451f88d
6 changed files with 1039 additions and 636 deletions
+16 -7
View File
@@ -10,6 +10,13 @@
</head>
<body>
<div class="container">
<div class="tab-bar" id="tab-bar">
<div class="tab active" data-tab-id="1">
<span class="tab-title">Untitled</span>
<button class="tab-close" title="Close tab">×</button>
</div>
<button class="new-tab-button" id="new-tab-btn" title="New tab">+</button>
</div>
<div class="toolbar">
<button id="btn-bold" title="Bold">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
@@ -108,14 +115,16 @@
</div>
<div class="editor-container">
<div id="editor-pane" class="pane">
<div class="editor-wrapper">
<div id="line-numbers" class="line-numbers hidden"></div>
<textarea id="editor"></textarea>
<div class="tab-content active" id="tab-content-1" data-tab-id="1">
<div id="editor-pane-1" class="pane">
<div class="editor-wrapper">
<div id="line-numbers-1" class="line-numbers hidden"></div>
<textarea id="editor-1" class="editor-textarea"></textarea>
</div>
</div>
<div id="preview-pane-1" class="pane">
<div id="preview-1" class="preview-content"></div>
</div>
</div>
<div id="preview-pane" class="pane">
<div id="preview"></div>
</div>
</div>
+106 -77
View File
@@ -28,7 +28,7 @@ const store = {
};
let mainWindow;
let currentFile = null;
let currentFile = null; // This will now represent the active tab's file
function createWindow() {
mainWindow = new BrowserWindow({
@@ -48,6 +48,14 @@ function createWindow() {
mainWindow.on('closed', () => {
mainWindow = null;
});
// Handle pending file from file association
if (app.pendingFile) {
mainWindow.webContents.once('dom-ready', () => {
openFileFromPath(app.pendingFile);
app.pendingFile = null;
});
}
}
function createMenu() {
@@ -76,6 +84,11 @@ function createMenu() {
click: saveAsFile
},
{ type: 'separator' },
{
label: 'Import Document...',
accelerator: 'CmdOrCtrl+I',
click: importDocument
},
{
label: 'Export',
submenu: [
@@ -121,32 +134,6 @@ function createMenu() {
}
]
},
{
label: 'Convert',
submenu: [
{
label: 'Import Document...',
accelerator: 'CmdOrCtrl+I',
click: importDocument
},
{ type: 'separator' },
{
label: 'Convert Current File',
submenu: [
{ label: 'To Markdown', click: () => convertToFormat('md') },
{ label: 'To HTML', click: () => convertToFormat('html') },
{ label: 'To PDF', click: () => convertToFormat('pdf') },
{ label: 'To DOCX', click: () => convertToFormat('docx') },
{ label: 'To LaTeX', click: () => convertToFormat('latex') },
{ label: 'To RTF', click: () => convertToFormat('rtf') },
{ label: 'To ODT', click: () => convertToFormat('odt') },
{ label: 'To EPUB', click: () => convertToFormat('epub') },
{ label: 'To PPTX', click: () => convertToFormat('pptx') },
{ label: 'To ODP', click: () => convertToFormat('odp') }
]
}
]
},
{
label: 'View',
submenu: [
@@ -184,7 +171,7 @@ function createMenu() {
type: 'info',
title: 'About PanConverter',
message: 'PanConverter',
detail: 'A cross-platform Markdown editor and converter using Pandoc.\n\nVersion: 1.2.1\nAuthor: Amit Haridas\nEmail: amit.wh@gmail.com\nLicense: MIT\n\nFeatures:\n• Advanced markdown editing with live preview\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.3.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• 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,23 +233,62 @@ function exportFile(format) {
});
if (outputFile) {
const pandocCmd = `pandoc "${currentFile}" -o "${outputFile}"`;
let pandocCmd = `pandoc "${currentFile}" -o "${outputFile}"`;
exec(pandocCmd, (error, stdout, stderr) => {
if (error) {
dialog.showErrorBox('Export Error', `Failed to export: ${error.message}\n\nMake sure Pandoc is installed.`);
} else {
dialog.showMessageBox(mainWindow, {
type: 'info',
title: 'Export Complete',
message: `File exported successfully to ${outputFile}`,
buttons: ['OK']
});
}
});
// 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
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);
}
});
} else {
showExportSuccess(outputFile);
}
});
} 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);
}
});
}
}
}
function showExportSuccess(outputFile) {
dialog.showMessageBox(mainWindow, {
type: 'info',
title: 'Export Complete',
message: `File exported successfully to ${outputFile}`,
buttons: ['OK']
});
}
function exportSpreadsheet(format) {
if (!currentFile) {
dialog.showErrorBox('Error', 'Please save the file first');
@@ -310,40 +336,6 @@ function importDocument() {
}
}
function convertToFormat(format) {
if (!currentFile) {
dialog.showErrorBox('Error', 'Please save or open a file first');
return;
}
const outputFile = dialog.showSaveDialogSync(mainWindow, {
defaultPath: currentFile.replace(/\.[^/.]+$/, `.${format}`),
filters: [
{ name: format.toUpperCase(), extensions: [format] }
]
});
if (outputFile) {
// For presentations, add slide level for better conversion
let pandocCmd = `pandoc "${currentFile}" -o "${outputFile}"`;
if (format === 'pptx' || format === 'odp') {
pandocCmd = `pandoc "${currentFile}" --slide-level=2 -o "${outputFile}"`;
}
exec(pandocCmd, (error, stdout, stderr) => {
if (error) {
dialog.showErrorBox('Conversion Error', `Failed to convert: ${error.message}\n\nMake sure Pandoc is installed.`);
} else {
dialog.showMessageBox(mainWindow, {
type: 'info',
title: 'Conversion Complete',
message: `File converted successfully to ${outputFile}`,
buttons: ['OK']
});
}
});
}
}
function setTheme(theme) {
store.set('theme', theme);
@@ -369,6 +361,11 @@ ipcMain.on('get-theme', (event) => {
event.reply('theme-changed', theme);
});
// Handle tab file tracking for exports
ipcMain.on('set-current-file', (event, filePath) => {
currentFile = filePath;
});
ipcMain.on('export-spreadsheet', (event, { content, format }) => {
const outputFile = dialog.showSaveDialogSync(mainWindow, {
defaultPath: currentFile.replace(/\.[^/.]+$/, `.${format}`),
@@ -452,7 +449,17 @@ function extractTablesFromMarkdown(markdown) {
return tables;
}
app.whenReady().then(createWindow);
app.whenReady().then(() => {
createWindow();
// Handle file association on app startup
if (process.argv.length > 1) {
const filePath = process.argv.find(arg => arg.endsWith('.md') || arg.endsWith('.markdown'));
if (filePath && fs.existsSync(filePath)) {
openFileFromPath(filePath);
}
}
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
@@ -464,4 +471,26 @@ app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
// Handle file opening on macOS
app.on('open-file', (event, filePath) => {
event.preventDefault();
if (mainWindow) {
openFileFromPath(filePath);
} else {
// Store the file path to open after window is created
app.pendingFile = filePath;
}
});
// Handle file opening from command line or file association
function openFileFromPath(filePath) {
if (fs.existsSync(filePath)) {
currentFile = filePath;
const content = fs.readFileSync(filePath, 'utf-8');
if (mainWindow && mainWindow.webContents) {
mainWindow.webContents.send('file-opened', { path: filePath, content });
}
}
}
+492 -551
View File
File diff suppressed because it is too large Load Diff
+121
View File
@@ -16,6 +16,93 @@ body {
height: 100vh;
}
/* Tab Bar */
.tab-bar {
display: flex;
align-items: center;
background: #f0f0f0;
border-bottom: 1px solid #ddd;
padding: 0 8px;
min-height: 36px;
}
.tab {
display: flex;
align-items: center;
padding: 6px 12px;
background: #e8e8e8;
border: 1px solid #ccc;
border-bottom: none;
border-radius: 6px 6px 0 0;
margin-right: 2px;
cursor: pointer;
user-select: none;
max-width: 200px;
min-width: 120px;
}
.tab.active {
background: #fff;
border-color: #999;
z-index: 1;
}
.tab-title {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 13px;
}
.tab-close {
background: none;
border: none;
font-size: 16px;
font-weight: bold;
cursor: pointer;
padding: 0;
margin-left: 8px;
width: 16px;
height: 16px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 3px;
color: #666;
}
.tab-close:hover {
background: #ddd;
color: #000;
}
.new-tab-button {
background: none;
border: 1px solid #ccc;
border-radius: 4px;
padding: 4px 8px;
cursor: pointer;
font-size: 14px;
margin-left: 8px;
color: #666;
}
.new-tab-button:hover {
background: #e8e8e8;
}
/* Tab Content */
.tab-content {
display: flex;
flex: 1;
overflow: hidden;
}
.tab-content:not(.active) {
display: none;
}
/* Toolbar */
.toolbar {
display: flex;
@@ -217,6 +304,40 @@ body.theme-dark {
color: #d4d4d4;
}
body.theme-dark .tab-bar {
background: #2d2d30;
border-bottom-color: #3e3e42;
}
body.theme-dark .tab {
background: #3c3c3c;
border-color: #555;
color: #d4d4d4;
}
body.theme-dark .tab.active {
background: #1e1e1e;
border-color: #666;
}
body.theme-dark .tab-close {
color: #ccc;
}
body.theme-dark .tab-close:hover {
background: #555;
color: #fff;
}
body.theme-dark .new-tab-button {
border-color: #555;
color: #ccc;
}
body.theme-dark .new-tab-button:hover {
background: #3c3c3c;
}
body.theme-dark .toolbar {
background: #2d2d30;
border-bottom-color: #3e3e42;