Compare commits

..
8 Commits
Author SHA1 Message Date
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
amitwhandClaude 5a11a9af75 Release v1.9.0: Custom Headers & Footers for Professional Document Exports
Comprehensive header and footer customization system for all standard export formats with dynamic field support and persistent settings.

 Major Features:
- Custom Headers & Footers for PDF, DOCX, ODT, and PowerPoint exports
- Three-column layout (Left/Center/Right) for both headers and footers
- Dynamic field support: $PAGE$, $TOTAL$, $DATE$, $TIME$, $TITLE$, $AUTHOR$, $FILENAME$
- Logo/image upload UI for header and footer branding
- Enable/disable toggle with persistent settings across sessions
- Field insertion helper dialog for quick dynamic field selection
- Complete dark theme support for all 22 themes

🎯 Export Format Support:
- PDF (Standard): LaTeX fancyhdr package with automatic page numbering
- DOCX (Standard): Word XML manipulation with PAGE and NUMPAGES fields
- ODT: OpenDocument format with Pandoc integration
- PowerPoint (PPTX): Footer text with dynamic field replacement
- Batch Converter: Full header/footer support for all formats

💡 Technical Implementation:
- processDynamicFields() function for field replacement
- LaTeX header generation with fancyhdr and lastpage packages
- Word XML manipulation using PizZip for DOCX headers/footers
- addHeaderFooterToDocx() function for post-processing DOCX exports
- buildPandocCommand() enhanced with header/footer parameters
- IPC handlers for dialog communication and settings management

🎨 User Interface:
- New "Header & Footer Settings..." menu option in File menu
- Professional configuration dialog with field previews
- Logo upload with file path display and clear buttons
- Dynamic field picker popup dialog
- Responsive grid layout for three-column design
- Smooth transitions and animations

📦 Integration Points:
- Menu system (File → Header & Footer Settings...)
- Standard export functions (PDF, DOCX, ODT, PPTX)
- Batch converter with full format support
- Settings persistence via electron-store
- Logo file management in userData directory

🔧 Code Changes:
- src/main.js: Added header/footer IPC handlers, state management, processDynamicFields(), addHeaderFooterToDocx(), enhanced buildPandocCommand() and performBatchConversion()
- src/index.html: Added header/footer dialog and field picker dialog
- src/renderer.js: Added dialog management, logo handling, field insertion
- src/styles.css: Added comprehensive dialog styling with dark theme support
- package.json: Updated version to 1.9.0
- CLAUDE.md: Documented v1.9.0 features and implementation details

📝 Note: Enhanced exports (DOCX Enhanced, PDF Enhanced) use Word templates with pre-configured headers/footers. This feature specifically targets standard (non-templated) exports.

🚀 Impact:
Users can now add professional headers and footers to all document exports with custom branding, dynamic page numbers, dates, and titles - significantly improving document presentation for business and academic use cases.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 17:34:32 +05:30
amitwh 8b52635032 Update CLAUDE.md: Add v1.9.0 header/footer feature planning
- Added comprehensive pending tasks section for v1.9.0
- Documented header & footer customization feature requirements
- Detailed implementation plan with technical specifications
- Updated current version to v1.8.3
- Added recent release summary and previous releases section
2025-10-28 17:12:40 +05:30
amitwhandClaude 7e11176290 Release v1.8.3: Major UI and Export Enhancements
This release includes significant improvements to PDF Editor UI, enhanced PDF/Word
export functionality, print fixes, and configurable template settings.

🎯 v1.8.1 - Streamlined PDF Editor UI:
- Clean, focused interface showing only the selected PDF operation
- Smooth fade-in animations when switching between functions
- Optimized dialog sizing (max-width 600px, max-height 85vh)
- Enhanced file list styling with better contrast
- Complete dark theme support for all PDF editor components
- Better scrolling behavior for long forms

📄 v1.8.2 - Enhanced PDF Export & Print Fix:
- New "PDF (Enhanced)" export option using Word template + LibreOffice conversion
- Added to both single-file export and batch converter
- Keyboard shortcut: Ctrl+Shift+P for enhanced PDF export
- Fixed print preview showing blank output issue
- Simplified print handler using CSS @media print rules
- Better print styling with proper element hiding/showing

⚙️ v1.8.3 - Configurable Template Settings:
- Added "Template Settings..." menu option
- Configurable start page for Word template content insertion
- Choose from Pages 1-5 or custom page number (1-100)
- Settings persisted across sessions
- Updated WordTemplateExporter to use configurable start page
- Works with both DOCX (Enhanced) and PDF (Enhanced) export
- Applied to single-file and batch conversion workflows

🔧 Technical Improvements:
- Enhanced WordTemplateExporter constructor to accept startPage parameter
- Updated insertContentAfterPage method for flexible page insertion
- Added Template Settings dialog with quick-select buttons
- IPC communication for custom page number input
- Settings stored in userData/settings.json
- Improved CSS specificity with PDF editor-specific classes
- Fixed @media print rules to ensure preview content is visible

🎨 UI/UX Enhancements:
- PDF Editor sections now animate smoothly when switching
- File lists for merge operations styled with proper contrast
- Remove buttons styled with danger color (#dc3545)
- Template settings dialog shows current template and start page
- All enhancements support all 22 existing themes

The application now provides professional-grade PDF editing, enhanced export
capabilities with full control over template-based generation, and a streamlined
user interface that makes complex operations simple and intuitive.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 16:59:15 +05:30
amitwhandClaude 4d5e552741 Release v1.8.1: Streamlined PDF Editor UI
Enhanced PDF Editor user experience with cleaner, more intuitive interface:

 UI Improvements:
- Streamlined PDF Editor dialog to show only selected function
- Removed cluttered "all functions at once" display
- Added smooth fade-in animation when showing function-specific sections
- Improved dialog sizing: max-width 600px, max-height 85vh
- Better visual separation with compact section spacing

🎨 Styling Enhancements:
- Added PDF editor-specific CSS classes for better organization
- Enhanced file list styling with better contrast
- Improved remove button styling with danger color
- Complete dark theme support for all PDF editor components
- Better scrolling behavior for long forms

🔧 Technical Improvements:
- Maintained existing show/hide logic (already working correctly)
- Enhanced CSS specificity with .pdf-editor-content and .pdf-editor-body classes
- Added @keyframes fadeIn animation for smooth transitions
- Improved overflow handling for better UX on smaller screens

🌙 Theme Support:
- Full dark mode styling for file lists and entries
- Proper color contrast for all interactive elements
- Consistent styling across all 22 themes

The PDF Editor now provides a clean, focused interface showing only the relevant
controls for each operation (merge, split, compress, rotate, delete, reorder,
watermark, encrypt, decrypt, permissions).

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 16:43:13 +05:30
8 changed files with 1851 additions and 185 deletions
File diff suppressed because one or more lines are too long
+199 -12
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.7.9 **Current Version**: v1.9.1
**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,175 @@ gh release create v1.2.1 --title "Title" --notes "Release notes" \
## Feature Implementation Guide ## Feature Implementation Guide
### v1.7.9 Enhanced Word Export with Template Support (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
**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`)
- **New Menu Option**: File → "Header & Footer Settings..." to configure headers/footers
- **Three-Column Layout**: Left, Center, and Right positioning for both headers and footers
- **Dynamic Field Support**: $PAGE$, $TOTAL$, $DATE$, $TIME$, $TITLE$, $AUTHOR$, $FILENAME$
- **Logo/Image Embedding**: Upload logos to display in headers and footers
- **Enable/Disable Toggle**: Quick on/off switch for all headers/footers
- **Persistent Settings**: Configurations saved across sessions
**Format Support:**
- **PDF Export** (Standard): LaTeX-based headers/footers using fancyhdr package with automatic page numbering
- **DOCX Export** (Standard): Word XML manipulation with field support for PAGE and NUMPAGES
- **ODT Export**: OpenDocument format support (similar to DOCX implementation)
- **PowerPoint (PPTX)**: Footer text with dynamic fields on slides
- **Batch Converter**: Full header/footer support for all batch conversions
**Dynamic Fields Processing:**
- `$PAGE$` → Current page number (LaTeX: \thepage, Word: PAGE field)
- `$TOTAL$` → Total pages (LaTeX: \pageref{LastPage}, Word: NUMPAGES field)
- `$DATE$` → Current date (processed at export time)
- `$TIME$` → Current time (processed at export time)
- `$TITLE$` → Document title (from metadata or filename)
- `$AUTHOR$` → Document author (from metadata)
- `$FILENAME$` → File name without extension
**Technical Implementation:**
```javascript
// processDynamicFields function in main.js
function processDynamicFields(text, metadata = {}) {
const now = new Date();
result = text.replace(/\$DATE\$/g, now.toLocaleDateString());
result = result.replace(/\$TIME\$/g, now.toLocaleTimeString());
result = result.replace(/\$TITLE\$/g, metadata.title || 'Untitled');
// ... other replacements
return result;
}
// LaTeX header generation for PDF exports
const latexHeader = `
\\usepackage{fancyhdr}
\\pagestyle{fancy}
\\fancyhf{}
\\lhead{${headerLeft}}
\\chead{${headerCenter}}
\\rhead{${headerRight}}
\\lfoot{${footerLeft}}
\\cfoot{${footerCenter}} // with \\thepage for page numbers
\\rfoot{${footerRight}}
`;
// Word XML generation for DOCX exports
const footerXml = `<?xml version="1.0"?>
<w:ftr xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:p><w:r><w:t>${footerLeft}</w:t></w:r></w:p>
<w:p>
<w:fldSimple w:instr="PAGE"/> // Page number field
<w:r><w:t> of </w:t></w:r>
<w:fldSimple w:instr="NUMPAGES"/> // Total pages field
</w:p>
<w:p><w:r><w:t>${footerRight}</w:t></w:r></w:p>
</w:ftr>`;
```
**Dialog Features:**
- **Field Insertion Buttons**: Quick-insert dynamic fields via popup dialog
- **Logo Upload**: Browse and select images for headers/footers
- **Logo Preview**: Show selected logo filename
- **Clear Buttons**: Remove uploaded logos
- **Enable Toggle**: Disable headers/footers without losing settings
- **Responsive Layout**: Grid-based three-column design
- **Dark Theme Support**: Complete styling for all 22 themes
**Integration Points:**
- `buildPandocCommand()` - Adds LaTeX headers for PDF exports
- `exportWithPandoc()` - Post-processes DOCX with header/footer XML
- `performBatchConversion()` - Batch support for all formats
- `addHeaderFooterToDocx()` - DOCX XML manipulation using PizZip
**Dependencies:**
- **PizZip** (v3.2.0) - ZIP file manipulation for DOCX/ODT
- **fancyhdr** (LaTeX package) - Advanced headers/footers for PDF
- **lastpage** (LaTeX package) - Total page count support
**Key Improvements in v1.9.0:**
1. ✅ Professional headers/footers for all standard export formats
2. ✅ Dynamic field support with automatic value replacement
3. ✅ Logo/image embedding in document headers/footers
4. ✅ Batch converter integration with full header/footer support
5. ✅ Persistent settings across application sessions
6. ✅ User-friendly dialog with field insertion helper
7. ✅ Complete dark theme support
**Note**: Enhanced exports (DOCX Enhanced, PDF Enhanced) use Word templates which already have their own headers/footers defined in the template itself, so this feature specifically targets standard (non-templated) exports.
---
### v1.7.9 Enhanced Word Export with Template Support
#### 📝 Template-Based Word Document Generation #### 📝 Template-Based Word Document Generation
**Added Enhanced DOCX Export** (`src/wordTemplateExporter.js`, `src/main.js:540-596`, `src/main.js:242`) **Added Enhanced DOCX Export** (`src/wordTemplateExporter.js`, `src/main.js:540-596`, `src/main.js:242`)
@@ -1009,14 +1177,33 @@ if (!gotTheLock) {
--- ---
**Last Updated**: October 27, 2025 ## Pending Tasks & Future Enhancements
**Claude Assistant**: Development completed for v1.7.9 with Template-Based Word Export:
- **Template Support**: Word XML manipulation using PizZip to preserve template branding and styles
- **Template Selection**: File menu option to choose custom Word templates, persisted across sessions
- **Table Styling**: Orange headers (#F58220) with white bold text, white data rows, orange borders
- **ASCII Art & Flowcharts**: Perfect monospace alignment with no-wrap properties, each line as separate paragraph
- **Red Arrows**: Flowchart arrows (↓→←↑) rendered in red color for enhanced visibility
- **Markdown Numbering Strip**: Removes markdown numbering from headings and lists to use template numbering
- **Template Page Preservation**: Keeps first 2 pages (cover + TOC) intact, inserts content after 2nd section break
Previous releases: v1.7.8 (file association & print preview fixes), v1.7.7 (print menu with two options), v1.7.6 (table header cleanup), v1.7.5 (single-instance lock). No pending tasks at this time. All planned v1.9.1 features have been completed.
### Potential Future Enhancements
- Image embedding in headers/footers (partially implemented - logo upload UI ready)
- Advanced page numbering formats (Roman numerals, custom prefixes)
- First-page-different header/footer support
- Per-section headers/footers within documents
---
**Last Updated**: December 12, 2025
**Claude Assistant**: Development completed for v1.9.1 with ASCII Art Preservation!
### v1.9.1 - Latest Release Summary
- **ASCII Art Preservation**: Code blocks, flowcharts, tables, and ASCII art now export exactly as shown in preview
- **Enhanced Code Block Rendering**: Each line rendered separately to preserve exact spacing and alignment
- **Monospace Font Support**: Consolas font used consistently across all export formats
- **Improved ASCII Detection**: Extended Unicode box-drawing and arrow character support
- **PDF Export Enhancement**: Added monofont and highlight-style options for better code rendering
- **HTML/Electron PDF Fix**: Enhanced CSS to prevent text wrapping in code blocks
### 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.2: Enhanced PDF Export & Print Fix
- v1.8.1: Streamlined PDF Editor UI
- v1.8.0: Enhanced Word Export with template support (batch)
- v1.7.9: Template-Based Word Export with ASCII art support
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "pan-converter", "name": "pan-converter",
"version": "1.8.0", "version": "1.9.1",
"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": {
+118 -2
View File
@@ -515,6 +515,7 @@
<select id="batch-format"> <select id="batch-format">
<option value="html">HTML</option> <option value="html">HTML</option>
<option value="pdf">PDF</option> <option value="pdf">PDF</option>
<option value="pdf-enhanced">PDF (Enhanced)</option>
<option value="docx">DOCX</option> <option value="docx">DOCX</option>
<option value="docx-enhanced">DOCX (Enhanced)</option> <option value="docx-enhanced">DOCX (Enhanced)</option>
<option value="latex">LaTeX</option> <option value="latex">LaTeX</option>
@@ -556,12 +557,12 @@
<!-- PDF Editor Dialog --> <!-- PDF Editor Dialog -->
<div id="pdf-editor-dialog" class="export-dialog hidden"> <div id="pdf-editor-dialog" class="export-dialog hidden">
<div class="export-dialog-content"> <div class="export-dialog-content pdf-editor-content">
<div class="export-dialog-header"> <div class="export-dialog-header">
<h3 id="pdf-editor-title">PDF Editor</h3> <h3 id="pdf-editor-title">PDF Editor</h3>
<button id="pdf-editor-dialog-close" title="Close">×</button> <button id="pdf-editor-dialog-close" title="Close">×</button>
</div> </div>
<div class="export-dialog-body"> <div class="export-dialog-body pdf-editor-body">
<!-- Merge PDFs Section --> <!-- Merge PDFs Section -->
<div id="pdf-merge-section" class="pdf-operation-section hidden"> <div id="pdf-merge-section" class="pdf-operation-section hidden">
<div class="export-section"> <div class="export-section">
@@ -925,6 +926,121 @@
</div> </div>
</div> </div>
<!-- Header & Footer Configuration Dialog -->
<div id="header-footer-dialog" class="export-dialog hidden">
<div class="export-dialog-content" style="max-width: 700px;">
<div class="export-dialog-header">
<h3>Header & Footer Settings</h3>
<button id="header-footer-close" class="dialog-close" title="Close">×</button>
</div>
<div class="export-dialog-body">
<div class="hf-enable-section">
<label>
<input type="checkbox" id="hf-enabled" checked>
Enable Headers and Footers
</label>
</div>
<div id="hf-config-content">
<!-- Header Configuration -->
<div class="hf-section">
<h4>Header</h4>
<div class="hf-row">
<div class="hf-column">
<label>Left</label>
<input type="text" id="header-left" placeholder="e.g., Document Title">
<button class="field-insert-btn" data-target="header-left" title="Insert dynamic field">+</button>
</div>
<div class="hf-column">
<label>Center</label>
<input type="text" id="header-center" placeholder="e.g., $TITLE$">
<button class="field-insert-btn" data-target="header-center" title="Insert dynamic field">+</button>
</div>
<div class="hf-column">
<label>Right</label>
<input type="text" id="header-right" placeholder="e.g., $DATE$">
<button class="field-insert-btn" data-target="header-right" title="Insert dynamic field">+</button>
</div>
</div>
<div class="hf-logo-row">
<label>Header Logo/Image:</label>
<button id="header-logo-browse" class="browse-btn">Browse...</button>
<button id="header-logo-clear" class="clear-btn">Clear</button>
<span id="header-logo-preview" class="logo-preview"></span>
</div>
</div>
<!-- Footer Configuration -->
<div class="hf-section">
<h4>Footer</h4>
<div class="hf-row">
<div class="hf-column">
<label>Left</label>
<input type="text" id="footer-left" placeholder="e.g., Company Name">
<button class="field-insert-btn" data-target="footer-left" title="Insert dynamic field">+</button>
</div>
<div class="hf-column">
<label>Center</label>
<input type="text" id="footer-center" placeholder="e.g., $PAGE$ of $TOTAL$">
<button class="field-insert-btn" data-target="footer-center" title="Insert dynamic field">+</button>
</div>
<div class="hf-column">
<label>Right</label>
<input type="text" id="footer-right" placeholder="e.g., $AUTHOR$">
<button class="field-insert-btn" data-target="footer-right" title="Insert dynamic field">+</button>
</div>
</div>
<div class="hf-logo-row">
<label>Footer Logo/Image:</label>
<button id="footer-logo-browse" class="browse-btn">Browse...</button>
<button id="footer-logo-clear" class="clear-btn">Clear</button>
<span id="footer-logo-preview" class="logo-preview"></span>
</div>
</div>
<!-- Dynamic Fields Help -->
<div class="hf-help">
<p><strong>Available Dynamic Fields:</strong></p>
<ul>
<li><code>$PAGE$</code> - Current page number</li>
<li><code>$TOTAL$</code> - Total page count</li>
<li><code>$DATE$</code> - Current date</li>
<li><code>$TIME$</code> - Current time</li>
<li><code>$TITLE$</code> - Document title</li>
<li><code>$AUTHOR$</code> - Document author</li>
<li><code>$FILENAME$</code> - File name</li>
</ul>
</div>
</div>
</div>
<div class="export-dialog-footer">
<button id="header-footer-cancel">Cancel</button>
<button id="header-footer-save" class="primary">Save Settings</button>
</div>
</div>
</div>
<!-- Dynamic Field Picker Dialog -->
<div id="field-picker-dialog" class="export-dialog hidden">
<div class="export-dialog-content" style="max-width: 400px;">
<div class="export-dialog-header">
<h3>Insert Dynamic Field</h3>
<button id="field-picker-close" class="dialog-close" title="Close">×</button>
</div>
<div class="export-dialog-body">
<div class="field-picker-list">
<button class="field-option" data-field="$PAGE$">Page Number</button>
<button class="field-option" data-field="$TOTAL$">Total Pages</button>
<button class="field-option" data-field="$DATE$">Current Date</button>
<button class="field-option" data-field="$TIME$">Current Time</button>
<button class="field-option" data-field="$TITLE$">Document Title</button>
<button class="field-option" data-field="$AUTHOR$">Document Author</button>
<button class="field-option" data-field="$FILENAME$">File Name</button>
</div>
</div>
</div>
</div>
<div class="editor-container"> <div class="editor-container">
<div class="tab-content active" id="tab-content-1" data-tab-id="1"> <div class="tab-content active" id="tab-content-1" data-tab-id="1">
<div id="editor-pane-1" class="pane"> <div id="editor-pane-1" class="pane">
+812 -12
View File
File diff suppressed because it is too large Load Diff
+194 -82
View File
@@ -1184,95 +1184,26 @@ function handlePrintPreview(withStyles) {
return; return;
} }
// Store original display values for restoration console.log('[RENDERER] Starting print with withStyles:', withStyles);
const elementsToHide = []; console.log('[RENDERER] Preview content length:', previewContent.innerHTML.length);
// Hide toolbar // Add body classes for print mode - let CSS handle everything
const toolbar = document.querySelector('.toolbar'); document.body.classList.add('printing');
if (toolbar) { if (!withStyles) {
elementsToHide.push({ elem: toolbar, display: toolbar.style.display }); document.body.classList.add('printing-no-styles');
toolbar.style.display = 'none';
} }
// Hide tab bar // Wait for CSS to apply then print
const tabBar = document.querySelector('.tab-bar');
if (tabBar) {
elementsToHide.push({ elem: tabBar, display: tabBar.style.display });
tabBar.style.display = 'none';
}
// Hide status bar
const statusBar = document.querySelector('.status-bar');
if (statusBar) {
elementsToHide.push({ elem: statusBar, display: statusBar.style.display });
statusBar.style.display = 'none';
}
// Hide editor pane
const editorPane = document.getElementById(`editor-pane-${activeTabId}`);
if (editorPane) {
elementsToHide.push({ elem: editorPane, display: editorPane.style.display });
editorPane.style.display = 'none';
}
// Hide all dialogs
const dialogs = document.querySelectorAll('.dialog, .modal-overlay');
dialogs.forEach(dialog => {
elementsToHide.push({ elem: dialog, display: dialog.style.display });
dialog.style.display = 'none';
});
// Make preview pane full width
const previewPane = document.getElementById(`preview-pane-${activeTabId}`);
let originalPreviewStyles = {};
if (previewPane) {
originalPreviewStyles = {
position: previewPane.style.position,
top: previewPane.style.top,
left: previewPane.style.left,
width: previewPane.style.width,
height: previewPane.style.height,
margin: previewPane.style.margin,
padding: previewPane.style.padding
};
previewPane.style.position = 'absolute';
previewPane.style.top = '0';
previewPane.style.left = '0';
previewPane.style.width = '100%';
previewPane.style.height = 'auto';
previewPane.style.margin = '0';
previewPane.style.padding = '20px';
}
// Apply no-styles if needed
if (!withStyles && previewContent) {
previewContent.style.color = '#000';
previewContent.style.background = '#fff';
}
// Wait for DOM updates then print
setTimeout(() => { setTimeout(() => {
console.log('[RENDERER] Sending do-print to main process');
ipcRenderer.send('do-print', { withStyles }); ipcRenderer.send('do-print', { withStyles });
// Restore everything after print dialog opens // Restore classes after print dialog opens
setTimeout(() => { setTimeout(() => {
// Restore hidden elements document.body.classList.remove('printing', 'printing-no-styles');
elementsToHide.forEach(({ elem, display }) => { console.log('[RENDERER] Print classes removed');
elem.style.display = display; }, 1000);
}); }, 100);
// Restore preview pane styles
if (previewPane) {
Object.assign(previewPane.style, originalPreviewStyles);
}
// Restore preview content styles
if (!withStyles && previewContent) {
previewContent.style.color = '';
previewContent.style.background = '';
}
}, 500);
}, 200);
} }
// Export Dialog functionality // Export Dialog functionality
@@ -2625,3 +2556,184 @@ function initMathSupport() {
// Initialize math support on load // Initialize math support on load
initMathSupport(); initMathSupport();
// ================================
// Header & Footer Dialog Management
// ================================
let currentFieldTarget = null; // Track which input field is being edited
// Open header/footer settings dialog
function openHeaderFooterDialog() {
const dialog = document.getElementById('header-footer-dialog');
dialog.classList.remove('hidden');
// Request current settings from main process
ipcRenderer.send('get-header-footer-settings');
}
// Close header/footer settings dialog
function closeHeaderFooterDialog() {
const dialog = document.getElementById('header-footer-dialog');
dialog.classList.add('hidden');
}
// Open field picker dialog
function openFieldPickerDialog(targetInputId) {
currentFieldTarget = targetInputId;
const dialog = document.getElementById('field-picker-dialog');
dialog.classList.remove('hidden');
}
// Close field picker dialog
function closeFieldPickerDialog() {
const dialog = document.getElementById('field-picker-dialog');
dialog.classList.add('hidden');
currentFieldTarget = null;
}
// Load settings into dialog
ipcRenderer.on('header-footer-settings-data', (event, settings) => {
// Enable/disable checkbox
document.getElementById('hf-enabled').checked = settings.enabled;
// Header fields
document.getElementById('header-left').value = settings.header.left || '';
document.getElementById('header-center').value = settings.header.center || '';
document.getElementById('header-right').value = settings.header.right || '';
// Footer fields
document.getElementById('footer-left').value = settings.footer.left || '';
document.getElementById('footer-center').value = settings.footer.center || '';
document.getElementById('footer-right').value = settings.footer.right || '';
// Logo previews
if (settings.header.logo) {
document.getElementById('header-logo-preview').textContent = path.basename(settings.header.logo);
} else {
document.getElementById('header-logo-preview').textContent = '';
}
if (settings.footer.logo) {
document.getElementById('footer-logo-preview').textContent = path.basename(settings.footer.logo);
} else {
document.getElementById('footer-logo-preview').textContent = '';
}
// Update config content visibility
toggleConfigContent();
});
// Toggle config content based on enabled checkbox
function toggleConfigContent() {
const enabled = document.getElementById('hf-enabled').checked;
const configContent = document.getElementById('hf-config-content');
if (enabled) {
configContent.classList.remove('disabled');
} else {
configContent.classList.add('disabled');
}
}
// Save header/footer settings
function saveHeaderFooterSettings() {
const settings = {
enabled: document.getElementById('hf-enabled').checked,
header: {
left: document.getElementById('header-left').value || '',
center: document.getElementById('header-center').value || '',
right: document.getElementById('header-right').value || '',
logo: null // Logo paths are managed separately
},
footer: {
left: document.getElementById('footer-left').value || '',
center: document.getElementById('footer-center').value || '',
right: document.getElementById('footer-right').value || '',
logo: null
}
};
ipcRenderer.send('save-header-footer-settings', settings);
closeHeaderFooterDialog();
}
// Handle logo browsing - ask main process to show open dialog
function browseForLogo(position) {
ipcRenderer.send('browse-header-footer-logo', position);
}
// Handle logo saved confirmation
ipcRenderer.on('header-footer-logo-saved', (event, { position, path }) => {
document.getElementById(`${position}-logo-preview`).textContent = path.split(/[\\/]/).pop();
});
// Clear logo
function clearLogo(position) {
ipcRenderer.send('clear-header-footer-logo', position);
document.getElementById(`${position}-logo-preview`).textContent = '';
}
// Handle logo cleared confirmation
ipcRenderer.on('header-footer-logo-cleared', (event, position) => {
console.log(`${position} logo cleared`);
});
// Insert dynamic field into input
function insertDynamicField(field) {
if (currentFieldTarget) {
const input = document.getElementById(currentFieldTarget);
const cursorPos = input.selectionStart;
const textBefore = input.value.substring(0, cursorPos);
const textAfter = input.value.substring(cursorPos);
input.value = textBefore + field + textAfter;
input.focus();
input.setSelectionRange(cursorPos + field.length, cursorPos + field.length);
}
closeFieldPickerDialog();
}
// Event Listeners for Header/Footer Dialog
// Close buttons
document.getElementById('header-footer-close').addEventListener('click', closeHeaderFooterDialog);
document.getElementById('header-footer-cancel').addEventListener('click', closeHeaderFooterDialog);
document.getElementById('header-footer-save').addEventListener('click', saveHeaderFooterSettings);
// Enable/disable checkbox
document.getElementById('hf-enabled').addEventListener('change', toggleConfigContent);
// Field insert buttons
document.querySelectorAll('.field-insert-btn').forEach(btn => {
btn.addEventListener('click', () => {
const target = btn.getAttribute('data-target');
openFieldPickerDialog(target);
});
});
// Logo browse buttons
document.getElementById('header-logo-browse').addEventListener('click', () => browseForLogo('header'));
document.getElementById('footer-logo-browse').addEventListener('click', () => browseForLogo('footer'));
// Logo clear buttons
document.getElementById('header-logo-clear').addEventListener('click', () => clearLogo('header'));
document.getElementById('footer-logo-clear').addEventListener('click', () => clearLogo('footer'));
// Field picker dialog
document.getElementById('field-picker-close').addEventListener('click', closeFieldPickerDialog);
document.querySelectorAll('.field-option').forEach(btn => {
btn.addEventListener('click', () => {
const field = btn.getAttribute('data-field');
insertDynamicField(field);
});
});
// Export function to make openHeaderFooterDialog accessible globally
window.openHeaderFooterDialog = openHeaderFooterDialog;
// Listen for menu command to open dialog
ipcRenderer.on('open-header-footer-dialog', () => {
openHeaderFooterDialog();
});
+427 -2
View File
@@ -1098,6 +1098,117 @@ body.theme-dark #add-metadata-field {
color: #f0f0f0; color: #f0f0f0;
} }
/* PDF Editor Specific Styles */
.pdf-editor-content {
max-width: 600px !important;
max-height: 85vh;
overflow-y: auto;
}
.pdf-editor-body {
min-height: 200px;
max-height: calc(85vh - 150px);
overflow-y: auto;
}
.pdf-operation-section {
animation: fadeIn 0.3s ease-in;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.pdf-operation-section.hidden {
display: none !important;
}
/* Make PDF Editor sections more compact */
.pdf-operation-section .export-section {
margin-bottom: 18px;
}
.pdf-operation-section .export-section:last-child {
margin-bottom: 0;
}
/* File list styling for merge operation */
.file-list {
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px;
margin-bottom: 10px;
min-height: 100px;
max-height: 200px;
overflow-y: auto;
background: #fafafa;
}
.file-entry {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px;
margin-bottom: 6px;
background: white;
border: 1px solid #e0e0e0;
border-radius: 4px;
}
.file-entry:last-child {
margin-bottom: 0;
}
.file-name {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 10px;
font-size: 13px;
}
.remove-file {
padding: 4px 10px;
background: #dc3545;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
}
.remove-file:hover {
background: #c82333;
}
/* Theme support for PDF Editor */
body.theme-dark .file-list {
background: #1a1a1a;
border-color: #555;
}
body.theme-dark .file-entry {
background: #2d2d2d;
border-color: #444;
color: #f0f0f0;
}
body.theme-dark .pdf-editor-content {
background: #2d2d2d;
}
body.theme-dark .pdf-editor-body {
background: #2d2d2d;
}
body.theme-dark .form-row button:hover, body.theme-dark .form-row button:hover,
body.theme-dark #add-metadata-field:hover { body.theme-dark #add-metadata-field:hover {
background: #333; background: #333;
@@ -2834,18 +2945,33 @@ body.theme-concrete-warm .status-bar {
.editor-container, .editor-container,
#status-bar, #status-bar,
.status-bar, .status-bar,
.toolbar,
.tab-bar,
.editor-pane, .editor-pane,
[id^="editor-pane-"] { [id^="editor-pane-"],
.dialog,
.modal-overlay,
.export-dialog {
display: none !important; display: none !important;
} }
/* Ensure body and html take full width */
html, body {
width: 100% !important;
height: auto !important;
margin: 0 !important;
padding: 0 !important;
overflow: visible !important;
}
/* Show preview in full width */ /* Show preview in full width */
#preview, #preview,
.preview, .preview,
.preview-content, .preview-content,
[id^="preview-"], [id^="preview-"],
[id^="preview-pane-"], [id^="preview-pane-"],
.tab-content { .tab-content,
.pane {
display: block !important; display: block !important;
width: 100% !important; width: 100% !important;
max-width: 100% !important; max-width: 100% !important;
@@ -2854,6 +2980,10 @@ body.theme-concrete-warm .status-bar {
padding: 20px !important; padding: 20px !important;
overflow: visible !important; overflow: visible !important;
position: static !important; position: static !important;
left: auto !important;
right: auto !important;
top: auto !important;
bottom: auto !important;
} }
/* Optimize preview content for printing */ /* Optimize preview content for printing */
@@ -2865,6 +2995,13 @@ body.theme-concrete-warm .status-bar {
background: white !important; background: white !important;
} }
/* No-styles mode for printing without theme colors */
body.printing-no-styles .preview-content,
body.printing-no-styles [id^="preview-"] {
color: #000 !important;
background: #fff !important;
}
/* Text formatting */ /* Text formatting */
.preview-content h1, .preview-content h1,
.preview-content h2, .preview-content h2,
@@ -3021,3 +3158,291 @@ body.printing-no-styles .preview-content pre,
color: #333 !important; color: #333 !important;
border-color: #999 !important; border-color: #999 !important;
} }
/* ================================
Header & Footer Dialog Styles
================================ */
.hf-enable-section {
margin-bottom: 20px;
padding: 15px;
background: var(--bg-secondary, #f5f5f5);
border-radius: 5px;
}
.hf-enable-section label {
display: flex;
align-items: center;
font-weight: 600;
cursor: pointer;
}
.hf-enable-section input[type="checkbox"] {
margin-right: 10px;
width: 18px;
height: 18px;
cursor: pointer;
}
#hf-config-content {
transition: opacity 0.3s ease;
}
#hf-config-content.disabled {
opacity: 0.5;
pointer-events: none;
}
.hf-section {
margin-bottom: 25px;
padding: 20px;
background: var(--bg-tertiary, #fafafa);
border-radius: 8px;
border: 1px solid var(--border-color, #e0e0e0);
}
.hf-section h4 {
margin: 0 0 15px 0;
color: var(--text-primary, #333);
font-size: 16px;
border-bottom: 2px solid var(--accent-color, #007bff);
padding-bottom: 8px;
}
.hf-row {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
margin-bottom: 15px;
}
.hf-column {
display: flex;
flex-direction: column;
position: relative;
}
.hf-column label {
font-weight: 600;
margin-bottom: 5px;
color: var(--text-secondary, #666);
font-size: 13px;
}
.hf-column input[type="text"] {
padding: 8px 35px 8px 10px;
border: 1px solid var(--border-color, #ccc);
border-radius: 4px;
font-size: 14px;
background: var(--input-bg, white);
color: var(--text-primary, #333);
transition: border-color 0.2s ease;
}
.hf-column input[type="text"]:focus {
outline: none;
border-color: var(--accent-color, #007bff);
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.1);
}
.field-insert-btn {
position: absolute;
right: 5px;
bottom: 5px;
width: 28px;
height: 28px;
padding: 0;
background: var(--accent-color, #007bff);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 18px;
line-height: 1;
transition: all 0.2s ease;
}
.field-insert-btn:hover {
background: var(--accent-hover, #0056b3);
transform: scale(1.05);
}
.field-insert-btn:active {
transform: scale(0.95);
}
.hf-logo-row {
display: flex;
align-items: center;
gap: 10px;
padding: 15px;
background: var(--bg-secondary, #f5f5f5);
border-radius: 6px;
margin-top: 10px;
}
.hf-logo-row label {
font-weight: 600;
color: var(--text-secondary, #666);
min-width: 120px;
}
.hf-logo-row input[type="file"] {
flex: 1;
padding: 6px 10px;
border: 1px solid var(--border-color, #ccc);
border-radius: 4px;
background: var(--input-bg, white);
color: var(--text-primary, #333);
font-size: 13px;
cursor: pointer;
}
.hf-logo-row input[type="file"]::-webkit-file-upload-button {
padding: 5px 12px;
background: var(--button-bg, #6c757d);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
margin-right: 10px;
}
.hf-logo-row input[type="file"]::-webkit-file-upload-button:hover {
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 {
padding: 6px 12px;
background: var(--danger-color, #dc3545);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
transition: background 0.2s ease;
}
.clear-btn:hover {
background: var(--danger-hover, #c82333);
}
.logo-preview {
font-size: 12px;
color: var(--text-secondary, #666);
max-width: 150px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.hf-help {
margin-top: 20px;
padding: 15px;
background: var(--info-bg, #e7f3ff);
border-left: 4px solid var(--info-color, #007bff);
border-radius: 4px;
}
.hf-help p {
margin: 0 0 10px 0;
font-weight: 600;
color: var(--text-primary, #333);
}
.hf-help ul {
margin: 0;
padding-left: 20px;
list-style-type: none;
}
.hf-help li {
margin-bottom: 6px;
color: var(--text-secondary, #555);
font-size: 13px;
}
.hf-help code {
background: rgba(0, 123, 255, 0.1);
padding: 2px 6px;
border-radius: 3px;
font-family: 'Courier New', monospace;
color: var(--accent-color, #007bff);
font-weight: 600;
}
/* Field Picker Dialog */
.field-picker-list {
display: grid;
grid-template-columns: 1fr;
gap: 8px;
}
.field-option {
padding: 12px 16px;
background: var(--bg-secondary, #f5f5f5);
border: 1px solid var(--border-color, #ddd);
border-radius: 6px;
text-align: left;
cursor: pointer;
font-size: 14px;
color: var(--text-primary, #333);
transition: all 0.2s ease;
}
.field-option:hover {
background: var(--accent-color, #007bff);
color: white;
border-color: var(--accent-color, #007bff);
transform: translateX(5px);
}
/* Dark Theme Support for Header/Footer Dialog */
body[data-theme="dark"] .hf-enable-section,
body[data-theme="dark"] .hf-logo-row {
background: #2d2d2d;
}
body[data-theme="dark"] .hf-section {
background: #252525;
border-color: #404040;
}
body[data-theme="dark"] .hf-column input[type="text"],
body[data-theme="dark"] .hf-logo-row input[type="file"] {
background: #2d2d2d;
color: #e0e0e0;
border-color: #404040;
}
body[data-theme="dark"] .hf-help {
background: rgba(0, 123, 255, 0.15);
border-left-color: #0d6efd;
}
body[data-theme="dark"] .field-option {
background: #2d2d2d;
border-color: #404040;
color: #e0e0e0;
}
body[data-theme="dark"] .field-option:hover {
background: #0d6efd;
color: white;
}
+62 -21
View File
@@ -10,8 +10,9 @@ const PizZip = require('pizzip');
const Docx = require('docx4js').default; const Docx = require('docx4js').default;
class WordTemplateExporter { class WordTemplateExporter {
constructor(templatePath) { constructor(templatePath, startPage = 3) {
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
} }
/** /**
@@ -29,8 +30,8 @@ class WordTemplateExporter {
// Parse markdown and generate Word XML // Parse markdown and generate Word XML
const newContentXml = this.markdownToWordXml(markdownContent); const newContentXml = this.markdownToWordXml(markdownContent);
// Insert new content after page 2 (after the section break) // Insert new content after the specified start page
const modifiedXml = this.insertContentAfterPage2(documentXml, newContentXml); const modifiedXml = this.insertContentAfterPage(documentXml, newContentXml, this.startPage);
// Update the zip with modified XML // Update the zip with modified XML
zip.file('word/document.xml', modifiedXml); zip.file('word/document.xml', modifiedXml);
@@ -47,20 +48,33 @@ class WordTemplateExporter {
} }
/** /**
* Insert markdown content after page 2 in the document * Insert markdown content after the specified page in the document
* @param {string} documentXml - The document XML
* @param {string} newContentXml - The new content to insert
* @param {number} afterPage - Insert content after this page number (1-based)
*/ */
insertContentAfterPage2(documentXml, newContentXml) { insertContentAfterPage(documentXml, newContentXml, afterPage) {
// Find the last section break (after page 2) // Find section breaks that mark page boundaries
// Look for the section properties tag that marks page breaks // Look for the section properties tag that marks page breaks
const sectionBreakRegex = /<w:sectPr[^>]*>[\s\S]*?<\/w:sectPr>/g; const sectionBreakRegex = /<w:sectPr[^>]*>[\s\S]*?<\/w:sectPr>/g;
const matches = [...documentXml.matchAll(sectionBreakRegex)]; const matches = [...documentXml.matchAll(sectionBreakRegex)];
if (matches.length >= 2) { // Calculate which section break to insert after
// Insert after the 2nd section break // Page 1 = before 1st section break
const insertPoint = matches[1].index + matches[1][0].length; // Page 2 = after 1st section break
// Page 3 = after 2nd section break, etc.
const sectionIndex = afterPage - 1;
if (matches.length >= sectionIndex && sectionIndex > 0) {
// Insert after the specified section break
const insertPoint = matches[sectionIndex - 1].index + matches[sectionIndex - 1][0].length;
return documentXml.slice(0, insertPoint) + newContentXml + documentXml.slice(insertPoint); return documentXml.slice(0, insertPoint) + newContentXml + documentXml.slice(insertPoint);
} else if (afterPage === 1 || matches.length === 0) {
// Insert at the beginning (after <w:body>) or if no section breaks found
const bodyStart = documentXml.indexOf('<w:body>') + 8;
return documentXml.slice(0, bodyStart) + newContentXml + documentXml.slice(bodyStart);
} else { } else {
// If no section breaks found, insert before closing body tag // If not enough section breaks, insert before closing body tag
return documentXml.replace('</w:body>', newContentXml + '</w:body>'); return documentXml.replace('</w:body>', newContentXml + '</w:body>');
} }
} }
@@ -239,23 +253,39 @@ 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)
*/ */
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
xml += `<w:p>
<w:pPr> <w:pPr>
<w:pStyle w:val="Code"/> <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="0"/>
<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:shd w:val="clear" w:color="auto" w:fill="F5F5F5"/>
<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;
} }
/** /**
@@ -383,15 +413,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
@@ -401,14 +438,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));