mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-03 02:11:07 +05:30
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1e0072eea | ||
|
|
1b2afb5f15 | ||
|
|
c811af9d48 | ||
|
|
a6e91b7403 | ||
|
|
a44b13d2fb | ||
|
|
015446bff3 |
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
**PanConverter** is a cross-platform Markdown editor and converter powered by Pandoc, built with Electron. It provides professional-grade editing capabilities with comprehensive export options.
|
**PanConverter** is a cross-platform Markdown editor and converter powered by Pandoc, built with Electron. It provides professional-grade editing capabilities with comprehensive export options.
|
||||||
|
|
||||||
**Current Version**: v1.7.1
|
**Current Version**: v1.7.2
|
||||||
**Author**: Amit Haridas (amit.wh@gmail.com)
|
**Author**: Amit Haridas (amit.wh@gmail.com)
|
||||||
**License**: MIT
|
**License**: MIT
|
||||||
**Repository**: https://github.com/amitwh/pan-converter
|
**Repository**: https://github.com/amitwh/pan-converter
|
||||||
@@ -111,7 +111,41 @@ gh release create v1.2.1 --title "Title" --notes "Release notes" \
|
|||||||
|
|
||||||
## Feature Implementation Guide
|
## Feature Implementation Guide
|
||||||
|
|
||||||
### v1.7.1 Modern UI Design (Latest)
|
### v1.7.2 Enhanced Themes & Bug Fixes (Latest)
|
||||||
|
|
||||||
|
#### 🎨 14 New Beautiful Themes
|
||||||
|
**Expanded Theme Collection** (`src/main.js:242-266`, `src/styles.css:1590-2555`)
|
||||||
|
- **Added 14 new professionally-designed themes** bringing the total to 19 themes
|
||||||
|
- **New Themes Include**:
|
||||||
|
- **Dracula** - Popular purple and pink dark theme
|
||||||
|
- **Nord** - Arctic-inspired blue theme
|
||||||
|
- **One Dark** - Atom's iconic dark theme
|
||||||
|
- **Atom One Light** - Clean, bright light theme
|
||||||
|
- **Material** - Google Material Design inspired
|
||||||
|
- **Gruvbox Dark** - Warm retro groove colors (dark)
|
||||||
|
- **Gruvbox Light** - Warm retro groove colors (light)
|
||||||
|
- **Tokyo Night** - Modern Japanese-inspired dark theme
|
||||||
|
- **Palenight** - Soft purple Material Design variant
|
||||||
|
- **Ayu Dark** - Simple, elegant dark theme
|
||||||
|
- **Ayu Light** - Minimalist light theme
|
||||||
|
- **Ayu Mirage** - Balanced dark-light hybrid
|
||||||
|
- **Oceanic Next** - Ocean-inspired teal and blue
|
||||||
|
- **Cobalt2** - Deep blue with yellow accents
|
||||||
|
|
||||||
|
**Technical Implementation:**
|
||||||
|
- Complete CSS styling for all UI elements per theme
|
||||||
|
- Consistent theming across tabs, toolbar, editor, preview, and status bar
|
||||||
|
- Theme-aware color schemes for syntax highlighting
|
||||||
|
- All themes support glassmorphism effects from v1.7.1
|
||||||
|
|
||||||
|
#### 🐛 Undo/Redo Fix
|
||||||
|
**Fixed Menu Integration** (`src/renderer.js:1107-1118`)
|
||||||
|
- Fixed undo/redo menu items not connecting to custom undo/redo functionality
|
||||||
|
- Added IPC event listeners in renderer to receive 'undo' and 'redo' messages
|
||||||
|
- Connected Edit menu commands to TabManager's undo() and redo() methods
|
||||||
|
- Keyboard shortcuts (Ctrl+Z, Ctrl+Shift+Z) now work correctly with custom undo stack
|
||||||
|
|
||||||
|
### v1.7.1 Modern UI Design
|
||||||
|
|
||||||
#### 🎨 Glassmorphism & Gradient UI
|
#### 🎨 Glassmorphism & Gradient UI
|
||||||
**Modern Design System** (`src/styles-modern.css`, `src/index.html:7-11`)
|
**Modern Design System** (`src/styles-modern.css`, `src/index.html:7-11`)
|
||||||
@@ -566,4 +600,4 @@ gh release create v1.2.1 --title "Title" --notes "Release notes" \
|
|||||||
---
|
---
|
||||||
|
|
||||||
**Last Updated**: October 11, 2025
|
**Last Updated**: October 11, 2025
|
||||||
**Claude Assistant**: Development completed for v1.7.1 with modern glassmorphism UI design featuring animated gradient backgrounds, translucent glass effects, smooth animations, enhanced typography with Inter and JetBrains Mono fonts, and comprehensive CSS custom properties for consistent theming.
|
**Claude Assistant**: Development completed for v1.7.2 with 14 new professionally-designed themes (Dracula, Nord, Tokyo Night, Gruvbox, Ayu, Material, Oceanic Next, Palenight, Cobalt2, and more) bringing total to 19 themes, plus fixed undo/redo menu integration for proper keyboard shortcut support.
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pan-converter",
|
"name": "pan-converter",
|
||||||
"version": "1.7.1",
|
"version": "1.7.6",
|
||||||
"description": "Cross-platform Markdown editor and converter using Pandoc",
|
"description": "Cross-platform Markdown editor and converter using Pandoc",
|
||||||
"main": "src/main.js",
|
"main": "src/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
+74
-22
@@ -49,6 +49,41 @@ let currentFile = null; // This will now represent the active tab's file
|
|||||||
let pandocAvailable = null; // Cache pandoc availability check
|
let pandocAvailable = null; // Cache pandoc availability check
|
||||||
let rendererReady = false; // Track if renderer is ready to receive file data
|
let rendererReady = false; // Track if renderer is ready to receive file data
|
||||||
|
|
||||||
|
// Handle single instance lock for Windows file association
|
||||||
|
// When a file is double-clicked and the app is already running,
|
||||||
|
// Windows tries to start a second instance. We prevent this and
|
||||||
|
// pass the file to the existing instance instead.
|
||||||
|
const gotTheLock = app.requestSingleInstanceLock();
|
||||||
|
|
||||||
|
if (!gotTheLock) {
|
||||||
|
// Another instance is already running, quit this one
|
||||||
|
app.quit();
|
||||||
|
} else {
|
||||||
|
// This is the first instance, handle second-instance events
|
||||||
|
app.on('second-instance', (event, commandLine, workingDirectory) => {
|
||||||
|
// Someone tried to run a second instance, focus our window instead
|
||||||
|
if (mainWindow) {
|
||||||
|
if (mainWindow.isMinimized()) mainWindow.restore();
|
||||||
|
mainWindow.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a file was passed to the second instance
|
||||||
|
// commandLine is an array like: ['electron.exe', 'app.asar', 'file.md']
|
||||||
|
const fileArgs = commandLine.slice(2);
|
||||||
|
for (const arg of fileArgs) {
|
||||||
|
if ((arg.endsWith('.md') || arg.endsWith('.markdown')) && fs.existsSync(arg)) {
|
||||||
|
// Open the file in the existing instance
|
||||||
|
if (rendererReady) {
|
||||||
|
openFileFromPath(arg);
|
||||||
|
} else {
|
||||||
|
app.pendingFile = arg;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Check if pandoc is available
|
// Check if pandoc is available
|
||||||
function checkPandocAvailability() {
|
function checkPandocAvailability() {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
@@ -86,14 +121,8 @@ function createWindow() {
|
|||||||
// Wait for the page to fully load before sending file data
|
// Wait for the page to fully load before sending file data
|
||||||
mainWindow.webContents.on('did-finish-load', () => {
|
mainWindow.webContents.on('did-finish-load', () => {
|
||||||
console.log('Window finished loading');
|
console.log('Window finished loading');
|
||||||
// Give renderer a moment to initialize
|
// Don't open file here - wait for renderer-ready signal
|
||||||
setTimeout(() => {
|
// The renderer will send renderer-ready when TabManager is initialized
|
||||||
if (app.pendingFile && !rendererReady) {
|
|
||||||
console.log('Opening pending file after did-finish-load:', app.pendingFile);
|
|
||||||
openFileFromPath(app.pendingFile);
|
|
||||||
app.pendingFile = null;
|
|
||||||
}
|
|
||||||
}, 500); // Wait 500ms for TabManager to initialize
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,18 +237,26 @@ function createMenu() {
|
|||||||
{
|
{
|
||||||
label: 'Edit',
|
label: 'Edit',
|
||||||
submenu: [
|
submenu: [
|
||||||
{ label: 'Undo', accelerator: 'CmdOrCtrl+Z', role: 'undo' },
|
{
|
||||||
{ label: 'Redo', accelerator: 'CmdOrCtrl+Shift+Z', role: 'redo' },
|
label: 'Undo',
|
||||||
|
accelerator: 'CmdOrCtrl+Z',
|
||||||
|
click: () => mainWindow.webContents.send('undo')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Redo',
|
||||||
|
accelerator: 'CmdOrCtrl+Shift+Z',
|
||||||
|
click: () => mainWindow.webContents.send('redo')
|
||||||
|
},
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ label: 'Cut', accelerator: 'CmdOrCtrl+X', role: 'cut' },
|
{ label: 'Cut', accelerator: 'CmdOrCtrl+X', role: 'cut' },
|
||||||
{ label: 'Copy', accelerator: 'CmdOrCtrl+C', role: 'copy' },
|
{ label: 'Copy', accelerator: 'CmdOrCtrl+C', role: 'copy' },
|
||||||
{ label: 'Paste', accelerator: 'CmdOrCtrl+V', role: 'paste' },
|
{ label: 'Paste', accelerator: 'CmdOrCtrl+V', role: 'paste' },
|
||||||
{ label: 'Select All', accelerator: 'CmdOrCtrl+A', role: 'selectAll' },
|
{ label: 'Select All', accelerator: 'CmdOrCtrl+A', role: 'selectAll' },
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{
|
{
|
||||||
label: 'Find & Replace',
|
label: 'Find & Replace',
|
||||||
accelerator: 'CmdOrCtrl+F',
|
accelerator: 'CmdOrCtrl+F',
|
||||||
click: () => mainWindow.webContents.send('toggle-find')
|
click: () => mainWindow.webContents.send('toggle-find')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -238,7 +275,26 @@ function createMenu() {
|
|||||||
{ label: 'Dark', click: () => setTheme('dark') },
|
{ label: 'Dark', click: () => setTheme('dark') },
|
||||||
{ label: 'Solarized', click: () => setTheme('solarized') },
|
{ label: 'Solarized', click: () => setTheme('solarized') },
|
||||||
{ label: 'Monokai', click: () => setTheme('monokai') },
|
{ label: 'Monokai', click: () => setTheme('monokai') },
|
||||||
{ label: 'GitHub', click: () => setTheme('github') }
|
{ label: 'GitHub', click: () => setTheme('github') },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ label: 'Dracula', click: () => setTheme('dracula') },
|
||||||
|
{ label: 'Nord', click: () => setTheme('nord') },
|
||||||
|
{ label: 'One Dark', click: () => setTheme('onedark') },
|
||||||
|
{ label: 'Atom One Light', click: () => setTheme('atomonelight') },
|
||||||
|
{ label: 'Material', click: () => setTheme('material') },
|
||||||
|
{ label: 'Gruvbox Dark', click: () => setTheme('gruvbox-dark') },
|
||||||
|
{ label: 'Gruvbox Light', click: () => setTheme('gruvbox-light') },
|
||||||
|
{ label: 'Tokyo Night', click: () => setTheme('tokyonight') },
|
||||||
|
{ label: 'Palenight', click: () => setTheme('palenight') },
|
||||||
|
{ label: 'Ayu Dark', click: () => setTheme('ayu-dark') },
|
||||||
|
{ label: 'Ayu Light', click: () => setTheme('ayu-light') },
|
||||||
|
{ label: 'Ayu Mirage', click: () => setTheme('ayu-mirage') },
|
||||||
|
{ label: 'Oceanic Next', click: () => setTheme('oceanic-next') },
|
||||||
|
{ label: 'Cobalt2', click: () => setTheme('cobalt2') },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ label: 'Concrete Dark', click: () => setTheme('concrete-dark') },
|
||||||
|
{ label: 'Concrete Light', click: () => setTheme('concrete-light') },
|
||||||
|
{ label: 'Concrete Warm', click: () => setTheme('concrete-warm') }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
@@ -389,7 +445,7 @@ function createMenu() {
|
|||||||
type: 'info',
|
type: 'info',
|
||||||
title: 'About PanConverter',
|
title: 'About PanConverter',
|
||||||
message: 'PanConverter',
|
message: 'PanConverter',
|
||||||
detail: 'A cross-platform Markdown editor and converter using Pandoc.\n\nVersion: 1.7.1\nAuthor: Amit Haridas\nEmail: amit.wh@gmail.com\nLicense: MIT\n\nFeatures:\n• Modern glassmorphism UI with gradient backgrounds\n• Comprehensive PDF Editor (merge, split, compress, rotate, watermark, encrypt)\n• Universal File Converter (LibreOffice, ImageMagick, FFmpeg, Pandoc)\n• Windows Explorer context menu integration\n• Tabbed interface for multiple files\n• Advanced markdown editing with live preview\n• Real-time preview updates while typing\n• Full toolbar markdown editing functions\n• Enhanced PDF export with built-in Electron fallback\n• File association support for .md files\n• Command-line interface for batch conversion\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\n• Live word count and statistics',
|
detail: 'A cross-platform Markdown editor and converter using Pandoc.\n\nVersion: 1.7.4\nAuthor: Amit Haridas\nEmail: amit.wh@gmail.com\nLicense: MIT\n\nFeatures:\n• Modern glassmorphism UI with gradient backgrounds\n• Comprehensive PDF Editor (merge, split, compress, rotate, watermark, encrypt)\n• Universal File Converter (LibreOffice, ImageMagick, FFmpeg, Pandoc)\n• Windows Explorer context menu integration\n• Tabbed interface for multiple files\n• Advanced markdown editing with live preview\n• Real-time preview updates while typing\n• Full toolbar markdown editing functions\n• Enhanced PDF export with built-in Electron fallback\n• File association support for .md files\n• Command-line interface for batch conversion\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• 22 beautiful themes (including Dracula, Nord, Tokyo Night, Gruvbox, Ayu, Concrete, and more)\n• Undo/redo functionality\n• Live word count and statistics',
|
||||||
buttons: ['OK']
|
buttons: ['OK']
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1562,16 +1618,12 @@ function openFileFromPath(filePath) {
|
|||||||
if (fs.existsSync(filePath)) {
|
if (fs.existsSync(filePath)) {
|
||||||
currentFile = filePath;
|
currentFile = filePath;
|
||||||
const content = fs.readFileSync(filePath, 'utf-8');
|
const content = fs.readFileSync(filePath, 'utf-8');
|
||||||
if (mainWindow && mainWindow.webContents) {
|
if (mainWindow && mainWindow.webContents && rendererReady) {
|
||||||
console.log('Attempting to open file:', filePath, 'rendererReady:', rendererReady);
|
|
||||||
// Always try to send, the renderer will handle it when ready
|
|
||||||
mainWindow.webContents.send('file-opened', { path: filePath, content });
|
mainWindow.webContents.send('file-opened', { path: filePath, content });
|
||||||
} else {
|
} else {
|
||||||
// Store file to open after window is created
|
// Store file to open after renderer is ready
|
||||||
app.pendingFile = filePath;
|
app.pendingFile = filePath;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
console.error('File does not exist:', filePath);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+40
-1
@@ -949,6 +949,7 @@ class TabManager {
|
|||||||
|
|
||||||
// File operations
|
// File operations
|
||||||
openFile(filePath, content) {
|
openFile(filePath, content) {
|
||||||
|
console.log('openFile called with:', filePath, 'content length:', content.length);
|
||||||
let tab = this.tabs.get(this.activeTabId);
|
let tab = this.tabs.get(this.activeTabId);
|
||||||
|
|
||||||
// Handle both forward and back slashes for cross-platform compatibility
|
// Handle both forward and back slashes for cross-platform compatibility
|
||||||
@@ -956,13 +957,21 @@ class TabManager {
|
|||||||
|
|
||||||
// If current tab is empty and untitled, reuse it
|
// If current tab is empty and untitled, reuse it
|
||||||
if (!tab.filePath && !tab.isDirty && tab.content === '') {
|
if (!tab.filePath && !tab.isDirty && tab.content === '') {
|
||||||
|
console.log('Reusing current tab');
|
||||||
tab.filePath = filePath;
|
tab.filePath = filePath;
|
||||||
tab.title = fileName;
|
tab.title = fileName;
|
||||||
tab.content = content;
|
tab.content = content;
|
||||||
tab.originalContent = content;
|
tab.originalContent = content;
|
||||||
tab.isDirty = false;
|
tab.isDirty = false;
|
||||||
|
|
||||||
|
// Update the editor immediately
|
||||||
|
const editor = document.getElementById(`editor-${this.activeTabId}`);
|
||||||
|
if (editor) {
|
||||||
|
editor.value = content;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Create new tab for the file
|
// Create new tab for the file
|
||||||
|
console.log('Creating new tab for file');
|
||||||
this.createNewTab();
|
this.createNewTab();
|
||||||
tab = this.tabs.get(this.activeTabId);
|
tab = this.tabs.get(this.activeTabId);
|
||||||
tab.filePath = filePath;
|
tab.filePath = filePath;
|
||||||
@@ -970,12 +979,29 @@ class TabManager {
|
|||||||
tab.content = content;
|
tab.content = content;
|
||||||
tab.originalContent = content;
|
tab.originalContent = content;
|
||||||
tab.isDirty = false;
|
tab.isDirty = false;
|
||||||
|
|
||||||
|
// Wait a moment for the DOM to update, then set content
|
||||||
|
setTimeout(() => {
|
||||||
|
const editor = document.getElementById(`editor-${this.activeTabId}`);
|
||||||
|
if (editor) {
|
||||||
|
editor.value = content;
|
||||||
|
this.updatePreview(this.activeTabId);
|
||||||
|
this.updateWordCount();
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.restoreTabState(this.activeTabId);
|
this.updatePreview(this.activeTabId);
|
||||||
|
this.updateWordCount();
|
||||||
this.startAutoSave();
|
this.startAutoSave();
|
||||||
this.addToRecentFiles(filePath);
|
this.addToRecentFiles(filePath);
|
||||||
this.updateTabBar();
|
this.updateTabBar();
|
||||||
|
|
||||||
|
// Notify main process about current file for exports
|
||||||
|
const { ipcRenderer } = require('electron');
|
||||||
|
ipcRenderer.send('set-current-file', filePath);
|
||||||
|
|
||||||
|
console.log('File opened successfully');
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentContent() {
|
getCurrentContent() {
|
||||||
@@ -1078,6 +1104,19 @@ ipcRenderer.on('theme-changed', (event, theme) => {
|
|||||||
document.body.className = `theme-${theme}`;
|
document.body.className = `theme-${theme}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Undo/Redo handlers
|
||||||
|
ipcRenderer.on('undo', () => {
|
||||||
|
if (tabManager) {
|
||||||
|
tabManager.undo();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcRenderer.on('redo', () => {
|
||||||
|
if (tabManager) {
|
||||||
|
tabManager.redo();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Font size adjustment
|
// Font size adjustment
|
||||||
let currentFontSize = parseInt(localStorage.getItem('fontSize')) || 15;
|
let currentFontSize = parseInt(localStorage.getItem('fontSize')) || 15;
|
||||||
|
|
||||||
|
|||||||
+1241
-4
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user