mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-03 02:11:07 +05:30
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b93129bc44 | ||
|
|
b23e60a704 | ||
|
|
c559b34f3e | ||
|
|
a780f2984a | ||
|
|
0c9961d412 | ||
|
|
ed7a80f99e | ||
|
|
18b7d82610 | ||
|
|
cd1f7ac37d | ||
|
|
7b8a94150a | ||
|
|
2a12a1b034 | ||
|
|
2bc3989c00 | ||
|
|
978e642f0c | ||
|
|
00749ac52c | ||
|
|
35310beccf | ||
|
|
e1f0aafa88 |
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(git checkout:*)",
|
||||
"Bash(npm install)",
|
||||
"Bash(npm run build:*)",
|
||||
"Bash(rmdir:*)",
|
||||
"Bash(powershell:*)",
|
||||
"Bash(npx electron-builder:*)",
|
||||
"Bash(dir dist)",
|
||||
"Bash(git tag:*)",
|
||||
"Bash(git push:*)",
|
||||
"Bash(gh release create:*)",
|
||||
"Bash(gh auth:*)",
|
||||
"Bash(gh release delete:*)",
|
||||
"Bash(gh release list:*)",
|
||||
"Bash(gh release upload:*)",
|
||||
"Bash(npm install:*)",
|
||||
"Bash(npm uninstall:*)",
|
||||
"Bash(git add:*)",
|
||||
"Bash(git commit:*)"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
name: New MCP server
|
||||
version: 0.0.1
|
||||
schema: v1
|
||||
mcpServers:
|
||||
- name: New MCP server
|
||||
command: npx
|
||||
args:
|
||||
- -y
|
||||
- <your-mcp-server>
|
||||
env: {}
|
||||
@@ -0,0 +1,103 @@
|
||||
# Export Functionality Fix - Summary
|
||||
|
||||
## Issues Found and Fixed
|
||||
|
||||
### 1. **Primary Issue: Pandoc Installation Problem**
|
||||
- **Problem**: Pandoc is installed but has a system error (paging file too small)
|
||||
- **Impact**: All pandoc-dependent exports (DOCX, LaTeX, etc.) were failing
|
||||
- **Solution**: Added robust fallback mechanisms
|
||||
|
||||
### 2. **Export Function Improvements**
|
||||
|
||||
#### Before (Issues):
|
||||
- ❌ No pandoc availability checking
|
||||
- ❌ Poor error messages
|
||||
- ❌ No fallback for missing pandoc
|
||||
- ❌ Limited debugging information
|
||||
|
||||
#### After (Fixed):
|
||||
- ✅ **Pandoc Detection**: Automatically checks if pandoc is available
|
||||
- ✅ **Built-in HTML Export**: Works without pandoc using marked library
|
||||
- ✅ **Built-in PDF Export**: Works without pandoc using Electron's printToPDF
|
||||
- ✅ **Better Error Messages**: Clear instructions for users
|
||||
- ✅ **Comprehensive Logging**: Debug information in console
|
||||
- ✅ **Graceful Fallbacks**: Falls back to built-in converters when pandoc fails
|
||||
|
||||
## How It Works Now
|
||||
|
||||
### Export Process Flow:
|
||||
1. **User clicks export** → Check if file is saved
|
||||
2. **Select output location** → Show save dialog
|
||||
3. **Check pandoc availability** → Async pandoc detection
|
||||
4. **Choose export method**:
|
||||
- **If pandoc available**: Use pandoc with format-specific options
|
||||
- **If pandoc not available**:
|
||||
- HTML → Use built-in marked converter
|
||||
- PDF → Use Electron's printToPDF
|
||||
- Other formats → Show helpful error with installation guide
|
||||
|
||||
### Supported Export Formats:
|
||||
|
||||
#### ✅ **Always Work** (no pandoc required):
|
||||
- **HTML**: Built-in converter using marked library
|
||||
- **PDF**: Built-in converter using Electron
|
||||
|
||||
#### ✅ **Work with Pandoc** (better quality):
|
||||
- **DOCX**: Microsoft Word format
|
||||
- **LaTeX**: LaTeX document
|
||||
- **RTF**: Rich Text Format
|
||||
- **ODT**: OpenDocument Text
|
||||
- **EPUB**: E-book format
|
||||
- **PPTX**: PowerPoint presentations
|
||||
- **ODP**: OpenDocument Presentations
|
||||
|
||||
## Testing the Fixes
|
||||
|
||||
### Manual Test Procedure:
|
||||
1. **Start the application**: `npm start`
|
||||
2. **Open test file**: Load `test-export.md`
|
||||
3. **Test HTML export**: File → Export → HTML (should work)
|
||||
4. **Test PDF export**: File → Export → PDF (should work)
|
||||
5. **Test DOCX export**: File → Export → DOCX (will show pandoc error)
|
||||
|
||||
### Expected Behavior:
|
||||
- **HTML/PDF exports**: Should work immediately and create files
|
||||
- **Other format exports**: Should show informative error about pandoc
|
||||
- **Console logs**: Should show debug information about export process
|
||||
|
||||
## Fix Summary
|
||||
|
||||
### Code Changes Made:
|
||||
1. **Added `checkPandocAvailability()` function** - Detects pandoc
|
||||
2. **Added `exportToHTML()` function** - Built-in HTML export
|
||||
3. **Added `exportToPDFElectron()` function** - Built-in PDF export
|
||||
4. **Added `exportWithPandoc()` helper** - Generic pandoc export
|
||||
5. **Added `exportWithPandocPDF()` helper** - PDF with fallbacks
|
||||
6. **Improved `exportFile()` function** - Main export logic with detection
|
||||
7. **Enhanced error handling** - Better user messages
|
||||
8. **Added comprehensive logging** - Debug information
|
||||
|
||||
### Files Modified:
|
||||
- `src/main.js` - Enhanced export functionality
|
||||
- `test-export.md` - Created test file
|
||||
- `test-export-functionality.js` - Created test script
|
||||
|
||||
## User Instructions
|
||||
|
||||
### For Users Without Pandoc:
|
||||
- ✅ **HTML and PDF exports work perfectly**
|
||||
- ✅ **No additional software needed**
|
||||
- ✅ **Professional-looking output with proper styling**
|
||||
|
||||
### For Users Who Want All Formats:
|
||||
1. **Install Pandoc**: Visit https://pandoc.org/installing.html
|
||||
2. **For PDF with LaTeX**: Also install MiKTeX or TeX Live
|
||||
3. **Restart the application** after installation
|
||||
4. **All export formats will then be available**
|
||||
|
||||
## Result
|
||||
🎉 **Export functionality is now working reliably!**
|
||||
- Built-in exports (HTML, PDF) work without any dependencies
|
||||
- Clear error messages guide users for advanced formats
|
||||
- Robust error handling prevents crashes
|
||||
- Better user experience with informative dialogs
|
||||
@@ -0,0 +1,82 @@
|
||||
# GEMINI.md
|
||||
|
||||
## Project Overview
|
||||
|
||||
This project is a cross-platform Markdown editor and converter named **PanConverter**. It is built using the Electron framework, allowing it to run on Windows, macOS, and Linux. The application provides a rich text editor for writing Markdown, a live preview pane, and robust export capabilities powered by Pandoc.
|
||||
|
||||
The core technologies used are:
|
||||
- **Electron:** For creating the desktop application.
|
||||
- **JavaScript:** The primary programming language.
|
||||
- **Pandoc:** For converting Markdown to various formats like PDF, DOCX, HTML, etc.
|
||||
- **Marked:** For parsing and rendering the Markdown preview in real-time.
|
||||
- **CodeMirror:** As the underlying text editor component.
|
||||
- **highlight.js:** For syntax highlighting in the editor.
|
||||
- **DOMPurify:** To sanitize the HTML output in the preview pane for security.
|
||||
|
||||
The application features a tabbed interface for working with multiple files, various themes, find and replace functionality, and detailed document statistics.
|
||||
|
||||
## Building and Running
|
||||
|
||||
To build and run this project locally, you will need to have Node.js and npm installed.
|
||||
|
||||
### Installation
|
||||
|
||||
1. **Clone the repository:**
|
||||
```bash
|
||||
git clone https://github.com/amitwh/pan-converter.git
|
||||
cd pan-converter
|
||||
```
|
||||
|
||||
2. **Install dependencies:**
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
### Running the Application
|
||||
|
||||
To run the application in development mode, use the following command:
|
||||
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
### Building the Application
|
||||
|
||||
You can build the application for different platforms using the scripts defined in `package.json`.
|
||||
|
||||
- **Build for the current platform:**
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
- **Build for a specific platform:**
|
||||
```bash
|
||||
npm run build:win # For Windows
|
||||
npm run build:mac # For macOS
|
||||
npm run build:linux # For Linux
|
||||
```
|
||||
|
||||
- **Build for all platforms at once:**
|
||||
```bash
|
||||
npm run dist:all
|
||||
```
|
||||
|
||||
The distributable files will be located in the `dist/` directory.
|
||||
|
||||
### Testing
|
||||
|
||||
The project does not have a dedicated test suite configured. The `test` script in `package.json` currently returns an error.
|
||||
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
|
||||
## Development Conventions
|
||||
|
||||
- **Code Style:** The codebase is written in JavaScript (ES6+). There is no linter or formatter configured, but the code generally follows standard JavaScript conventions.
|
||||
- **Main vs. Renderer Process:** The application logic is split between the Electron main process (`src/main.js`) and the renderer process (`src/renderer.js`).
|
||||
- `src/main.js` handles window management, application menus, file system operations, and communication with the operating system.
|
||||
- `src/renderer.js` manages the user interface, editor functionality, and the Markdown preview.
|
||||
- **Dependencies:** Project dependencies are managed through `package.json`. `devDependencies` are used for the build process, while `dependencies` are required for the application to run.
|
||||
- **User Data:** The application stores settings and recent files in the user's application data directory.
|
||||
- **Pandoc Integration:** The application relies on a system-installed version of Pandoc for its export functionality. It does not bundle Pandoc.
|
||||
@@ -54,8 +54,8 @@ Download the latest release for your platform from the [Releases](https://github
|
||||
|
||||
#### Linux
|
||||
- **AppImage**: `PanConverter-1.5.0.AppImage` (universal, may require `--no-sandbox` flag)
|
||||
- **Debian Package**: `pan-converter_1.5.0_amd64.deb`
|
||||
- **Snap Package**: `pan-converter_1.5.0_amd64.snap`
|
||||
- **Debian Package**: `pan-converter_1.5.6_amd64.deb`
|
||||
- **Snap Package**: `pan-converter_1.5.6_amd64.snap`
|
||||
|
||||
### Install from Source
|
||||
```bash
|
||||
@@ -138,6 +138,8 @@ npm run dist:all
|
||||
|
||||
## Version History
|
||||
|
||||
- **v1.5.6** - (Your release notes here)
|
||||
- **v1.5.5** - Refactored PDF export, simplified Pandoc pathing, and removed XLSX dependency.
|
||||
- **v1.3.1** - Bug fixes: Fixed file associations for double-clicking .md files, corrected 50/50 layout alignment for editor/preview panes
|
||||
- **v1.3.0** - Major update: Tabbed interface for multiple files, enhanced PDF export with LaTeX engines, fixed file associations, removed redundant converter menu, improved UI architecture
|
||||
- **v1.2.1** - Comprehensive editor enhancements: Find & Replace, Line Numbers, Undo/Redo, Auto-indentation, PowerPoint export, document conversion menu, table creation helper, spreadsheet export
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 5.5 KiB |
+19
-14
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pan-converter",
|
||||
"version": "1.5.0",
|
||||
"version": "1.6.0",
|
||||
"description": "Cross-platform Markdown editor and converter using Pandoc",
|
||||
"main": "src/main.js",
|
||||
"scripts": {
|
||||
@@ -31,10 +31,13 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"codemirror": "^6.0.2",
|
||||
"convertapi": "^1.15.0",
|
||||
"dompurify": "^3.2.6",
|
||||
"electron-store": "^10.1.0",
|
||||
"highlight.js": "^11.11.1",
|
||||
"marked": "^16.2.1"
|
||||
"marked": "^16.2.1",
|
||||
"tslib": "^2.8.1",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"build": {
|
||||
"appId": "com.panconverter.app",
|
||||
@@ -42,9 +45,11 @@
|
||||
"directories": {
|
||||
"output": "dist"
|
||||
},
|
||||
"icon": "assets/icon",
|
||||
"files": [
|
||||
"src/**/*",
|
||||
"assets/**/*",
|
||||
"scripts/**/*",
|
||||
"node_modules/**/*",
|
||||
"package.json"
|
||||
],
|
||||
@@ -65,37 +70,38 @@
|
||||
}
|
||||
],
|
||||
"mac": {
|
||||
"category": "public.app-category.productivity",
|
||||
"icon": "assets/icon.icns"
|
||||
"category": "public.app-category.productivity"
|
||||
},
|
||||
"win": {
|
||||
"target": [
|
||||
{
|
||||
"target": "nsis",
|
||||
"arch": ["x64"]
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "portable",
|
||||
"arch": ["x64"]
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "zip",
|
||||
"arch": ["x64"]
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
}
|
||||
],
|
||||
"icon": "assets/icon.png",
|
||||
"artifactName": "${productName}-${version}-${arch}.${ext}",
|
||||
"requestedExecutionLevel": "asInvoker",
|
||||
"signAndEditExecutable": true
|
||||
"signAndEditExecutable": false
|
||||
},
|
||||
"nsis": {
|
||||
"oneClick": false,
|
||||
"perMachine": false,
|
||||
"allowToChangeInstallationDirectory": true,
|
||||
"displayLanguageSelector": true,
|
||||
"installerIcon": "assets/icon-proper.ico",
|
||||
"uninstallerIcon": "assets/icon-proper.ico",
|
||||
"installerHeaderIcon": "assets/icon-proper.ico",
|
||||
"createDesktopShortcut": true,
|
||||
"createStartMenuShortcut": true,
|
||||
"shortcutName": "PanConverter",
|
||||
@@ -113,8 +119,7 @@
|
||||
"AppImage",
|
||||
"snap"
|
||||
],
|
||||
"category": "Utility",
|
||||
"icon": "assets/icon.png"
|
||||
"category": "Utility"
|
||||
},
|
||||
"deb": {
|
||||
"depends": [
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
# PanConverter Windows Explorer Context Menu Integration
|
||||
|
||||
This directory contains scripts to add PanConverter to the Windows Explorer context menu, allowing you to right-click on files and convert them directly without opening the full application.
|
||||
|
||||
## Features
|
||||
|
||||
### Context Menu Options
|
||||
- **Convert with PanConverter**: Shows a format selection dialog for any supported file
|
||||
- **PanConverter > Convert to...**: Direct conversion options (for Markdown files)
|
||||
- PDF
|
||||
- HTML
|
||||
- DOCX
|
||||
- LaTeX
|
||||
- PowerPoint
|
||||
|
||||
### Supported File Types
|
||||
- **Markdown**: `.md`, `.markdown`
|
||||
- **HTML**: `.html`, `.htm`
|
||||
- **Documents**: `.docx`, `.odt`, `.rtf`
|
||||
- **LaTeX**: `.tex`
|
||||
- **PDF**: `.pdf`
|
||||
- **Presentations**: `.pptx`, `.ppt`, `.odp`
|
||||
|
||||
## Installation Methods
|
||||
|
||||
### Method 1: Automatic (During PanConverter Installation)
|
||||
When installing PanConverter using the Windows installer, you'll be prompted to install context menu integration automatically.
|
||||
|
||||
### Method 2: Manual Installation
|
||||
|
||||
#### Using PowerShell (Recommended)
|
||||
1. Right-click on `install-context-menu.ps1`
|
||||
2. Select "Run with PowerShell"
|
||||
3. Follow the prompts (will request administrator privileges)
|
||||
|
||||
#### Using Batch File
|
||||
1. Right-click on `install-context-menu.bat`
|
||||
2. Select "Run as administrator"
|
||||
3. Follow the prompts
|
||||
|
||||
#### Using Registry File Directly
|
||||
1. Right-click on `install-context-menu.reg`
|
||||
2. Select "Merge"
|
||||
3. Confirm the registry modification
|
||||
|
||||
## Uninstallation
|
||||
|
||||
### Using PowerShell
|
||||
```powershell
|
||||
.\install-context-menu.ps1 -Uninstall
|
||||
```
|
||||
|
||||
### Using Batch File
|
||||
Run `uninstall-context-menu.bat` as administrator
|
||||
|
||||
### Using Registry File
|
||||
Run `uninstall-context-menu.reg`
|
||||
|
||||
## How It Works
|
||||
|
||||
### Command Line Interface
|
||||
The context menu integration works by passing command line arguments to PanConverter:
|
||||
|
||||
- `--convert <file>`: Shows conversion dialog for the specified file
|
||||
- `--convert-to <format> <file>`: Directly converts to the specified format
|
||||
|
||||
### Examples
|
||||
```batch
|
||||
# Show conversion dialog
|
||||
PanConverter.exe --convert "document.md"
|
||||
|
||||
# Direct conversion to PDF
|
||||
PanConverter.exe --convert-to pdf "document.md"
|
||||
```
|
||||
|
||||
### Conversion Process
|
||||
1. PanConverter reads the input file
|
||||
2. Creates a temporary file if needed
|
||||
3. Uses Pandoc to convert to the target format
|
||||
4. Saves the output in the same directory as the input file
|
||||
5. Shows a Windows notification when complete
|
||||
|
||||
## Requirements
|
||||
|
||||
- PanConverter must be installed in the default location: `%LOCALAPPDATA%\Programs\PanConverter\`
|
||||
- Pandoc must be installed and accessible from the command line
|
||||
- Administrator privileges required for registry modifications
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Context Menu Not Appearing
|
||||
1. Ensure you ran the installation script as administrator
|
||||
2. Try logging out and back in, or restart Windows Explorer
|
||||
3. Check if PanConverter is installed in the expected location
|
||||
|
||||
### Conversion Failures
|
||||
1. Verify Pandoc is installed: `pandoc --version`
|
||||
2. Check if the input file is not corrupted or locked
|
||||
3. Ensure you have write permissions in the output directory
|
||||
|
||||
### Permission Errors
|
||||
- The installation scripts require administrator privileges to modify the registry
|
||||
- If you get permission errors, right-click and "Run as administrator"
|
||||
|
||||
## File Descriptions
|
||||
|
||||
- `install-context-menu.reg`: Registry entries for context menu
|
||||
- `uninstall-context-menu.reg`: Registry entries removal
|
||||
- `install-context-menu.ps1`: PowerShell installation script
|
||||
- `install-context-menu.bat`: Batch installation script
|
||||
- `uninstall-context-menu.bat`: Batch uninstallation script
|
||||
- `nsis-installer.nsh`: NSIS installer integration script
|
||||
|
||||
## Security Note
|
||||
|
||||
The scripts modify the Windows registry to add context menu entries. Only run these scripts if you trust the source and understand the changes being made to your system.
|
||||
@@ -0,0 +1,48 @@
|
||||
@echo off
|
||||
echo PanConverter Context Menu Installation
|
||||
echo =====================================
|
||||
echo.
|
||||
|
||||
REM Check for administrator privileges
|
||||
net session >nul 2>&1
|
||||
if %errorLevel% == 0 (
|
||||
echo Running with administrator privileges...
|
||||
) else (
|
||||
echo This script requires administrator privileges.
|
||||
echo Please right-click and select "Run as administrator"
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Check if PanConverter is installed
|
||||
if not exist "%LOCALAPPDATA%\Programs\PanConverter\PanConverter.exe" (
|
||||
echo Warning: PanConverter not found at the expected location.
|
||||
echo Please ensure PanConverter is installed before continuing.
|
||||
echo Expected location: %LOCALAPPDATA%\Programs\PanConverter\PanConverter.exe
|
||||
echo.
|
||||
set /p continue="Continue anyway? (y/N): "
|
||||
if /i not "%continue%"=="y" exit /b 1
|
||||
)
|
||||
|
||||
echo Installing context menu entries...
|
||||
reg import "%~dp0install-context-menu.reg"
|
||||
|
||||
if %errorLevel% == 0 (
|
||||
echo.
|
||||
echo Context menu integration installed successfully!
|
||||
echo.
|
||||
echo You can now right-click on supported files and select:
|
||||
echo • "Convert with PanConverter" - Shows conversion dialog
|
||||
echo • "PanConverter > Convert to..." - Direct conversion ^(for Markdown^)
|
||||
echo.
|
||||
echo Supported file types:
|
||||
echo .md .markdown .html .htm .docx .odt .rtf .tex .pdf .pptx .ppt .odp
|
||||
) else (
|
||||
echo.
|
||||
echo Failed to install context menu entries.
|
||||
echo Please check that the registry file exists and try again.
|
||||
)
|
||||
|
||||
echo.
|
||||
pause
|
||||
@@ -0,0 +1,67 @@
|
||||
# PanConverter Context Menu Installation Script
|
||||
# This script installs PanConverter context menu integration for Windows Explorer
|
||||
|
||||
param(
|
||||
[switch]$Uninstall = $false
|
||||
)
|
||||
|
||||
# Check if running as administrator
|
||||
if (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
|
||||
Write-Host "This script requires Administrator privileges. Restarting with elevated permissions..." -ForegroundColor Yellow
|
||||
Start-Process PowerShell -Verb RunAs "-File `"$PSCommandPath`" $(if ($Uninstall) { '-Uninstall' })"
|
||||
exit
|
||||
}
|
||||
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$appPath = "${env:LOCALAPPDATA}\Programs\PanConverter\PanConverter.exe"
|
||||
|
||||
if ($Uninstall) {
|
||||
Write-Host "Uninstalling PanConverter context menu integration..." -ForegroundColor Yellow
|
||||
$regFile = Join-Path $scriptDir "uninstall-context-menu.reg"
|
||||
|
||||
if (Test-Path $regFile) {
|
||||
reg import $regFile
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Host "PanConverter context menu has been successfully removed!" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "Failed to remove context menu entries." -ForegroundColor Red
|
||||
}
|
||||
} else {
|
||||
Write-Host "Uninstall registry file not found: $regFile" -ForegroundColor Red
|
||||
}
|
||||
} else {
|
||||
Write-Host "Installing PanConverter context menu integration..." -ForegroundColor Yellow
|
||||
|
||||
# Check if PanConverter is installed
|
||||
if (-not (Test-Path $appPath)) {
|
||||
Write-Host "Warning: PanConverter executable not found at: $appPath" -ForegroundColor Yellow
|
||||
Write-Host "Please make sure PanConverter is installed before running this script." -ForegroundColor Yellow
|
||||
$continue = Read-Host "Continue anyway? (y/N)"
|
||||
if ($continue -ne 'y' -and $continue -ne 'Y') {
|
||||
exit
|
||||
}
|
||||
}
|
||||
|
||||
$regFile = Join-Path $scriptDir "install-context-menu.reg"
|
||||
|
||||
if (Test-Path $regFile) {
|
||||
reg import $regFile
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Host "PanConverter context menu has been successfully installed!" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "You can now right-click on supported files (MD, HTML, DOCX, PDF, etc.) and select:" -ForegroundColor Cyan
|
||||
Write-Host "• 'Convert with PanConverter' - Shows conversion dialog" -ForegroundColor Cyan
|
||||
Write-Host "• 'PanConverter > Convert to...' - Direct conversion options (for Markdown files)" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "Supported file types: .md, .markdown, .html, .htm, .docx, .odt, .rtf, .tex, .pdf, .pptx, .ppt, .odp" -ForegroundColor Gray
|
||||
} else {
|
||||
Write-Host "Failed to install context menu entries." -ForegroundColor Red
|
||||
}
|
||||
} else {
|
||||
Write-Host "Install registry file not found: $regFile" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Press any key to exit..."
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
@@ -0,0 +1,134 @@
|
||||
Windows Registry Editor Version 5.00
|
||||
|
||||
; PanConverter Context Menu Integration
|
||||
; This registry file adds context menu options for converting files using PanConverter
|
||||
|
||||
; Add context menu for Markdown files (.md, .markdown)
|
||||
[HKEY_CLASSES_ROOT\.md\shell\PanConverter]
|
||||
@="Convert with PanConverter"
|
||||
"Icon"="\"%LOCALAPPDATA%\\Programs\\PanConverter\\PanConverter.exe\""
|
||||
|
||||
[HKEY_CLASSES_ROOT\.md\shell\PanConverter\command]
|
||||
@="\"%LOCALAPPDATA%\\Programs\\PanConverter\\PanConverter.exe\" --convert \"%1\""
|
||||
|
||||
[HKEY_CLASSES_ROOT\.markdown\shell\PanConverter]
|
||||
@="Convert with PanConverter"
|
||||
"Icon"="\"%LOCALAPPDATA%\\Programs\\PanConverter\\PanConverter.exe\""
|
||||
|
||||
[HKEY_CLASSES_ROOT\.markdown\shell\PanConverter\command]
|
||||
@="\"%LOCALAPPDATA%\\Programs\\PanConverter\\PanConverter.exe\" --convert \"%1\""
|
||||
|
||||
; Add context menu for HTML files
|
||||
[HKEY_CLASSES_ROOT\.html\shell\PanConverter]
|
||||
@="Convert with PanConverter"
|
||||
"Icon"="\"%LOCALAPPDATA%\\Programs\\PanConverter\\PanConverter.exe\""
|
||||
|
||||
[HKEY_CLASSES_ROOT\.html\shell\PanConverter\command]
|
||||
@="\"%LOCALAPPDATA%\\Programs\\PanConverter\\PanConverter.exe\" --convert \"%1\""
|
||||
|
||||
[HKEY_CLASSES_ROOT\.htm\shell\PanConverter]
|
||||
@="Convert with PanConverter"
|
||||
"Icon"="\"%LOCALAPPDATA%\\Programs\\PanConverter\\PanConverter.exe\""
|
||||
|
||||
[HKEY_CLASSES_ROOT\.htm\shell\PanConverter\command]
|
||||
@="\"%LOCALAPPDATA%\\Programs\\PanConverter\\PanConverter.exe\" --convert \"%1\""
|
||||
|
||||
; Add context menu for DOCX files
|
||||
[HKEY_CLASSES_ROOT\.docx\shell\PanConverter]
|
||||
@="Convert with PanConverter"
|
||||
"Icon"="\"%LOCALAPPDATA%\\Programs\\PanConverter\\PanConverter.exe\""
|
||||
|
||||
[HKEY_CLASSES_ROOT\.docx\shell\PanConverter\command]
|
||||
@="\"%LOCALAPPDATA%\\Programs\\PanConverter\\PanConverter.exe\" --convert \"%1\""
|
||||
|
||||
; Add context menu for ODT files
|
||||
[HKEY_CLASSES_ROOT\.odt\shell\PanConverter]
|
||||
@="Convert with PanConverter"
|
||||
"Icon"="\"%LOCALAPPDATA%\\Programs\\PanConverter\\PanConverter.exe\""
|
||||
|
||||
[HKEY_CLASSES_ROOT\.odt\shell\PanConverter\command]
|
||||
@="\"%LOCALAPPDATA%\\Programs\\PanConverter\\PanConverter.exe\" --convert \"%1\""
|
||||
|
||||
; Add context menu for RTF files
|
||||
[HKEY_CLASSES_ROOT\.rtf\shell\PanConverter]
|
||||
@="Convert with PanConverter"
|
||||
"Icon"="\"%LOCALAPPDATA%\\Programs\\PanConverter\\PanConverter.exe\""
|
||||
|
||||
[HKEY_CLASSES_ROOT\.rtf\shell\PanConverter\command]
|
||||
@="\"%LOCALAPPDATA%\\Programs\\PanConverter\\PanConverter.exe\" --convert \"%1\""
|
||||
|
||||
; Add context menu for LaTeX files
|
||||
[HKEY_CLASSES_ROOT\.tex\shell\PanConverter]
|
||||
@="Convert with PanConverter"
|
||||
"Icon"="\"%LOCALAPPDATA%\\Programs\\PanConverter\\PanConverter.exe\""
|
||||
|
||||
[HKEY_CLASSES_ROOT\.tex\shell\PanConverter\command]
|
||||
@="\"%LOCALAPPDATA%\\Programs\\PanConverter\\PanConverter.exe\" --convert \"%1\""
|
||||
|
||||
; Add context menu for PDF files
|
||||
[HKEY_CLASSES_ROOT\.pdf\shell\PanConverter]
|
||||
@="Convert with PanConverter"
|
||||
"Icon"="\"%LOCALAPPDATA%\\Programs\\PanConverter\\PanConverter.exe\""
|
||||
|
||||
[HKEY_CLASSES_ROOT\.pdf\shell\PanConverter\command]
|
||||
@="\"%LOCALAPPDATA%\\Programs\\PanConverter\\PanConverter.exe\" --convert \"%1\""
|
||||
|
||||
; Add context menu for PowerPoint files
|
||||
[HKEY_CLASSES_ROOT\.pptx\shell\PanConverter]
|
||||
@="Convert with PanConverter"
|
||||
"Icon"="\"%LOCALAPPDATA%\\Programs\\PanConverter\\PanConverter.exe\""
|
||||
|
||||
[HKEY_CLASSES_ROOT\.pptx\shell\PanConverter\command]
|
||||
@="\"%LOCALAPPDATA%\\Programs\\PanConverter\\PanConverter.exe\" --convert \"%1\""
|
||||
|
||||
[HKEY_CLASSES_ROOT\.ppt\shell\PanConverter]
|
||||
@="Convert with PanConverter"
|
||||
"Icon"="\"%LOCALAPPDATA%\\Programs\\PanConverter\\PanConverter.exe\""
|
||||
|
||||
[HKEY_CLASSES_ROOT\.ppt\shell\PanConverter\command]
|
||||
@="\"%LOCALAPPDATA%\\Programs\\PanConverter\\PanConverter.exe\" --convert \"%1\""
|
||||
|
||||
; Add context menu for OpenDocument Presentation files
|
||||
[HKEY_CLASSES_ROOT\.odp\shell\PanConverter]
|
||||
@="Convert with PanConverter"
|
||||
"Icon"="\"%LOCALAPPDATA%\\Programs\\PanConverter\\PanConverter.exe\""
|
||||
|
||||
[HKEY_CLASSES_ROOT\.odp\shell\PanConverter\command]
|
||||
@="\"%LOCALAPPDATA%\\Programs\\PanConverter\\PanConverter.exe\" --convert \"%1\""
|
||||
|
||||
; Add submenu with specific conversion options for Markdown files
|
||||
[HKEY_CLASSES_ROOT\.md\shell\PanConverterMenu]
|
||||
@="PanConverter"
|
||||
"MUIVerb"="Convert to..."
|
||||
"Icon"="\"%LOCALAPPDATA%\\Programs\\PanConverter\\PanConverter.exe\""
|
||||
"SubCommands"=""
|
||||
|
||||
[HKEY_CLASSES_ROOT\.md\shell\PanConverterMenu\shell\PDF]
|
||||
@="PDF"
|
||||
|
||||
[HKEY_CLASSES_ROOT\.md\shell\PanConverterMenu\shell\PDF\command]
|
||||
@="\"%LOCALAPPDATA%\\Programs\\PanConverter\\PanConverter.exe\" --convert-to pdf \"%1\""
|
||||
|
||||
[HKEY_CLASSES_ROOT\.md\shell\PanConverterMenu\shell\HTML]
|
||||
@="HTML"
|
||||
|
||||
[HKEY_CLASSES_ROOT\.md\shell\PanConverterMenu\shell\HTML\command]
|
||||
@="\"%LOCALAPPDATA%\\Programs\\PanConverter\\PanConverter.exe\" --convert-to html \"%1\""
|
||||
|
||||
[HKEY_CLASSES_ROOT\.md\shell\PanConverterMenu\shell\DOCX]
|
||||
@="DOCX"
|
||||
|
||||
[HKEY_CLASSES_ROOT\.md\shell\PanConverterMenu\shell\DOCX\command]
|
||||
@="\"%LOCALAPPDATA%\\Programs\\PanConverter\\PanConverter.exe\" --convert-to docx \"%1\""
|
||||
|
||||
[HKEY_CLASSES_ROOT\.md\shell\PanConverterMenu\shell\LaTeX]
|
||||
@="LaTeX"
|
||||
|
||||
[HKEY_CLASSES_ROOT\.md\shell\PanConverterMenu\shell\LaTeX\command]
|
||||
@="\"%LOCALAPPDATA%\\Programs\\PanConverter\\PanConverter.exe\" --convert-to latex \"%1\""
|
||||
|
||||
[HKEY_CLASSES_ROOT\.md\shell\PanConverterMenu\shell\PPTX]
|
||||
@="PowerPoint"
|
||||
|
||||
[HKEY_CLASSES_ROOT\.md\shell\PanConverterMenu\shell\PPTX\command]
|
||||
@="\"%LOCALAPPDATA%\\Programs\\PanConverter\\PanConverter.exe\" --convert-to pptx \"%1\""
|
||||
@@ -0,0 +1,145 @@
|
||||
; PanConverter NSIS Installer Include File
|
||||
; Handles context menu installation and uninstallation
|
||||
|
||||
!include "LogicLib.nsh"
|
||||
!include "MUI2.nsh"
|
||||
|
||||
; Custom installation page for context menu option
|
||||
Var ContextMenuCheckbox
|
||||
Var ContextMenuState
|
||||
|
||||
Function ContextMenuPage
|
||||
!insertmacro MUI_HEADER_TEXT "Additional Options" "Choose additional installation options"
|
||||
|
||||
nsDialogs::Create 1018
|
||||
Pop $0
|
||||
|
||||
${If} $0 == error
|
||||
Abort
|
||||
${EndIf}
|
||||
|
||||
${NSD_CreateLabel} 0 0 100% 20u "Select additional features to install:"
|
||||
Pop $0
|
||||
|
||||
${NSD_CreateCheckbox} 20u 30u 280u 15u "Add PanConverter to Windows Explorer context menu"
|
||||
Pop $ContextMenuCheckbox
|
||||
${NSD_SetState} $ContextMenuCheckbox ${BST_CHECKED}
|
||||
|
||||
${NSD_CreateLabel} 20u 50u 280u 30u "This will allow you to right-click on supported files and convert them directly using PanConverter."
|
||||
Pop $0
|
||||
|
||||
nsDialogs::Show
|
||||
FunctionEnd
|
||||
|
||||
Function ContextMenuPageLeave
|
||||
${NSD_GetState} $ContextMenuCheckbox $ContextMenuState
|
||||
FunctionEnd
|
||||
|
||||
; Install context menu entries
|
||||
Function InstallContextMenu
|
||||
${If} $ContextMenuState == ${BST_CHECKED}
|
||||
DetailPrint "Installing context menu integration..."
|
||||
|
||||
; Create registry entries for context menu
|
||||
WriteRegStr HKCR ".md\shell\PanConverter" "" "Convert with PanConverter"
|
||||
WriteRegStr HKCR ".md\shell\PanConverter" "Icon" "$INSTDIR\PanConverter.exe"
|
||||
WriteRegStr HKCR ".md\shell\PanConverter\command" "" '"$INSTDIR\PanConverter.exe" --convert "%1"'
|
||||
|
||||
WriteRegStr HKCR ".markdown\shell\PanConverter" "" "Convert with PanConverter"
|
||||
WriteRegStr HKCR ".markdown\shell\PanConverter" "Icon" "$INSTDIR\PanConverter.exe"
|
||||
WriteRegStr HKCR ".markdown\shell\PanConverter\command" "" '"$INSTDIR\PanConverter.exe" --convert "%1"'
|
||||
|
||||
; Context menu for HTML files
|
||||
WriteRegStr HKCR ".html\shell\PanConverter" "" "Convert with PanConverter"
|
||||
WriteRegStr HKCR ".html\shell\PanConverter" "Icon" "$INSTDIR\PanConverter.exe"
|
||||
WriteRegStr HKCR ".html\shell\PanConverter\command" "" '"$INSTDIR\PanConverter.exe" --convert "%1"'
|
||||
|
||||
WriteRegStr HKCR ".htm\shell\PanConverter" "" "Convert with PanConverter"
|
||||
WriteRegStr HKCR ".htm\shell\PanConverter" "Icon" "$INSTDIR\PanConverter.exe"
|
||||
WriteRegStr HKCR ".htm\shell\PanConverter\command" "" '"$INSTDIR\PanConverter.exe" --convert "%1"'
|
||||
|
||||
; Context menu for DOCX files
|
||||
WriteRegStr HKCR ".docx\shell\PanConverter" "" "Convert with PanConverter"
|
||||
WriteRegStr HKCR ".docx\shell\PanConverter" "Icon" "$INSTDIR\PanConverter.exe"
|
||||
WriteRegStr HKCR ".docx\shell\PanConverter\command" "" '"$INSTDIR\PanConverter.exe" --convert "%1"'
|
||||
|
||||
; Context menu for ODT files
|
||||
WriteRegStr HKCR ".odt\shell\PanConverter" "" "Convert with PanConverter"
|
||||
WriteRegStr HKCR ".odt\shell\PanConverter" "Icon" "$INSTDIR\PanConverter.exe"
|
||||
WriteRegStr HKCR ".odt\shell\PanConverter\command" "" '"$INSTDIR\PanConverter.exe" --convert "%1"'
|
||||
|
||||
; Context menu for RTF files
|
||||
WriteRegStr HKCR ".rtf\shell\PanConverter" "" "Convert with PanConverter"
|
||||
WriteRegStr HKCR ".rtf\shell\PanConverter" "Icon" "$INSTDIR\PanConverter.exe"
|
||||
WriteRegStr HKCR ".rtf\shell\PanConverter\command" "" '"$INSTDIR\PanConverter.exe" --convert "%1"'
|
||||
|
||||
; Context menu for LaTeX files
|
||||
WriteRegStr HKCR ".tex\shell\PanConverter" "" "Convert with PanConverter"
|
||||
WriteRegStr HKCR ".tex\shell\PanConverter" "Icon" "$INSTDIR\PanConverter.exe"
|
||||
WriteRegStr HKCR ".tex\shell\PanConverter\command" "" '"$INSTDIR\PanConverter.exe" --convert "%1"'
|
||||
|
||||
; Context menu for PDF files
|
||||
WriteRegStr HKCR ".pdf\shell\PanConverter" "" "Convert with PanConverter"
|
||||
WriteRegStr HKCR ".pdf\shell\PanConverter" "Icon" "$INSTDIR\PanConverter.exe"
|
||||
WriteRegStr HKCR ".pdf\shell\PanConverter\command" "" '"$INSTDIR\PanConverter.exe" --convert "%1"'
|
||||
|
||||
; Context menu for PowerPoint files
|
||||
WriteRegStr HKCR ".pptx\shell\PanConverter" "" "Convert with PanConverter"
|
||||
WriteRegStr HKCR ".pptx\shell\PanConverter" "Icon" "$INSTDIR\PanConverter.exe"
|
||||
WriteRegStr HKCR ".pptx\shell\PanConverter\command" "" '"$INSTDIR\PanConverter.exe" --convert "%1"'
|
||||
|
||||
WriteRegStr HKCR ".ppt\shell\PanConverter" "" "Convert with PanConverter"
|
||||
WriteRegStr HKCR ".ppt\shell\PanConverter" "Icon" "$INSTDIR\PanConverter.exe"
|
||||
WriteRegStr HKCR ".ppt\shell\PanConverter\command" "" '"$INSTDIR\PanConverter.exe" --convert "%1"'
|
||||
|
||||
; Context menu for ODP files
|
||||
WriteRegStr HKCR ".odp\shell\PanConverter" "" "Convert with PanConverter"
|
||||
WriteRegStr HKCR ".odp\shell\PanConverter" "Icon" "$INSTDIR\PanConverter.exe"
|
||||
WriteRegStr HKCR ".odp\shell\PanConverter\command" "" '"$INSTDIR\PanConverter.exe" --convert "%1"'
|
||||
|
||||
; Submenu for Markdown files with direct conversion options
|
||||
WriteRegStr HKCR ".md\shell\PanConverterMenu" "" "PanConverter"
|
||||
WriteRegStr HKCR ".md\shell\PanConverterMenu" "MUIVerb" "Convert to..."
|
||||
WriteRegStr HKCR ".md\shell\PanConverterMenu" "Icon" "$INSTDIR\PanConverter.exe"
|
||||
WriteRegStr HKCR ".md\shell\PanConverterMenu" "SubCommands" ""
|
||||
|
||||
WriteRegStr HKCR ".md\shell\PanConverterMenu\shell\PDF" "" "PDF"
|
||||
WriteRegStr HKCR ".md\shell\PanConverterMenu\shell\PDF\command" "" '"$INSTDIR\PanConverter.exe" --convert-to pdf "%1"'
|
||||
|
||||
WriteRegStr HKCR ".md\shell\PanConverterMenu\shell\HTML" "" "HTML"
|
||||
WriteRegStr HKCR ".md\shell\PanConverterMenu\shell\HTML\command" "" '"$INSTDIR\PanConverter.exe" --convert-to html "%1"'
|
||||
|
||||
WriteRegStr HKCR ".md\shell\PanConverterMenu\shell\DOCX" "" "DOCX"
|
||||
WriteRegStr HKCR ".md\shell\PanConverterMenu\shell\DOCX\command" "" '"$INSTDIR\PanConverter.exe" --convert-to docx "%1"'
|
||||
|
||||
WriteRegStr HKCR ".md\shell\PanConverterMenu\shell\LaTeX" "" "LaTeX"
|
||||
WriteRegStr HKCR ".md\shell\PanConverterMenu\shell\LaTeX\command" "" '"$INSTDIR\PanConverter.exe" --convert-to latex "%1"'
|
||||
|
||||
WriteRegStr HKCR ".md\shell\PanConverterMenu\shell\PPTX" "" "PowerPoint"
|
||||
WriteRegStr HKCR ".md\shell\PanConverterMenu\shell\PPTX\command" "" '"$INSTDIR\PanConverter.exe" --convert-to pptx "%1"'
|
||||
|
||||
DetailPrint "Context menu integration installed successfully!"
|
||||
${EndIf}
|
||||
FunctionEnd
|
||||
|
||||
; Uninstall context menu entries
|
||||
Function un.RemoveContextMenu
|
||||
DetailPrint "Removing context menu integration..."
|
||||
|
||||
; Remove context menu entries for all file types
|
||||
DeleteRegKey HKCR ".md\shell\PanConverter"
|
||||
DeleteRegKey HKCR ".md\shell\PanConverterMenu"
|
||||
DeleteRegKey HKCR ".markdown\shell\PanConverter"
|
||||
DeleteRegKey HKCR ".html\shell\PanConverter"
|
||||
DeleteRegKey HKCR ".htm\shell\PanConverter"
|
||||
DeleteRegKey HKCR ".docx\shell\PanConverter"
|
||||
DeleteRegKey HKCR ".odt\shell\PanConverter"
|
||||
DeleteRegKey HKCR ".rtf\shell\PanConverter"
|
||||
DeleteRegKey HKCR ".tex\shell\PanConverter"
|
||||
DeleteRegKey HKCR ".pdf\shell\PanConverter"
|
||||
DeleteRegKey HKCR ".pptx\shell\PanConverter"
|
||||
DeleteRegKey HKCR ".ppt\shell\PanConverter"
|
||||
DeleteRegKey HKCR ".odp\shell\PanConverter"
|
||||
|
||||
DetailPrint "Context menu integration removed successfully!"
|
||||
FunctionEnd
|
||||
@@ -0,0 +1,32 @@
|
||||
@echo off
|
||||
echo PanConverter Context Menu Uninstallation
|
||||
echo ========================================
|
||||
echo.
|
||||
|
||||
REM Check for administrator privileges
|
||||
net session >nul 2>&1
|
||||
if %errorLevel% == 0 (
|
||||
echo Running with administrator privileges...
|
||||
) else (
|
||||
echo This script requires administrator privileges.
|
||||
echo Please right-click and select "Run as administrator"
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo Removing context menu entries...
|
||||
reg import "%~dp0uninstall-context-menu.reg"
|
||||
|
||||
if %errorLevel% == 0 (
|
||||
echo.
|
||||
echo Context menu integration removed successfully!
|
||||
echo PanConverter entries have been removed from the Windows Explorer context menu.
|
||||
) else (
|
||||
echo.
|
||||
echo Failed to remove context menu entries.
|
||||
echo Please check that the registry file exists and try again.
|
||||
)
|
||||
|
||||
echo.
|
||||
pause
|
||||
@@ -0,0 +1,36 @@
|
||||
Windows Registry Editor Version 5.00
|
||||
|
||||
; PanConverter Context Menu Uninstall Script
|
||||
; This registry file removes PanConverter context menu entries
|
||||
|
||||
; Remove context menu for Markdown files (.md, .markdown)
|
||||
[-HKEY_CLASSES_ROOT\.md\shell\PanConverter]
|
||||
[-HKEY_CLASSES_ROOT\.md\shell\PanConverterMenu]
|
||||
|
||||
[-HKEY_CLASSES_ROOT\.markdown\shell\PanConverter]
|
||||
|
||||
; Remove context menu for HTML files
|
||||
[-HKEY_CLASSES_ROOT\.html\shell\PanConverter]
|
||||
[-HKEY_CLASSES_ROOT\.htm\shell\PanConverter]
|
||||
|
||||
; Remove context menu for DOCX files
|
||||
[-HKEY_CLASSES_ROOT\.docx\shell\PanConverter]
|
||||
|
||||
; Remove context menu for ODT files
|
||||
[-HKEY_CLASSES_ROOT\.odt\shell\PanConverter]
|
||||
|
||||
; Remove context menu for RTF files
|
||||
[-HKEY_CLASSES_ROOT\.rtf\shell\PanConverter]
|
||||
|
||||
; Remove context menu for LaTeX files
|
||||
[-HKEY_CLASSES_ROOT\.tex\shell\PanConverter]
|
||||
|
||||
; Remove context menu for PDF files
|
||||
[-HKEY_CLASSES_ROOT\.pdf\shell\PanConverter]
|
||||
|
||||
; Remove context menu for PowerPoint files
|
||||
[-HKEY_CLASSES_ROOT\.pptx\shell\PanConverter]
|
||||
[-HKEY_CLASSES_ROOT\.ppt\shell\PanConverter]
|
||||
|
||||
; Remove context menu for OpenDocument Presentation files
|
||||
[-HKEY_CLASSES_ROOT\.odp\shell\PanConverter]
|
||||
@@ -75,6 +75,27 @@
|
||||
<line x1="15" y1="3" x2="15" y2="21"></line>
|
||||
</svg>
|
||||
</button>
|
||||
<button id="btn-strikethrough" title="Strikethrough">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M16 4H9a3 3 0 0 0-2.83 4"></path>
|
||||
<path d="M14 12a4 4 0 0 1 0 8H6"></path>
|
||||
<line x1="4" y1="12" x2="20" y2="12"></line>
|
||||
</svg>
|
||||
</button>
|
||||
<button id="btn-code-block" title="Code Block">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="2" y="6" width="20" height="12" rx="2"></rect>
|
||||
<path d="m10 10-2 2 2 2"></path>
|
||||
<path d="m14 10 2 2-2 2"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button id="btn-horizontal-rule" title="Horizontal Rule">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<line x1="3" y1="12" x2="21" y2="12"></line>
|
||||
<line x1="3" y1="6" x2="21" y2="6"></line>
|
||||
<line x1="3" y1="18" x2="21" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="toolbar-separator"></div>
|
||||
<button id="btn-find" title="Find & Replace (Ctrl+F)">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
@@ -233,6 +254,58 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ConvertAPI Dialog -->
|
||||
<div id="convertapi-dialog" class="export-dialog hidden">
|
||||
<div class="export-dialog-content">
|
||||
<div class="export-dialog-header">
|
||||
<h3>ConvertAPI Conversion</h3>
|
||||
<button id="convertapi-dialog-close" title="Close">×</button>
|
||||
</div>
|
||||
<div class="export-dialog-body">
|
||||
<div class="export-section">
|
||||
<label for="convertapi-key">API Key:</label>
|
||||
<input type="text" id="convertapi-key" placeholder="Enter your ConvertAPI key">
|
||||
<small>Get your free API key at <a href="https://www.convertapi.com" target="_blank">convertapi.com</a></small>
|
||||
</div>
|
||||
|
||||
<div class="export-section">
|
||||
<label for="convertapi-from">Convert From:</label>
|
||||
<select id="convertapi-from">
|
||||
<option value="md">Markdown (MD)</option>
|
||||
<option value="docx">Word (DOCX)</option>
|
||||
<option value="pdf">PDF</option>
|
||||
<option value="html">HTML</option>
|
||||
<option value="txt">Text</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="export-section">
|
||||
<label for="convertapi-to">Convert To:</label>
|
||||
<select id="convertapi-to">
|
||||
<option value="pdf">PDF</option>
|
||||
<option value="docx">Word (DOCX)</option>
|
||||
<option value="html">HTML</option>
|
||||
<option value="txt">Text</option>
|
||||
<option value="jpg">JPG Image</option>
|
||||
<option value="png">PNG Image</option>
|
||||
<option value="epub">EPUB</option>
|
||||
<option value="odt">ODT</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="convertapi-progress" class="batch-progress hidden">
|
||||
<div class="progress-text">
|
||||
<span id="convertapi-status">Converting...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="export-dialog-footer">
|
||||
<button id="convertapi-cancel">Cancel</button>
|
||||
<button id="convertapi-convert" class="primary">Convert</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Batch Conversion Dialog -->
|
||||
<div id="batch-dialog" class="batch-dialog hidden">
|
||||
<div class="batch-dialog-content">
|
||||
|
||||
+563
-38
@@ -5,14 +5,16 @@ const { exec } = require('child_process');
|
||||
|
||||
// Get the system Pandoc path
|
||||
function getPandocPath() {
|
||||
// Always use system pandoc - no bundled binaries
|
||||
// Pandoc is expected to be in the system's PATH.
|
||||
// The command will be executed directly. Quoting is handled by exec.
|
||||
return 'pandoc';
|
||||
}
|
||||
|
||||
// Check if Pandoc is available
|
||||
function checkPandocAvailable() {
|
||||
return new Promise((resolve) => {
|
||||
exec('pandoc --version', (error) => {
|
||||
const pandocPath = getPandocPath();
|
||||
exec(`${pandocPath} --version`, (error) => {
|
||||
resolve(!error);
|
||||
});
|
||||
});
|
||||
@@ -43,6 +45,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({
|
||||
@@ -163,7 +181,7 @@ function createMenu() {
|
||||
{ label: 'PowerPoint (PPTX)', click: () => exportFile('pptx') },
|
||||
{ label: 'OpenDocument Presentation (ODP)', click: () => exportFile('odp') },
|
||||
{ type: 'separator' },
|
||||
{ label: 'CSV (Tables)', click: () => exportSpreadsheet('csv') }
|
||||
{ label: 'CSV (Tables)', click: () => exportSpreadsheet('csv') },
|
||||
]
|
||||
},
|
||||
{ type: 'separator' },
|
||||
@@ -249,6 +267,30 @@ function createMenu() {
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'ConvertAPI',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Cloud Conversion...',
|
||||
click: () => showConvertAPIDialog()
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'About ConvertAPI',
|
||||
click: () => {
|
||||
dialog.showMessageBox(mainWindow, {
|
||||
type: 'info',
|
||||
title: 'About ConvertAPI',
|
||||
message: 'ConvertAPI Integration',
|
||||
detail: 'ConvertAPI provides cloud-based file conversion with support for 200+ formats.\n\nFeatures:\n• Convert between 200+ file formats\n• High-quality conversions\n• Fast processing\n• Secure cloud storage\n\nGet your free API key at:\nhttps://www.convertapi.com\n\nFree tier includes 250 conversions/month.',
|
||||
buttons: ['OK']
|
||||
});
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Help',
|
||||
submenu: [
|
||||
@@ -259,7 +301,7 @@ function createMenu() {
|
||||
type: 'info',
|
||||
title: 'About PanConverter',
|
||||
message: 'PanConverter',
|
||||
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',
|
||||
detail: 'A cross-platform Markdown editor and converter using Pandoc.\n\nVersion: 1.6.0\nAuthor: Amit Haridas\nEmail: amit.wh@gmail.com\nLicense: MIT\n\nFeatures:\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',
|
||||
buttons: ['OK']
|
||||
});
|
||||
}
|
||||
@@ -325,6 +367,54 @@ function showBatchConversionDialog() {
|
||||
mainWindow.webContents.send('show-batch-dialog');
|
||||
}
|
||||
|
||||
// ConvertAPI integration
|
||||
function showConvertAPIDialog() {
|
||||
mainWindow.webContents.send('show-convertapi-dialog');
|
||||
}
|
||||
|
||||
// Handle ConvertAPI conversion
|
||||
ipcMain.on('convertapi-convert', async (event, { apiKey, fromFormat, toFormat, filePath }) => {
|
||||
try {
|
||||
const ConvertAPI = require('convertapi')(apiKey);
|
||||
|
||||
mainWindow.webContents.send('convertapi-status', 'Converting file...');
|
||||
|
||||
const result = await ConvertAPI.convert(toFormat, {
|
||||
File: filePath
|
||||
}, fromFormat);
|
||||
|
||||
// Save the converted file
|
||||
const outputPath = filePath.replace(/\.[^/.]+$/, `.${toFormat}`);
|
||||
await result.saveFiles(path.dirname(outputPath));
|
||||
|
||||
mainWindow.webContents.send('convertapi-complete', {
|
||||
success: true,
|
||||
outputPath: result.files[0].path
|
||||
});
|
||||
|
||||
dialog.showMessageBox(mainWindow, {
|
||||
type: 'info',
|
||||
title: 'Conversion Complete',
|
||||
message: 'File converted successfully!',
|
||||
detail: `Saved to: ${result.files[0].path}`,
|
||||
buttons: ['OK']
|
||||
});
|
||||
} catch (error) {
|
||||
mainWindow.webContents.send('convertapi-complete', {
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
|
||||
dialog.showMessageBox(mainWindow, {
|
||||
type: 'error',
|
||||
title: 'Conversion Failed',
|
||||
message: 'ConvertAPI conversion failed',
|
||||
detail: error.message,
|
||||
buttons: ['OK']
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function performExportWithOptions(format, options) {
|
||||
const outputFile = dialog.showSaveDialogSync(mainWindow, {
|
||||
defaultPath: currentFile.replace(/\.[^/.]+$/, `.${format}`),
|
||||
@@ -333,7 +423,36 @@ function performExportWithOptions(format, options) {
|
||||
]
|
||||
});
|
||||
|
||||
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 with advanced options
|
||||
console.log('Using Pandoc for export');
|
||||
let pandocCmd = `${getPandocPath()} "${currentFile}" -o "${outputFile}"`;
|
||||
|
||||
// Add template if specified
|
||||
@@ -369,12 +488,12 @@ function performExportWithOptions(format, options) {
|
||||
|
||||
// Add specific options for PDF export to ensure proper generation
|
||||
if (format === 'pdf') {
|
||||
const pdfEngine = options.pdfEngine || 'xelatex';
|
||||
const geometry = options.geometry || 'margin=1in';
|
||||
pandocCmd += ` --pdf-engine=${pdfEngine} -V geometry:${geometry}`;
|
||||
const pdfEngine = options.pdfEngine || 'xelatex'; // Default to xelatex
|
||||
pandocCmd += ` --pdf-engine="${pdfEngine}"`;
|
||||
if (options.geometry) pandocCmd += ` -V geometry:"${options.geometry}"`;
|
||||
|
||||
// Try with specified PDF engine
|
||||
exec(pandocCmd, (error, stdout, stderr) => {
|
||||
exec(pandocCmd, (error) => {
|
||||
if (error) {
|
||||
// Try fallback engines if the specified one fails
|
||||
const fallbackEngines = ['pdflatex', 'lualatex'];
|
||||
@@ -383,16 +502,17 @@ function performExportWithOptions(format, options) {
|
||||
showExportSuccess(outputFile);
|
||||
}
|
||||
});
|
||||
} else if (format === 'docx') {
|
||||
pandocCmd += ' -t docx';
|
||||
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 tryPdfFallback(inputFile, outputFile, engines, index, options, lastError) {
|
||||
@@ -408,9 +528,10 @@ function tryPdfFallback(inputFile, outputFile, engines, index, options, lastErro
|
||||
}
|
||||
|
||||
const engine = engines[index];
|
||||
const geometry = options.geometry || 'margin=1in';
|
||||
let pandocCmd = `${getPandocPath()} "${inputFile}" --pdf-engine=${engine} -V geometry:${geometry} -o "${outputFile}"`;
|
||||
|
||||
if (options.geometry) pandocCmd += ` -V geometry:"${options.geometry}"`;
|
||||
|
||||
// Add all other options
|
||||
if (options.template && options.template !== 'default') {
|
||||
pandocCmd += ` --template="${options.template}"`;
|
||||
@@ -424,7 +545,7 @@ function tryPdfFallback(inputFile, outputFile, engines, index, options, lastErro
|
||||
}
|
||||
}
|
||||
|
||||
exec(pandocCmd, (error, stdout, stderr) => {
|
||||
exec(pandocCmd, (error) => {
|
||||
if (error) {
|
||||
tryPdfFallback(inputFile, outputFile, engines, index + 1, options, error);
|
||||
} else {
|
||||
@@ -442,6 +563,265 @@ function showExportSuccess(outputFile) {
|
||||
});
|
||||
}
|
||||
|
||||
// Helper function to export with pandoc (general)
|
||||
function exportWithPandoc(pandocCmd, outputFile, format) {
|
||||
console.log(`Executing Pandoc command: ${pandocCmd}`);
|
||||
|
||||
exec(pandocCmd, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
console.error(`Pandoc error for ${format}:`, error);
|
||||
console.error(`Pandoc stderr:`, stderr);
|
||||
console.error(`Pandoc stdout:`, stdout);
|
||||
|
||||
// Provide more specific error messages
|
||||
let errorMessage = `Failed to export to ${format.toUpperCase()}`;
|
||||
|
||||
if (error.message.includes('not found') || error.message.includes('not recognized')) {
|
||||
errorMessage += '\n\nPandoc is not installed or not found in PATH.';
|
||||
errorMessage += '\nPlease install Pandoc from: https://pandoc.org/installing.html';
|
||||
} else if (stderr) {
|
||||
errorMessage += `\n\nError details: ${stderr}`;
|
||||
} else {
|
||||
errorMessage += `\n\nError details: ${error.message}`;
|
||||
}
|
||||
|
||||
errorMessage += `\n\nCommand used: ${pandocCmd}`;
|
||||
|
||||
dialog.showErrorBox('Export Error', errorMessage);
|
||||
} else {
|
||||
console.log(`Successfully exported to ${format}:`, outputFile);
|
||||
console.log(`Pandoc stdout:`, stdout);
|
||||
if (stderr) {
|
||||
console.warn(`Pandoc stderr (non-fatal):`, stderr);
|
||||
}
|
||||
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');
|
||||
@@ -566,30 +946,31 @@ ipcMain.on('export-spreadsheet', (event, { content, format }) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert tables to CSV format
|
||||
let csvContent = '';
|
||||
tables.forEach((table, index) => {
|
||||
if (index > 0) csvContent += '\n\n'; // Separate multiple tables
|
||||
if (tables.length > 1) csvContent += `"Table ${index + 1}"\n`;
|
||||
if (format === 'csv') {
|
||||
// Convert tables to CSV format
|
||||
let csvContent = '';
|
||||
tables.forEach((table, index) => {
|
||||
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';
|
||||
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 CSV file
|
||||
fs.writeFileSync(outputFile, csvContent, 'utf-8');
|
||||
fs.writeFileSync(outputFile, csvContent, 'utf-8');
|
||||
}
|
||||
|
||||
dialog.showMessageBox(mainWindow, {
|
||||
type: 'info',
|
||||
title: 'Export Complete',
|
||||
message: `CSV exported successfully to ${outputFile}`,
|
||||
message: `${format.toUpperCase()} exported successfully to ${outputFile}`,
|
||||
buttons: ['OK']
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -746,8 +1127,8 @@ function performBatchConversion(inputFolder, outputFolder, format, options) {
|
||||
// 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}`;
|
||||
pandocCmd += ` --pdf-engine="${pdfEngine}"`;
|
||||
if (options.geometry) pandocCmd += ` -V geometry:"${options.geometry}"`;
|
||||
}
|
||||
|
||||
// Execute conversion
|
||||
@@ -773,7 +1154,151 @@ function performBatchConversion(inputFolder, outputFolder, format, options) {
|
||||
processNextFile(0);
|
||||
}
|
||||
|
||||
// Handle command line interface for file conversion
|
||||
function handleCLIConversion(args) {
|
||||
const command = args[0];
|
||||
const filePath = args[args.length - 1]; // File path is always last argument
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
console.error(`Error: File not found: ${filePath}`);
|
||||
app.quit();
|
||||
return;
|
||||
}
|
||||
|
||||
// Show conversion dialog for --convert command
|
||||
if (command === '--convert') {
|
||||
showConversionDialog(filePath);
|
||||
return;
|
||||
}
|
||||
|
||||
// Direct conversion for --convert-to command
|
||||
if (command === '--convert-to' && args.length >= 3) {
|
||||
const format = args[1];
|
||||
performCLIConversion(filePath, format);
|
||||
return;
|
||||
}
|
||||
|
||||
console.error('Usage: --convert <file> OR --convert-to <format> <file>');
|
||||
app.quit();
|
||||
}
|
||||
|
||||
// Show conversion dialog for CLI
|
||||
function showConversionDialog(filePath) {
|
||||
const { dialog } = require('electron');
|
||||
|
||||
// Create a hidden window for dialog operations
|
||||
const hiddenWindow = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false
|
||||
}
|
||||
});
|
||||
|
||||
const formats = [
|
||||
{ name: 'PDF', value: 'pdf' },
|
||||
{ name: 'HTML', value: 'html' },
|
||||
{ name: 'DOCX', value: 'docx' },
|
||||
{ name: 'LaTeX', value: 'latex' },
|
||||
{ name: 'RTF', value: 'rtf' },
|
||||
{ name: 'ODT', value: 'odt' },
|
||||
{ name: 'PowerPoint', value: 'pptx' }
|
||||
];
|
||||
|
||||
// Create format selection dialog using message box
|
||||
const formatButtons = formats.map(f => f.name);
|
||||
formatButtons.push('Cancel');
|
||||
|
||||
dialog.showMessageBox(hiddenWindow, {
|
||||
type: 'question',
|
||||
title: 'PanConverter - Choose Format',
|
||||
message: `Convert "${path.basename(filePath)}" to:`,
|
||||
detail: 'Select the output format for conversion',
|
||||
buttons: formatButtons,
|
||||
defaultId: 0,
|
||||
cancelId: formatButtons.length - 1
|
||||
}).then(result => {
|
||||
if (result.response < formats.length) {
|
||||
const selectedFormat = formats[result.response].value;
|
||||
performCLIConversion(filePath, selectedFormat);
|
||||
} else {
|
||||
console.log('Conversion cancelled');
|
||||
app.quit();
|
||||
}
|
||||
hiddenWindow.destroy();
|
||||
});
|
||||
}
|
||||
|
||||
// Perform CLI conversion
|
||||
function performCLIConversion(inputPath, format) {
|
||||
try {
|
||||
const content = fs.readFileSync(inputPath, 'utf-8');
|
||||
const outputPath = inputPath.replace(/\.[^/.]+$/, `.${format}`);
|
||||
|
||||
console.log(`Converting "${path.basename(inputPath)}" to ${format.toUpperCase()}...`);
|
||||
|
||||
// Use existing export functions but with CLI output
|
||||
const pandocCommand = buildPandocCommand(content, format, outputPath);
|
||||
|
||||
exec(pandocCommand, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
console.error(`Conversion failed: ${error.message}`);
|
||||
if (stderr) console.error(`Details: ${stderr}`);
|
||||
app.quit();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Successfully converted to: ${outputPath}`);
|
||||
|
||||
// Show Windows notification
|
||||
if (process.platform === 'win32') {
|
||||
exec(`powershell -Command "New-BurntToastNotification -Text 'PanConverter', 'File converted to ${format.toUpperCase()}' -AppLogo '${path.join(__dirname, '../assets/icon.png')}'"`, () => {});
|
||||
}
|
||||
|
||||
app.quit();
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Error reading file: ${error.message}`);
|
||||
app.quit();
|
||||
}
|
||||
}
|
||||
|
||||
// Build Pandoc command for CLI conversion
|
||||
function buildPandocCommand(content, format, outputPath) {
|
||||
const inputFile = path.join(require('os').tmpdir(), `panconverter_temp_${Date.now()}.md`);
|
||||
fs.writeFileSync(inputFile, content, 'utf-8');
|
||||
|
||||
let command = `pandoc "${inputFile}" -o "${outputPath}"`;
|
||||
|
||||
switch (format) {
|
||||
case 'pdf':
|
||||
command += ' --pdf-engine=xelatex --variable geometry:margin=1in';
|
||||
break;
|
||||
case 'html':
|
||||
command += ' --self-contained --css';
|
||||
break;
|
||||
case 'docx':
|
||||
command += ' --reference-doc';
|
||||
break;
|
||||
case 'latex':
|
||||
command += ' --standalone';
|
||||
break;
|
||||
case 'pptx':
|
||||
command += ' --slide-level=2';
|
||||
break;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
// Check for command line conversion requests
|
||||
const args = process.argv.slice(2);
|
||||
if (args.length >= 2 && (args[0] === '--convert' || args[0] === '--convert-to')) {
|
||||
handleCLIConversion(args);
|
||||
return; // Don't create window for CLI operations
|
||||
}
|
||||
|
||||
createWindow();
|
||||
|
||||
// Handle file association on app startup
|
||||
|
||||
+537
-38
@@ -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;
|
||||
},
|
||||
@@ -74,6 +77,7 @@ class TabManager {
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
switch (e.key) {
|
||||
case 'n':
|
||||
case 't':
|
||||
e.preventDefault();
|
||||
this.createNewTab();
|
||||
break;
|
||||
@@ -83,10 +87,6 @@ class TabManager {
|
||||
this.closeTab(this.activeTabId);
|
||||
}
|
||||
break;
|
||||
case 't':
|
||||
e.preventDefault();
|
||||
this.createNewTab();
|
||||
break;
|
||||
case 'Tab':
|
||||
if (this.tabs.size > 1) {
|
||||
e.preventDefault();
|
||||
@@ -139,6 +139,24 @@ class TabManager {
|
||||
`;
|
||||
|
||||
document.querySelector('.editor-container').appendChild(tabContent);
|
||||
|
||||
// Directly attach input listener to the new editor
|
||||
const editor = document.getElementById(`editor-${tab.id}`);
|
||||
if (editor) {
|
||||
editor.addEventListener('input', () => {
|
||||
this.handleEditorInput(tab.id);
|
||||
});
|
||||
|
||||
// Add scroll listener for line number sync
|
||||
editor.addEventListener('scroll', () => {
|
||||
if (this.showLineNumbers && this.activeTabId === tab.id) {
|
||||
const lineNumbers = document.getElementById(`line-numbers-${tab.id}`);
|
||||
if (lineNumbers) {
|
||||
lineNumbers.scrollTop = editor.scrollTop;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
switchToTab(tabId) {
|
||||
@@ -156,7 +174,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);
|
||||
}
|
||||
}
|
||||
@@ -173,14 +191,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) {
|
||||
@@ -262,7 +282,6 @@ class TabManager {
|
||||
if (!tab) return;
|
||||
|
||||
const editor = document.getElementById(`editor-${tabId}`);
|
||||
const preview = document.getElementById(`preview-${tabId}`);
|
||||
|
||||
if (editor) {
|
||||
editor.value = tab.content;
|
||||
@@ -305,7 +324,8 @@ class TabManager {
|
||||
}
|
||||
}
|
||||
} 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>';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,11 +351,14 @@ class TabManager {
|
||||
if (!editor || !lineNumbers) return;
|
||||
|
||||
if (this.showLineNumbers) {
|
||||
const lines = editor.value.split('\\n');
|
||||
const lines = editor.value.split('\n');
|
||||
lineNumbers.innerHTML = lines.map((_, i) =>
|
||||
`<div class="line-number">${i + 1}</div>`
|
||||
).join('');
|
||||
lineNumbers.classList.remove('hidden');
|
||||
|
||||
// Sync scroll position
|
||||
lineNumbers.scrollTop = editor.scrollTop;
|
||||
} else {
|
||||
lineNumbers.classList.add('hidden');
|
||||
}
|
||||
@@ -374,24 +397,22 @@ class TabManager {
|
||||
}
|
||||
|
||||
setupEditorEvents() {
|
||||
// Set up editor events using event delegation
|
||||
document.addEventListener('input', (e) => {
|
||||
if (e.target.classList.contains('editor-textarea')) {
|
||||
const tabId = parseInt(e.target.id.split('-')[1]);
|
||||
if (tabId === this.activeTabId) {
|
||||
// Set up editor events using event delegation on the container
|
||||
const editorContainer = document.querySelector('.editor-container');
|
||||
if (editorContainer) {
|
||||
editorContainer.addEventListener('input', (e) => {
|
||||
if (e.target.classList.contains('editor-textarea')) {
|
||||
const tabId = parseInt(e.target.id.split('-')[1]);
|
||||
this.handleEditorInput(tabId);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('scroll', (e) => {
|
||||
if (e.target.classList.contains('editor-textarea')) {
|
||||
const tabId = parseInt(e.target.id.split('-')[1]);
|
||||
if (tabId === this.activeTabId) {
|
||||
editorContainer.addEventListener('scroll', (e) => {
|
||||
if (e.target.classList.contains('editor-textarea')) {
|
||||
this.updateLineNumbers();
|
||||
}
|
||||
}
|
||||
});
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
|
||||
handleEditorInput(tabId) {
|
||||
@@ -399,6 +420,8 @@ class TabManager {
|
||||
if (!tab) return;
|
||||
|
||||
const editor = document.getElementById(`editor-${tabId}`);
|
||||
if (!editor) return;
|
||||
|
||||
tab.content = editor.value;
|
||||
tab.isDirty = true;
|
||||
|
||||
@@ -515,7 +538,7 @@ class TabManager {
|
||||
|
||||
// Save to localStorage and sync with main process
|
||||
localStorage.setItem('recentFiles', JSON.stringify(this.recentFiles));
|
||||
window.electronAPI.send('save-recent-files', this.recentFiles);
|
||||
ipcRenderer.send('save-recent-files', this.recentFiles);
|
||||
}
|
||||
|
||||
getRecentFiles() {
|
||||
@@ -530,40 +553,411 @@ class TabManager {
|
||||
}
|
||||
|
||||
setupToolbarEvents() {
|
||||
// Existing toolbar setup...
|
||||
// Bold
|
||||
document.getElementById('btn-bold').addEventListener('click', () => {
|
||||
this.wrapSelection('**', '**');
|
||||
});
|
||||
|
||||
// Italic
|
||||
document.getElementById('btn-italic').addEventListener('click', () => {
|
||||
this.wrapSelection('*', '*');
|
||||
});
|
||||
|
||||
// Heading
|
||||
document.getElementById('btn-heading').addEventListener('click', () => {
|
||||
this.insertAtLineStart('## ');
|
||||
});
|
||||
|
||||
// Link
|
||||
document.getElementById('btn-link').addEventListener('click', () => {
|
||||
this.wrapSelection('[', '](url)');
|
||||
});
|
||||
|
||||
// Code
|
||||
document.getElementById('btn-code').addEventListener('click', () => {
|
||||
this.wrapSelection('`', '`');
|
||||
});
|
||||
|
||||
// List
|
||||
document.getElementById('btn-list').addEventListener('click', () => {
|
||||
this.insertAtLineStart('- ');
|
||||
});
|
||||
|
||||
// Quote
|
||||
document.getElementById('btn-quote').addEventListener('click', () => {
|
||||
this.insertAtLineStart('> ');
|
||||
});
|
||||
|
||||
// Table
|
||||
document.getElementById('btn-table').addEventListener('click', () => {
|
||||
this.insertTable();
|
||||
});
|
||||
|
||||
// Strikethrough
|
||||
document.getElementById('btn-strikethrough').addEventListener('click', () => {
|
||||
this.wrapSelection('~~', '~~');
|
||||
});
|
||||
|
||||
// Code Block
|
||||
document.getElementById('btn-code-block').addEventListener('click', () => {
|
||||
this.insertCodeBlock();
|
||||
});
|
||||
|
||||
// Horizontal Rule
|
||||
document.getElementById('btn-horizontal-rule').addEventListener('click', () => {
|
||||
this.insertHorizontalRule();
|
||||
});
|
||||
|
||||
// Preview toggle
|
||||
document.getElementById('btn-preview-toggle').addEventListener('click', () => {
|
||||
this.isPreviewVisible = !this.isPreviewVisible;
|
||||
this.updatePreviewVisibility();
|
||||
});
|
||||
|
||||
// Line numbers
|
||||
document.getElementById('btn-line-numbers').addEventListener('click', () => {
|
||||
this.showLineNumbers = !this.showLineNumbers;
|
||||
this.updateLineNumbers();
|
||||
});
|
||||
}
|
||||
|
||||
// Add other toolbar events...
|
||||
// Helper function to wrap selected text
|
||||
wrapSelection(before, after) {
|
||||
const editor = document.getElementById(`editor-${this.activeTabId}`);
|
||||
if (!editor) return;
|
||||
|
||||
const start = editor.selectionStart;
|
||||
const end = editor.selectionEnd;
|
||||
const selectedText = editor.value.substring(start, end);
|
||||
const replacement = before + (selectedText || 'text') + after;
|
||||
|
||||
editor.value = editor.value.substring(0, start) + replacement + editor.value.substring(end);
|
||||
|
||||
// Update cursor position
|
||||
const newCursorPos = selectedText ? start + replacement.length : start + before.length;
|
||||
editor.selectionStart = editor.selectionEnd = newCursorPos;
|
||||
editor.focus();
|
||||
|
||||
// Trigger update
|
||||
this.handleEditorInput(this.activeTabId);
|
||||
}
|
||||
|
||||
// Helper function to insert text at the start of current line
|
||||
insertAtLineStart(prefix) {
|
||||
const editor = document.getElementById(`editor-${this.activeTabId}`);
|
||||
if (!editor) return;
|
||||
|
||||
const start = editor.selectionStart;
|
||||
const text = editor.value;
|
||||
|
||||
// Find the start of the current line
|
||||
let lineStart = text.lastIndexOf('\n', start - 1) + 1;
|
||||
|
||||
// Insert the prefix
|
||||
editor.value = text.substring(0, lineStart) + prefix + text.substring(lineStart);
|
||||
|
||||
// Update cursor position
|
||||
editor.selectionStart = editor.selectionEnd = start + prefix.length;
|
||||
editor.focus();
|
||||
|
||||
// Trigger update
|
||||
this.handleEditorInput(this.activeTabId);
|
||||
}
|
||||
|
||||
// Insert a markdown table
|
||||
insertTable() {
|
||||
const editor = document.getElementById(`editor-${this.activeTabId}`);
|
||||
if (!editor) return;
|
||||
|
||||
const table = '\n| Column 1 | Column 2 | Column 3 |\n' +
|
||||
'|----------|----------|----------|\n' +
|
||||
'| Cell 1 | Cell 2 | Cell 3 |\n' +
|
||||
'| Cell 4 | Cell 5 | Cell 6 |\n';
|
||||
|
||||
const start = editor.selectionStart;
|
||||
editor.value = editor.value.substring(0, start) + table + editor.value.substring(start);
|
||||
|
||||
// Update cursor position
|
||||
editor.selectionStart = editor.selectionEnd = start + table.length;
|
||||
editor.focus();
|
||||
|
||||
// Trigger update
|
||||
this.handleEditorInput(this.activeTabId);
|
||||
}
|
||||
|
||||
// Insert a code block
|
||||
insertCodeBlock() {
|
||||
const editor = document.getElementById(`editor-${this.activeTabId}`);
|
||||
if (!editor) return;
|
||||
|
||||
const selectedText = editor.value.substring(editor.selectionStart, editor.selectionEnd);
|
||||
const codeBlock = '\n```\n' + (selectedText || 'code here') + '\n```\n';
|
||||
|
||||
const start = editor.selectionStart;
|
||||
editor.value = editor.value.substring(0, start) + codeBlock + editor.value.substring(editor.selectionEnd);
|
||||
|
||||
// Update cursor position
|
||||
editor.selectionStart = editor.selectionEnd = start + codeBlock.length;
|
||||
editor.focus();
|
||||
|
||||
// Trigger update
|
||||
this.handleEditorInput(this.activeTabId);
|
||||
}
|
||||
|
||||
// Insert a horizontal rule
|
||||
insertHorizontalRule() {
|
||||
const editor = document.getElementById(`editor-${this.activeTabId}`);
|
||||
if (!editor) return;
|
||||
|
||||
const hr = '\n\n---\n\n';
|
||||
const start = editor.selectionStart;
|
||||
|
||||
editor.value = editor.value.substring(0, start) + hr + editor.value.substring(start);
|
||||
|
||||
// Update cursor position
|
||||
editor.selectionStart = editor.selectionEnd = start + hr.length;
|
||||
editor.focus();
|
||||
|
||||
// Trigger update
|
||||
this.handleEditorInput(this.activeTabId);
|
||||
}
|
||||
|
||||
setupFindEvents() {
|
||||
// Find dialog implementation...
|
||||
document.getElementById('btn-find').addEventListener('click', () => {
|
||||
const btnFind = document.getElementById('btn-find');
|
||||
const btnFindClose = document.getElementById('btn-find-close');
|
||||
const findInput = document.getElementById('find-input');
|
||||
const btnFindNext = document.getElementById('btn-find-next');
|
||||
const btnFindPrev = document.getElementById('btn-find-prev');
|
||||
const btnReplace = document.getElementById('btn-replace');
|
||||
const btnReplaceAll = document.getElementById('btn-replace-all');
|
||||
|
||||
if (!btnFind || !btnFindClose || !findInput || !btnFindNext || !btnFindPrev || !btnReplace || !btnReplaceAll) {
|
||||
console.error('Find dialog elements not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Show find dialog
|
||||
btnFind.addEventListener('click', () => {
|
||||
document.getElementById('find-dialog').classList.remove('hidden');
|
||||
document.getElementById('find-input').focus();
|
||||
findInput.focus();
|
||||
});
|
||||
|
||||
document.getElementById('btn-find-close').addEventListener('click', () => {
|
||||
// Close find dialog
|
||||
btnFindClose.addEventListener('click', () => {
|
||||
document.getElementById('find-dialog').classList.add('hidden');
|
||||
this.clearFindHighlights();
|
||||
});
|
||||
|
||||
// Find input change - update matches
|
||||
findInput.addEventListener('input', () => {
|
||||
this.performFind();
|
||||
});
|
||||
|
||||
// Find next
|
||||
btnFindNext.addEventListener('click', () => {
|
||||
this.findNext();
|
||||
});
|
||||
|
||||
// Find previous
|
||||
btnFindPrev.addEventListener('click', () => {
|
||||
this.findPrevious();
|
||||
});
|
||||
|
||||
// Replace
|
||||
btnReplace.addEventListener('click', () => {
|
||||
this.replaceOne();
|
||||
});
|
||||
|
||||
// Replace all
|
||||
btnReplaceAll.addEventListener('click', () => {
|
||||
this.replaceAll();
|
||||
});
|
||||
|
||||
// Enter key in find input - find next
|
||||
findInput.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
if (e.shiftKey) {
|
||||
this.findPrevious();
|
||||
} else {
|
||||
this.findNext();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
performFind() {
|
||||
const findText = document.getElementById('find-input').value;
|
||||
const tab = this.tabs.get(this.activeTabId);
|
||||
const editor = document.getElementById(`editor-${this.activeTabId}`);
|
||||
|
||||
if (!findText || !tab || !editor) {
|
||||
this.clearFindHighlights();
|
||||
const findCount = document.getElementById('find-count');
|
||||
if (findCount) {
|
||||
findCount.textContent = '0 matches';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const content = editor.value;
|
||||
const matches = [];
|
||||
let index = 0;
|
||||
|
||||
// Find all matches
|
||||
while ((index = content.indexOf(findText, index)) !== -1) {
|
||||
matches.push(index);
|
||||
index += findText.length;
|
||||
}
|
||||
|
||||
tab.findMatches = matches;
|
||||
tab.currentMatchIndex = matches.length > 0 ? 0 : -1;
|
||||
|
||||
// Update match count
|
||||
const findCount = document.getElementById('find-count');
|
||||
if (findCount) {
|
||||
findCount.textContent = `${matches.length} match${matches.length !== 1 ? 'es' : ''}`;
|
||||
}
|
||||
|
||||
// Highlight first match
|
||||
if (matches.length > 0) {
|
||||
this.highlightMatch(0);
|
||||
}
|
||||
}
|
||||
|
||||
findNext() {
|
||||
const tab = this.tabs.get(this.activeTabId);
|
||||
if (!tab || tab.findMatches.length === 0) return;
|
||||
|
||||
tab.currentMatchIndex = (tab.currentMatchIndex + 1) % tab.findMatches.length;
|
||||
this.highlightMatch(tab.currentMatchIndex);
|
||||
}
|
||||
|
||||
findPrevious() {
|
||||
const tab = this.tabs.get(this.activeTabId);
|
||||
if (!tab || tab.findMatches.length === 0) return;
|
||||
|
||||
tab.currentMatchIndex = tab.currentMatchIndex - 1;
|
||||
if (tab.currentMatchIndex < 0) {
|
||||
tab.currentMatchIndex = tab.findMatches.length - 1;
|
||||
}
|
||||
this.highlightMatch(tab.currentMatchIndex);
|
||||
}
|
||||
|
||||
highlightMatch(matchIndex) {
|
||||
const tab = this.tabs.get(this.activeTabId);
|
||||
const editor = document.getElementById(`editor-${this.activeTabId}`);
|
||||
const findText = document.getElementById('find-input').value;
|
||||
|
||||
if (!tab || !editor || matchIndex < 0 || matchIndex >= tab.findMatches.length) return;
|
||||
|
||||
const position = tab.findMatches[matchIndex];
|
||||
|
||||
// Select the match WITHOUT focusing (to keep focus on find input)
|
||||
editor.setSelectionRange(position, position + findText.length);
|
||||
|
||||
// Make the selection visible by briefly focusing and then restoring focus
|
||||
const findInput = document.getElementById('find-input');
|
||||
const hadFocus = document.activeElement === findInput;
|
||||
|
||||
// Temporarily focus editor to make selection visible
|
||||
editor.focus();
|
||||
|
||||
// Restore focus to find input if it had focus
|
||||
if (hadFocus) {
|
||||
setTimeout(() => {
|
||||
findInput.focus();
|
||||
// Restore cursor position in find input
|
||||
findInput.setSelectionRange(findInput.value.length, findInput.value.length);
|
||||
}, 10);
|
||||
}
|
||||
|
||||
// Scroll into view
|
||||
const lineHeight = 20; // Approximate line height
|
||||
const charPosition = position;
|
||||
const numLines = editor.value.substring(0, charPosition).split('\n').length;
|
||||
const scrollPosition = (numLines - 5) * lineHeight; // Show match 5 lines from top
|
||||
|
||||
editor.scrollTop = Math.max(0, scrollPosition);
|
||||
|
||||
// Update match counter
|
||||
const findCount = document.getElementById('find-count');
|
||||
if (findCount) {
|
||||
findCount.textContent = `Match ${matchIndex + 1} of ${tab.findMatches.length}`;
|
||||
}
|
||||
}
|
||||
|
||||
replaceOne() {
|
||||
const tab = this.tabs.get(this.activeTabId);
|
||||
const editor = document.getElementById(`editor-${this.activeTabId}`);
|
||||
const findText = document.getElementById('find-input').value;
|
||||
const replaceText = document.getElementById('replace-input').value;
|
||||
|
||||
if (!tab || !editor || tab.findMatches.length === 0 || tab.currentMatchIndex < 0) return;
|
||||
|
||||
const position = tab.findMatches[tab.currentMatchIndex];
|
||||
const before = editor.value.substring(0, position);
|
||||
const after = editor.value.substring(position + findText.length);
|
||||
|
||||
editor.value = before + replaceText + after;
|
||||
tab.content = editor.value;
|
||||
tab.isDirty = true;
|
||||
|
||||
this.updatePreview(this.activeTabId);
|
||||
this.updateWordCount();
|
||||
this.updateTabBar();
|
||||
|
||||
// Re-perform find to update matches
|
||||
this.performFind();
|
||||
}
|
||||
|
||||
replaceAll() {
|
||||
const tab = this.tabs.get(this.activeTabId);
|
||||
const editor = document.getElementById(`editor-${this.activeTabId}`);
|
||||
const findText = document.getElementById('find-input').value;
|
||||
const replaceText = document.getElementById('replace-input').value;
|
||||
|
||||
if (!tab || !editor || !findText) return;
|
||||
|
||||
// Simple replace all
|
||||
const newContent = editor.value.split(findText).join(replaceText);
|
||||
const replacedCount = tab.findMatches.length;
|
||||
|
||||
editor.value = newContent;
|
||||
tab.content = newContent;
|
||||
tab.isDirty = true;
|
||||
|
||||
this.updatePreview(this.activeTabId);
|
||||
this.updateWordCount();
|
||||
this.updateTabBar();
|
||||
|
||||
// Update match count
|
||||
document.getElementById('find-count').textContent = `Replaced ${replacedCount} match${replacedCount !== 1 ? 'es' : ''}`;
|
||||
|
||||
// Re-perform find
|
||||
this.performFind();
|
||||
}
|
||||
|
||||
clearFindHighlights() {
|
||||
const tab = this.tabs.get(this.activeTabId);
|
||||
if (tab) {
|
||||
tab.findMatches = [];
|
||||
tab.currentMatchIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// File operations
|
||||
openFile(filePath, content) {
|
||||
let tab = this.tabs.get(this.activeTabId);
|
||||
|
||||
// Handle both forward and back slashes for cross-platform compatibility
|
||||
const fileName = filePath.split(/[\\/]/).pop();
|
||||
|
||||
// If current tab is empty and untitled, reuse it
|
||||
if (!tab.filePath && !tab.isDirty && tab.content === '') {
|
||||
tab.filePath = filePath;
|
||||
tab.title = filePath.split('/').pop();
|
||||
tab.title = fileName;
|
||||
tab.content = content;
|
||||
tab.originalContent = content;
|
||||
tab.isDirty = false;
|
||||
@@ -572,7 +966,7 @@ class TabManager {
|
||||
this.createNewTab();
|
||||
tab = this.tabs.get(this.activeTabId);
|
||||
tab.filePath = filePath;
|
||||
tab.title = filePath.split('/').pop();
|
||||
tab.title = fileName;
|
||||
tab.content = content;
|
||||
tab.originalContent = content;
|
||||
tab.isDirty = false;
|
||||
@@ -601,6 +995,24 @@ let tabManager;
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
tabManager = new TabManager();
|
||||
|
||||
// Attach input listener to the initial editor (tab 1)
|
||||
const initialEditor = document.getElementById('editor-1');
|
||||
if (initialEditor) {
|
||||
initialEditor.addEventListener('input', () => {
|
||||
tabManager.handleEditorInput(1);
|
||||
});
|
||||
|
||||
// Add scroll listener for line number sync
|
||||
initialEditor.addEventListener('scroll', () => {
|
||||
if (tabManager.showLineNumbers && tabManager.activeTabId === 1) {
|
||||
const lineNumbers = document.getElementById('line-numbers-1');
|
||||
if (lineNumbers) {
|
||||
lineNumbers.scrollTop = initialEditor.scrollTop;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Request current theme
|
||||
ipcRenderer.send('get-theme');
|
||||
|
||||
@@ -624,7 +1036,9 @@ ipcRenderer.on('file-new', () => {
|
||||
});
|
||||
|
||||
ipcRenderer.on('file-opened', (event, data) => {
|
||||
tabManager.openFile(data.path, data.content);
|
||||
if (tabManager) {
|
||||
tabManager.openFile(data.path, data.content);
|
||||
}
|
||||
});
|
||||
|
||||
ipcRenderer.on('file-save', () => {
|
||||
@@ -852,6 +1266,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const advancedOptions = document.getElementById('advanced-export-options');
|
||||
if (e.target.checked) {
|
||||
advancedOptions.classList.remove('hidden');
|
||||
// Scroll the advanced options into view after they become visible
|
||||
setTimeout(() => {
|
||||
advancedOptions.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}, 100);
|
||||
} else {
|
||||
advancedOptions.classList.add('hidden');
|
||||
}
|
||||
@@ -951,6 +1369,22 @@ ipcRenderer.on('show-batch-dialog', () => {
|
||||
showBatchDialog();
|
||||
});
|
||||
|
||||
// ConvertAPI dialog handlers
|
||||
ipcRenderer.on('show-convertapi-dialog', () => {
|
||||
showConvertAPIDialog();
|
||||
});
|
||||
|
||||
ipcRenderer.on('convertapi-status', (event, status) => {
|
||||
document.getElementById('convertapi-status').textContent = status;
|
||||
});
|
||||
|
||||
ipcRenderer.on('convertapi-complete', (event, result) => {
|
||||
document.getElementById('convertapi-progress').classList.add('hidden');
|
||||
if (result.success) {
|
||||
document.getElementById('convertapi-dialog').classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
ipcRenderer.on('batch-progress', (event, progress) => {
|
||||
updateBatchProgress(progress);
|
||||
});
|
||||
@@ -1103,14 +1537,79 @@ if (originalExportConfirm) {
|
||||
});
|
||||
}
|
||||
|
||||
// ConvertAPI Dialog Functions
|
||||
function showConvertAPIDialog() {
|
||||
const dialog = document.getElementById('convertapi-dialog');
|
||||
dialog.classList.remove('hidden');
|
||||
|
||||
// Load saved API key if exists
|
||||
const savedKey = localStorage.getItem('convertapi-key');
|
||||
if (savedKey) {
|
||||
document.getElementById('convertapi-key').value = savedKey;
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// ConvertAPI dialog close
|
||||
const convertAPIDialogClose = document.getElementById('convertapi-dialog-close');
|
||||
if (convertAPIDialogClose) {
|
||||
convertAPIDialogClose.addEventListener('click', () => {
|
||||
document.getElementById('convertapi-dialog').classList.add('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
// ConvertAPI cancel
|
||||
const convertAPICancel = document.getElementById('convertapi-cancel');
|
||||
if (convertAPICancel) {
|
||||
convertAPICancel.addEventListener('click', () => {
|
||||
document.getElementById('convertapi-dialog').classList.add('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
// ConvertAPI convert
|
||||
const convertAPIConvert = document.getElementById('convertapi-convert');
|
||||
if (convertAPIConvert) {
|
||||
convertAPIConvert.addEventListener('click', () => {
|
||||
const apiKey = document.getElementById('convertapi-key').value;
|
||||
const fromFormat = document.getElementById('convertapi-from').value;
|
||||
const toFormat = document.getElementById('convertapi-to').value;
|
||||
const filePath = tabManager.getCurrentFilePath();
|
||||
|
||||
if (!apiKey) {
|
||||
alert('Please enter your ConvertAPI key');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!filePath) {
|
||||
alert('Please save the file first');
|
||||
return;
|
||||
}
|
||||
|
||||
// Save API key for future use
|
||||
localStorage.setItem('convertapi-key', apiKey);
|
||||
|
||||
// Show progress
|
||||
document.getElementById('convertapi-progress').classList.remove('hidden');
|
||||
|
||||
// Send conversion request
|
||||
ipcRenderer.send('convertapi-convert', {
|
||||
apiKey,
|
||||
fromFormat,
|
||||
toFormat,
|
||||
filePath
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// IPC event listeners for recent files functionality
|
||||
if (window.electronAPI) {
|
||||
window.electronAPI.on('recent-files-cleared', () => {
|
||||
ipcRenderer.on('recent-files-cleared', () => {
|
||||
if (tabManager) {
|
||||
tabManager.recentFiles = [];
|
||||
localStorage.setItem('recentFiles', JSON.stringify([]));
|
||||
console.log('Recent files cleared');
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Add math rendering support using KaTeX for enhanced preview
|
||||
function initMathSupport() {
|
||||
|
||||
+5
-2
@@ -859,8 +859,9 @@ body.theme-github .status-bar {
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
|
||||
width: 90%;
|
||||
max-width: 600px;
|
||||
max-height: 80vh;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.export-dialog-header {
|
||||
@@ -1437,7 +1438,9 @@ body.theme-github .line-numbers {
|
||||
|
||||
.advanced-options {
|
||||
transition: all 0.3s ease;
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.advanced-options.hidden {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// Test script to verify export functionality
|
||||
const { exec } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
console.log('Testing export functionality...');
|
||||
|
||||
// Test 1: Check if pandoc is available
|
||||
console.log('\n1. Checking Pandoc availability...');
|
||||
exec('pandoc --version', (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
console.log('❌ Pandoc not available:', error.message);
|
||||
console.log('✅ Built-in HTML and PDF export should work');
|
||||
} else {
|
||||
console.log('✅ Pandoc is available');
|
||||
console.log(' Version info:', stdout.split('\n')[0]);
|
||||
}
|
||||
});
|
||||
|
||||
// Test 2: Check if test markdown file exists
|
||||
console.log('\n2. Checking test file...');
|
||||
const testFile = path.join(__dirname, 'test-export.md');
|
||||
if (fs.existsSync(testFile)) {
|
||||
console.log('✅ Test markdown file exists:', testFile);
|
||||
const content = fs.readFileSync(testFile, 'utf8');
|
||||
console.log(' File size:', content.length, 'characters');
|
||||
} else {
|
||||
console.log('❌ Test markdown file not found');
|
||||
}
|
||||
|
||||
// Test 3: Check marked library
|
||||
console.log('\n3. Testing marked library...');
|
||||
try {
|
||||
const marked = require('marked');
|
||||
const testMarkdown = '# Test\nThis is a **test** markdown.';
|
||||
const html = marked.parse(testMarkdown);
|
||||
console.log('✅ Marked library working');
|
||||
console.log(' Sample output:', html.substring(0, 50) + '...');
|
||||
} catch (error) {
|
||||
console.log('❌ Marked library error:', error.message);
|
||||
}
|
||||
|
||||
console.log('\n✅ Export functionality test completed!');
|
||||
console.log('\nHow to test exports:');
|
||||
console.log('1. Open the application');
|
||||
console.log('2. Open test-export.md file');
|
||||
console.log('3. Try exporting to HTML (should work without Pandoc)');
|
||||
console.log('4. Try exporting to PDF (should work without Pandoc)');
|
||||
console.log('5. Try exporting to DOCX (requires Pandoc)');
|
||||
@@ -0,0 +1,26 @@
|
||||
# Test Document
|
||||
|
||||
This is a test markdown document for export testing.
|
||||
|
||||
## Features
|
||||
|
||||
- **Bold text**
|
||||
- *Italic text*
|
||||
- [Links](https://example.com)
|
||||
|
||||
## Code Example
|
||||
|
||||
```javascript
|
||||
function hello() {
|
||||
console.log("Hello World!");
|
||||
}
|
||||
```
|
||||
|
||||
## Table
|
||||
|
||||
| Column 1 | Column 2 | Column 3 |
|
||||
|----------|----------|----------|
|
||||
| Value 1 | Value 2 | Value 3 |
|
||||
| Value 4 | Value 5 | Value 6 |
|
||||
|
||||
This is a test document to verify export functionality.
|
||||
Reference in New Issue
Block a user