mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-02 18:10:18 +05:30
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d23ce91506 | ||
|
|
b281255e90 |
+166
@@ -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
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pan-converter",
|
||||
"version": "1.9.2",
|
||||
"version": "2.1.0",
|
||||
"description": "Cross-platform Markdown editor and converter using Pandoc",
|
||||
"main": "src/main.js",
|
||||
"scripts": {
|
||||
|
||||
+224
@@ -11,6 +11,8 @@
|
||||
<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/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>
|
||||
<body>
|
||||
<div class="container">
|
||||
@@ -148,6 +150,19 @@
|
||||
</div>
|
||||
<div class="export-dialog-body">
|
||||
<!-- 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">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="advanced-export-toggle">
|
||||
@@ -243,6 +258,37 @@
|
||||
</div> <!-- End advanced-export-options -->
|
||||
|
||||
<!-- 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">
|
||||
<label>Quick Export Settings:</label>
|
||||
<div class="checkbox-group">
|
||||
@@ -258,6 +304,180 @@
|
||||
</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 -->
|
||||
<div id="universal-converter-dialog" class="export-dialog hidden">
|
||||
<div class="export-dialog-content">
|
||||
@@ -1049,7 +1269,11 @@
|
||||
<textarea id="editor-1" class="editor-textarea"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pane-resizer" id="pane-resizer-1" title="Drag to resize"></div>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
+233
-11
@@ -78,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
|
||||
// When a file is double-clicked and the app is already running,
|
||||
// Windows tries to start a second instance. We prevent this and
|
||||
@@ -505,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',
|
||||
submenu: [
|
||||
@@ -515,7 +592,7 @@ function createMenu() {
|
||||
type: 'info',
|
||||
title: 'About PanConverter',
|
||||
message: 'PanConverter',
|
||||
detail: 'A cross-platform Markdown editor and converter using Pandoc.\n\nVersion: 1.9.2\nAuthor: Amit Haridas\nEmail: amit.wh@gmail.com\nLicense: MIT\n\nFeatures:\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• 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']
|
||||
});
|
||||
}
|
||||
@@ -672,6 +749,18 @@ 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
|
||||
// Browse for header/footer logo
|
||||
ipcMain.on('browse-header-footer-logo', async (event, position) => {
|
||||
@@ -794,6 +883,67 @@ function processDynamicFields(text, metadata = {}) {
|
||||
}
|
||||
|
||||
// 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 = {}) {
|
||||
if (!headerFooterSettings.enabled) return;
|
||||
|
||||
@@ -939,8 +1089,8 @@ async function exportWordWithTemplate() {
|
||||
|
||||
if (result.canceled) return;
|
||||
|
||||
// Create exporter instance with selected template and start page
|
||||
const exporter = new WordTemplateExporter(wordTemplatePath, templateStartPage);
|
||||
// Create exporter instance with selected template, start page, and page settings
|
||||
const exporter = new WordTemplateExporter(wordTemplatePath, templateStartPage, pageSettings);
|
||||
|
||||
// Convert markdown to DOCX
|
||||
await exporter.convert(content, result.filePath);
|
||||
@@ -1444,6 +1594,16 @@ function exportWithPandoc(pandocCmd, outputFile, format) {
|
||||
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
|
||||
if (format === 'docx' && headerFooterSettings.enabled) {
|
||||
try {
|
||||
@@ -1461,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
|
||||
if (format === 'odt' && headerFooterSettings.enabled) {
|
||||
// ODT format is similar to DOCX in structure, we could implement this
|
||||
@@ -1764,32 +1934,58 @@ function importDocument() {
|
||||
const files = dialog.showOpenDialogSync(mainWindow, {
|
||||
properties: ['openFile'],
|
||||
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: '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: ['*'] }
|
||||
]
|
||||
});
|
||||
|
||||
if (files && files[0]) {
|
||||
const inputFile = files[0];
|
||||
const ext = path.extname(inputFile).toLowerCase().slice(1);
|
||||
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
|
||||
const pandocCmd = `${getPandocPath()} "${inputFile}" -t markdown -o "${outputFile}"`;
|
||||
|
||||
const pandocCmd = `${getPandocPath()} "${inputFile}" -t markdown ${additionalOptions} -o "${outputFile}"`;
|
||||
|
||||
exec(pandocCmd, (error, stdout, stderr) => {
|
||||
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 {
|
||||
// Open the converted markdown file
|
||||
currentFile = outputFile;
|
||||
const content = fs.readFileSync(outputFile, 'utf-8');
|
||||
mainWindow.webContents.send('file-opened', { path: outputFile, content });
|
||||
|
||||
|
||||
dialog.showMessageBox(mainWindow, {
|
||||
type: 'info',
|
||||
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']
|
||||
});
|
||||
}
|
||||
@@ -2198,9 +2394,13 @@ function performBatchConversion(inputFolder, outputFolder, format, options) {
|
||||
// Add PDF-specific options with header/footer support
|
||||
if (format === 'pdf') {
|
||||
const pdfEngine = options.pdfEngine || 'xelatex';
|
||||
pandocCmd += ` --pdf-engine="${pdfEngine}"`;
|
||||
pandocCmd += ` --pdf-engine=${pdfEngine}`;
|
||||
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 = path.basename(inputFile, path.extname(inputFile));
|
||||
@@ -2407,6 +2607,22 @@ function buildPandocCommand(content, format, outputPath) {
|
||||
switch (format) {
|
||||
case 'pdf':
|
||||
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';
|
||||
@@ -2495,6 +2711,12 @@ app.whenReady().then(() => {
|
||||
headerFooterSettings = savedHFSettings;
|
||||
}
|
||||
|
||||
// Load page size settings
|
||||
const savedPageSettings = store.get('pageSettings', null);
|
||||
if (savedPageSettings) {
|
||||
pageSettings = savedPageSettings;
|
||||
}
|
||||
|
||||
// Check for command line conversion requests
|
||||
const args = process.argv.slice(2);
|
||||
if (args.length >= 2 && (args[0] === '--convert' || args[0] === '--convert-to')) {
|
||||
|
||||
+1321
-1
File diff suppressed because it is too large
Load Diff
@@ -444,8 +444,8 @@ body {
|
||||
|
||||
.preview-content table th {
|
||||
font-weight: 600;
|
||||
background: var(--primary-gradient);
|
||||
color: white;
|
||||
background: #f0f0f0;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.preview-content table tr:nth-child(even) {
|
||||
|
||||
+366
-10
@@ -10,6 +10,10 @@ body {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -156,7 +160,54 @@ body {
|
||||
}
|
||||
|
||||
.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 {
|
||||
@@ -207,10 +258,15 @@ body {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
#preview {
|
||||
/* GitHub Light Theme for Preview (Default) */
|
||||
#preview, [id^="preview-"], .preview-content {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
font-size: 15px;
|
||||
padding: 20px;
|
||||
background: #ffffff;
|
||||
color: #24292f;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Status Bar */
|
||||
@@ -251,22 +307,25 @@ body {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
#preview code, .preview-content code {
|
||||
#preview code, .preview-content code, [id^="preview-"] code {
|
||||
padding: 0.2em 0.4em;
|
||||
margin: 0;
|
||||
font-size: 85%;
|
||||
background-color: rgba(27,31,35,0.05);
|
||||
border-radius: 3px;
|
||||
font-family: 'SF Mono', Monaco, 'Courier New', monospace;
|
||||
background-color: #f6f8fa;
|
||||
border: 1px solid #d0d7de;
|
||||
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;
|
||||
overflow: auto;
|
||||
font-size: 85%;
|
||||
line-height: 1.45;
|
||||
background-color: #f6f8fa;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #d0d7de;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
@@ -289,6 +348,126 @@ body {
|
||||
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 {
|
||||
padding-left: 2em;
|
||||
margin-bottom: 16px;
|
||||
@@ -326,10 +505,11 @@ body {
|
||||
|
||||
#preview table th, .preview-content table th {
|
||||
font-weight: 600;
|
||||
background-color: #f6f8fa;
|
||||
}
|
||||
|
||||
#preview table tr:nth-child(2n), .preview-content table tr:nth-child(2n) {
|
||||
background-color: #f6f8fa;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
/* Theme: Dark */
|
||||
@@ -1516,6 +1696,42 @@ body.theme-github .line-numbers {
|
||||
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 */
|
||||
.export-mode-toggle {
|
||||
border-bottom: 1px solid #ddd;
|
||||
@@ -3445,4 +3661,144 @@ body[data-theme="dark"] .field-option {
|
||||
body[data-theme="dark"] .field-option:hover {
|
||||
background: #0d6efd;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -10,9 +10,10 @@ const PizZip = require('pizzip');
|
||||
const Docx = require('docx4js').default;
|
||||
|
||||
class WordTemplateExporter {
|
||||
constructor(templatePath, startPage = 3) {
|
||||
constructor(templatePath, startPage = 3, pageSettings = null) {
|
||||
this.templatePath = templatePath || path.join(__dirname, '../word_template.docx');
|
||||
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);
|
||||
|
||||
// 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
|
||||
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
|
||||
* @param {string} documentXml - The document XML
|
||||
@@ -306,6 +362,7 @@ class WordTemplateExporter {
|
||||
|
||||
/**
|
||||
* Create table XML from markdown table lines with template styling
|
||||
* Uses full-width tables with equal column distribution matching template style
|
||||
*/
|
||||
createTableXml(tableLines) {
|
||||
// Parse table
|
||||
@@ -327,13 +384,19 @@ class WordTemplateExporter {
|
||||
// Calculate number of columns
|
||||
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
|
||||
let tableXml = '<w:tbl>';
|
||||
|
||||
// Table properties - use template table style
|
||||
// Table properties - use template table style with full width
|
||||
tableXml += `<w:tblPr>
|
||||
<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:tblBorders>
|
||||
<w:top w:val="single" w:sz="8" w:space="0" w:color="F58220"/>
|
||||
@@ -345,20 +408,22 @@ class WordTemplateExporter {
|
||||
</w:tblBorders>
|
||||
</w:tblPr>`;
|
||||
|
||||
// Table grid
|
||||
// Table grid with explicit column widths
|
||||
tableXml += '<w:tblGrid>';
|
||||
for (let i = 0; i < numCols; i++) {
|
||||
tableXml += '<w:gridCol/>';
|
||||
tableXml += `<w:gridCol w:w="${colWidth}"/>`;
|
||||
}
|
||||
tableXml += '</w:tblGrid>';
|
||||
|
||||
// Table rows
|
||||
rows.forEach((rowCells, rowIndex) => {
|
||||
const isHeader = rowIndex === 0;
|
||||
const isEvenRow = rowIndex % 2 === 0;
|
||||
|
||||
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
|
||||
while (rowCells.length < numCols) {
|
||||
rowCells.push('');
|
||||
@@ -368,9 +433,14 @@ class WordTemplateExporter {
|
||||
tableXml += '<w:tc>';
|
||||
tableXml += '<w:tcPr>';
|
||||
|
||||
// Cell width
|
||||
tableXml += `<w:tcW w:w="${colWidth}" w:type="dxa"/>`;
|
||||
|
||||
// Cell shading - orange for header, white for data rows
|
||||
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
|
||||
@@ -380,12 +450,21 @@ class WordTemplateExporter {
|
||||
'<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: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>';
|
||||
|
||||
// Cell content
|
||||
tableXml += '<w:p>';
|
||||
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>';
|
||||
|
||||
const runs = this.parseInlineFormatting(cellText);
|
||||
@@ -407,6 +486,9 @@ class WordTemplateExporter {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user