Compare commits

...
7 Commits
Author SHA1 Message Date
amitwhandClaude Sonnet 4.5 d23ce91506 Release v2.1.0: UI Enhancements & Comprehensive Format Support
🎨 UI/UX Improvements:
- Subtle & small preview popout button with minimalist design
- Simplified table headers with light gray background
- Better readability and cleaner appearance

📥 Enhanced Import:
- Support for 30+ formats: RST, Textile, MediaWiki, Org-mode, AsciiDoc, CSV, JSON, and more
- Format-specific conversion optimizations
- Improved error messages

🎨 Exhaustive ASCII Art Generator:
- 5 text banner styles: Standard, Banner, Block, Bubble, Digital
- 19 professional templates organized in 4 categories
- Flowcharts, sequence diagrams, network diagrams, hierarchy trees
- Note boxes, warning boxes, decorative elements

📝 Documentation:
- Added UPDATES.md with comprehensive changelog
- Updated version to 2.1.0 in all files

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-14 10:27:36 +05:30
amitwhandClaude Sonnet 4.5 b281255e90 Release v2.0.0: Major Feature Update
New Features:
• Export Profiles - Save and load export configurations
• Mermaid.js diagram support - Render flowcharts, sequence diagrams, and more
• Command Palette (Ctrl+Shift+P) - Quick access to all commands
• GitHub Light/Dark preview themes - Beautiful code preview styling
• Table Generator - Interactive table creation tool
• ASCII Art Generator - Create text banners, boxes, and templates
• Resizable Preview Pane - Drag divider to adjust editor/preview sizes
• Pop-out Preview Window - Open preview in separate window with live sync

UI/UX Improvements:
• Fixed advanced export dialog visibility (added .hidden CSS class)
• GitHub-style table headers with light grey background
• Removed gradients from table headers
• Clean, professional preview rendering

Technical Changes:
• Updated all UI components for new features
• Enhanced TabManager to support resizable panes
• Added MutationObserver for live preview sync in pop-out window
• Improved CSS styling consistency

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-13 19:40:30 +05:30
amitwhandClaude d8e0748037 Release v1.9.2: Full-width background for code blocks
Fixed code block and ASCII art background to extend full paragraph width:

🔧 Background Fix:
- Moved shading (w:shd) from run level (<w:rPr>) to paragraph level (<w:pPr>)
- Background now extends full width like in the preview
- Applied to both createCodeBlockXml() and createAsciiArtXml() functions

📝 Technical Changes:
- src/wordTemplateExporter.js: Paragraph-level shading for full-width backgrounds
- Added w:ind with left="284" right="284" for consistent margins
- Removed redundant w:shd from run properties

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-12 09:10:18 +05:30
amitwhandClaude cb98549db8 Release v1.9.1: ASCII Art & Code Block Preservation
Fixed export of ASCII art, charts, tables, and flowcharts to match preview exactly:

🎨 ASCII Art Preservation:
- Code blocks now export exactly as shown in preview
- Each line rendered separately to preserve exact spacing and alignment
- Monospace font (Consolas) used consistently across all export formats
- Line breaks, spacing, and box-drawing characters preserved perfectly

📝 Word Template Exporter Improvements:
- createCodeBlockXml: Renders each line as separate paragraph with exact spacing
- Added <w:keepLines/> and <w:wordWrap w:val="0"/> for no-wrap behavior
- Enhanced isAsciiArt detection with more Unicode box-drawing characters
- Added support for rounded corners (╭╮╯╰), heavy box chars, more arrows

📄 PDF Export Enhancements:
- Added -V monofont="Consolas" for proper code block rendering
- Added --highlight-style=tango for syntax highlighting
- Applied to main export, fallback engines, and CLI conversion

🖨️ HTML/Electron PDF Improvements:
- Enhanced CSS: white-space: pre, word-wrap: normal
- font-family: Consolas, Monaco, 'Courier New', monospace
- Print media queries preserve code block formatting
- overflow: visible prevents content truncation

🔧 Technical Changes:
- src/wordTemplateExporter.js: Enhanced code block and ASCII art rendering
- src/main.js: Added monofont settings to all PDF export paths
- Updated version to 1.9.1

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-12 09:01:17 +05:30
amitwh ff651e9b21 Update Claude Code permissions settings 2025-11-08 17:39:14 +05:30
amitwhandClaude e013e5fd69 Fix v1.9.0 logo upload and add MiKTeX PATH support
This commit fixes the logo upload issue and adds MiKTeX to the app's PATH for LaTeX/PDF support.

**Logo Upload Fix:**
- Replaced file input with Browse button that uses dialog.showOpenDialog
- Added browse-header-footer-logo IPC handler in main process
- Now gets reliable file paths without depending on file.path property
- Added browse-btn CSS styling

**MiKTeX PATH Addition:**
- Automatically adds MiKTeX bin directory to process.env.PATH on Windows
- Enables XeLaTeX/PDFLaTeX for PDF exports with headers/footers
- Logs PATH addition for debugging

**Files Changed:**
- src/main.js: Added MiKTeX PATH setup, browse logo IPC handler
- src/renderer.js: Changed to browseForLogo() function, updated event listeners
- src/index.html: Replaced file inputs with browse buttons
- src/styles.css: Added .browse-btn styling

**Testing Notes:**
- Logo upload now works without "path undefined" errors
- DOCX exports with headers/footers confirmed working
- PDF exports should now use Pandoc+XeLaTeX with MiKTeX in PATH
- Requires MiKTeX installation and PC restart for full functionality

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 19:33:36 +05:30
amitwhandClaude f7362bec46 Fix v1.9.0 header/footer visibility issues
This commit fixes two critical issues in the v1.9.0 header/footer feature:

1. **Logo Upload Error Fix**:
   - Fixed "path undefined" error when uploading logos
   - Added validation in main.js to check filePath before processing
   - Added fallback in renderer.js to use webUtils.getPathForFile if file.path is undefined
   - Added file existence check before copying

2. **Header/Footer Visibility Fix**:
   - Added header/footer support to tryPdfFallback function (PDF fallback engines)
   - Previously, only performExportWithOptions had header/footer logic
   - Now all PDF export paths include headers/footers when enabled
   - Fallback engines now generate fancyhdr LaTeX code for headers/footers

Technical details:
- src/main.js:667-709: Enhanced logo upload handler with error checking
- src/main.js:1289-1319: Added header/footer to tryPdfFallback function
- src/renderer.js:2661-2678: Fixed logo file path handling

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 17:41:37 +05:30
10 changed files with 2796 additions and 127 deletions
File diff suppressed because one or more lines are too long
+83 -13
View File
@@ -4,7 +4,7 @@
**PanConverter** is a cross-platform Markdown editor and converter powered by Pandoc, built with Electron. It provides professional-grade editing capabilities with comprehensive export options. **PanConverter** is a cross-platform Markdown editor and converter powered by Pandoc, built with Electron. It provides professional-grade editing capabilities with comprehensive export options.
**Current Version**: v1.9.0 **Current Version**: v1.9.2
**Author**: Amit Haridas (amit.wh@gmail.com) **Author**: Amit Haridas (amit.wh@gmail.com)
**License**: MIT **License**: MIT
**Repository**: https://github.com/amitwh/pan-converter **Repository**: https://github.com/amitwh/pan-converter
@@ -113,7 +113,77 @@ gh release create v1.2.1 --title "Title" --notes "Release notes" \
## Feature Implementation Guide ## Feature Implementation Guide
### v1.9.0 Custom Headers & Footers (Latest) ### v1.9.1 ASCII Art & Code Block Preservation (Latest)
#### 🎨 Preserved ASCII Art in Exports
**ASCII Art, Charts, Tables & Flowcharts Now Export Exactly as Previewed** (`src/wordTemplateExporter.js`, `src/main.js`)
**Problem Solved:**
- Previously, ASCII art, flowcharts, and code blocks were being corrupted during export to Word and PDF
- Line breaks, spacing, and monospace alignment were lost
- Box-drawing characters and diagrams became unreadable
**Solution Implemented:**
1. **Enhanced Word Template Exporter** (`src/wordTemplateExporter.js:259-289`)
- Code blocks now render each line as a separate paragraph with exact spacing
- `white-space: pre` and `word-wrap: normal` prevent text wrapping
- Monospace font (Consolas) with consistent 18pt size
- Light gray background (#F5F5F5) for visual distinction
- `<w:keepLines/>` and `<w:wordWrap w:val="0"/>` preserve exact formatting
2. **Improved ASCII Art Detection** (`src/wordTemplateExporter.js:410-456`)
- Extended Unicode box-drawing character support (╭╮╯╰ rounded corners, heavy box characters)
- Additional arrow characters (↔↕⇒⇐⇓⇑)
- More ASCII patterns detected (+---+---+, <---->, etc.)
- Better markdown table vs ASCII art differentiation
3. **Enhanced PDF Export** (`src/main.js:1266-1268`, `src/main.js:1345-1347`, `src/main.js:2410-2412`)
- Added `-V monofont="Consolas"` for monospace code blocks
- Added `--highlight-style=tango` for syntax highlighting
- Applied to main export, fallback export, and CLI conversion
4. **Electron PDF Fallback** (`src/main.js:1613-1637`)
- Enhanced CSS for `<pre>` and `<code>` elements
- `white-space: pre` prevents wrapping
- `font-family: Consolas, Monaco, 'Courier New', monospace`
- Print media query preserves formatting when printing
5. **HTML Export** (`src/main.js:1525-1548`)
- Same CSS improvements as Electron PDF
- Code blocks maintain exact preview appearance
**Key CSS Properties for ASCII Preservation:**
```css
pre {
white-space: pre;
word-wrap: normal;
font-family: Consolas, Monaco, 'Courier New', monospace;
font-size: 12px;
line-height: 1.4;
}
pre code {
white-space: pre;
word-wrap: normal;
display: block;
}
```
**Word XML Properties for ASCII Preservation:**
```xml
<w:pPr>
<w:spacing w:line="240" w:lineRule="exact"/>
<w:keepLines/>
<w:wordWrap w:val="0"/>
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Consolas" w:hAnsi="Consolas" w:cs="Consolas"/>
<w:noProof/>
</w:rPr>
```
---
### v1.9.0 Custom Headers & Footers
#### 📄 Comprehensive Header & Footer System #### 📄 Comprehensive Header & Footer System
**Custom Headers & Footers for Standard Exports** (`src/main.js:701-853`, `src/index.html:929-1042`, `src/renderer.js:2560-2747`, `src/styles.css:3162-3432`) **Custom Headers & Footers for Standard Exports** (`src/main.js:701-853`, `src/index.html:929-1042`, `src/renderer.js:2560-2747`, `src/styles.css:3162-3432`)
@@ -1109,7 +1179,7 @@ if (!gotTheLock) {
## Pending Tasks & Future Enhancements ## Pending Tasks & Future Enhancements
No pending tasks at this time. All planned v1.9.0 features have been completed. No pending tasks at this time. All planned v1.9.1 features have been completed.
### Potential Future Enhancements ### Potential Future Enhancements
- Image embedding in headers/footers (partially implemented - logo upload UI ready) - Image embedding in headers/footers (partially implemented - logo upload UI ready)
@@ -1119,19 +1189,19 @@ No pending tasks at this time. All planned v1.9.0 features have been completed.
--- ---
**Last Updated**: October 28, 2025 **Last Updated**: December 12, 2025
**Claude Assistant**: Development completed for v1.9.0 with Custom Headers & Footers! **Claude Assistant**: Development completed for v1.9.1 with ASCII Art Preservation!
### v1.9.0 - Latest Release Summary ### v1.9.1 - Latest Release Summary
- **Custom Headers & Footers**: Professional headers/footers for PDF, DOCX, ODT, and PowerPoint exports - **ASCII Art Preservation**: Code blocks, flowcharts, tables, and ASCII art now export exactly as shown in preview
- **Dynamic Field Support**: $PAGE$, $TOTAL$, $DATE$, $TIME$, $TITLE$, $AUTHOR$, $FILENAME$ - **Enhanced Code Block Rendering**: Each line rendered separately to preserve exact spacing and alignment
- **Logo Upload UI**: Ready for image embedding in headers/footers - **Monospace Font Support**: Consolas font used consistently across all export formats
- **Three-Column Layout**: Left/Center/Right positioning for headers and footers - **Improved ASCII Detection**: Extended Unicode box-drawing and arrow character support
- **Batch Support**: Full header/footer integration in batch converter - **PDF Export Enhancement**: Added monofont and highlight-style options for better code rendering
- **Persistent Settings**: Configurations saved across sessions - **HTML/Electron PDF Fix**: Enhanced CSS to prevent text wrapping in code blocks
- **Dark Theme Support**: Complete styling for all 22 themes
### Previous Releases ### Previous Releases
- v1.9.0: Custom Headers & Footers for PDF, DOCX, ODT, PowerPoint exports
- v1.8.3: Streamlined PDF Editor UI & Configurable Template Settings - v1.8.3: Streamlined PDF Editor UI & Configurable Template Settings
- v1.8.2: Enhanced PDF Export & Print Fix - v1.8.2: Enhanced PDF Export & Print Fix
- v1.8.1: Streamlined PDF Editor UI - v1.8.1: Streamlined PDF Editor UI
+166
View File
@@ -0,0 +1,166 @@
# PanConverter - Updates & Changelog
## Version 2.1.0 (December 14, 2025)
### 🎨 UI/UX Improvements
#### Subtle & Small Preview Popout Button
- Redesigned popout button with minimalist aesthetic
- Removed border for cleaner appearance
- Reduced size: 11px font, 2px×6px padding (previously 14px font, 4px×8px padding)
- Added opacity transition: 50% when idle, 100% on hover
- Subtle background effect on hover instead of heavy border styling
- **File**: `src/styles.css:195-211`
#### Simplified Table Headers in Preview
- Removed gradient background from table headers in modern theme
- Changed from `var(--primary-gradient)` (purple gradient) to simple light gray (#f0f0f0)
- Updated text color to dark (#333333) for better readability
- Clean, professional appearance matching standard themes
- **File**: `src/styles-modern.css:445-449`
### 📥 Enhanced Import Capabilities
#### Comprehensive Format-to-Markdown Conversion
Dramatically expanded the "Import Document" feature to support 30+ file formats:
**Supported Formats:**
- **Documents**: DOCX, ODT, RTF, HTML, HTM, TEX, EPUB, PDF, TXT
- **Presentations**: PPTX, ODP
- **Markup Languages**: RST, Textile, MediaWiki, Org-mode, AsciiDoc, TWiki, OPML
- **E-book Formats**: EPUB, FB2
- **LaTeX Formats**: TEX, LATEX, LTX
- **Web Formats**: HTML, HTM, XHTML
- **Wiki Formats**: MediaWiki, DokuWiki, TikiWiki, TWiki
- **Data Formats**: CSV, TSV, JSON
**Format-Specific Optimizations:**
- PDF text extraction with XeLaTeX engine
- CSV/TSV automatic table conversion
- JSON structure handling
- Improved error messages with format hints
**Access**: File → Import Document (Ctrl+I)
**File**: `src/main.js:1933-1994`
### 🎨 Exhaustive ASCII Art Generator
#### 5 New Text Banner Styles
Complete alphabet (A-Z) and numbers (0-9) support for all styles:
1. **Standard** - Classic ASCII art with slashes and underscores
2. **Banner** - Large format using # characters (7-line height)
3. **Block** - Modern Unicode block characters (█ ╔ ╗ ═ ║)
4. **Bubble** - Circular bubble letters (Ⓐ Ⓑ Ⓒ)
5. **Digital** - Digital display style (▄ ▀ ▐ ▌)
**File**: `src/renderer.js:3397-3537`
#### 19 Professional ASCII Templates
Organized into 4 categories with expanded options:
**Arrows & Flow (4 templates):**
- Arrow Right - Horizontal flow indicators
- Arrow Down - Vertical flow indicators
- Decision - Binary decision diagrams
- Process Flow - Multi-step process visualization
**Diagrams & Charts (6 templates):**
- Flowchart - Advanced flowchart with decision branches and loops
- Sequence - Sequence diagrams for User-System-Database interactions
- Network - Server-client network topology
- Hierarchy - Organizational tree structures
- Timeline - Milestone visualization with dates
- Table Simple - Basic table template with borders
**Boxes & Containers (4 templates):**
- Header - Section header with decorative borders
- Note Box - Important notes with rounded corners (┏━━┓)
- Warning Box - Warning messages with bold borders (╔═══╗)
- Info Box - Information boxes with subtle styling (╭───╮)
**Decorative Elements (6 templates):**
- Divider - Horizontal section separator (═══)
- Separator Fancy - Elegant rounded divider
- Brackets - Japanese-style brackets 【 】
- Banner Stars - Star-bordered banners
- Checklist - Task lists with ✓ checkmarks
- Progress Bar - Visual progress indicators
**Features:**
- All ASCII art automatically wrapped in code blocks for proper rendering
- Preserved formatting in markdown preview and all export formats
- Categorized template selection interface
- Real-time preview generation
**Access**: Tools → ASCII Art Generator
**Files**: `src/renderer.js:3513-3671`, `src/index.html:427-466`
### 📝 Technical Improvements
- Enhanced ASCII art detection in Word template exporter
- Improved monospace font rendering across all export formats
- Better code block preservation in PDF and Word exports
- Optimized template categorization and organization
### 🔧 Files Modified
- `src/styles.css` - Preview popout button styling
- `src/styles-modern.css` - Table header simplification
- `src/main.js` - Enhanced import function, version update
- `src/renderer.js` - ASCII art generator enhancements
- `src/index.html` - ASCII template UI organization
- `package.json` - Version bump to 2.1.0
---
## Version 2.0.0 (Previous Release)
### Major Features
- Export Profiles - Save and reuse export configurations
- Mermaid.js diagram support
- Command Palette (Ctrl+Shift+P)
- GitHub Light/Dark preview themes
- Table Generator
- ASCII Art Generator (basic)
- Resizable Preview Pane
- Pop-out Preview Window
- Configurable page sizes (A3-A5, B4-B5, Letter, Legal, Tabloid, Custom)
- Custom Headers & Footers for exports
- Enhanced PDF and Word export with templates
- 22 beautiful themes
### Core Capabilities
- Cross-platform markdown editor with live preview
- Universal document conversion (30+ formats)
- PDF Editor (merge, split, compress, rotate, watermark, encrypt)
- Batch file conversion
- File association support
- Advanced export options
- Multi-tab interface
---
## Installation & Usage
### Prerequisites
- **Pandoc** - Required for document conversion
- **Optional**: LibreOffice, ImageMagick, FFmpeg for universal converter
### Download
Get the latest release from: https://github.com/amitwh/pan-converter/releases
### Supported Platforms
- Windows (x64)
- Linux (AppImage, .deb, .snap)
- macOS (planned)
---
## Contributing
Contributions are welcome! Please see [CLAUDE.md](CLAUDE.md) for development guidelines.
**Author**: Amit Haridas (amit.wh@gmail.com)
**License**: MIT
**Repository**: https://github.com/amitwh/pan-converter
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "pan-converter", "name": "pan-converter",
"version": "1.9.0", "version": "2.1.0",
"description": "Cross-platform Markdown editor and converter using Pandoc", "description": "Cross-platform Markdown editor and converter using Pandoc",
"main": "src/main.js", "main": "src/main.js",
"scripts": { "scripts": {
+226 -2
View File
@@ -11,6 +11,8 @@
<link rel="stylesheet" href="styles-modern.css"> <link rel="stylesheet" href="styles-modern.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/codemirror.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/codemirror.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css">
<!-- Mermaid.js for diagram rendering -->
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
@@ -148,6 +150,19 @@
</div> </div>
<div class="export-dialog-body"> <div class="export-dialog-body">
<!-- Simple/Advanced Export Toggle --> <!-- Simple/Advanced Export Toggle -->
<!-- Export Profiles -->
<div class="export-section export-profiles">
<label>Export Profile:</label>
<div class="profile-controls">
<select id="export-profile-select">
<option value="">Custom Settings</option>
</select>
<button id="save-profile-btn" type="button" title="Save current settings as profile">💾 Save</button>
<button id="delete-profile-btn" type="button" title="Delete selected profile">🗑️ Delete</button>
</div>
<small class="export-help">Save and reuse your favorite export configurations</small>
</div>
<div class="export-section export-mode-toggle"> <div class="export-section export-mode-toggle">
<label class="checkbox-label"> <label class="checkbox-label">
<input type="checkbox" id="advanced-export-toggle"> <input type="checkbox" id="advanced-export-toggle">
@@ -243,6 +258,37 @@
</div> <!-- End advanced-export-options --> </div> <!-- End advanced-export-options -->
<!-- Basic Export Options (always visible) --> <!-- Basic Export Options (always visible) -->
<div class="export-section basic-options">
<label>Page Size & Orientation:</label>
<div class="form-row">
<label for="page-size">Size:</label>
<select id="page-size">
<option value="a4">A4 (210×297mm)</option>
<option value="a3">A3 (297×420mm)</option>
<option value="a5">A5 (148×210mm)</option>
<option value="b4">B4 (250×353mm)</option>
<option value="b5">B5 (176×250mm)</option>
<option value="letter">Letter (8.5×11in)</option>
<option value="legal">Legal (8.5×14in)</option>
<option value="tabloid">Tabloid (11×17in)</option>
<option value="custom">Custom...</option>
</select>
</div>
<div class="form-row">
<label for="page-orientation">Orientation:</label>
<select id="page-orientation">
<option value="portrait">Portrait</option>
<option value="landscape">Landscape</option>
</select>
</div>
<div id="custom-page-size" class="form-row" style="display: none;">
<label>Custom Size:</label>
<input type="text" id="custom-width" placeholder="Width (e.g., 210mm)" style="width: 45%; display: inline-block;">
<span style="display: inline-block; width: 5%; text-align: center;">×</span>
<input type="text" id="custom-height" placeholder="Height (e.g., 297mm)" style="width: 45%; display: inline-block;">
</div>
</div>
<div class="export-section basic-options"> <div class="export-section basic-options">
<label>Quick Export Settings:</label> <label>Quick Export Settings:</label>
<div class="checkbox-group"> <div class="checkbox-group">
@@ -258,6 +304,180 @@
</div> </div>
</div> </div>
<!-- Command Palette -->
<div id="command-palette" class="command-palette hidden">
<div class="command-palette-content">
<input type="text" id="command-search" placeholder="Type a command..." autofocus>
<div id="command-list" class="command-list"></div>
<div class="command-palette-footer">
<span>↑↓ Navigate</span>
<span>Enter Execute</span>
<span>Esc Close</span>
</div>
</div>
</div>
<!-- Table Generator Dialog -->
<div id="table-generator-dialog" class="export-dialog hidden">
<div class="export-dialog-content" style="max-width: 500px;">
<div class="export-dialog-header">
<h3>📊 Table Generator</h3>
<button id="table-dialog-close" title="Close">×</button>
</div>
<div class="export-dialog-body">
<div class="export-section">
<label for="table-rows">Number of Rows:</label>
<input type="number" id="table-rows" min="1" max="50" value="3" style="width: 100px;">
<small>Total rows including header (if enabled)</small>
</div>
<div class="export-section">
<label for="table-cols">Number of Columns:</label>
<input type="number" id="table-cols" min="1" max="20" value="3" style="width: 100px;">
</div>
<div class="export-section">
<label class="checkbox-label">
<input type="checkbox" id="table-has-header" checked>
Include Header Row
</label>
<small>First row will be formatted as table headers</small>
</div>
<div class="export-section">
<label for="table-alignment">Column Alignment:</label>
<select id="table-alignment">
<option value="left">Left</option>
<option value="center">Center</option>
<option value="right">Right</option>
</select>
<small>Applies to all columns</small>
</div>
<div class="export-section">
<label for="table-preview">Preview:</label>
<pre id="table-preview" style="background: #f5f5f5; padding: 10px; border-radius: 4px; font-size: 12px; max-height: 300px; overflow: auto;"></pre>
</div>
</div>
<div class="export-dialog-footer">
<button id="table-generate-preview" class="btn-secondary">Update Preview</button>
<button id="table-insert" class="btn-primary">Insert Table</button>
<button id="table-cancel" class="btn-secondary">Cancel</button>
</div>
</div>
</div>
<!-- ASCII Art Generator Dialog -->
<div id="ascii-art-dialog" class="export-dialog hidden">
<div class="export-dialog-content" style="max-width: 700px;">
<div class="export-dialog-header">
<h3>🎨 ASCII Art Generator</h3>
<button id="ascii-dialog-close" title="Close">×</button>
</div>
<div class="export-dialog-body">
<div class="export-section">
<label>Mode:</label>
<div style="display: flex; gap: 10px; margin-bottom: 10px;">
<button id="ascii-mode-text" class="btn-secondary ascii-mode-btn active">Text Banner</button>
<button id="ascii-mode-box" class="btn-secondary ascii-mode-btn">Box/Frame</button>
<button id="ascii-mode-templates" class="btn-secondary ascii-mode-btn">Templates</button>
</div>
</div>
<!-- Text Banner Mode -->
<div id="ascii-text-mode" class="ascii-mode-section">
<div class="export-section">
<label for="ascii-text-input">Text to Convert:</label>
<input type="text" id="ascii-text-input" placeholder="Enter your text..." maxlength="50">
</div>
<div class="export-section">
<label for="ascii-font-style">Style:</label>
<select id="ascii-font-style">
<option value="standard">Standard</option>
<option value="banner">Banner</option>
<option value="block">Block</option>
<option value="bubble">Bubble</option>
<option value="digital">Digital</option>
</select>
</div>
</div>
<!-- Box/Frame Mode -->
<div id="ascii-box-mode" class="ascii-mode-section hidden">
<div class="export-section">
<label for="ascii-box-text">Text Content:</label>
<textarea id="ascii-box-text" rows="3" placeholder="Enter text for the box..."></textarea>
</div>
<div class="export-section">
<label for="ascii-box-style">Box Style:</label>
<select id="ascii-box-style">
<option value="single">Single Line (─│┌┐└┘)</option>
<option value="double">Double Line (═║╔╗╚╝)</option>
<option value="rounded">Rounded (─│╭╮╰╯)</option>
<option value="bold">Bold (━┃┏┓┗┛)</option>
<option value="ascii">ASCII (+|-)</option>
</select>
</div>
<div class="export-section">
<label for="ascii-box-padding">Padding:</label>
<input type="number" id="ascii-box-padding" min="1" max="10" value="2" style="width: 80px;">
</div>
</div>
<!-- Templates Mode -->
<div id="ascii-templates-mode" class="ascii-mode-section hidden">
<div class="export-section">
<label>Arrows & Flow:</label>
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin-bottom: 15px;">
<button class="btn-secondary ascii-template-btn" data-template="arrow-right">Arrow →</button>
<button class="btn-secondary ascii-template-btn" data-template="arrow-down">Arrow ↓</button>
<button class="btn-secondary ascii-template-btn" data-template="decision">Decision</button>
<button class="btn-secondary ascii-template-btn" data-template="process-flow">Process Flow</button>
</div>
<label>Diagrams & Charts:</label>
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin-bottom: 15px;">
<button class="btn-secondary ascii-template-btn" data-template="flowchart">Flowchart</button>
<button class="btn-secondary ascii-template-btn" data-template="sequence">Sequence</button>
<button class="btn-secondary ascii-template-btn" data-template="network">Network</button>
<button class="btn-secondary ascii-template-btn" data-template="hierarchy">Hierarchy</button>
<button class="btn-secondary ascii-template-btn" data-template="timeline">Timeline</button>
<button class="btn-secondary ascii-template-btn" data-template="table-simple">Table</button>
</div>
<label>Boxes & Containers:</label>
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin-bottom: 15px;">
<button class="btn-secondary ascii-template-btn" data-template="header">Header</button>
<button class="btn-secondary ascii-template-btn" data-template="note-box">Note Box</button>
<button class="btn-secondary ascii-template-btn" data-template="warning-box">Warning</button>
<button class="btn-secondary ascii-template-btn" data-template="info-box">Info Box</button>
</div>
<label>Decorative Elements:</label>
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin-bottom: 15px;">
<button class="btn-secondary ascii-template-btn" data-template="divider">Divider</button>
<button class="btn-secondary ascii-template-btn" data-template="separator-fancy">Separator</button>
<button class="btn-secondary ascii-template-btn" data-template="brackets">Brackets</button>
<button class="btn-secondary ascii-template-btn" data-template="banner-stars">Banner</button>
<button class="btn-secondary ascii-template-btn" data-template="check">Checklist ✓</button>
<button class="btn-secondary ascii-template-btn" data-template="progress-bar">Progress Bar</button>
</div>
</div>
</div>
<div class="export-section">
<label for="ascii-preview">Preview:</label>
<pre id="ascii-preview" style="background: #f5f5f5; padding: 15px; border-radius: 4px; font-size: 12px; max-height: 400px; overflow: auto; font-family: 'Courier New', monospace; line-height: 1.2;"></pre>
</div>
</div>
<div class="export-dialog-footer">
<button id="ascii-generate" class="btn-secondary">Generate Preview</button>
<button id="ascii-insert" class="btn-primary">Insert ASCII Art</button>
<button id="ascii-cancel" class="btn-secondary">Cancel</button>
</div>
</div>
</div>
<!-- Universal File Converter Dialog --> <!-- Universal File Converter Dialog -->
<div id="universal-converter-dialog" class="export-dialog hidden"> <div id="universal-converter-dialog" class="export-dialog hidden">
<div class="export-dialog-content"> <div class="export-dialog-content">
@@ -964,7 +1184,7 @@
</div> </div>
<div class="hf-logo-row"> <div class="hf-logo-row">
<label>Header Logo/Image:</label> <label>Header Logo/Image:</label>
<input type="file" id="header-logo" accept="image/*"> <button id="header-logo-browse" class="browse-btn">Browse...</button>
<button id="header-logo-clear" class="clear-btn">Clear</button> <button id="header-logo-clear" class="clear-btn">Clear</button>
<span id="header-logo-preview" class="logo-preview"></span> <span id="header-logo-preview" class="logo-preview"></span>
</div> </div>
@@ -992,7 +1212,7 @@
</div> </div>
<div class="hf-logo-row"> <div class="hf-logo-row">
<label>Footer Logo/Image:</label> <label>Footer Logo/Image:</label>
<input type="file" id="footer-logo" accept="image/*"> <button id="footer-logo-browse" class="browse-btn">Browse...</button>
<button id="footer-logo-clear" class="clear-btn">Clear</button> <button id="footer-logo-clear" class="clear-btn">Clear</button>
<span id="footer-logo-preview" class="logo-preview"></span> <span id="footer-logo-preview" class="logo-preview"></span>
</div> </div>
@@ -1049,7 +1269,11 @@
<textarea id="editor-1" class="editor-textarea"></textarea> <textarea id="editor-1" class="editor-textarea"></textarea>
</div> </div>
</div> </div>
<div class="pane-resizer" id="pane-resizer-1" title="Drag to resize"></div>
<div id="preview-pane-1" class="pane"> <div id="preview-pane-1" class="pane">
<div class="preview-header">
<button class="preview-popout-btn" id="preview-popout-1" title="Pop out preview in new window"></button>
</div>
<div id="preview-1" class="preview-content"></div> <div id="preview-1" class="preview-content"></div>
</div> </div>
</div> </div>
+440 -12
View File
@@ -5,6 +5,15 @@ const { exec } = require('child_process');
const { PDFDocument, rgb, degrees, StandardFonts } = require('pdf-lib'); const { PDFDocument, rgb, degrees, StandardFonts } = require('pdf-lib');
const WordTemplateExporter = require('./wordTemplateExporter'); const WordTemplateExporter = require('./wordTemplateExporter');
// Add MiKTeX to PATH for LaTeX support
if (process.platform === 'win32') {
const miktexPath = 'C:\\Program Files\\MiKTeX\\miktex\\bin\\x64';
if (fs.existsSync(miktexPath)) {
process.env.PATH = `${miktexPath};${process.env.PATH}`;
console.log('[MAIN] Added MiKTeX to PATH:', miktexPath);
}
}
// Get the system Pandoc path // Get the system Pandoc path
function getPandocPath() { function getPandocPath() {
// Pandoc is expected to be in the system's PATH. // Pandoc is expected to be in the system's PATH.
@@ -69,6 +78,66 @@ let headerFooterSettings = {
} }
}; };
// Page Size Definitions (in twentieths of a point for Word, mm/inches for Pandoc)
const PAGE_SIZES = {
a4: {
name: 'A4',
pandoc: 'a4',
word: { width: 11906, height: 16838 }, // 210×297mm
dimensions: '210×297mm'
},
a3: {
name: 'A3',
pandoc: 'a3',
word: { width: 16838, height: 23811 }, // 297×420mm
dimensions: '297×420mm'
},
a5: {
name: 'A5',
pandoc: 'a5',
word: { width: 8391, height: 11906 }, // 148×210mm
dimensions: '148×210mm'
},
b4: {
name: 'B4',
pandoc: 'b4',
word: { width: 14170, height: 20015 }, // 250×353mm
dimensions: '250×353mm'
},
b5: {
name: 'B5',
pandoc: 'b5',
word: { width: 9979, height: 14170 }, // 176×250mm
dimensions: '176×250mm'
},
letter: {
name: 'Letter',
pandoc: 'letter',
word: { width: 12240, height: 15840 }, // 8.5×11in
dimensions: '8.5×11in'
},
legal: {
name: 'Legal',
pandoc: 'legal',
word: { width: 12240, height: 20160 }, // 8.5×14in
dimensions: '8.5×14in'
},
tabloid: {
name: 'Tabloid',
pandoc: 'tabloid',
word: { width: 15840, height: 24480 }, // 11×17in
dimensions: '11×17in'
}
};
// Default page settings
let pageSettings = {
size: 'a4',
orientation: 'portrait',
customWidth: null,
customHeight: null
};
// Handle single instance lock for Windows file association // Handle single instance lock for Windows file association
// When a file is double-clicked and the app is already running, // When a file is double-clicked and the app is already running,
// Windows tries to start a second instance. We prevent this and // Windows tries to start a second instance. We prevent this and
@@ -496,6 +565,23 @@ function createMenu() {
} }
] ]
}, },
{
label: 'Tools',
submenu: [
{
label: 'Table Generator',
click: () => mainWindow.webContents.send('show-table-generator')
},
{
label: 'ASCII Art Generator',
click: () => mainWindow.webContents.send('show-ascii-generator')
},
{
label: 'Document Compare',
click: () => mainWindow.webContents.send('show-document-compare')
}
]
},
{ {
label: 'Help', label: 'Help',
submenu: [ submenu: [
@@ -506,7 +592,7 @@ function createMenu() {
type: 'info', type: 'info',
title: 'About PanConverter', title: 'About PanConverter',
message: 'PanConverter', message: 'PanConverter',
detail: 'A cross-platform Markdown editor and converter using Pandoc.\n\nVersion: 1.9.0\nAuthor: Amit Haridas\nEmail: amit.wh@gmail.com\nLicense: MIT\n\nFeatures:\n• Custom Headers & Footers for PDF, DOCX, ODT, and PowerPoint exports\n• Dynamic field support: $PAGE$, $TOTAL$, $DATE$, $TIME$, $TITLE$, $AUTHOR$, $FILENAME$\n• Logo/image embedding in headers and footers\n• Modern glassmorphism UI with gradient backgrounds\n• Enhanced PDF export via Word template with configurable start page\n• Configurable template settings (start page selection)\n• Streamlined PDF Editor UI (merge, split, compress, rotate, watermark, encrypt)\n• Universal File Converter (LibreOffice, ImageMagick, FFmpeg, Pandoc)\n• Windows Explorer context menu integration\n• Tabbed interface for multiple files\n• Advanced markdown editing with live preview\n• Real-time preview updates while typing\n• Full toolbar markdown editing functions\n• Enhanced PDF export with built-in Electron fallback\n• Enhanced Word export with template support (single file & batch)\n• File association support for .md files\n• Command-line interface for batch conversion\n• Advanced export options with templates and metadata\n• Batch file conversion with progress tracking\n• Improved preview typography and spacing\n• Adjustable font sizes via menu (Ctrl+Shift+Plus/Minus)\n• Complete theme support including Monokai fixes\n• Find & replace with match highlighting\n• Line numbers and auto-indentation\n• Export to multiple formats via Pandoc\n• PowerPoint & presentation export\n• Export tables to Excel/ODS spreadsheets\n• Document import & conversion\n• Table creation helper\n• 22 beautiful themes (including Dracula, Nord, Tokyo Night, Gruvbox, Ayu, Concrete, and more)\n• Undo/redo functionality\n• Live word count and statistics', detail: 'A cross-platform Markdown editor and converter using Pandoc.\n\nVersion: 2.1.0\nAuthor: Amit Haridas\nEmail: amit.wh@gmail.com\nLicense: MIT\n\n✨ New in v2.1.0:\n• Subtle & Small Preview Popout Button - Cleaner, minimalist design\n• Simplified Table Headers - Light gray background with better readability\n• Comprehensive Format-to-Markdown Conversion - Support for 30+ formats (RST, Textile, MediaWiki, Org-mode, AsciiDoc, CSV, JSON, and more)\n• Exhaustive ASCII Art Generator - 5 text banner styles (Standard, Banner, Block, Bubble, Digital) and 19 professional templates\n• Enhanced Templates - Flowcharts, sequence diagrams, network diagrams, hierarchy trees, timelines, note boxes, warning boxes, and decorative elements\n\n✨ Features from v2.0.0:\n• Export Profiles - Save and load export configurations\n• Mermaid.js diagram support - Render flowcharts, sequence diagrams, and more\n• Command Palette (Ctrl+Shift+P) - Quick access to all commands\n• GitHub Light/Dark preview themes - Beautiful code preview styling\n• Table Generator - Interactive table creation tool\n• Resizable Preview Pane - Drag divider to adjust editor/preview sizes\n• Pop-out Preview Window - Open preview in separate window with live sync\n\nCore Features:\n• Configurable page sizes (A3, A4, A5, B4, B5, Letter, Legal, Tabloid, Custom) with orientation support\n• Page size support for all export formats (PDF, DOCX, ODT, PowerPoint) and batch conversion\n• Preserved ASCII art, charts, tables, flowcharts in exports with full-width background\n• Custom Headers & Footers for PDF, DOCX, ODT, and PowerPoint exports\n• Dynamic field support: $PAGE$, $TOTAL$, $DATE$, $TIME$, $TITLE$, $AUTHOR$, $FILENAME$\n• Logo/image embedding in headers and footers\n• Modern glassmorphism UI with gradient backgrounds\n• Enhanced PDF export via Word template with configurable start page\n• Streamlined PDF Editor UI (merge, split, compress, rotate, watermark, encrypt)\n• Universal File Converter (LibreOffice, ImageMagick, FFmpeg, Pandoc)\n• Windows Explorer context menu integration\n• Tabbed interface for multiple files\n• Advanced markdown editing with live preview\n• Real-time preview updates while typing\n• Full toolbar markdown editing functions\n• Enhanced PDF export with built-in Electron fallback\n• Enhanced Word export with template support (single file & batch)\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• Adjustable font sizes via menu (Ctrl+Shift+Plus/Minus)\n• 22 beautiful themes (including Dracula, Nord, Tokyo Night, Gruvbox, Ayu, Concrete, and more)\n• Find & replace with match highlighting\n• Line numbers and auto-indentation\n• Undo/redo functionality\n• Live word count and statistics',
buttons: ['OK'] buttons: ['OK']
}); });
} }
@@ -663,9 +749,33 @@ ipcMain.on('save-header-footer-settings', (event, settings) => {
}); });
}); });
// Get current page settings
ipcMain.on('get-page-settings', (event) => {
event.reply('page-settings-data', pageSettings);
});
// Update page settings from export dialog
ipcMain.on('update-page-settings', (event, settings) => {
pageSettings = settings;
store.set('pageSettings', pageSettings);
console.log('Page settings updated:', pageSettings);
});
// Save header/footer logo image // Save header/footer logo image
ipcMain.on('save-header-footer-logo', async (event, { position, filePath }) => { // Browse for header/footer logo
ipcMain.on('browse-header-footer-logo', async (event, position) => {
try { try {
const result = await dialog.showOpenDialog(mainWindow, {
title: `Select ${position.charAt(0).toUpperCase() + position.slice(1)} Logo/Image`,
filters: [
{ name: 'Images', extensions: ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'svg', 'webp'] }
],
properties: ['openFile']
});
if (!result.canceled && result.filePaths.length > 0) {
const filePath = result.filePaths[0];
// Copy image to userData directory for persistent storage // Copy image to userData directory for persistent storage
const userDataPath = app.getPath('userData'); const userDataPath = app.getPath('userData');
const logoDir = path.join(userDataPath, 'logos'); const logoDir = path.join(userDataPath, 'logos');
@@ -691,7 +801,53 @@ ipcMain.on('save-header-footer-logo', async (event, { position, filePath }) => {
} }
event.reply('header-footer-logo-saved', { position, path: destPath }); event.reply('header-footer-logo-saved', { position, path: destPath });
}
} catch (error) { } catch (error) {
console.error('Logo browse error:', error);
dialog.showErrorBox('Logo Error', `Failed to select logo: ${error.message}`);
}
});
ipcMain.on('save-header-footer-logo', async (event, { position, filePath }) => {
try {
if (!filePath) {
dialog.showErrorBox('Logo Error', 'Failed to save logo: The "path" argument must be of type string. Received undefined');
return;
}
// Copy image to userData directory for persistent storage
const userDataPath = app.getPath('userData');
const logoDir = path.join(userDataPath, 'logos');
// Create logos directory if it doesn't exist
if (!fs.existsSync(logoDir)) {
fs.mkdirSync(logoDir, { recursive: true });
}
// Verify source file exists
if (!fs.existsSync(filePath)) {
dialog.showErrorBox('Logo Error', `Source file not found: ${filePath}`);
return;
}
// Generate unique filename
const ext = path.extname(filePath);
const filename = `${position}_${Date.now()}${ext}`;
const destPath = path.join(logoDir, filename);
// Copy file
fs.copyFileSync(filePath, destPath);
// Update settings
if (position === 'header') {
headerFooterSettings.header.logo = destPath;
} else if (position === 'footer') {
headerFooterSettings.footer.logo = destPath;
}
event.reply('header-footer-logo-saved', { position, path: destPath });
} catch (error) {
console.error('Logo save error:', error);
dialog.showErrorBox('Logo Error', `Failed to save logo: ${error.message}`); dialog.showErrorBox('Logo Error', `Failed to save logo: ${error.message}`);
} }
}); });
@@ -727,6 +883,67 @@ function processDynamicFields(text, metadata = {}) {
} }
// Add headers/footers to DOCX file using PizZip and docx4js // Add headers/footers to DOCX file using PizZip and docx4js
// Function to set page size in DOCX files
async function setDocxPageSize(docxPath) {
try {
const PizZip = require('pizzip');
// Read the DOCX file
const docxBuffer = fs.readFileSync(docxPath);
const zip = new PizZip(docxBuffer);
// Get document.xml
let documentXml = zip.file('word/document.xml').asText();
// Get page dimensions
let width, height;
const pageSize = PAGE_SIZES[pageSettings.size];
if (pageSize) {
width = pageSize.word.width;
height = pageSize.word.height;
} else if (pageSettings.customWidth && pageSettings.customHeight) {
// Parse custom dimensions (convert to twentieths of a point)
// Note: This is simplified - production code should handle various units
width = parseInt(pageSettings.customWidth) || 11906;
height = parseInt(pageSettings.customHeight) || 16838;
} else {
// Default to A4
width = 11906;
height = 16838;
}
// Swap dimensions for landscape
if (pageSettings.orientation === 'landscape') {
[width, height] = [height, width];
}
// Update all <w:pgSz> elements in section properties
const pgSzRegex = /<w:pgSz[^>]*\/>/g;
documentXml = documentXml.replace(pgSzRegex, () => {
return `<w:pgSz w:w="${width}" w:h="${height}" w:orient="${pageSettings.orientation}"/>`;
});
// If no pgSz found, add it to all sectPr elements
if (!pgSzRegex.test(documentXml)) {
const sectPrRegex = /<w:sectPr[^>]*>/g;
documentXml = documentXml.replace(sectPrRegex, (match) => {
return `${match}<w:pgSz w:w="${width}" w:h="${height}" w:orient="${pageSettings.orientation}"/>`;
});
}
// Save updated document.xml
zip.file('word/document.xml', documentXml);
// Write modified DOCX
const newDocxBuffer = zip.generate({ type: 'nodebuffer' });
fs.writeFileSync(docxPath, newDocxBuffer);
} catch (error) {
console.error('Failed to set DOCX page size:', error);
}
}
async function addHeaderFooterToDocx(docxPath, metadata = {}) { async function addHeaderFooterToDocx(docxPath, metadata = {}) {
if (!headerFooterSettings.enabled) return; if (!headerFooterSettings.enabled) return;
@@ -872,8 +1089,8 @@ async function exportWordWithTemplate() {
if (result.canceled) return; if (result.canceled) return;
// Create exporter instance with selected template and start page // Create exporter instance with selected template, start page, and page settings
const exporter = new WordTemplateExporter(wordTemplatePath, templateStartPage); const exporter = new WordTemplateExporter(wordTemplatePath, templateStartPage, pageSettings);
// Convert markdown to DOCX // Convert markdown to DOCX
await exporter.convert(content, result.filePath); await exporter.convert(content, result.filePath);
@@ -1196,6 +1413,42 @@ function performExportWithOptions(format, options) {
pandocCmd += ` --pdf-engine="${pdfEngine}"`; pandocCmd += ` --pdf-engine="${pdfEngine}"`;
if (options.geometry) pandocCmd += ` -V geometry:"${options.geometry}"`; if (options.geometry) pandocCmd += ` -V geometry:"${options.geometry}"`;
// Add monospace font settings for code blocks (ASCII art preservation)
pandocCmd += ' -V monofont="Consolas"';
pandocCmd += ' --highlight-style=tango';
// Add header/footer if enabled
if (headerFooterSettings.enabled) {
const filename = currentFile ? path.basename(currentFile, path.extname(currentFile)) : 'document';
const metadata = { filename, title: filename, author: '' };
const headerLeft = processDynamicFields(headerFooterSettings.header.left, metadata);
const headerCenter = processDynamicFields(headerFooterSettings.header.center, metadata);
const headerRight = processDynamicFields(headerFooterSettings.header.right, metadata);
const footerLeft = processDynamicFields(headerFooterSettings.footer.left, metadata);
const footerCenter = processDynamicFields(headerFooterSettings.footer.center, metadata);
const footerRight = processDynamicFields(headerFooterSettings.footer.right, metadata);
// Create LaTeX header
const latexHeader = `
\\usepackage{fancyhdr}
\\pagestyle{fancy}
\\fancyhf{}
\\lhead{${headerLeft.replace(/\\/g, '\\\\')}}
\\chead{${headerCenter.replace(/\\/g, '\\\\')}}
\\rhead{${headerRight.replace(/\\/g, '\\\\')}}
\\lfoot{${footerLeft.replace(/\\/g, '\\\\')}}
\\cfoot{${footerCenter.replace(/[$]PAGE[$]/g, '\\\\thepage').replace(/[$]TOTAL[$]/g, '\\\\pageref{LastPage}').replace(/\\/g, '\\\\')}}
\\rfoot{${footerRight.replace(/\\/g, '\\\\')}}
\\renewcommand{\\headrulewidth}{0.4pt}
\\renewcommand{\\footrulewidth}{0.4pt}
`;
const headerFile = path.join(require('os').tmpdir(), `header_export_${Date.now()}.tex`);
fs.writeFileSync(headerFile, latexHeader, 'utf-8');
pandocCmd += ` --include-in-header="${headerFile}"`;
pandocCmd += ' --variable header-includes="\\\\usepackage{lastpage}"';
}
// Try with specified PDF engine // Try with specified PDF engine
exec(pandocCmd, (error) => { exec(pandocCmd, (error) => {
if (error) { if (error) {
@@ -1209,6 +1462,15 @@ function performExportWithOptions(format, options) {
} else if (format === 'docx') { } else if (format === 'docx') {
pandocCmd += ' -t docx'; pandocCmd += ' -t docx';
exportWithPandoc(pandocCmd, outputFile, format); exportWithPandoc(pandocCmd, outputFile, format);
} else if (format === 'pptx') {
// Add PowerPoint footer if enabled
if (headerFooterSettings.enabled && headerFooterSettings.footer.center) {
const filename = currentFile ? path.basename(currentFile, path.extname(currentFile)) : 'document';
const metadata = { filename, title: filename, author: '' };
const footerText = processDynamicFields(headerFooterSettings.footer.center, metadata);
pandocCmd += ` --variable footer="${footerText}"`;
}
exportWithPandoc(pandocCmd, outputFile, format);
} else { } else {
// Generic export for other formats // Generic export for other formats
exportWithPandoc(pandocCmd, outputFile, format); exportWithPandoc(pandocCmd, outputFile, format);
@@ -1230,9 +1492,45 @@ function tryPdfFallback(inputFile, outputFile, engines, index, options, lastErro
const engine = engines[index]; const engine = engines[index];
let pandocCmd = `${getPandocPath()} "${inputFile}" --pdf-engine=${engine} -o "${outputFile}"`; let pandocCmd = `${getPandocPath()} "${inputFile}" --pdf-engine=${engine} -o "${outputFile}"`;
// Add monospace font settings for code blocks (ASCII art preservation)
pandocCmd += ' -V monofont="Consolas"';
pandocCmd += ' --highlight-style=tango';
// Add geometry if specified // Add geometry if specified
if (options.geometry) pandocCmd = pandocCmd.replace(` -o `, ` -V geometry:"${options.geometry}" -o `); if (options.geometry) pandocCmd = pandocCmd.replace(` -o `, ` -V geometry:"${options.geometry}" -o `);
// Add header/footer if enabled
if (headerFooterSettings.enabled) {
const filename = path.basename(inputFile, path.extname(inputFile));
const metadata = { filename, title: filename, author: options.metadata?.author || '' };
const headerLeft = processDynamicFields(headerFooterSettings.header.left, metadata);
const headerCenter = processDynamicFields(headerFooterSettings.header.center, metadata);
const headerRight = processDynamicFields(headerFooterSettings.header.right, metadata);
const footerLeft = processDynamicFields(headerFooterSettings.footer.left, metadata);
const footerCenter = processDynamicFields(headerFooterSettings.footer.center, metadata);
const footerRight = processDynamicFields(headerFooterSettings.footer.right, metadata);
// Create LaTeX header for fancyhdr
const latexHeader = `
\\usepackage{fancyhdr}
\\usepackage{lastpage}
\\pagestyle{fancy}
\\fancyhf{}
\\lhead{${headerLeft.replace(/\\/g, '\\\\')}}
\\chead{${headerCenter.replace(/\\/g, '\\\\')}}
\\rhead{${headerRight.replace(/\\/g, '\\\\')}}
\\lfoot{${footerLeft.replace(/\\/g, '\\\\')}}
\\cfoot{${footerCenter.replace(/\$PAGE\$/g, '\\\\thepage').replace(/\$TOTAL\$/g, '\\\\pageref{LastPage}').replace(/\\/g, '\\\\')}}
\\rfoot{${footerRight.replace(/\\/g, '\\\\')}}
\\renewcommand{\\headrulewidth}{0.4pt}
\\renewcommand{\\footrulewidth}{0.4pt}
`;
const headerFile = path.join(require('os').tmpdir(), `header_fallback_${Date.now()}.tex`);
fs.writeFileSync(headerFile, latexHeader, 'utf-8');
pandocCmd += ` --include-in-header="${headerFile}"`;
}
// Add all other options // Add all other options
if (options.template && options.template !== 'default') { if (options.template && options.template !== 'default') {
pandocCmd += ` --template="${options.template}"`; pandocCmd += ` --template="${options.template}"`;
@@ -1296,6 +1594,16 @@ function exportWithPandoc(pandocCmd, outputFile, format) {
console.warn(`Pandoc stderr (non-fatal):`, stderr); console.warn(`Pandoc stderr (non-fatal):`, stderr);
} }
// Set page size for DOCX
if (format === 'docx') {
try {
await setDocxPageSize(outputFile);
console.log('Page size set for DOCX');
} catch (pageSizeError) {
console.error('Error setting page size for DOCX:', pageSizeError);
}
}
// Add headers/footers to DOCX if enabled // Add headers/footers to DOCX if enabled
if (format === 'docx' && headerFooterSettings.enabled) { if (format === 'docx' && headerFooterSettings.enabled) {
try { try {
@@ -1313,6 +1621,16 @@ function exportWithPandoc(pandocCmd, outputFile, format) {
} }
} }
// Set page size for ODT
if (format === 'odt') {
try {
await setDocxPageSize(outputFile); // ODT has similar structure
console.log('Page size set for ODT');
} catch (pageSizeError) {
console.error('Error setting page size for ODT:', pageSizeError);
}
}
// Add headers/footers to ODT if enabled // Add headers/footers to ODT if enabled
if (format === 'odt' && headerFooterSettings.enabled) { if (format === 'odt' && headerFooterSettings.enabled) {
// ODT format is similar to DOCX in structure, we could implement this // ODT format is similar to DOCX in structure, we could implement this
@@ -1374,21 +1692,37 @@ function exportToHTML(outputFile) {
margin-top: 1.5em; margin-top: 1.5em;
margin-bottom: 0.5em; margin-bottom: 0.5em;
} }
/* Inline code */
code { code {
background: #f4f4f4; background: #f4f4f4;
padding: 2px 4px; padding: 2px 4px;
border-radius: 3px; border-radius: 3px;
font-family: Consolas, Monaco, 'Courier New', monospace; font-family: Consolas, Monaco, 'Courier New', monospace;
font-size: 0.9em;
} }
/* Code blocks - critical for ASCII art preservation */
pre { pre {
background: #f4f4f4; background: #f5f5f5;
padding: 1em; padding: 1em;
border-radius: 5px; border-radius: 5px;
overflow-x: auto; overflow-x: auto;
white-space: pre;
word-wrap: normal;
font-family: Consolas, Monaco, 'Courier New', monospace;
font-size: 13px;
line-height: 1.4;
border: 1px solid #e0e0e0;
margin: 1em 0;
} }
pre code { pre code {
background: transparent; background: transparent;
padding: 0; padding: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
white-space: pre;
word-wrap: normal;
display: block;
} }
blockquote { blockquote {
border-left: 4px solid #ddd; border-left: 4px solid #ddd;
@@ -1420,6 +1754,18 @@ function exportToHTML(outputFile) {
max-width: 100%; max-width: 100%;
height: auto; height: auto;
} }
@media print {
pre {
white-space: pre;
word-wrap: normal;
overflow: visible;
page-break-inside: avoid;
}
pre code {
white-space: pre;
word-wrap: normal;
}
}
</style> </style>
</head> </head>
<body> <body>
@@ -1462,21 +1808,38 @@ function exportToPDFElectron(outputFile) {
margin-top: 1.5em; margin-top: 1.5em;
margin-bottom: 0.5em; margin-bottom: 0.5em;
} }
/* Inline code */
code { code {
background: #f4f4f4; background: #f4f4f4;
padding: 2px 4px; padding: 2px 4px;
border-radius: 3px; border-radius: 3px;
font-family: Consolas, Monaco, 'Courier New', monospace; font-family: Consolas, Monaco, 'Courier New', monospace;
font-size: 0.9em;
} }
/* Code blocks - critical for ASCII art preservation */
pre { pre {
background: #f4f4f4; background: #f5f5f5;
padding: 1em; padding: 1em;
border-radius: 5px; border-radius: 5px;
overflow-x: auto; overflow-x: visible;
overflow-y: visible;
white-space: pre;
word-wrap: normal;
font-family: Consolas, Monaco, 'Courier New', monospace;
font-size: 12px;
line-height: 1.4;
border: 1px solid #e0e0e0;
margin: 1em 0;
} }
pre code { pre code {
background: transparent; background: transparent;
padding: 0; padding: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
white-space: pre;
word-wrap: normal;
display: block;
} }
blockquote { blockquote {
border-left: 4px solid #ddd; border-left: 4px solid #ddd;
@@ -1503,6 +1866,16 @@ function exportToPDFElectron(outputFile) {
} }
@media print { @media print {
body { padding: 20px; } body { padding: 20px; }
pre {
white-space: pre;
word-wrap: normal;
overflow: visible;
page-break-inside: avoid;
}
pre code {
white-space: pre;
word-wrap: normal;
}
} }
</style> </style>
</head> </head>
@@ -1561,22 +1934,48 @@ function importDocument() {
const files = dialog.showOpenDialogSync(mainWindow, { const files = dialog.showOpenDialogSync(mainWindow, {
properties: ['openFile'], properties: ['openFile'],
filters: [ filters: [
{ name: 'Documents', extensions: ['docx', 'odt', 'rtf', 'html', 'tex', 'epub', 'pdf'] }, { name: 'Documents', extensions: ['docx', 'odt', 'rtf', 'html', 'htm', 'tex', 'epub', 'pdf', 'txt'] },
{ name: 'Presentations', extensions: ['pptx', 'odp'] }, { name: 'Presentations', extensions: ['pptx', 'odp'] },
{ name: 'Markup Languages', extensions: ['rst', 'textile', 'mediawiki', 'org', 'asciidoc', 'twiki', 'opml'] },
{ name: 'E-book Formats', extensions: ['epub', 'fb2'] },
{ name: 'LaTeX Formats', extensions: ['tex', 'latex', 'ltx'] },
{ name: 'Web Formats', extensions: ['html', 'htm', 'xhtml'] },
{ name: 'Wiki Formats', extensions: ['mediawiki', 'dokuwiki', 'tikiwiki', 'twiki'] },
{ name: 'CSV/TSV', extensions: ['csv', 'tsv'] },
{ name: 'JSON', extensions: ['json'] },
{ name: 'All Files', extensions: ['*'] } { name: 'All Files', extensions: ['*'] }
] ]
}); });
if (files && files[0]) { if (files && files[0]) {
const inputFile = files[0]; const inputFile = files[0];
const ext = path.extname(inputFile).toLowerCase().slice(1);
const outputFile = inputFile.replace(/\.[^/.]+$/, '.md'); const outputFile = inputFile.replace(/\.[^/.]+$/, '.md');
// Determine format-specific conversion options
let additionalOptions = '';
// For PDFs, extract text properly
if (ext === 'pdf') {
additionalOptions = '--pdf-engine=xelatex';
}
// For CSV/TSV, convert as tables
if (ext === 'csv' || ext === 'tsv') {
additionalOptions = '--from=csv -t markdown';
}
// For JSON, handle structure
if (ext === 'json') {
additionalOptions = '--from=json -t markdown';
}
// Convert to markdown using pandoc // Convert to markdown using pandoc
const pandocCmd = `${getPandocPath()} "${inputFile}" -t markdown -o "${outputFile}"`; const pandocCmd = `${getPandocPath()} "${inputFile}" -t markdown ${additionalOptions} -o "${outputFile}"`;
exec(pandocCmd, (error, stdout, stderr) => { exec(pandocCmd, (error, stdout, stderr) => {
if (error) { if (error) {
dialog.showErrorBox('Import Error', `Failed to import: ${error.message}\n\nMake sure Pandoc is installed.`); dialog.showErrorBox('Import Error', `Failed to import: ${error.message}\n\nMake sure Pandoc is installed.\n\nSupported formats: DOCX, ODT, RTF, HTML, LaTeX, EPUB, PDF, PPTX, ODP, RST, Textile, MediaWiki, Org-mode, AsciiDoc, CSV, and more.`);
} else { } else {
// Open the converted markdown file // Open the converted markdown file
currentFile = outputFile; currentFile = outputFile;
@@ -1586,7 +1985,7 @@ function importDocument() {
dialog.showMessageBox(mainWindow, { dialog.showMessageBox(mainWindow, {
type: 'info', type: 'info',
title: 'Import Complete', title: 'Import Complete',
message: `Document imported successfully as ${outputFile}`, message: `Document imported successfully as ${path.basename(outputFile)}\n\nOriginal format: ${ext.toUpperCase()}\nConverted to: Markdown`,
buttons: ['OK'] buttons: ['OK']
}); });
} }
@@ -1995,9 +2394,13 @@ function performBatchConversion(inputFolder, outputFolder, format, options) {
// Add PDF-specific options with header/footer support // Add PDF-specific options with header/footer support
if (format === 'pdf') { if (format === 'pdf') {
const pdfEngine = options.pdfEngine || 'xelatex'; const pdfEngine = options.pdfEngine || 'xelatex';
pandocCmd += ` --pdf-engine="${pdfEngine}"`; pandocCmd += ` --pdf-engine=${pdfEngine}`;
if (options.geometry) pandocCmd += ` -V geometry:"${options.geometry}"`; if (options.geometry) pandocCmd += ` -V geometry:"${options.geometry}"`;
// Add monospace font settings for code blocks (ASCII art preservation)
pandocCmd += ' -V monofont="Consolas"';
pandocCmd += ' --highlight-style=tango';
// Add header/footer if enabled // Add header/footer if enabled
if (headerFooterSettings.enabled) { if (headerFooterSettings.enabled) {
const filename = path.basename(inputFile, path.extname(inputFile)); const filename = path.basename(inputFile, path.extname(inputFile));
@@ -2205,6 +2608,25 @@ function buildPandocCommand(content, format, outputPath) {
case 'pdf': case 'pdf':
command += ' --pdf-engine=xelatex --variable geometry:margin=1in'; command += ' --pdf-engine=xelatex --variable geometry:margin=1in';
// Add page size and orientation
const pageSize = PAGE_SIZES[pageSettings.size];
if (pageSize) {
command += ` -V geometry:papersize=${pageSize.pandoc}`;
} else if (pageSettings.customWidth && pageSettings.customHeight) {
// Custom page size
command += ` -V geometry:paperwidth=${pageSettings.customWidth}`;
command += ` -V geometry:paperheight=${pageSettings.customHeight}`;
}
// Add orientation
if (pageSettings.orientation === 'landscape') {
command += ' -V geometry:landscape';
}
// Add monospace font settings for code blocks (ASCII art preservation)
command += ' -V monofont="Consolas"';
command += ' --highlight-style=tango';
// Add header/footer if enabled // Add header/footer if enabled
if (headerFooterSettings.enabled) { if (headerFooterSettings.enabled) {
// Process dynamic fields // Process dynamic fields
@@ -2289,6 +2711,12 @@ app.whenReady().then(() => {
headerFooterSettings = savedHFSettings; headerFooterSettings = savedHFSettings;
} }
// Load page size settings
const savedPageSettings = store.get('pageSettings', null);
if (savedPageSettings) {
pageSettings = savedPageSettings;
}
// Check for command line conversion requests // Check for command line conversion requests
const args = process.argv.slice(2); const args = process.argv.slice(2);
if (args.length >= 2 && (args[0] === '--convert' || args[0] === '--convert-to')) { if (args.length >= 2 && (args[0] === '--convert' || args[0] === '--convert-to')) {
+1326 -14
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -444,8 +444,8 @@ body {
.preview-content table th { .preview-content table th {
font-weight: 600; font-weight: 600;
background: var(--primary-gradient); background: #f0f0f0;
color: white; color: #333333;
} }
.preview-content table tr:nth-child(even) { .preview-content table tr:nth-child(even) {
+381 -9
View File
@@ -10,6 +10,10 @@ body {
height: 100vh; height: 100vh;
} }
.hidden {
display: none !important;
}
.container { .container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -156,7 +160,54 @@ body {
} }
.pane:first-child { .pane:first-child {
border-right: 1px solid #ddd; border-right: none;
}
/* Pane Resizer */
.pane-resizer {
width: 6px;
background: #e0e0e0;
cursor: col-resize;
position: relative;
flex-shrink: 0;
transition: background 0.2s;
}
.pane-resizer:hover {
background: #999;
}
.pane-resizer:active {
background: #666;
}
/* Preview Header */
.preview-header {
display: flex;
justify-content: flex-end;
align-items: center;
padding: 4px 8px;
background: #f6f8fa;
border-bottom: 1px solid #dfe2e5;
min-height: 32px;
}
.preview-popout-btn {
background: transparent;
border: none;
border-radius: 3px;
padding: 2px 6px;
cursor: pointer;
font-size: 11px;
color: #8c959f;
opacity: 0.5;
transition: all 0.2s;
}
.preview-popout-btn:hover {
background: rgba(0, 0, 0, 0.05);
opacity: 1;
color: #24292f;
} }
.editor-textarea { .editor-textarea {
@@ -207,10 +258,15 @@ body {
background: #fff; background: #fff;
} }
#preview { /* GitHub Light Theme for Preview (Default) */
#preview, [id^="preview-"], .preview-content {
max-width: 800px; max-width: 800px;
margin: 0 auto; margin: 0 auto;
font-size: 15px; font-size: 15px;
padding: 20px;
background: #ffffff;
color: #24292f;
line-height: 1.6;
} }
/* Status Bar */ /* Status Bar */
@@ -251,22 +307,25 @@ body {
line-height: 1.6; line-height: 1.6;
} }
#preview code, .preview-content code { #preview code, .preview-content code, [id^="preview-"] code {
padding: 0.2em 0.4em; padding: 0.2em 0.4em;
margin: 0; margin: 0;
font-size: 85%; font-size: 85%;
background-color: rgba(27,31,35,0.05); background-color: #f6f8fa;
border-radius: 3px; border: 1px solid #d0d7de;
font-family: 'SF Mono', Monaco, 'Courier New', monospace; border-radius: 6px;
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
color: #24292f;
} }
#preview pre, .preview-content pre { #preview pre, .preview-content pre, [id^="preview-"] pre {
padding: 16px; padding: 16px;
overflow: auto; overflow: auto;
font-size: 85%; font-size: 85%;
line-height: 1.45; line-height: 1.45;
background-color: #f6f8fa; background-color: #f6f8fa;
border-radius: 3px; border: 1px solid #d0d7de;
border-radius: 6px;
margin-bottom: 16px; margin-bottom: 16px;
} }
@@ -289,6 +348,126 @@ body {
margin-bottom: 16px; margin-bottom: 16px;
} }
/* GitHub Dark Theme for Preview */
body.theme-dark #preview,
body.theme-dark [id^="preview-"],
body.theme-dark .preview-content {
background: #0d1117;
color: #c9d1d9;
}
body.theme-dark #preview h1,
body.theme-dark [id^="preview-"] h1,
body.theme-dark .preview-content h1 {
color: #c9d1d9;
border-bottom-color: #21262d;
}
body.theme-dark #preview h2,
body.theme-dark [id^="preview-"] h2,
body.theme-dark .preview-content h2 {
color: #c9d1d9;
border-bottom-color: #21262d;
}
body.theme-dark #preview h3,
body.theme-dark #preview h4,
body.theme-dark #preview h5,
body.theme-dark #preview h6,
body.theme-dark [id^="preview-"] h3,
body.theme-dark [id^="preview-"] h4,
body.theme-dark [id^="preview-"] h5,
body.theme-dark [id^="preview-"] h6,
body.theme-dark .preview-content h3,
body.theme-dark .preview-content h4,
body.theme-dark .preview-content h5,
body.theme-dark .preview-content h6 {
color: #c9d1d9;
}
body.theme-dark #preview p,
body.theme-dark [id^="preview-"] p,
body.theme-dark .preview-content p {
color: #c9d1d9;
}
body.theme-dark #preview code,
body.theme-dark [id^="preview-"] code,
body.theme-dark .preview-content code {
background-color: #161b22;
border-color: #30363d;
color: #c9d1d9;
}
body.theme-dark #preview pre,
body.theme-dark [id^="preview-"] pre,
body.theme-dark .preview-content pre {
background-color: #161b22;
border-color: #30363d;
}
body.theme-dark #preview blockquote,
body.theme-dark [id^="preview-"] blockquote,
body.theme-dark .preview-content blockquote {
color: #8b949e;
border-left-color: #3b434b;
}
body.theme-dark #preview a,
body.theme-dark [id^="preview-"] a,
body.theme-dark .preview-content a {
color: #58a6ff;
}
body.theme-dark #preview a:hover,
body.theme-dark [id^="preview-"] a:hover,
body.theme-dark .preview-content a:hover {
color: #79c0ff;
}
body.theme-dark #preview table,
body.theme-dark [id^="preview-"] table,
body.theme-dark .preview-content table {
border-color: #30363d;
}
body.theme-dark #preview table th,
body.theme-dark [id^="preview-"] table th,
body.theme-dark .preview-content table th {
background-color: #161b22;
color: #c9d1d9;
border-color: #30363d;
}
body.theme-dark #preview table td,
body.theme-dark [id^="preview-"] table td,
body.theme-dark .preview-content table td {
border-color: #30363d;
color: #c9d1d9;
}
body.theme-dark #preview hr,
body.theme-dark [id^="preview-"] hr,
body.theme-dark .preview-content hr {
background-color: #21262d;
border-color: #21262d;
}
body.theme-dark #preview ul,
body.theme-dark #preview ol,
body.theme-dark [id^="preview-"] ul,
body.theme-dark [id^="preview-"] ol,
body.theme-dark .preview-content ul,
body.theme-dark .preview-content ol {
color: #c9d1d9;
}
body.theme-dark #preview li,
body.theme-dark [id^="preview-"] li,
body.theme-dark .preview-content li {
color: #c9d1d9;
}
#preview ul, #preview ol, .preview-content ul, .preview-content ol { #preview ul, #preview ol, .preview-content ul, .preview-content ol {
padding-left: 2em; padding-left: 2em;
margin-bottom: 16px; margin-bottom: 16px;
@@ -326,10 +505,11 @@ body {
#preview table th, .preview-content table th { #preview table th, .preview-content table th {
font-weight: 600; font-weight: 600;
background-color: #f6f8fa;
} }
#preview table tr:nth-child(2n), .preview-content table tr:nth-child(2n) { #preview table tr:nth-child(2n), .preview-content table tr:nth-child(2n) {
background-color: #f6f8fa; background-color: #ffffff;
} }
/* Theme: Dark */ /* Theme: Dark */
@@ -1516,6 +1696,42 @@ body.theme-github .line-numbers {
color: #586069; color: #586069;
} }
/* Export Profiles Styles */
.export-profiles {
border-bottom: 1px solid #ddd;
padding-bottom: 15px;
margin-bottom: 15px;
}
.profile-controls {
display: flex;
gap: 8px;
align-items: center;
margin-top: 8px;
}
.profile-controls select {
flex: 1;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.profile-controls button {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
background: #f5f5f5;
cursor: pointer;
font-size: 13px;
transition: all 0.2s;
}
.profile-controls button:hover {
background: #e8e8e8;
}
/* Advanced Export Toggle Styles */ /* Advanced Export Toggle Styles */
.export-mode-toggle { .export-mode-toggle {
border-bottom: 1px solid #ddd; border-bottom: 1px solid #ddd;
@@ -3312,6 +3528,22 @@ body.printing-no-styles .preview-content pre,
background: var(--button-hover, #5a6268); background: var(--button-hover, #5a6268);
} }
.browse-btn {
padding: 6px 12px;
background: var(--accent-color, #007bff);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
transition: background 0.2s ease;
margin-right: 8px;
}
.browse-btn:hover {
background: var(--accent-hover, #0056b3);
}
.clear-btn { .clear-btn {
padding: 6px 12px; padding: 6px 12px;
background: var(--danger-color, #dc3545); background: var(--danger-color, #dc3545);
@@ -3430,3 +3662,143 @@ body[data-theme="dark"] .field-option:hover {
background: #0d6efd; background: #0d6efd;
color: white; color: white;
} }
/* Mermaid Diagram Styles */
.mermaid {
background: #f9f9f9;
padding: 20px;
border-radius: 8px;
margin: 20px 0;
text-align: center;
border: 1px solid #e0e0e0;
}
.mermaid svg {
max-width: 100%;
height: auto;
}
body.theme-dark .mermaid {
background: #2d2d2d;
border-color: #444;
}
body.theme-solarized .mermaid {
background: #fdf6e3;
border-color: #eee8d5;
}
body.theme-monokai .mermaid {
background: #2f2f2f;
border-color: #49483e;
}
body.theme-github .mermaid {
background: #f6f8fa;
border-color: #e1e4e8;
}
/* Command Palette Styles */
.command-palette {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
align-items: flex-start;
justify-content: center;
padding-top: 100px;
z-index: 10000;
}
.command-palette.hidden {
display: none;
}
.command-palette-content {
background: #fff;
border-radius: 12px;
width: 600px;
max-width: 90%;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
overflow: hidden;
}
#command-search {
width: 100%;
padding: 20px;
font-size: 18px;
border: none;
border-bottom: 1px solid #e0e0e0;
outline: none;
}
.command-list {
max-height: 400px;
overflow-y: auto;
}
.command-item {
padding: 12px 20px;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
transition: background 0.15s;
}
.command-item:hover,
.command-item.selected {
background: #f0f0f0;
}
.command-name {
font-weight: 500;
}
.command-shortcut {
font-size: 12px;
color: #666;
background: #f5f5f5;
padding: 3px 8px;
border-radius: 3px;
}
.command-palette-footer {
padding: 12px 20px;
background: #f9f9f9;
border-top: 1px solid #e0e0e0;
display: flex;
gap: 20px;
font-size: 12px;
color: #666;
}
/* Dark theme support for command palette */
body.theme-dark .command-palette-content {
background: #2d2d2d;
color: #f0f0f0;
}
body.theme-dark #command-search {
background: #2d2d2d;
color: #f0f0f0;
border-color: #444;
}
body.theme-dark .command-item:hover,
body.theme-dark .command-item.selected {
background: #3d3d3d;
}
body.theme-dark .command-shortcut {
background: #3d3d3d;
color: #ccc;
}
body.theme-dark .command-palette-footer {
background: #252525;
border-color: #444;
color: #999;
}
+137 -26
View File
@@ -10,9 +10,10 @@ const PizZip = require('pizzip');
const Docx = require('docx4js').default; const Docx = require('docx4js').default;
class WordTemplateExporter { class WordTemplateExporter {
constructor(templatePath, startPage = 3) { constructor(templatePath, startPage = 3, pageSettings = null) {
this.templatePath = templatePath || path.join(__dirname, '../word_template.docx'); this.templatePath = templatePath || path.join(__dirname, '../word_template.docx');
this.startPage = startPage; // Which page to start inserting content this.startPage = startPage; // Which page to start inserting content
this.pageSettings = pageSettings; // Page size and orientation settings
} }
/** /**
@@ -25,7 +26,12 @@ class WordTemplateExporter {
const zip = new PizZip(templateBuffer); const zip = new PizZip(templateBuffer);
// Extract document.xml // Extract document.xml
const documentXml = zip.file('word/document.xml').asText(); let documentXml = zip.file('word/document.xml').asText();
// Set page size if settings provided
if (this.pageSettings) {
documentXml = this.setPageSize(documentXml);
}
// Parse markdown and generate Word XML // Parse markdown and generate Word XML
const newContentXml = this.markdownToWordXml(markdownContent); const newContentXml = this.markdownToWordXml(markdownContent);
@@ -47,6 +53,56 @@ class WordTemplateExporter {
} }
} }
/**
* Set page size in document XML
*/
setPageSize(documentXml) {
// Import PAGE_SIZES from main process (need to pass as parameter)
const PAGE_SIZES = {
a4: { width: 11906, height: 16838 },
a3: { width: 16838, height: 23811 },
a5: { width: 8391, height: 11906 },
b4: { width: 14170, height: 20015 },
b5: { width: 9979, height: 14170 },
letter: { width: 12240, height: 15840 },
legal: { width: 12240, height: 20160 },
tabloid: { width: 15840, height: 24480 }
};
let width, height;
const pageSize = PAGE_SIZES[this.pageSettings.size];
if (pageSize) {
width = pageSize.width;
height = pageSize.height;
} else {
// Default to A4
width = 11906;
height = 16838;
}
// Swap dimensions for landscape
if (this.pageSettings.orientation === 'landscape') {
[width, height] = [height, width];
}
// Update all <w:pgSz> elements in section properties
const pgSzRegex = /<w:pgSz[^>]*\/>/g;
let modifiedXml = documentXml.replace(pgSzRegex, () => {
return `<w:pgSz w:w="${width}" w:h="${height}" w:orient="${this.pageSettings.orientation}"/>`;
});
// If no pgSz found, add it to all sectPr elements
if (!pgSzRegex.test(documentXml)) {
const sectPrRegex = /<w:sectPr[^>]*>/g;
modifiedXml = modifiedXml.replace(sectPrRegex, (match) => {
return `${match}<w:pgSz w:w="${width}" w:h="${height}" w:orient="${this.pageSettings.orientation}"/>`;
});
}
return modifiedXml;
}
/** /**
* Insert markdown content after the specified page in the document * Insert markdown content after the specified page in the document
* @param {string} documentXml - The document XML * @param {string} documentXml - The document XML
@@ -253,23 +309,42 @@ class WordTemplateExporter {
} }
/** /**
* Create code block XML * Create code block XML - renders each line as separate paragraph
* to preserve exact formatting like in preview (monospace, no wrapping)
* Background shading applied at paragraph level to extend full width
*/ */
createCodeBlockXml(code) { createCodeBlockXml(code) {
const escapedCode = this.escapeXml(code); const lines = code.split('\n');
let xml = '';
return `<w:p> lines.forEach((line, index) => {
const escapedLine = this.escapeXml(line);
// Each line gets its own paragraph with exact spacing and no wrapping
// Shading (shd) is at paragraph level so background extends full width
xml += `<w:p>
<w:pPr> <w:pPr>
<w:pStyle w:val="Code"/> <w:pStyle w:val="Normal"/>
<w:shd w:val="clear" w:color="auto" w:fill="F5F5F5"/>
<w:spacing w:before="${index === 0 ? '120' : '0'}" w:after="${index === lines.length - 1 ? '120' : '0'}" w:line="240" w:lineRule="exact"/>
<w:ind w:left="284" w:right="284"/>
<w:jc w:val="left"/>
<w:keepLines/>
<w:wordWrap w:val="0"/>
</w:pPr> </w:pPr>
<w:r> <w:r>
<w:rPr> <w:rPr>
<w:rFonts w:ascii="Consolas" w:hAnsi="Consolas"/> <w:rFonts w:ascii="Consolas" w:hAnsi="Consolas" w:cs="Consolas"/>
<w:sz w:val="18"/> <w:sz w:val="18"/>
<w:szCs w:val="18"/>
<w:noProof/>
</w:rPr> </w:rPr>
<w:t xml:space="preserve">${escapedCode}</w:t> <w:t xml:space="preserve">${escapedLine}</w:t>
</w:r> </w:r>
</w:p>`; </w:p>`;
});
return xml;
} }
/** /**
@@ -287,6 +362,7 @@ class WordTemplateExporter {
/** /**
* Create table XML from markdown table lines with template styling * Create table XML from markdown table lines with template styling
* Uses full-width tables with equal column distribution matching template style
*/ */
createTableXml(tableLines) { createTableXml(tableLines) {
// Parse table // Parse table
@@ -308,13 +384,19 @@ class WordTemplateExporter {
// Calculate number of columns // Calculate number of columns
const numCols = Math.max(...rows.map(row => row.length)); const numCols = Math.max(...rows.map(row => row.length));
// Calculate column width in twips (1440 twips = 1 inch)
// Assume standard page width of 9360 twips (6.5 inches usable)
const totalTableWidth = 9360;
const colWidth = Math.floor(totalTableWidth / numCols);
// Build table XML // Build table XML
let tableXml = '<w:tbl>'; let tableXml = '<w:tbl>';
// Table properties - use template table style // Table properties - use template table style with full width
tableXml += `<w:tblPr> tableXml += `<w:tblPr>
<w:tblStyle w:val="TableGrid"/> <w:tblStyle w:val="TableGrid"/>
<w:tblW w:w="5000" w:type="pct"/> <w:tblW w:w="0" w:type="auto"/>
<w:tblLayout w:type="fixed"/>
<w:tblLook w:val="04A0" w:firstRow="1" w:lastRow="0" w:firstColumn="0" w:lastColumn="0" w:noHBand="0" w:noVBand="1"/> <w:tblLook w:val="04A0" w:firstRow="1" w:lastRow="0" w:firstColumn="0" w:lastColumn="0" w:noHBand="0" w:noVBand="1"/>
<w:tblBorders> <w:tblBorders>
<w:top w:val="single" w:sz="8" w:space="0" w:color="F58220"/> <w:top w:val="single" w:sz="8" w:space="0" w:color="F58220"/>
@@ -326,20 +408,22 @@ class WordTemplateExporter {
</w:tblBorders> </w:tblBorders>
</w:tblPr>`; </w:tblPr>`;
// Table grid // Table grid with explicit column widths
tableXml += '<w:tblGrid>'; tableXml += '<w:tblGrid>';
for (let i = 0; i < numCols; i++) { for (let i = 0; i < numCols; i++) {
tableXml += '<w:gridCol/>'; tableXml += `<w:gridCol w:w="${colWidth}"/>`;
} }
tableXml += '</w:tblGrid>'; tableXml += '</w:tblGrid>';
// Table rows // Table rows
rows.forEach((rowCells, rowIndex) => { rows.forEach((rowCells, rowIndex) => {
const isHeader = rowIndex === 0; const isHeader = rowIndex === 0;
const isEvenRow = rowIndex % 2 === 0;
tableXml += '<w:tr>'; tableXml += '<w:tr>';
// Row properties for consistent height
tableXml += '<w:trPr><w:trHeight w:val="0" w:hRule="atLeast"/></w:trPr>';
// Pad row to have same number of columns // Pad row to have same number of columns
while (rowCells.length < numCols) { while (rowCells.length < numCols) {
rowCells.push(''); rowCells.push('');
@@ -349,9 +433,14 @@ class WordTemplateExporter {
tableXml += '<w:tc>'; tableXml += '<w:tc>';
tableXml += '<w:tcPr>'; tableXml += '<w:tcPr>';
// Cell width
tableXml += `<w:tcW w:w="${colWidth}" w:type="dxa"/>`;
// Cell shading - orange for header, white for data rows // Cell shading - orange for header, white for data rows
if (isHeader) { if (isHeader) {
tableXml += '<w:shd w:val="clear" w:color="FFFFFF" w:fill="F58220"/>'; tableXml += '<w:shd w:val="clear" w:color="auto" w:fill="F58220"/>';
} else {
tableXml += '<w:shd w:val="clear" w:color="auto" w:fill="FFFFFF"/>';
} }
// Cell borders // Cell borders
@@ -361,12 +450,21 @@ class WordTemplateExporter {
'<w:bottom w:val="single" w:sz="8" w:space="0" w:color="F58220"/>' + '<w:bottom w:val="single" w:sz="8" w:space="0" w:color="F58220"/>' +
'<w:right w:val="single" w:sz="8" w:space="0" w:color="F58220"/>' + '<w:right w:val="single" w:sz="8" w:space="0" w:color="F58220"/>' +
'</w:tcBorders>'; '</w:tcBorders>';
// Cell margins for proper padding
tableXml += '<w:tcMar>' +
'<w:top w:w="80" w:type="dxa"/>' +
'<w:left w:w="120" w:type="dxa"/>' +
'<w:bottom w:w="80" w:type="dxa"/>' +
'<w:right w:w="120" w:type="dxa"/>' +
'</w:tcMar>';
tableXml += '</w:tcPr>'; tableXml += '</w:tcPr>';
// Cell content // Cell content
tableXml += '<w:p>'; tableXml += '<w:p>';
tableXml += '<w:pPr>'; tableXml += '<w:pPr>';
tableXml += '<w:spacing w:before="100" w:after="100"/>'; tableXml += '<w:spacing w:before="0" w:after="0" w:line="240" w:lineRule="auto"/>';
tableXml += '</w:pPr>'; tableXml += '</w:pPr>';
const runs = this.parseInlineFormatting(cellText); const runs = this.parseInlineFormatting(cellText);
@@ -388,6 +486,9 @@ class WordTemplateExporter {
tableXml += '</w:tbl>'; tableXml += '</w:tbl>';
// Add spacing after table
tableXml += '<w:p><w:pPr><w:spacing w:before="120" w:after="0"/></w:pPr></w:p>';
return tableXml; return tableXml;
} }
@@ -397,15 +498,22 @@ class WordTemplateExporter {
isAsciiArt(line) { isAsciiArt(line) {
// Don't treat markdown tables as ASCII art // Don't treat markdown tables as ASCII art
if (line.trim().startsWith('|') && line.trim().endsWith('|')) { if (line.trim().startsWith('|') && line.trim().endsWith('|')) {
// Check if it's a proper markdown table (has multiple cells)
const cells = line.split('|').filter(c => c.trim());
if (cells.length >= 2) {
return false; return false;
} }
}
// Common ASCII art characters // Common ASCII art characters (Unicode box drawing and symbols)
const asciiArtChars = [ const asciiArtChars = [
'─', '│', '┌', '┐', '└', '┘', '├', '┤', '┬', '┴', '┼', // Box drawing '─', '│', '┌', '┐', '└', '┘', '├', '┤', '┬', '┴', '┼', // Box drawing
'═', '║', '╔', '╗', '╚', '╝', '╠', '╣', '╦', '╩', '╬', // Double box '═', '║', '╔', '╗', '╚', '╝', '╠', '╣', '╦', '╩', '╬', // Double box
'', '', '', '', '♦', '●', '○', '■', '□', // Shapes '', '', '', '', // Rounded corners
'', '', '', '', // Arrows '', '', '', '', '♦', '●', '○', '■', '□', '◆', '◇', // Shapes
'↓', '→', '←', '↑', '↔', '↕', '⇒', '⇐', '⇓', '⇑', // Arrows
'┃', '━', '┏', '┓', '┗', '┛', '┣', '┫', '┳', '┻', '╋', // Heavy box
'░', '▒', '▓', '█', // Shading
]; ];
// Check for box drawing characters // Check for box drawing characters
@@ -415,14 +523,18 @@ class WordTemplateExporter {
// Check for ASCII box patterns with regular characters // Check for ASCII box patterns with regular characters
const asciiPatterns = [ const asciiPatterns = [
/^\s*\+[-=_]+\+/, // +-----+ /^\s*\+[-=_+]+\+/, // +-----+ or +=====+
/^\s*\|[-=_]{3,}\|/, // |-----| /^\s*\|[-=_]{3,}\|/, // |-----|
/^\s*[-=_]{5,}$/, // ----- /^\s*[-=_]{5,}$/, // -----
/^\s*\+[-]+\+[-]+\+/, // +---+---+
/^\s*\([A-Z][a-z]+\)\s*\|\|/, // (Deformable) || /^\s*\([A-Z][a-z]+\)\s*\|\|/, // (Deformable) ||
/^\s*\|\s+[A-Z\s]+[-:]\s+[A-Z]/, // | TILE ADHESIVE - TYPE /^\s*\|\s+[A-Z\s]+[-:]\s+[A-Z]/, // | TILE ADHESIVE - TYPE
/^\s*\|\s*\[[^\]]+\]/, // | [BIS Mark / CE Mark] /^\s*\|\s*\[[^\]]+\]/, // | [BIS Mark / CE Mark]
/^\s*\|\s{2,}\w+.*\|\|/, // || text || /^\s*\|\s{2,}\w+.*\|\|/, // || text ||
/^\s*\[[^\]]+\]/, // [Step in brackets] /^\s*\[[^\]]+\]\s*$/, // [Step in brackets]
/^\s*<[-=]+>/, // <----> or <====>
/^\s*\/[-_\\\/]+\//, // /----/
/^\s*\*[-=\*]+\*/, // *----*
]; ];
return asciiPatterns.some(pattern => pattern.test(line)); return asciiPatterns.some(pattern => pattern.test(line));
@@ -430,6 +542,7 @@ class WordTemplateExporter {
/** /**
* Create ASCII art XML with monospace font * Create ASCII art XML with monospace font
* Background shading applied at paragraph level to extend full width
*/ */
createAsciiArtXml(asciiContent) { createAsciiArtXml(asciiContent) {
// Split ASCII art into individual lines and create a paragraph for each // Split ASCII art into individual lines and create a paragraph for each
@@ -437,10 +550,13 @@ class WordTemplateExporter {
let xml = ''; let xml = '';
lines.forEach((line, index) => { lines.forEach((line, index) => {
// Shading (shd) is at paragraph level so background extends full width
xml += `<w:p> xml += `<w:p>
<w:pPr> <w:pPr>
<w:pStyle w:val="Normal"/>
<w:shd w:val="clear" w:color="auto" w:fill="F5F5F5"/>
<w:spacing w:before="${index === 0 ? '120' : '0'}" w:after="${index === lines.length - 1 ? '120' : '0'}" w:line="240" w:lineRule="exact"/> <w:spacing w:before="${index === 0 ? '120' : '0'}" w:after="${index === lines.length - 1 ? '120' : '0'}" w:line="240" w:lineRule="exact"/>
<w:ind w:left="0" w:right="0"/> <w:ind w:left="284" w:right="284"/>
<w:jc w:val="left"/> <w:jc w:val="left"/>
<w:keepLines/> <w:keepLines/>
<w:wordWrap w:val="0"/> <w:wordWrap w:val="0"/>
@@ -453,7 +569,6 @@ class WordTemplateExporter {
if (hasArrow) { if (hasArrow) {
// Split line into parts and color arrows red // Split line into parts and color arrows red
let remainingLine = line; let remainingLine = line;
let processedText = '';
while (remainingLine.length > 0) { while (remainingLine.length > 0) {
let foundArrow = false; let foundArrow = false;
@@ -479,7 +594,6 @@ class WordTemplateExporter {
<w:rFonts w:ascii="Consolas" w:hAnsi="Consolas" w:cs="Consolas"/> <w:rFonts w:ascii="Consolas" w:hAnsi="Consolas" w:cs="Consolas"/>
<w:sz w:val="16"/> <w:sz w:val="16"/>
<w:szCs w:val="16"/> <w:szCs w:val="16"/>
<w:shd w:val="clear" w:color="auto" w:fill="F5F5F5"/>
<w:noProof/> <w:noProof/>
</w:rPr> </w:rPr>
<w:t xml:space="preserve">${beforeArrow}</w:t> <w:t xml:space="preserve">${beforeArrow}</w:t>
@@ -494,7 +608,6 @@ class WordTemplateExporter {
<w:sz w:val="16"/> <w:sz w:val="16"/>
<w:szCs w:val="16"/> <w:szCs w:val="16"/>
<w:color w:val="FF0000"/> <w:color w:val="FF0000"/>
<w:shd w:val="clear" w:color="auto" w:fill="F5F5F5"/>
<w:noProof/> <w:noProof/>
</w:rPr> </w:rPr>
<w:t xml:space="preserve">${escapedArrow}</w:t> <w:t xml:space="preserve">${escapedArrow}</w:t>
@@ -510,7 +623,6 @@ class WordTemplateExporter {
<w:rFonts w:ascii="Consolas" w:hAnsi="Consolas" w:cs="Consolas"/> <w:rFonts w:ascii="Consolas" w:hAnsi="Consolas" w:cs="Consolas"/>
<w:sz w:val="16"/> <w:sz w:val="16"/>
<w:szCs w:val="16"/> <w:szCs w:val="16"/>
<w:shd w:val="clear" w:color="auto" w:fill="F5F5F5"/>
<w:noProof/> <w:noProof/>
</w:rPr> </w:rPr>
<w:t xml:space="preserve">${escapedRemaining}</w:t> <w:t xml:space="preserve">${escapedRemaining}</w:t>
@@ -526,7 +638,6 @@ class WordTemplateExporter {
<w:rFonts w:ascii="Consolas" w:hAnsi="Consolas" w:cs="Consolas"/> <w:rFonts w:ascii="Consolas" w:hAnsi="Consolas" w:cs="Consolas"/>
<w:sz w:val="16"/> <w:sz w:val="16"/>
<w:szCs w:val="16"/> <w:szCs w:val="16"/>
<w:shd w:val="clear" w:color="auto" w:fill="F5F5F5"/>
<w:noProof/> <w:noProof/>
</w:rPr> </w:rPr>
<w:t xml:space="preserve">${escapedLine}</w:t> <w:t xml:space="preserve">${escapedLine}</w:t>