Compare commits

..
24 Commits
Author SHA1 Message Date
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
amitwhandClaude 58314efc7a Release v1.8.0: Enhanced Word Export in Batch Converter
Added enhanced Word export with template support to the batch file converter,
enabling batch conversion of markdown files to professionally-styled DOCX
documents using custom templates.

New Features:
- Added "DOCX (Enhanced)" option to batch converter format selection
- Integrated WordTemplateExporter with batch conversion workflow
- Batch converter now supports template-based Word export with:
  * Custom template preservation
  * Professional table styling (orange headers, white data rows)
  * Perfect ASCII art and flowchart alignment
  * Red-colored arrows in flowcharts
  * Markdown numbering stripped from headings and lists

Technical Changes:
- Modified performBatchConversion() to handle 'docx-enhanced' format
- Async/await pattern for WordTemplateExporter in batch processing
- Proper error handling and progress tracking for enhanced exports
- Updated version to v1.8.0 in package.json, main.js, and README.md

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 08:00:05 +05:30
amitwhandClaude 478b404135 Release v1.7.9: Enhanced Word Export with Template Support
Major Features:
- Template-based Word export with custom template selection
- File menu option to select and persist Word templates
- Table styling with orange headers and white data rows
- ASCII art and flowchart rendering with perfect alignment
- Red-colored arrows in flowcharts for enhanced visibility
- Markdown numbering stripped from headings and lists

Technical Improvements:
- Enhanced ASCII art detection for arrows and flowchart patterns
- Line-by-line rendering with no-wrap properties
- Arrow character detection and red coloring (↓→←↑)
- Template path persistence across sessions
- Word XML manipulation using PizZip

Files Modified:
- src/wordTemplateExporter.js: Enhanced ASCII art rendering, table styling
- src/main.js: Template selection function and menu integration
- CLAUDE.md: Comprehensive v1.7.9 documentation
- package.json: Version bump to 1.7.9

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 00:31:38 +05:30
amitwhandClaude 8be2651b0e Add Enhanced Word Export with native DOCX generation (v1.7.9)
- Created wordTemplateExporter.js module using docx npm package
- Added "DOCX (Enhanced)" option in File → Export menu
- Keyboard shortcut: Ctrl+Shift+W
- Full markdown support: headings, formatting, code, lists, tables
- No external dependencies (Pandoc/Python not required)
- Proper numbering configuration for lists
- Professional Word document output

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-26 23:54:00 +05:30
amitwhandClaude 922e74d671 Fix print preview to properly hide toolbar and show only markdown content
- Rewrote handlePrintPreview to explicitly hide all UI elements
- Store and restore original display values for clean restoration
- Make preview pane absolute positioned and full width for printing
- Hide toolbar, tab bar, status bar, editor pane, and dialogs
- Added proper timing for DOM updates before printing

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-26 23:36:28 +05:30
amitwhandClaude 7e67667035 Fix critical bugs in v1.7.8: file association and print preview
- Fixed command-line argument parsing for packaged apps using app.isPackaged
- Fixed print preview to show markdown content instead of toolbar
- Rewrote print handler to rely on CSS @media print rules
- Enhanced CSS with attribute selectors for dynamic preview panes
- Removed DevTools auto-open for production build
- Updated version to 1.7.8 across all files

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-26 21:22:36 +05:30
amitwhandClaude 7783fbf622 Fix print null errors and add main process logging to renderer console
- Added null checks for toolbar, tab-bar, status-bar before accessing .style
- Prevents TypeError when elements don't exist
- Added executeJavaScript to send main process logs to renderer console
- Now we can see main process state in DevTools
- Print should now work without errors

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-26 21:05:24 +05:30
amitwhandClaude 458a387c15 Add comprehensive debugging logging for file loading and print issues
- Added console.log statements throughout file loading flow
- Traces: command-line args → pendingFile → renderer-ready → openFileFromPath → file-opened
- Added logging to print-preview handlers
- Added logging to theme-changed and renderer-ready
- Enabled DevTools automatically for debugging
- Backup renderer-ready timeout (100ms) in case theme-changed doesn't fire

This debugging build will help identify exactly where the flow breaks.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-26 20:45:24 +05:30
amitwhandClaude 72677698d2 Fix print preview, file loading, and PDF export - complete code analysis and fixes
CRITICAL PRINT PREVIEW FIX:
- FOUND: Redundant ipcMain handlers creating message loop at lines 1171-1182
- Menu sends 'print-preview' to renderer 
- ipcMain.on('print-preview') was re-sending to renderer  (REMOVED)
- Renderer hideUI, waits 300ms, sends 'do-print' to main 
- Main process prints immediately (no extra delay needed)
- Print now correctly captures hidden UI and shows preview content

FILE LOADING ON DOUBLE-CLICK FIX:
- FOUND: theme-changed handler waiting 1500ms was too long
- Changed from setTimeout 1500ms to requestAnimationFrame (respects browser rendering)
- renderer-ready signal sent after next animation frame
- openFileFromPath now sends file-opened immediately when rendererReady
- Files now load and render quickly on first double-click
- Proper async timing: DOMContentLoaded → theme request → theme applied → ready

PDF EXPORT FALLBACK FIX:
- When Pandoc PDF engines (XeLaTeX, PDFLaTeX, LuaLaTeX) unavailable
- System now falls back to Electron's built-in PDF export
- Users get working PDF export even without LaTeX installed
- No error dialog, seamless fallback to working solution

Code Analysis Summary:
1. Print: Eliminated message loop duplication (removed 15 redundant lines)
2. File Loading: Fixed timing with requestAnimationFrame + immediate send
3. PDF: Added graceful fallback instead of error dialog

All dependencies already present:
- html2pdf.js 
- pdf-lib 
- pdfkit 

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-26 20:30:30 +05:30
amitwhandClaude 59c6513b49 Fix PDF export geometry error and improve print/file loading timing
PDF Export Fix:
- Fixed ReferenceError: geometry is not defined in PDF export
- Removed hardcoded undefined geometry variable from pandoc command
- Geometry option now properly extracted from options object when present
- PDF export now works without errors

Print Preview Timing:
- Changed from requestAnimationFrame to setTimeout for more reliable rendering
- Increased delay from 0ms to 300ms before sending print command
- Post-print UI restoration after 500ms to ensure printer captured output
- More reliable toolbar hiding before print dialog opens

File Loading Timing:
- Increased delay from 500ms to 2000ms (2 seconds) for file-opened message
- Ensures complete theme application and preview pane initialization
- Files now load and render consistently on first double-click

All timing issues related to browser rendering and DOM updates resolved.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-26 20:15:52 +05:30
amitwhandClaude c305789428 FINAL FIX: Proper print preview and file loading with correct DOM timing
Print Preview - FIXED:
- Eliminated race condition: renderer now waits for DOM to render before telling main to print
- Uses double requestAnimationFrame to ensure browser has repainted
- Main process only waits 50ms (DOM already rendered by renderer)
- Renderer hides UI elements, applies print-mode class, THEN signals main to print
- This ensures print dialog captures the properly rendered preview, not toolbar

File Loading - FIXED:
- Fixed critical bug in openFile(): updatePreview was called BEFORE editor value was set
- When reusing current tab, preview now updates AFTER editor content is loaded
- Ensures preview always has content to render
- Both tab reuse and new tab creation now properly update preview

Technical Details:
Print flow:
1. User clicks Print Preview
2. Menu sends 'print-preview' to renderer
3. Renderer: hide UI, set print-mode class
4. Renderer: requestAnimationFrame x2 (waits for repaints)
5. Renderer: sends 'do-print' to main (DOM fully rendered)
6. Main: small 50ms delay, then prints
7. Result: prints preview content, not toolbar!

File loading flow:
1. User double-clicks .md file (or file association)
2. Main process stores file path
3. Renderer signals ready after theme loads
4. Main sends file-opened message
5. Renderer openFile() called
6. Editor value set
7. THEN updatePreview() called (content exists)
8. Preview renders correctly!

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-26 14:36:05 +05:30
amitwhandClaude fefb299e6d CRITICAL FIX: Print preview and file loading - complete rewrite of timing logic
Print Preview Fix:
- Changed to hide only editor-pane instead of entire editor-container
- Preview was inside editor-container, so hiding it hid the preview too!
- Now correctly shows preview-pane-{tabId} with print-mode class
- Hide all dialogs including find-dialog during print
- Preview now displays correctly for printing

File Loading Fix - Completely Rewritten:
- Root cause: renderer-ready was sent BEFORE theme was applied
- Solution: Moved renderer-ready signal INTO theme-changed handler
- Now waits 1500ms AFTER theme is applied before signaling ready
- Main process delay increased to 500ms for extra safety
- Ensures complete UI render cycle: DOMContentLoaded -> theme request -> theme applied -> wait -> renderer-ready -> file opens
- Files now load and render properly on first double-click

Technical Flow:
1. DOMContentLoaded fires, TabManager initializes
2. get-theme request sent to main
3. theme-changed received, theme applied to body
4. After 1500ms delay, renderer-ready sent
5. Main process waits 500ms more, then sends file-opened
6. Total delay: ~2 seconds ensures rock-solid first-load rendering

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-26 14:27:17 +05:30
amitwhandClaude 3f3bdf5637 Fix print preview to show content and improve file loading timing
Print Preview Fixes:
- Fixed print preview to show actual preview content instead of formatting menu
- Changed from generic 'preview' ID to active tab's 'preview-{tabId}' element
- Added CSS positioning to make preview fixed and full-screen during print
- Hide all dialog overlays (export, batch, PDF, converter) during print
- Restore dialogs after print completes
- Enhanced print-mode CSS with z-index and positioning for proper display

File Loading Timing Improvements:
- Increased renderer-ready delay from 500ms to 1000ms for slower first loads
- Increased openFileFromPath delay from 100ms to 300ms
- Ensures UI fully initializes before files are displayed
- Files now render correctly on first double-click launch

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-26 14:19:59 +05:30
amitwhandClaude 2ab8deffeb Fix file rendering timing on first launch - delay initialization until UI fully loads
- Added 500ms delay to renderer-ready signal to ensure all UI initialization completes before opening files
- Added 100ms delay in openFileFromPath to ensure main process doesn't send file before renderer is fully prepared
- Prevents race condition where files would open before theme is applied and interface is ready
- Files now render properly on first launch regardless of app startup time
- Improved startup UX by ensuring smooth file rendering

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-26 14:02:49 +05:30
amitwhandClaude cc471d4101 Fix print preview functionality and set GitHub as default theme
- Fixed print-preview event handling by adding missing ipcRenderer listeners
- Renderer now properly relays print-preview and print-preview-styled events to main process
- Changed default theme from 'light' to 'github' for better visual appearance
- Rebuilt release with v1.7.7 fixes

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-26 13:58:36 +05:30
amitwh 0ac92b7060 Bump version to 1.7.7 for release 2025-10-24 22:38:31 +05:30
amitwhandClaude f74f5f4221 Enhance print menu with preview-only printing options (v1.7.7)
- Added Print submenu to File menu with two print options:
  * Print Preview (Ctrl+P): Black text, no colors, ink-saving
  * Print Preview (With Styles): Full colors and styling
- Implemented print handler that hides all editor UI and shows only preview
- Editor, toolbar, tabs, and status bar hidden during printing
- Added comprehensive CSS print stylesheet with:
  * Smart page breaks for headings, tables, and paragraphs
  * Optimized formatting for code blocks, lists, and tables
  * Professional typography (12pt font size, 1.6 line height)
  * Image optimization and proper link handling
  * Support for blockquotes, horizontal rules, and other elements
- Print-specific CSS classes for styling control
- Preview automatically restored after printing

Features:
- Prints only the rendered markdown preview (not source)
- Two print modes: simple (black) and styled (colored)
- Native OS print dialogs for all platforms
- Professional output formatting with page breaks
- Cross-platform support (Windows, macOS, Linux)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-24 22:14:53 +05:30
amitwhandClaude e1c0bc387f Add print functionality and native PDF support (v1.7.7)
- Added Print menu item with Ctrl+P keyboard shortcut for native OS printing
- Implemented print IPC handlers in main and renderer processes
- Remapped Toggle Preview from Ctrl+P to Ctrl+Shift+P to avoid conflicts
- Added PDFKit (v0.14.0) for native PDF generation without Pandoc
- Added html2pdf.js (v0.10.1) for HTML to PDF conversion
- Enhanced CLAUDE.md with new v1.7.7 feature documentation
- Print supports cross-platform printing (Windows, macOS, Linux)

Features:
- Native print dialog with background color printing
- Professional PDF export without system dependencies
- Improved portability and offline support

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-24 22:10:30 +05:30
amitwhandClaude ef3ce1647e Update CLAUDE.md with v1.7.5 and v1.7.6 changes
Added comprehensive documentation for:
- v1.7.6: Table header styling cleanup
- v1.7.5: Critical file association fix with single-instance lock
- Updated current version to v1.7.6
- Updated last modified summary

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-11 19:08:23 +05:30
amitwhandClaude e1e0072eea Remove table header background styling (v1.7.6)
Removed custom background colors from table headers to improve preview appearance and maintain consistency with normal theming.

## Changes
- Removed background-color from default theme table headers (was #f6f8fa)
- Removed dark theme table header background styling block
- Headers retain font-weight: 600 for visual distinction
- Table borders and alternating row colors remain intact

## User Request
User feedback: "remove the theming of table headers, it looks bad. Keep normal theming of preview"

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-11 18:54:54 +05:30
amitwhandClaude 1b2afb5f15 Fix double-click file opening in installed Windows app (v1.7.5)
This fix addresses the critical issue where double-clicking .md files would not open them in the installed PanConverter application on Windows.

## Root Cause
When a user double-clicks a file and the app is already running, Windows tries to launch a SECOND INSTANCE with the file path. Without single-instance lock handling, that second instance would exit and the first instance would never receive the file path.

## Changes
- Added single-instance lock handling in main.js (lines 52-85)
- Implemented second-instance event handler to capture file paths
- Focus existing window when second instance is attempted
- Pass file path to existing instance using openFileFromPath()
- Properly handle rendererReady state for file opening

## Testing
- Works in development mode (npm start)
- Should now work correctly in installed Windows app with double-click
- Maintains single instance behavior across all file opening methods

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-11 13:46:48 +05:30
amitwhandClaude c811af9d48 Remove debug console.log statements (v1.7.4 cleanup)
Cleaned up debug logging from file opening investigation:
- Removed console.log from command line arg processing
- Removed console.log from renderer-ready handler
- Removed console.log from openFileFromPath function
- Removed console.log from file-opened IPC handler

The double-click file opening fix is confirmed working.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-11 13:13:27 +05:30
amitwhandClaude a6e91b7403 Fix critical double-click file opening bug (v1.7.4)
CRITICAL BUG FIX: Files opened via double-click were not rendering in the application.

Root Cause:
- The openFileFromPath() function was sending IPC 'file-opened' message WITHOUT checking if rendererReady was true
- When files were double-clicked before renderer initialization, the IPC message was sent but lost
- The comment "Always try to send, the renderer will handle it when ready" was incorrect

The Fix (src/main.js:1586):
- Added rendererReady check to the conditional: if (mainWindow && mainWindow.webContents && rendererReady)
- Now properly stores file in app.pendingFile when renderer is not ready
- File is opened correctly when renderer-ready signal arrives

Flow:
1. File double-clicked → openFileFromPath() called
2. If renderer NOT ready → store in app.pendingFile
3. When renderer sends 'renderer-ready' → openFileFromPath(app.pendingFile) is called
4. This time renderer IS ready → file opens successfully

Version: 1.7.4
- Updated package.json version to 1.7.4
- Updated About dialog version to 1.7.4

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-11 12:57:52 +05:30
35 changed files with 3076 additions and 137 deletions
+11 -1
View File
@@ -26,7 +26,17 @@
"Bash(dir:*)",
"Bash(git merge:*)",
"Bash(timeout:*)",
"Bash(findstr:*)"
"Bash(findstr:*)",
"Bash(git stash:*)",
"Bash(npm start:*)",
"Bash(gh release delete-asset:*)",
"Bash(git rm:*)",
"Bash(gh release view:*)",
"Bash(npm run build:linux:*)",
"Bash(git reset:*)",
"Read(//i/Template_exporter/**)",
"Bash(copy \"I:\\Template_exporter\\md_to_docx_converter.py\" \"H:\\Development\\pan-converter\\word-template-exporter\"\" && copy \"I:Template_exporterrequirements.txt\" \"H:Developmentpan-converterword-template-exporter\"\")",
"Bash(copy \"I:\\Template_exporter\\word_template.docx\" \"H:\\Development\\pan-converter\\word-template-exporter\"\")"
],
"deny": []
}
+3
View File
@@ -0,0 +1,3 @@
{
"CurrentProjectSetting": null
}
+7
View File
@@ -0,0 +1,7 @@
{
"ExpandedNodes": [
""
],
"SelectedNode": "\\package.json",
"PreviewInSolutionExplorer": false
}
File diff suppressed because it is too large Load Diff
Binary file not shown.
@@ -0,0 +1,41 @@
{
"Version": 1,
"WorkspaceRootPath": "H:\\Development\\pan-converter\\",
"Documents": [
{
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|H:\\Development\\pan-converter\\package.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}",
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:package.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}"
}
],
"DocumentGroupContainers": [
{
"Orientation": 0,
"VerticalTabListWidth": 256,
"DocumentGroups": [
{
"DockedWidth": 200,
"SelectedChildIndex": 0,
"Children": [
{
"$type": "Document",
"DocumentIndex": 0,
"Title": "package.json",
"DocumentMoniker": "H:\\Development\\pan-converter\\package.json",
"RelativeDocumentMoniker": "package.json",
"ToolTip": "H:\\Development\\pan-converter\\package.json",
"RelativeToolTip": "package.json",
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAsAAAAqAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001642|",
"WhenOpened": "2025-10-11T13:44:30.357Z",
"EditorCaption": ""
},
{
"$type": "Bookmark",
"Name": "ST:0:0:{cce594b6-0c39-4442-ba28-10c64ac7e89f}"
}
]
}
]
}
]
}
+41
View File
@@ -0,0 +1,41 @@
{
"Version": 1,
"WorkspaceRootPath": "H:\\Development\\pan-converter\\",
"Documents": [
{
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|H:\\Development\\pan-converter\\package.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}",
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:package.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}"
}
],
"DocumentGroupContainers": [
{
"Orientation": 0,
"VerticalTabListWidth": 256,
"DocumentGroups": [
{
"DockedWidth": 200,
"SelectedChildIndex": 0,
"Children": [
{
"$type": "Document",
"DocumentIndex": 0,
"Title": "package.json",
"DocumentMoniker": "H:\\Development\\pan-converter\\package.json",
"RelativeDocumentMoniker": "package.json",
"ToolTip": "H:\\Development\\pan-converter\\package.json",
"RelativeToolTip": "package.json",
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAsAAAAqAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001642|",
"WhenOpened": "2025-10-11T13:44:30.357Z",
"EditorCaption": ""
},
{
"$type": "Bookmark",
"Name": "ST:0:0:{cce594b6-0c39-4442-ba28-10c64ac7e89f}"
}
]
}
]
}
]
}
BIN
View File
Binary file not shown.
+423 -4
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.
**Current Version**: v1.7.2
**Current Version**: v1.7.9
**Author**: Amit Haridas (amit.wh@gmail.com)
**License**: MIT
**Repository**: https://github.com/amitwh/pan-converter
@@ -19,6 +19,8 @@
- **highlight.js** - Syntax highlighting
- **KaTeX** - Mathematical expression rendering
- **DOMPurify** - HTML sanitization
- **PDFKit** - Native PDF generation without external dependencies (v1.7.7)
- **html2pdf** - HTML to PDF conversion library (v1.7.7)
### Application Structure
```
@@ -111,7 +113,415 @@ gh release create v1.2.1 --title "Title" --notes "Release notes" \
## Feature Implementation Guide
### v1.7.2 Enhanced Themes & Bug Fixes (Latest)
### v1.7.9 Enhanced Word Export with Template Support (Latest)
#### 📝 Template-Based Word Document Generation
**Added Enhanced DOCX Export** (`src/wordTemplateExporter.js`, `src/main.js:540-596`, `src/main.js:242`)
- **New Export Option**: "DOCX (Enhanced)" in File → Export menu with keyboard shortcut `Ctrl+Shift+W`
- **Template Support**: Use custom Word templates to preserve branding, styles, and formatting
- **Template Selection**: File → "Select Word Template..." menu option to choose custom templates
- **Persistent Template**: Selected template is saved and reused across sessions
- **Word XML Manipulation**: Direct manipulation of Word document XML using PizZip
- **Template Page Preservation**: Preserves first 2 pages (cover + TOC) when using templates
- **Smart Content Insertion**: Inserts markdown content after 2nd section break
**Full Markdown Support:**
- Headings (H1-H6) with markdown numbering stripped (e.g., "1.1 Title" → "Title")
- Text formatting (bold, italic, bold+italic, strikethrough)
- Inline code with Consolas font and gray background
- Code blocks with monospace font and light gray background
- Ordered and unordered lists (uses template numbering, strips markdown numbering)
- Blockquotes with indentation
- **Tables with orange header styling**:
- Orange header row (#F58220) with white bold text
- White data rows (no alternating colors)
- Orange borders (#F58220) on all cells
- Proper column alignment and spacing
- Horizontal rules
- Links
- **ASCII Art and Flowcharts**:
- Detects box-drawing characters (┌, ─, │, └, etc.)
- Detects flowchart patterns with square brackets
- Preserves monospace alignment with Consolas font
- Each line rendered separately to prevent wrapping
- No-wrap paragraph properties for exact spacing
- **Red-colored arrows** (↓, →, ←, ↑) for enhanced visibility
- Gray background (#F5F5F5) for distinction from regular text
**ASCII Art Detection Patterns:**
- Unicode box-drawing characters: ┌┐└┘├┤┬┴┼─│═║╔╗╚╝╠╣╦╩╬
- Arrow characters: ↓→←↑▼►◄▲
- ASCII box patterns: +-----+, |-----|, [Text in brackets]
- Flowchart steps: START, [Step Description], END
**ASCII Art Rendering Features:**
```javascript
// Each line gets its own paragraph with no-wrap
xml += `<w:p>
<w:pPr>
<w:wordWrap w:val="0"/> // Disable wrapping
<w:keepLines/> // Keep lines together
<w:line="240" w:lineRule="exact"/> // Exact line height
</w:pPr>
// Arrows colored red (#FF0000)
// Text in Consolas 16pt with gray background
</w:p>`;
```
**Template Selection Workflow:**
1. User clicks File → "Select Word Template..."
2. Dialog opens to browse for .docx files
3. Selected template path stored in global variable and persisted
4. Template automatically loaded on app startup
5. Enhanced export uses selected template (or defaults to `word_template.docx`)
**Technical Implementation:**
```javascript
// Template selection function
async function selectWordTemplate() {
const result = await dialog.showOpenDialog(mainWindow, {
title: 'Select Word Template',
filters: [{ name: 'Word Document', extensions: ['docx'] }],
properties: ['openFile']
});
if (!result.canceled && result.filePaths.length > 0) {
wordTemplatePath = result.filePaths[0];
store.set('wordTemplatePath', wordTemplatePath);
}
}
// Word Template Exporter with template support
class WordTemplateExporter {
constructor(templatePath) {
this.templatePath = templatePath || path.join(__dirname, '../word_template.docx');
}
async convert(markdownContent, outputPath) {
// Load template as ZIP
const templateBuffer = fs.readFileSync(this.templatePath);
const zip = new PizZip(templateBuffer);
// Extract and modify document.xml
const documentXml = zip.file('word/document.xml').asText();
const newContentXml = this.markdownToWordXml(markdownContent);
const modifiedXml = this.insertContentAfterPage2(documentXml, newContentXml);
// Save modified document
zip.file('word/document.xml', modifiedXml);
const newDocBuffer = zip.generate({ type: 'nodebuffer' });
fs.writeFileSync(outputPath, newDocBuffer);
}
}
```
**Menu Integration:**
- Export: File → Export → "DOCX (Enhanced)" (`Ctrl+Shift+W`)
- Template Selection: File → "Select Word Template..."
- Template path saved to: `userData/settings.json`
**Dependencies Added:**
```json
{
"docx": "^9.5.1",
"pizzip": "^3.2.0",
"docx4js": "^3.3.0"
}
```
**Key Improvements in v1.7.9:**
1. ✅ Template-based export preserving corporate branding
2. ✅ Persistent template selection across sessions
3. ✅ Tables with professional orange header styling and white data rows
4. ✅ ASCII art and flowcharts with perfect alignment and no wrapping
5. ✅ Red-colored arrows in flowcharts for enhanced readability
6. ✅ Markdown numbering stripped from headings and lists
7. ✅ Template's automatic numbering system used instead
### v1.7.8 Critical Bug Fixes
#### 🐛 File Association Fix for Packaged Apps
**Fixed Command-Line Argument Parsing** (`src/main.js:1598-1627`, `src/main.js:70-90`)
- **Root Cause**: `process.argv.slice(2)` works in development but fails in packaged apps
- **Solution**: Detect if app is packaged using `app.isPackaged` and use correct slice index
- Development: `process.argv.slice(2)` - ['electron', 'app.js', 'file.md']
- Packaged: `process.argv.slice(1)` - ['PanConverter.exe', 'file.md']
- **Path Resolution**: Added proper path resolution for both absolute and relative paths
- **Comprehensive Logging**: Added detailed console logs to debug file loading issues
- **Second Instance Handler**: Also fixed second-instance handler to use same logic
**Technical Implementation:**
```javascript
// Detect packaged vs development mode
const startIndex = app.isPackaged ? 1 : 2;
const fileArgs = process.argv.slice(startIndex);
// Resolve paths properly
for (const arg of fileArgs) {
if ((arg.endsWith('.md') || arg.endsWith('.markdown'))) {
const resolvedPath = path.isAbsolute(arg) ? arg : path.resolve(process.cwd(), arg);
if (fs.existsSync(resolvedPath)) {
app.pendingFile = resolvedPath;
break;
}
}
}
```
**Fixed Issues:**
- ✅ Files now open correctly on first double-click in packaged app
- ✅ Command-line arguments properly parsed in both dev and production
- ✅ Relative and absolute paths handled correctly
- ✅ Comprehensive logging for debugging file association issues
#### 🖨️ Print Preview Fix
**Fixed Print Output to Show Preview Content** (`src/renderer.js:1178-1204`, `src/styles.css:2828-3014`)
- **Root Cause**: Manual DOM manipulation conflicted with CSS `@media print` rules
- **Solution**: Simplified handler to rely on CSS `@media print` for hiding UI elements
- **CSS Enhancements**:
- Added selectors for dynamic preview panes `[id^="preview-pane-"]` and `[id^="editor-pane-"]`
- Added `.tab-content` to ensure full-width rendering
- Added body class-based styling for no-styles mode
- **Removed Manual Hiding**: Eliminated manual `display: none` manipulation in favor of CSS
**Technical Implementation:**
```javascript
// Simplified print handler - let CSS do the work
function handlePrintPreview(withStyles) {
const activeTabId = tabManager ? tabManager.activeTabId : 1;
const previewContent = document.getElementById(`preview-${activeTabId}`);
if (!previewContent || !previewContent.innerHTML.trim()) {
alert('Nothing to print...');
return;
}
// Add body classes for CSS styling
if (!withStyles) {
document.body.classList.add('printing-no-styles');
}
document.body.classList.add('printing');
setTimeout(() => {
ipcRenderer.send('do-print', { withStyles });
setTimeout(() => {
document.body.classList.remove('printing', 'printing-no-styles');
}, 1000);
}, 100);
}
```
**CSS Updates:**
```css
@media print {
/* Hide all UI elements */
#toolbar, #tab-bar, .editor-pane, [id^="editor-pane-"] {
display: none !important;
}
/* Show preview in full width */
[id^="preview-"], [id^="preview-pane-"], .tab-content {
display: block !important;
width: 100% !important;
padding: 20px !important;
}
}
```
**Fixed Issues:**
- ✅ Print now correctly renders preview content, not toolbar
- ✅ Both print modes work (with styles and without styles)
- ✅ Clean, professional print output with proper formatting
- ✅ Proper page breaks, typography, and layout optimization
#### 🔧 Development Cleanup
**Removed Debugging Code** (`src/main.js:120-123`)
- Removed auto-opening of DevTools
- Clean production-ready build
- Debugging logs remain for troubleshooting but DevTools disabled by default
### v1.7.7 Print & Enhanced PDF Support
#### 🖨️ Native Print Functionality
**Added Print Submenu to File Menu** (`src/main.js:202-215`, `src/renderer.js:1152-1188`, `src/styles.css:2828-2994`)
- **Print Menu**: Submenu in File menu with two print options
- **Print Preview** (`Ctrl+P`) - Prints preview in black text, no background colors (ink-saving)
- **Print Preview (With Styles)** - Prints with full theme colors and styling
- **Preview-Only Printing**: Only renders the markdown preview, hides all editor UI
- **Native Print Dialog**: Uses Electron's webContents.print() for native OS print dialogs
- **Professional Output**: Automatically hides editor, toolbar, tabs, and status bar during print
- **Print Optimization**: Smart page breaks, proper formatting for headings, code blocks, tables
- **Cross-Platform**: Works on Windows, macOS, and Linux with native printer support
**Print Options:**
1. **Print Preview** (Ctrl+P)
- Black text on white background for professional printing
- No background colors to save ink
- Perfect for documents and reports
- Minimal ink consumption
2. **Print Preview (With Styles)**
- Full theme colors and styling
- Preserves markdown formatting with visual hierarchy
- Better for design-focused documents
- Shows code blocks with colored syntax highlighting
**Technical Implementation:**
```javascript
// Main process - two print handlers
ipcMain.on('print-preview', (event) => {
mainWindow.webContents.send('prepare-print-preview', false);
setTimeout(() => {
mainWindow.webContents.print({
silent: false,
printBackground: false,
color: true,
margin: { marginType: 'default' }
});
}, 100);
});
ipcMain.on('print-preview-styled', (event) => {
mainWindow.webContents.send('prepare-print-preview', true);
setTimeout(() => {
mainWindow.webContents.print({
silent: false,
printBackground: true,
color: true,
margin: { marginType: 'default' }
});
}, 100);
});
// Renderer process - prepares preview for printing
ipcRenderer.on('prepare-print-preview', (event, withStyles) => {
// Hide editor and UI elements
document.getElementById('editor-container').style.display = 'none';
document.getElementById('toolbar').style.display = 'none';
document.getElementById('tab-bar').style.display = 'none';
document.getElementById('status-bar').style.display = 'none';
// Add print mode classes
const preview = document.getElementById('preview');
preview.classList.add('print-mode');
if (!withStyles) {
preview.classList.add('print-no-styles');
}
// Restore UI after print
setTimeout(() => {
document.getElementById('editor-container').style.display = '';
document.getElementById('toolbar').style.display = '';
document.getElementById('tab-bar').style.display = '';
document.getElementById('status-bar').style.display = '';
preview.classList.remove('print-mode', 'print-no-styles');
}, 500);
});
```
**CSS Print Styles** (`src/styles.css:2828-2994`)
- Comprehensive @media print stylesheet
- Hides all UI elements in print view
- Optimizes preview for paper output
- Smart page breaks for headings and tables
- Proper formatting for code blocks, lists, tables
- Image optimization and link handling
- Professional typography with 12pt font size
#### 📦 Enhanced PDF Export Dependencies
**Added Native PDF Generation Libraries** (package.json)
- **PDFKit** (v0.14.0) - Native PDF generation without Pandoc dependency
- **html2pdf.js** (v0.10.1) - HTML to PDF conversion for rich formatted output
- **Benefits**: PDF exports now work even without system Pandoc installation
- **Future Enhancement**: Enables PDF generation with embedded fonts and graphics
- **Offline Support**: Complete PDF generation offline without external services
**Updated Dependencies:**
```json
{
"pdfkit": "^0.14.0",
"html2pdf.js": "^0.10.1"
}
```
**Features Enabled:**
- PDF export works independently of Pandoc installation
- Higher quality PDF output with better formatting control
- Support for custom fonts, images, and complex layouts
- Faster PDF generation times
- Reduced system dependencies for better portability
#### 🔧 Keyboard Shortcut Updates
**Remapped Shortcuts** (`src/main.js`)
- **Print**: `Ctrl+P` (new in v1.7.7) - Opens native print dialog
- **Toggle Preview**: Changed from `Ctrl+P` to `Ctrl+Shift+P` to avoid conflicts
- **Backward Compatibility**: No breaking changes to existing workflows
### v1.7.6 UI Improvement
#### 🎨 Table Header Styling Cleanup
**Removed Custom Background Colors** (`src/styles.css:327-329`, `src/styles.css:451-453`)
- Removed gray background color (#f6f8fa) from table headers in default theme
- Removed dark theme table header background styling block entirely
- Table headers now blend naturally with preview background for cleaner appearance
- Headers maintain font-weight: 600 for visual distinction
- All other table styling preserved (borders, alternating row colors)
**User Feedback Implementation:**
- Based on user feedback: "remove the theming of table headers, it looks bad"
- Result: Professional, clean table presentation matching overall preview styling
- Consistent with normal preview theming across all themes
### v1.7.5 Critical File Association Fix
#### 🐛 Single-Instance Lock for Windows File Association
**Fixed Installed App Double-Click Behavior** (`src/main.js:52-85`)
- **Root Cause**: Windows launches second instance when double-clicking files with app already running
- **Solution**: Implemented `app.requestSingleInstanceLock()` pattern
- **Second Instance Handler**: Captures file path from new instance attempts and opens in existing instance
- **Focus Management**: Automatically focuses and restores existing window
- **Renderer Readiness**: Proper handling of file queue with `rendererReady` state
**Technical Implementation:**
```javascript
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.quit();
} else {
app.on('second-instance', (event, commandLine, workingDirectory) => {
// Focus existing window
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.focus();
}
// Extract and open file from second instance
const fileArgs = commandLine.slice(2);
for (const arg of fileArgs) {
if ((arg.endsWith('.md') || arg.endsWith('.markdown')) && fs.existsSync(arg)) {
if (rendererReady) {
openFileFromPath(arg);
} else {
app.pendingFile = arg;
}
break;
}
}
});
}
```
**Fixed Issues:**
- ✅ Double-click `.md` files in Windows Explorer now opens correctly
- ✅ Right-click "Open with PanConverter" now works in installed app
- ✅ File association behavior now consistent between development and production
- ✅ Single instance maintained across all file opening methods
**Why Previous Attempts Failed:**
- v1.7.3-1.7.4: Only added `rendererReady` checks but missed second-instance handling
- Development testing passed but production failed (different execution paths)
- Second instance would start, receive file, then exit without passing to first instance
### v1.7.2 Enhanced Themes & Bug Fixes
#### 🎨 14 New Beautiful Themes
**Expanded Theme Collection** (`src/main.js:242-266`, `src/styles.css:1590-2555`)
@@ -599,5 +1009,14 @@ gh release create v1.2.1 --title "Title" --notes "Release notes" \
---
**Last Updated**: October 11, 2025
**Claude Assistant**: Development completed for v1.7.2 with 14 new professionally-designed themes (Dracula, Nord, Tokyo Night, Gruvbox, Ayu, Material, Oceanic Next, Palenight, Cobalt2, and more) bringing total to 19 themes, plus fixed undo/redo menu integration for proper keyboard shortcut support.
**Last Updated**: October 27, 2025
**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).
+9 -92
View File
@@ -1,95 +1,12 @@
# Push Instructions for Pan Converter
# Test Double-Click File Opening
## Prerequisites
1. Create a new repository on GitHub: https://github.com/new
- Repository name: `pan-converter`
- Keep it empty (no README, .gitignore, or license)
- Make it public or private as desired
This is a test file to verify double-click functionality.
2. Set up authentication:
- **Option A - SSH Key** (Recommended):
```bash
# Check if you have SSH key
ls -la ~/.ssh/id_rsa.pub
## Features to Test
- File should load automatically
- Content should display in editor
- Tab should show filename
- Preview should render markdown
# If not, generate one:
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
# Add to GitHub: Settings > SSH and GPG keys > New SSH key
cat ~/.ssh/id_rsa.pub
```
- **Option B - Personal Access Token**:
- Go to GitHub Settings > Developer settings > Personal access tokens
- Generate new token with 'repo' scope
- Save the token securely
## Push Commands
### Using SSH (Recommended)
```bash
# Set SSH remote
git remote set-url origin git@github.com:amitwh/pan-converter.git
# Push all branches
git push -u origin master
git push origin linux
git push origin macos
git push origin windows
```
### Using HTTPS with Token
```bash
# Set HTTPS remote
git remote set-url origin https://github.com/amitwh/pan-converter.git
# Push all branches (you'll be prompted for username and token)
git push -u origin master
git push origin linux
git push origin macos
git push origin windows
# When prompted:
# Username: amitwh
# Password: [paste your Personal Access Token]
```
### Using GitHub CLI (if installed)
```bash
# Login to GitHub CLI
gh auth login
# Create repo and push
gh repo create pan-converter --public --source=. --remote=origin --push
```
## Automated Script
```bash
# Run the provided script
./push-to-github.sh
```
## Verify Success
After pushing, verify at: https://github.com/amitwh/pan-converter
You should see:
- 4 branches (master, linux, macos, windows)
- All project files
- Complete commit history
## Troubleshooting
### Authentication Failed
- For SSH: Ensure your SSH key is added to GitHub
- For HTTPS: Use Personal Access Token, not password
- Check token has 'repo' scope
### Repository Not Found
- Ensure repository exists on GitHub
- Check spelling: `pan-converter`
- Verify you're logged into the correct GitHub account
### Permission Denied
- Check repository ownership
- Ensure you have write access
- Verify authentication method is correct
**This text should be bold**
*This text should be italic*
+2
View File
@@ -138,6 +138,8 @@ npm run dist:all
## Version History
- **v1.8.0** - Added enhanced Word export with template support to batch converter
- **v1.7.9** - Enhanced Word Export with Template Support: Template-based DOCX generation with custom templates, table styling, ASCII art support, and red-colored flowchart arrows
- **v1.5.6** - (Your release notes here)
- **v1.5.5** - Refactored PDF export, simplified Pandoc pathing, and removed XLSX dependency.
- **v1.3.1** - Bug fixes: Fixed file associations for double-clicking .md files, corrected 50/50 layout alignment for editor/preview panes
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

View File
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

+7 -1
View File
@@ -1,6 +1,6 @@
{
"name": "pan-converter",
"version": "1.7.3",
"version": "1.8.3",
"description": "Cross-platform Markdown editor and converter using Pandoc",
"main": "src/main.js",
"scripts": {
@@ -31,11 +31,17 @@
},
"dependencies": {
"codemirror": "^6.0.2",
"core-util-is": "^1.0.3",
"docx": "^9.5.1",
"docx4js": "^3.3.0",
"dompurify": "^3.2.6",
"electron-store": "^10.1.0",
"highlight.js": "^11.11.1",
"html2pdf.js": "^0.10.1",
"marked": "^16.2.1",
"pdf-lib": "^1.17.1",
"pdfkit": "^0.14.0",
"pizzip": "^3.2.0",
"tslib": "^2.8.1",
"xlsx": "^0.18.5"
},
Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
View File
Binary file not shown.
+4 -2
View File
@@ -515,7 +515,9 @@
<select id="batch-format">
<option value="html">HTML</option>
<option value="pdf">PDF</option>
<option value="pdf-enhanced">PDF (Enhanced)</option>
<option value="docx">DOCX</option>
<option value="docx-enhanced">DOCX (Enhanced)</option>
<option value="latex">LaTeX</option>
<option value="rtf">RTF</option>
<option value="odt">ODT</option>
@@ -555,12 +557,12 @@
<!-- PDF Editor Dialog -->
<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">
<h3 id="pdf-editor-title">PDF Editor</h3>
<button id="pdf-editor-dialog-close" title="Close">×</button>
</div>
<div class="export-dialog-body">
<div class="export-dialog-body pdf-editor-body">
<!-- Merge PDFs Section -->
<div id="pdf-merge-section" class="pdf-operation-section hidden">
<div class="export-section">
+446 -26
View File
@@ -3,6 +3,7 @@ const path = require('path');
const fs = require('fs');
const { exec } = require('child_process');
const { PDFDocument, rgb, degrees, StandardFonts } = require('pdf-lib');
const WordTemplateExporter = require('./wordTemplateExporter');
// Get the system Pandoc path
function getPandocPath() {
@@ -47,8 +48,52 @@ const store = {
let mainWindow;
let currentFile = null; // This will now represent the active tab's file
let pandocAvailable = null; // Cache pandoc availability check
let wordTemplatePath = null; // Path to selected Word template
let templateStartPage = 3; // Which page to start inserting content (default: page 3)
let rendererReady = false; // Track if renderer is ready to receive file data
// Handle single instance lock for Windows file association
// When a file is double-clicked and the app is already running,
// Windows tries to start a second instance. We prevent this and
// pass the file to the existing instance instead.
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
// Another instance is already running, quit this one
app.quit();
} else {
// This is the first instance, handle second-instance events
app.on('second-instance', (event, commandLine, workingDirectory) => {
// Someone tried to run a second instance, focus our window instead
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.focus();
}
// Check if a file was passed to the second instance
// commandLine is an array like: ['PanConverter.exe', 'file.md']
console.log('[MAIN] Second instance commandLine:', JSON.stringify(commandLine));
const startIndex = app.isPackaged ? 1 : 2;
const fileArgs = commandLine.slice(startIndex);
console.log('[MAIN] Second instance file args:', fileArgs);
for (const arg of fileArgs) {
if ((arg.endsWith('.md') || arg.endsWith('.markdown'))) {
const resolvedPath = path.isAbsolute(arg) ? arg : path.resolve(workingDirectory, arg);
console.log('[MAIN] Second instance resolved path:', resolvedPath);
if (fs.existsSync(resolvedPath)) {
// Open the file in the existing instance
if (rendererReady) {
openFileFromPath(resolvedPath);
} else {
app.pendingFile = resolvedPath;
}
break;
}
}
}
});
}
// Check if pandoc is available
function checkPandocAvailability() {
return new Promise((resolve) => {
@@ -164,6 +209,21 @@ function createMenu() {
click: saveAsFile
},
{ type: 'separator' },
{
label: 'Print',
submenu: [
{
label: 'Print Preview',
accelerator: 'CmdOrCtrl+P',
click: () => mainWindow.webContents.send('print-preview')
},
{
label: 'Print Preview (With Styles)',
click: () => mainWindow.webContents.send('print-preview-styled')
}
]
},
{ type: 'separator' },
{
label: 'Recent Files',
submenu: buildRecentFilesMenu()
@@ -179,7 +239,9 @@ function createMenu() {
submenu: [
{ label: 'HTML', click: () => exportFile('html') },
{ label: 'PDF', click: () => exportFile('pdf') },
{ label: 'PDF (Enhanced)', click: () => exportPDFViaWordTemplate(), accelerator: 'Ctrl+Shift+P' },
{ label: 'DOCX', click: () => exportFile('docx') },
{ label: 'DOCX (Enhanced)', click: () => exportWordWithTemplate(), accelerator: 'Ctrl+Shift+W' },
{ label: 'LaTeX', click: () => exportFile('latex') },
{ label: 'RTF', click: () => exportFile('rtf') },
{ label: 'ODT', click: () => exportFile('odt') },
@@ -192,6 +254,15 @@ function createMenu() {
]
},
{ type: 'separator' },
{
label: 'Select Word Template...',
click: selectWordTemplate
},
{
label: 'Template Settings...',
click: showTemplateSettings
},
{ type: 'separator' },
{
label: 'Quit',
accelerator: process.platform === 'darwin' ? 'Cmd+Q' : 'Ctrl+Q',
@@ -230,7 +301,7 @@ function createMenu() {
submenu: [
{
label: 'Toggle Preview',
accelerator: 'CmdOrCtrl+P',
accelerator: 'CmdOrCtrl+Shift+P',
click: () => mainWindow.webContents.send('toggle-preview')
},
{
@@ -410,7 +481,7 @@ function createMenu() {
type: 'info',
title: 'About PanConverter',
message: 'PanConverter',
detail: 'A cross-platform Markdown editor and converter using Pandoc.\n\nVersion: 1.7.3\nAuthor: Amit Haridas\nEmail: amit.wh@gmail.com\nLicense: MIT\n\nFeatures:\n• Modern glassmorphism UI with gradient backgrounds\n• Comprehensive PDF Editor (merge, split, compress, rotate, watermark, encrypt)\n• Universal File Converter (LibreOffice, ImageMagick, FFmpeg, Pandoc)\n• Windows Explorer context menu integration\n• Tabbed interface for multiple files\n• Advanced markdown editing with live preview\n• Real-time preview updates while typing\n• Full toolbar markdown editing functions\n• Enhanced PDF export with built-in Electron fallback\n• File association support for .md files\n• Command-line interface for batch conversion\n• Advanced export options with templates and metadata\n• Batch file conversion with progress tracking\n• Improved preview typography and spacing\n• Adjustable font sizes via menu (Ctrl+Shift+Plus/Minus)\n• Complete theme support including Monokai fixes\n• Find & replace with match highlighting\n• Line numbers and auto-indentation\n• Export to multiple formats via Pandoc\n• PowerPoint & presentation export\n• Export tables to Excel/ODS spreadsheets\n• Document import & conversion\n• Table creation helper\n• 22 beautiful themes (including Dracula, Nord, Tokyo Night, Gruvbox, Ayu, Concrete, and more)\n• Undo/redo functionality\n• Live word count and statistics',
detail: 'A cross-platform Markdown editor and converter using Pandoc.\n\nVersion: 1.8.3\nAuthor: Amit Haridas\nEmail: amit.wh@gmail.com\nLicense: MIT\n\nFeatures:\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',
buttons: ['OK']
});
}
@@ -476,6 +547,188 @@ function showBatchConversionDialog() {
mainWindow.webContents.send('show-batch-dialog');
}
// Select Word Template
async function selectWordTemplate() {
const result = await dialog.showOpenDialog(mainWindow, {
title: 'Select Word Template',
filters: [{ name: 'Word Document', extensions: ['docx'] }],
properties: ['openFile']
});
if (!result.canceled && result.filePaths.length > 0) {
wordTemplatePath = result.filePaths[0];
store.set('wordTemplatePath', wordTemplatePath);
dialog.showMessageBox(mainWindow, {
type: 'info',
title: 'Template Selected',
message: 'Word template has been updated',
detail: `Template: ${path.basename(wordTemplatePath)}`
});
}
}
// Template Settings Dialog
async function showTemplateSettings() {
const result = await dialog.showMessageBox(mainWindow, {
type: 'question',
title: 'Template Settings',
message: 'Configure Word Template Export',
detail: `Current template: ${wordTemplatePath ? path.basename(wordTemplatePath) : 'Default template'}\nContent starts from page: ${templateStartPage}\n\nWhich page should content start from?\n(Templates usually have cover pages, TOC, etc.)`,
buttons: ['Page 1', 'Page 2', 'Page 3', 'Page 4', 'Page 5', 'Custom...', 'Cancel'],
defaultId: templateStartPage - 1,
cancelId: 6
});
if (result.response === 6) return; // Cancel
let newStartPage;
if (result.response === 5) { // Custom
// Show input dialog for custom page number
mainWindow.webContents.send('show-custom-start-page-dialog', templateStartPage);
} else {
newStartPage = result.response + 1; // Convert button index to page number
templateStartPage = newStartPage;
store.set('templateStartPage', templateStartPage);
dialog.showMessageBox(mainWindow, {
type: 'info',
title: 'Settings Updated',
message: 'Template settings have been updated',
detail: `Content will now start from page ${templateStartPage}`
});
}
}
// Handle custom start page input from renderer
ipcMain.on('set-custom-start-page', (event, pageNumber) => {
const page = parseInt(pageNumber);
if (page >= 1 && page <= 100) {
templateStartPage = page;
store.set('templateStartPage', templateStartPage);
dialog.showMessageBox(mainWindow, {
type: 'info',
title: 'Settings Updated',
message: 'Template settings have been updated',
detail: `Content will now start from page ${templateStartPage}`
});
} else {
dialog.showErrorBox('Invalid Page Number', 'Please enter a page number between 1 and 100');
}
});
// Enhanced Word Export with Template Support
async function exportWordWithTemplate() {
if (!currentFile) {
dialog.showErrorBox('Error', 'Please save the file first');
return;
}
try {
// Get markdown content
const content = fs.readFileSync(currentFile, 'utf-8');
// Show dialog for output file
const result = await dialog.showSaveDialog(mainWindow, {
title: 'Export to Word (Enhanced)',
defaultPath: currentFile.replace(/\.md$/, '.docx'),
filters: [{ name: 'Word Document', extensions: ['docx'] }]
});
if (result.canceled) return;
// Create exporter instance with selected template and start page
const exporter = new WordTemplateExporter(wordTemplatePath, templateStartPage);
// Convert markdown to DOCX
await exporter.convert(content, result.filePath);
dialog.showMessageBox(mainWindow, {
type: 'info',
title: 'Export Successful',
message: 'Document exported successfully!',
detail: `Saved to: ${result.filePath}`
});
} catch (error) {
dialog.showErrorBox('Export Error', `Failed to export document: ${error.message}`);
}
}
// Enhanced PDF Export via Word Template
async function exportPDFViaWordTemplate() {
if (!currentFile) {
dialog.showErrorBox('Error', 'Please save the file first');
return;
}
try {
// Get markdown content
const content = fs.readFileSync(currentFile, 'utf-8');
// Show dialog for output file
const result = await dialog.showSaveDialog(mainWindow, {
title: 'Export to PDF (Enhanced)',
defaultPath: currentFile.replace(/\.md$/, '.pdf'),
filters: [{ name: 'PDF Document', extensions: ['pdf'] }]
});
if (result.canceled) return;
// Step 1: Create temporary DOCX file using Word template
const tempDocxPath = result.filePath.replace(/\.pdf$/, '_temp.docx');
const exporter = new WordTemplateExporter(wordTemplatePath, templateStartPage);
await exporter.convert(content, tempDocxPath);
// Step 2: Convert DOCX to PDF using LibreOffice
const soffice = process.platform === 'win32'
? '"C:\\Program Files\\LibreOffice\\program\\soffice.exe"'
: 'soffice';
const outputDir = path.dirname(result.filePath);
const convertCmd = `${soffice} --headless --convert-to pdf --outdir "${outputDir}" "${tempDocxPath}"`;
exec(convertCmd, (error, stdout, stderr) => {
// Clean up temporary DOCX file
try {
fs.unlinkSync(tempDocxPath);
} catch (e) {
console.error('Failed to delete temp file:', e);
}
if (error) {
dialog.showErrorBox('PDF Conversion Error',
`Failed to convert to PDF. Please ensure LibreOffice is installed.\n\nError: ${error.message}`);
return;
}
// LibreOffice creates file with same base name as input
const generatedPdfPath = tempDocxPath.replace(/\.docx$/, '.pdf');
// Rename if needed
if (generatedPdfPath !== result.filePath) {
try {
fs.renameSync(generatedPdfPath, result.filePath);
} catch (e) {
console.error('Failed to rename PDF:', e);
}
}
dialog.showMessageBox(mainWindow, {
type: 'info',
title: 'Export Successful',
message: 'PDF exported successfully using Word template!',
detail: `Saved to: ${result.filePath}`
});
});
} catch (error) {
dialog.showErrorBox('Export Error', `Failed to export PDF: ${error.message}`);
}
}
// Universal File Converter integration
function showUniversalConverterDialog() {
mainWindow.webContents.send('show-universal-converter-dialog');
@@ -734,20 +987,17 @@ function performExportWithOptions(format, options) {
function tryPdfFallback(inputFile, outputFile, engines, index, options, lastError) {
if (index >= engines.length) {
dialog.showErrorBox('PDF Export Error',
`Failed to export PDF with all available engines. Please ensure you have one of the following installed:\n` +
`• XeLaTeX (recommended): sudo apt-get install texlive-xetex\n` +
`• PDFLaTeX: sudo apt-get install texlive-latex-base\n` +
`• LuaLaTeX: sudo apt-get install texlive-luatex\n\n` +
`Last error: ${lastError.message}`
);
// All Pandoc PDF engines failed, fallback to Electron's built-in PDF export
console.log('All Pandoc PDF engines failed, falling back to Electron PDF export');
exportToPDFElectron(outputFile);
return;
}
const engine = engines[index];
let pandocCmd = `${getPandocPath()} "${inputFile}" --pdf-engine=${engine} -V geometry:${geometry} -o "${outputFile}"`;
let pandocCmd = `${getPandocPath()} "${inputFile}" --pdf-engine=${engine} -o "${outputFile}"`;
if (options.geometry) pandocCmd += ` -V geometry:"${options.geometry}"`;
// Add geometry if specified
if (options.geometry) pandocCmd = pandocCmd.replace(` -o `, ` -V geometry:"${options.geometry}" -o `);
// Add all other options
if (options.template && options.template !== 'default') {
@@ -1107,7 +1357,7 @@ ipcMain.on('save-current-file', (event, content) => {
});
ipcMain.on('get-theme', (event) => {
const theme = store.get('theme', 'light');
const theme = store.get('theme', 'github');
event.reply('theme-changed', theme);
});
@@ -1116,10 +1366,36 @@ ipcMain.on('set-current-file', (event, filePath) => {
currentFile = filePath;
});
// Handle actual printing when renderer is ready
ipcMain.on('do-print', (event, { withStyles }) => {
if (mainWindow) {
// Renderer has already hidden UI, waited 300ms, and prepared the page
// Print immediately - DOM is fully rendered
mainWindow.webContents.print({
silent: false,
printBackground: withStyles,
color: true,
margin: { marginType: 'default' }
});
}
});
// Handle renderer ready for file association
ipcMain.on('renderer-ready', (event) => {
console.log('[MAIN] renderer-ready received, rendererReady was:', rendererReady);
if (mainWindow) {
mainWindow.webContents.executeJavaScript(`console.log('[MAIN->RENDERER] renderer-ready received, rendererReady was: ${rendererReady}')`);
}
rendererReady = true;
console.log('[MAIN] app.pendingFile:', app.pendingFile);
if (mainWindow) {
mainWindow.webContents.executeJavaScript(`console.log('[MAIN->RENDERER] app.pendingFile: ${app.pendingFile}')`);
}
if (app.pendingFile) {
console.log('[MAIN] Opening pending file:', app.pendingFile);
if (mainWindow) {
mainWindow.webContents.executeJavaScript(`console.log('[MAIN->RENDERER] Opening pending file: ${app.pendingFile}')`);
}
openFileFromPath(app.pendingFile);
app.pendingFile = null;
}
@@ -1285,7 +1561,7 @@ function performBatchConversion(inputFolder, outputFolder, format, options) {
const totalCount = markdownFiles.length;
// Process each file
const processNextFile = (index) => {
const processNextFile = async (index) => {
if (index >= markdownFiles.length) {
// All files processed
dialog.showMessageBox(mainWindow, {
@@ -1300,7 +1576,10 @@ function performBatchConversion(inputFolder, outputFolder, format, options) {
const inputFile = markdownFiles[index];
const relativePath = path.relative(inputFolder, inputFile);
const baseName = path.basename(relativePath, path.extname(relativePath));
const outputFile = path.join(outputFolder, relativePath.replace(/\.(md|markdown)$/i, `.${format}`));
let outputExtension = format;
if (format === 'docx-enhanced') outputExtension = 'docx';
if (format === 'pdf-enhanced') outputExtension = 'pdf';
const outputFile = path.join(outputFolder, relativePath.replace(/\.(md|markdown)$/i, `.${outputExtension}`));
// Create subdirectories in output folder if needed
const outputDir = path.dirname(outputFile);
@@ -1308,7 +1587,120 @@ function performBatchConversion(inputFolder, outputFolder, format, options) {
fs.mkdirSync(outputDir, { recursive: true });
}
// Build pandoc command
// Handle DOCX Enhanced format with WordTemplateExporter
if (format === 'docx-enhanced') {
try {
const content = fs.readFileSync(inputFile, 'utf-8');
const exporter = new WordTemplateExporter(wordTemplatePath, templateStartPage);
await exporter.convert(content, outputFile);
completedCount++;
// Update progress
mainWindow.webContents.send('batch-progress', {
completed: index + 1,
total: totalCount,
currentFile: path.basename(inputFile),
success: true
});
// Process next file
processNextFile(index + 1);
} catch (error) {
// Update progress with error
mainWindow.webContents.send('batch-progress', {
completed: index + 1,
total: totalCount,
currentFile: path.basename(inputFile),
success: false
});
// Process next file even if this one failed
processNextFile(index + 1);
}
return;
}
// Handle PDF Enhanced format with Word Template → PDF conversion
if (format === 'pdf-enhanced') {
try {
const content = fs.readFileSync(inputFile, 'utf-8');
// Step 1: Create temporary DOCX file using Word template
const tempDocxPath = outputFile.replace(/\.pdf$/, '_temp.docx');
const exporter = new WordTemplateExporter(wordTemplatePath, templateStartPage);
await exporter.convert(content, tempDocxPath);
// Step 2: Convert DOCX to PDF using LibreOffice
const soffice = process.platform === 'win32'
? '"C:\\Program Files\\LibreOffice\\program\\soffice.exe"'
: 'soffice';
const outputDir = path.dirname(outputFile);
const convertCmd = `${soffice} --headless --convert-to pdf --outdir "${outputDir}" "${tempDocxPath}"`;
exec(convertCmd, (error, stdout, stderr) => {
// Clean up temporary DOCX file
try {
fs.unlinkSync(tempDocxPath);
} catch (e) {
console.error('Failed to delete temp file:', e);
}
if (error) {
// Update progress with error
mainWindow.webContents.send('batch-progress', {
completed: index + 1,
total: totalCount,
currentFile: path.basename(inputFile),
success: false
});
processNextFile(index + 1);
return;
}
// LibreOffice creates file with same base name as input
const generatedPdfPath = tempDocxPath.replace(/\.docx$/, '.pdf');
// Rename if needed
if (generatedPdfPath !== outputFile) {
try {
fs.renameSync(generatedPdfPath, outputFile);
} catch (e) {
console.error('Failed to rename PDF:', e);
}
}
completedCount++;
// Update progress
mainWindow.webContents.send('batch-progress', {
completed: index + 1,
total: totalCount,
currentFile: path.basename(inputFile),
success: true
});
// Process next file
processNextFile(index + 1);
});
} catch (error) {
// Update progress with error
mainWindow.webContents.send('batch-progress', {
completed: index + 1,
total: totalCount,
currentFile: path.basename(inputFile),
success: false
});
// Process next file even if this one failed
processNextFile(index + 1);
}
return;
}
// Build pandoc command for other formats
let pandocCmd = `${getPandocPath()} "${inputFile}" -o "${outputFile}"`;
// Add template if specified
@@ -1510,6 +1902,10 @@ function buildPandocCommand(content, format, outputPath) {
}
app.whenReady().then(() => {
// Load saved Word template path and settings
wordTemplatePath = store.get('wordTemplatePath', null);
templateStartPage = store.get('templateStartPage', 3);
// Check for command line conversion requests
const args = process.argv.slice(2);
if (args.length >= 2 && (args[0] === '--convert' || args[0] === '--convert-to')) {
@@ -1520,15 +1916,35 @@ app.whenReady().then(() => {
createWindow();
// Handle file association on app startup
// Process all command line arguments except the first two (node and script path)
const fileArgs = process.argv.slice(2);
// In packaged apps, process.argv structure is different:
// Development: ['electron', 'app.js', 'file.md'] - need slice(2)
// Packaged: ['PanConverter.exe', 'file.md'] - need slice(1)
// We'll check all arguments after the executable
console.log('[MAIN] Full command line args:', JSON.stringify(process.argv));
console.log('[MAIN] App is packaged:', app.isPackaged);
// Start from index 1 (skip executable) and check each argument
const startIndex = app.isPackaged ? 1 : 2;
const fileArgs = process.argv.slice(startIndex);
console.log('[MAIN] File args to process (starting from index', startIndex + '):', fileArgs);
for (const arg of fileArgs) {
if ((arg.endsWith('.md') || arg.endsWith('.markdown')) && fs.existsSync(arg)) {
// Store the file to open after window is ready
app.pendingFile = arg;
break;
console.log('[MAIN] Checking arg:', arg);
if ((arg.endsWith('.md') || arg.endsWith('.markdown'))) {
// Try to resolve the path (might be relative)
const resolvedPath = path.isAbsolute(arg) ? arg : path.resolve(process.cwd(), arg);
console.log('[MAIN] Resolved path:', resolvedPath);
if (fs.existsSync(resolvedPath)) {
// Store the file to open after window is ready
console.log('[MAIN] Setting pendingFile to:', resolvedPath);
app.pendingFile = resolvedPath;
break;
} else {
console.log('[MAIN] File does not exist:', resolvedPath);
}
}
}
console.log('[MAIN] Final app.pendingFile:', app.pendingFile);
});
app.on('window-all-closed', () => {
@@ -1580,19 +1996,23 @@ app.on('open-file', (event, filePath) => {
// Handle file opening from command line or file association
function openFileFromPath(filePath) {
console.log('[MAIN] openFileFromPath called with:', filePath);
console.log('[MAIN] rendererReady:', rendererReady, 'mainWindow exists:', !!mainWindow);
if (fs.existsSync(filePath)) {
currentFile = filePath;
const content = fs.readFileSync(filePath, 'utf-8');
if (mainWindow && mainWindow.webContents) {
console.log('Attempting to open file:', filePath, 'rendererReady:', rendererReady);
// Always try to send, the renderer will handle it when ready
console.log('[MAIN] File read successfully, content length:', content.length);
if (mainWindow && mainWindow.webContents && rendererReady) {
// Send file immediately - renderer-ready means UI is initialized
console.log('[MAIN] Sending file-opened to renderer');
mainWindow.webContents.send('file-opened', { path: filePath, content });
} else {
// Store file to open after window is created
// Store file to open after renderer is ready
console.log('[MAIN] Storing as pending file');
app.pendingFile = filePath;
}
} else {
console.error('File does not exist:', filePath);
console.error('[MAIN] File does not exist:', filePath);
}
}
+63 -6
View File
@@ -964,10 +964,13 @@ class TabManager {
tab.originalContent = content;
tab.isDirty = false;
// Update the editor immediately
// Update the editor and preview
const editor = document.getElementById(`editor-${this.activeTabId}`);
if (editor) {
editor.value = content;
// Update preview after editor is updated
this.updatePreview(this.activeTabId);
this.updateWordCount();
}
} else {
// Create new tab for the file
@@ -990,9 +993,6 @@ class TabManager {
}
}, 50);
}
this.updatePreview(this.activeTabId);
this.updateWordCount();
this.startAutoSave();
this.addToRecentFiles(filePath);
this.updateTabBar();
@@ -1042,8 +1042,12 @@ document.addEventListener('DOMContentLoaded', () => {
// Request current theme
ipcRenderer.send('get-theme');
// Signal that renderer is ready for file operations
ipcRenderer.send('renderer-ready');
// Also send renderer-ready immediately as backup
// This ensures we don't get stuck waiting for theme-changed
setTimeout(() => {
console.log('Backup renderer-ready timeout triggered');
ipcRenderer.send('renderer-ready');
}, 100);
// Set up auto-save interval
setInterval(() => {
@@ -1062,8 +1066,11 @@ ipcRenderer.on('file-new', () => {
});
ipcRenderer.on('file-opened', (event, data) => {
console.log('[RENDERER] file-opened received:', data.path, 'content length:', data.content.length);
if (tabManager) {
tabManager.openFile(data.path, data.content);
} else {
console.error('[RENDERER] tabManager not initialized!');
}
});
@@ -1101,7 +1108,15 @@ ipcRenderer.on('toggle-find', () => {
});
ipcRenderer.on('theme-changed', (event, theme) => {
console.log('[RENDERER] Theme changed to:', theme);
document.body.className = `theme-${theme}`;
// After theme is applied, wait for next frame then signal renderer is ready
// This ensures complete UI initialization before files are opened
requestAnimationFrame(() => {
console.log('[RENDERER] Sending renderer-ready from theme-changed');
ipcRenderer.send('renderer-ready');
});
});
// Undo/Redo handlers
@@ -1149,6 +1164,48 @@ ipcRenderer.on('adjust-font-size', (event, action) => {
updateFontSizes(currentFontSize);
});
// Print preview request handlers - handle printing directly
ipcRenderer.on('print-preview', () => {
console.log('[RENDERER] print-preview received');
handlePrintPreview(false);
});
ipcRenderer.on('print-preview-styled', () => {
console.log('[RENDERER] print-preview-styled received');
handlePrintPreview(true);
});
function handlePrintPreview(withStyles) {
const activeTabId = tabManager ? tabManager.activeTabId : 1;
const previewContent = document.getElementById(`preview-${activeTabId}`);
if (!previewContent || !previewContent.innerHTML.trim()) {
alert('Nothing to print. Please create or open a document and ensure the preview is visible.');
return;
}
console.log('[RENDERER] Starting print with withStyles:', withStyles);
console.log('[RENDERER] Preview content length:', previewContent.innerHTML.length);
// Add body classes for print mode - let CSS handle everything
document.body.classList.add('printing');
if (!withStyles) {
document.body.classList.add('printing-no-styles');
}
// Wait for CSS to apply then print
setTimeout(() => {
console.log('[RENDERER] Sending do-print to main process');
ipcRenderer.send('do-print', { withStyles });
// Restore classes after print dialog opens
setTimeout(() => {
document.body.classList.remove('printing', 'printing-no-styles');
console.log('[RENDERER] Print classes removed');
}, 1000);
}, 100);
}
// Export Dialog functionality
let currentExportFormat = null;
+334 -4
View File
@@ -326,7 +326,6 @@ 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) {
@@ -449,9 +448,6 @@ body.theme-dark .preview-content table th, body.theme-dark .preview-content tabl
border-color: #3e3e42;
}
body.theme-dark #preview table th, body.theme-dark .preview-content table th {
background-color: #2d2d30;
}
body.theme-dark #preview table tr:nth-child(2n), body.theme-dark .preview-content table tr:nth-child(2n) {
background-color: #2d2d30;
@@ -1102,6 +1098,117 @@ body.theme-dark #add-metadata-field {
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 #add-metadata-field:hover {
background: #333;
@@ -2828,3 +2935,226 @@ body.theme-concrete-warm .status-bar {
border-top-color: #9a9696;
color: #e3e3e3;
}
/* Print Styles */
@media print {
/* Hide UI elements for clean print output */
#toolbar,
#tab-bar,
#editor-container,
.editor-container,
#status-bar,
.status-bar,
.toolbar,
.tab-bar,
.editor-pane,
[id^="editor-pane-"],
.dialog,
.modal-overlay,
.export-dialog {
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 */
#preview,
.preview,
.preview-content,
[id^="preview-"],
[id^="preview-pane-"],
.tab-content,
.pane {
display: block !important;
width: 100% !important;
max-width: 100% !important;
height: auto !important;
margin: 0 !important;
padding: 20px !important;
overflow: visible !important;
position: static !important;
left: auto !important;
right: auto !important;
top: auto !important;
bottom: auto !important;
}
/* Optimize preview content for printing */
.preview-content,
[id^="preview-"] {
line-height: 1.6;
font-size: 12pt;
color: #000 !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 */
.preview-content h1,
.preview-content h2,
.preview-content h3,
.preview-content h4,
.preview-content h5,
.preview-content h6 {
page-break-after: avoid;
margin-top: 1em;
margin-bottom: 0.5em;
}
.preview-content p {
margin-bottom: 0.5em;
page-break-inside: avoid;
}
/* Code blocks */
.preview-content pre {
background: #f5f5f5 !important;
border: 1px solid #ddd !important;
page-break-inside: avoid;
padding: 10px;
font-size: 10pt;
}
.preview-content code {
background: #f5f5f5 !important;
color: #000 !important;
font-size: 10pt;
}
/* Lists */
.preview-content ul,
.preview-content ol {
margin-left: 20px;
page-break-inside: avoid;
}
.preview-content li {
page-break-inside: avoid;
margin-bottom: 0.3em;
}
/* Tables */
.preview-content table {
width: 100%;
border-collapse: collapse;
page-break-inside: avoid;
margin-bottom: 1em;
}
.preview-content th,
.preview-content td {
border: 1px solid #999 !important;
padding: 8px !important;
text-align: left;
}
.preview-content th {
background: #f0f0f0 !important;
font-weight: bold;
}
/* Links - remove underline for print */
.preview-content a {
color: #000 !important;
text-decoration: none;
}
/* Blockquotes */
.preview-content blockquote {
border-left: 3px solid #ccc;
padding-left: 15px;
margin-left: 0;
color: #333 !important;
}
/* Images */
.preview-content img {
max-width: 100%;
height: auto;
page-break-inside: avoid;
}
/* Horizontal rules */
.preview-content hr {
border: none;
border-top: 1px solid #999;
margin: 1em 0;
}
}
/* Print mode classes */
.print-mode {
display: block !important;
width: 100% !important;
max-width: 100% !important;
position: fixed !important;
top: 0 !important;
left: 0 !important;
z-index: 9999 !important;
background: white !important;
padding: 20px !important;
}
/* Ensure preview panes are visible in print mode */
#preview-pane-1, #preview-pane-2, #preview-pane-3, #preview-pane-4, #preview-pane-5 {
display: block !important;
}
/* No-styles printing mode */
body.printing-no-styles .preview-content,
body.printing-no-styles [id^="preview-"],
.print-no-styles .preview-content {
color: #000 !important;
background: white !important;
}
body.printing-no-styles .preview-content h1,
body.printing-no-styles .preview-content h2,
body.printing-no-styles .preview-content h3,
body.printing-no-styles .preview-content h4,
body.printing-no-styles .preview-content h5,
body.printing-no-styles .preview-content h6,
.print-no-styles .preview-content h1,
.print-no-styles .preview-content h2,
.print-no-styles .preview-content h3,
.print-no-styles .preview-content h4,
.print-no-styles .preview-content h5,
.print-no-styles .preview-content h6 {
color: #000 !important;
border-color: #999 !important;
}
body.printing-no-styles .preview-content code,
.print-no-styles .preview-content code {
background: #f5f5f5 !important;
color: #000 !important;
}
body.printing-no-styles .preview-content pre,
.print-no-styles .preview-content pre {
background: #f5f5f5 !important;
color: #000 !important;
border-color: #999 !important;
}
.print-no-styles .preview-content a {
color: #000 !important;
}
.print-no-styles .preview-content blockquote {
color: #333 !important;
border-color: #999 !important;
}
+632
View File
@@ -0,0 +1,632 @@
/**
* Word Template Exporter
* Loads word_template.docx, preserves first 2 pages (cover + TOC),
* and adds markdown content starting from page 3 using template styles
*/
const fs = require('fs');
const path = require('path');
const PizZip = require('pizzip');
const Docx = require('docx4js').default;
class WordTemplateExporter {
constructor(templatePath, startPage = 3) {
this.templatePath = templatePath || path.join(__dirname, '../word_template.docx');
this.startPage = startPage; // Which page to start inserting content
}
/**
* Convert markdown to Word document using template
*/
async convert(markdownContent, outputPath) {
try {
// Load template
const templateBuffer = fs.readFileSync(this.templatePath);
const zip = new PizZip(templateBuffer);
// Extract document.xml
const documentXml = zip.file('word/document.xml').asText();
// Parse markdown and generate Word XML
const newContentXml = this.markdownToWordXml(markdownContent);
// Insert new content after the specified start page
const modifiedXml = this.insertContentAfterPage(documentXml, newContentXml, this.startPage);
// Update the zip with modified XML
zip.file('word/document.xml', modifiedXml);
// Generate and save the new document
const newDocBuffer = zip.generate({ type: 'nodebuffer' });
fs.writeFileSync(outputPath, newDocBuffer);
return outputPath;
} catch (error) {
console.error('Error in Word export:', error);
throw error;
}
}
/**
* 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)
*/
insertContentAfterPage(documentXml, newContentXml, afterPage) {
// Find section breaks that mark page boundaries
// Look for the section properties tag that marks page breaks
const sectionBreakRegex = /<w:sectPr[^>]*>[\s\S]*?<\/w:sectPr>/g;
const matches = [...documentXml.matchAll(sectionBreakRegex)];
// Calculate which section break to insert after
// Page 1 = before 1st section break
// 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);
} 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 {
// If not enough section breaks, insert before closing body tag
return documentXml.replace('</w:body>', newContentXml + '</w:body>');
}
}
/**
* Convert markdown to Word XML format
*/
markdownToWordXml(markdown) {
const lines = markdown.split('\n');
let xml = '';
let inCodeBlock = false;
let codeLines = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// Handle code blocks
if (line.trim().startsWith('```')) {
if (inCodeBlock) {
// End code block
xml += this.createCodeBlockXml(codeLines.join('\n'));
codeLines = [];
inCodeBlock = false;
} else {
inCodeBlock = true;
}
continue;
}
if (inCodeBlock) {
codeLines.push(line);
continue;
}
// Empty lines
if (!line.trim()) {
xml += '<w:p><w:pPr></w:pPr></w:p>';
continue;
}
// Tables - detect table lines
if (line.includes('|') && line.trim().startsWith('|')) {
const tableLines = [line];
i++;
// Collect all consecutive table lines
while (i < lines.length && lines[i].includes('|')) {
tableLines.push(lines[i]);
i++;
}
i--; // Back up one line
xml += this.createTableXml(tableLines);
continue;
}
// ASCII flowcharts/diagrams - detect box drawing characters
if (this.isAsciiArt(line)) {
const asciiLines = [line];
i++;
// Collect all consecutive ASCII art lines
while (i < lines.length && (this.isAsciiArt(lines[i]) || !lines[i].trim())) {
asciiLines.push(lines[i]);
i++;
if (i < lines.length && lines[i].trim() && !this.isAsciiArt(lines[i])) {
break;
}
}
i--; // Back up one line
xml += this.createAsciiArtXml(asciiLines.join('\n'));
continue;
}
// Headings - strip markdown numbering
if (line.trim().startsWith('#')) {
const level = (line.match(/^#+/) || [''])[0].length;
let text = line.replace(/^#+\s*/, '').trim();
// Remove markdown numbering like "1.1 Title" -> "Title"
text = text.replace(/^\d+(\.\d+)*\.?\s+/, '');
xml += this.createHeadingXml(text, level);
continue;
}
// Blockquotes
if (line.trim().startsWith('>')) {
const text = line.replace(/^>\s*/, '').trim();
xml += this.createQuoteXml(text);
continue;
}
// Ordered lists - strip markdown numbering, use template numbering
if (/^\s*\d+\.\s+/.test(line)) {
const text = line.replace(/^\s*\d+\.\s+/, '');
xml += this.createListItemXml(text, true);
continue;
}
// Unordered lists
if (/^\s*[-*+]\s+/.test(line)) {
const text = line.replace(/^\s*[-*+]\s+/, '');
xml += this.createListItemXml(text, false);
continue;
}
// Horizontal rule
if (/^[-*_]{3,}$/.test(line.trim())) {
xml += this.createHorizontalRuleXml();
continue;
}
// Normal paragraph
xml += this.createParagraphXml(line);
}
return xml;
}
/**
* Create heading XML using template styles
*/
createHeadingXml(text, level) {
const styleName = `Heading${level}`;
const runs = this.parseInlineFormatting(text);
return `<w:p>
<w:pPr>
<w:pStyle w:val="${styleName}"/>
</w:pPr>
${runs}
</w:p>`;
}
/**
* Create paragraph XML with Normal style
*/
createParagraphXml(text) {
const runs = this.parseInlineFormatting(text);
return `<w:p>
<w:pPr>
<w:pStyle w:val="Normal"/>
</w:pPr>
${runs}
</w:p>`;
}
/**
* Create quote XML
*/
createQuoteXml(text) {
const runs = this.parseInlineFormatting(text);
return `<w:p>
<w:pPr>
<w:pStyle w:val="Quote"/>
</w:pPr>
${runs}
</w:p>`;
}
/**
* Create list item XML using template numbering
*/
createListItemXml(text, numbered) {
const runs = this.parseInlineFormatting(text);
const numId = numbered ? '1' : '2'; // Template numbering IDs
return `<w:p>
<w:pPr>
<w:pStyle w:val="${numbered ? 'ListNumber' : 'ListBullet'}"/>
<w:numPr>
<w:ilvl w:val="0"/>
<w:numId w:val="${numId}"/>
</w:numPr>
</w:pPr>
${runs}
</w:p>`;
}
/**
* Create code block XML
*/
createCodeBlockXml(code) {
const escapedCode = this.escapeXml(code);
return `<w:p>
<w:pPr>
<w:pStyle w:val="Code"/>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:ascii="Consolas" w:hAnsi="Consolas"/>
<w:sz w:val="18"/>
</w:rPr>
<w:t xml:space="preserve">${escapedCode}</w:t>
</w:r>
</w:p>`;
}
/**
* Create horizontal rule XML
*/
createHorizontalRuleXml() {
return `<w:p>
<w:pPr>
<w:pBdr>
<w:bottom w:val="single" w:sz="6" w:space="1" w:color="auto"/>
</w:pBdr>
</w:pPr>
</w:p>`;
}
/**
* Create table XML from markdown table lines with template styling
*/
createTableXml(tableLines) {
// Parse table
const rows = [];
for (const line of tableLines) {
// Skip separator lines (e.g., |---|---|)
if (/^\s*\|[\s\-:]+\|\s*$/.test(line)) {
continue;
}
// Split by | and trim
const cells = line.split('|').filter(cell => cell.trim()).map(cell => cell.trim());
if (cells.length > 0) {
rows.push(cells);
}
}
if (rows.length === 0) return '';
// Calculate number of columns
const numCols = Math.max(...rows.map(row => row.length));
// Build table XML
let tableXml = '<w:tbl>';
// Table properties - use template table style
tableXml += `<w:tblPr>
<w:tblStyle w:val="TableGrid"/>
<w:tblW w:w="5000" w:type="pct"/>
<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"/>
<w:left w:val="single" w:sz="8" w:space="0" w:color="F58220"/>
<w:bottom w:val="single" w:sz="8" w:space="0" w:color="F58220"/>
<w:right w:val="single" w:sz="8" w:space="0" w:color="F58220"/>
<w:insideH w:val="single" w:sz="8" w:space="0" w:color="F58220"/>
<w:insideV w:val="single" w:sz="8" w:space="0" w:color="F58220"/>
</w:tblBorders>
</w:tblPr>`;
// Table grid
tableXml += '<w:tblGrid>';
for (let i = 0; i < numCols; i++) {
tableXml += '<w:gridCol/>';
}
tableXml += '</w:tblGrid>';
// Table rows
rows.forEach((rowCells, rowIndex) => {
const isHeader = rowIndex === 0;
const isEvenRow = rowIndex % 2 === 0;
tableXml += '<w:tr>';
// Pad row to have same number of columns
while (rowCells.length < numCols) {
rowCells.push('');
}
rowCells.forEach((cellText, colIndex) => {
tableXml += '<w:tc>';
tableXml += '<w:tcPr>';
// Cell shading - orange for header, white for data rows
if (isHeader) {
tableXml += '<w:shd w:val="clear" w:color="FFFFFF" w:fill="F58220"/>';
}
// Cell borders
tableXml += '<w:tcBorders>' +
'<w:top w:val="single" w:sz="8" w:space="0" w:color="F58220"/>' +
'<w:left w:val="single" w:sz="8" w:space="0" w:color="F58220"/>' +
'<w:bottom w:val="single" w:sz="8" w:space="0" w:color="F58220"/>' +
'<w:right w:val="single" w:sz="8" w:space="0" w:color="F58220"/>' +
'</w:tcBorders>';
tableXml += '</w:tcPr>';
// Cell content
tableXml += '<w:p>';
tableXml += '<w:pPr>';
tableXml += '<w:spacing w:before="100" w:after="100"/>';
tableXml += '</w:pPr>';
const runs = this.parseInlineFormatting(cellText);
if (isHeader) {
// Header: bold white text
tableXml += runs.replace(/<w:rPr>/g, '<w:rPr><w:b/><w:color w:val="FFFFFF"/>');
} else {
// Data rows: normal black text
tableXml += runs;
}
tableXml += '</w:p>';
tableXml += '</w:tc>';
});
tableXml += '</w:tr>';
});
tableXml += '</w:tbl>';
return tableXml;
}
/**
* Detect if line contains ASCII art/flowchart characters
*/
isAsciiArt(line) {
// Don't treat markdown tables as ASCII art
if (line.trim().startsWith('|') && line.trim().endsWith('|')) {
return false;
}
// Common ASCII art characters
const asciiArtChars = [
'─', '│', '┌', '┐', '└', '┘', '├', '┤', '┬', '┴', '┼', // Box drawing
'═', '║', '╔', '╗', '╚', '╝', '╠', '╣', '╦', '╩', '╬', // Double box
'▲', '▼', '◄', '►', '♦', '●', '○', '■', '□', // Shapes
'↓', '→', '←', '↑', // Arrows
];
// Check for box drawing characters
if (asciiArtChars.some(char => line.includes(char))) {
return true;
}
// Check for ASCII box patterns with regular characters
const asciiPatterns = [
/^\s*\+[-=_]+\+/, // +-----+
/^\s*\|[-=_]{3,}\|/, // |-----|
/^\s*[-=_]{5,}$/, // -----
/^\s*\([A-Z][a-z]+\)\s*\|\|/, // (Deformable) ||
/^\s*\|\s+[A-Z\s]+[-:]\s+[A-Z]/, // | TILE ADHESIVE - TYPE
/^\s*\|\s*\[[^\]]+\]/, // | [BIS Mark / CE Mark]
/^\s*\|\s{2,}\w+.*\|\|/, // || text ||
/^\s*\[[^\]]+\]/, // [Step in brackets]
];
return asciiPatterns.some(pattern => pattern.test(line));
}
/**
* Create ASCII art XML with monospace font
*/
createAsciiArtXml(asciiContent) {
// Split ASCII art into individual lines and create a paragraph for each
const lines = asciiContent.split('\n');
let xml = '';
lines.forEach((line, index) => {
xml += `<w:p>
<w:pPr>
<w:spacing w:before="${index === 0 ? '120' : '0'}" w:after="${index === lines.length - 1 ? '120' : '0'}" w:line="240" w:lineRule="exact"/>
<w:ind w:left="0" w:right="0"/>
<w:jc w:val="left"/>
<w:keepLines/>
<w:wordWrap w:val="0"/>
</w:pPr>`;
// Check if line contains arrow characters and color them red
const arrowChars = ['↓', '→', '←', '↑', '▼', '►', '◄', '▲'];
const hasArrow = arrowChars.some(arrow => line.includes(arrow));
if (hasArrow) {
// Split line into parts and color arrows red
let remainingLine = line;
let processedText = '';
while (remainingLine.length > 0) {
let foundArrow = false;
let arrowIndex = -1;
let foundArrowChar = '';
// Find the first arrow in the remaining text
for (const arrow of arrowChars) {
const idx = remainingLine.indexOf(arrow);
if (idx !== -1 && (arrowIndex === -1 || idx < arrowIndex)) {
arrowIndex = idx;
foundArrowChar = arrow;
foundArrow = true;
}
}
if (foundArrow) {
// Add text before arrow (if any)
if (arrowIndex > 0) {
const beforeArrow = this.escapeXml(remainingLine.substring(0, arrowIndex));
xml += `<w:r>
<w:rPr>
<w:rFonts w:ascii="Consolas" w:hAnsi="Consolas" w:cs="Consolas"/>
<w:sz w:val="16"/>
<w:szCs w:val="16"/>
<w:shd w:val="clear" w:color="auto" w:fill="F5F5F5"/>
<w:noProof/>
</w:rPr>
<w:t xml:space="preserve">${beforeArrow}</w:t>
</w:r>`;
}
// Add arrow in red
const escapedArrow = this.escapeXml(foundArrowChar);
xml += `<w:r>
<w:rPr>
<w:rFonts w:ascii="Consolas" w:hAnsi="Consolas" w:cs="Consolas"/>
<w:sz w:val="16"/>
<w:szCs w:val="16"/>
<w:color w:val="FF0000"/>
<w:shd w:val="clear" w:color="auto" w:fill="F5F5F5"/>
<w:noProof/>
</w:rPr>
<w:t xml:space="preserve">${escapedArrow}</w:t>
</w:r>`;
// Continue with remaining text
remainingLine = remainingLine.substring(arrowIndex + foundArrowChar.length);
} else {
// No more arrows, add remaining text
const escapedRemaining = this.escapeXml(remainingLine);
xml += `<w:r>
<w:rPr>
<w:rFonts w:ascii="Consolas" w:hAnsi="Consolas" w:cs="Consolas"/>
<w:sz w:val="16"/>
<w:szCs w:val="16"/>
<w:shd w:val="clear" w:color="auto" w:fill="F5F5F5"/>
<w:noProof/>
</w:rPr>
<w:t xml:space="preserve">${escapedRemaining}</w:t>
</w:r>`;
remainingLine = '';
}
}
} else {
// No arrows, just add the line normally
const escapedLine = this.escapeXml(line);
xml += `<w:r>
<w:rPr>
<w:rFonts w:ascii="Consolas" w:hAnsi="Consolas" w:cs="Consolas"/>
<w:sz w:val="16"/>
<w:szCs w:val="16"/>
<w:shd w:val="clear" w:color="auto" w:fill="F5F5F5"/>
<w:noProof/>
</w:rPr>
<w:t xml:space="preserve">${escapedLine}</w:t>
</w:r>`;
}
xml += `</w:p>`;
});
return xml;
}
/**
* Parse inline formatting (bold, italic, code)
*/
parseInlineFormatting(text) {
let xml = '';
let pos = 0;
// Patterns for inline formatting
const patterns = [
{ regex: /\*\*\*(.+?)\*\*\*/g, bold: true, italic: true },
{ regex: /\*\*(.+?)\*\*/g, bold: true },
{ regex: /\*(.+?)\*/g, italic: true },
{ regex: /`(.+?)`/g, code: true }
];
// Simple approach: process text sequentially
let remaining = text;
while (remaining.length > 0) {
let foundMatch = false;
let earliestPos = remaining.length;
let matchedPattern = null;
let match = null;
// Find earliest match
for (const pattern of patterns) {
pattern.regex.lastIndex = 0;
const m = pattern.regex.exec(remaining);
if (m && m.index < earliestPos) {
earliestPos = m.index;
matchedPattern = pattern;
match = m;
foundMatch = true;
}
}
if (foundMatch) {
// Add text before match
if (earliestPos > 0) {
xml += this.createRunXml(remaining.substring(0, earliestPos));
}
// Add formatted text
xml += this.createRunXml(match[1], matchedPattern.bold, matchedPattern.italic, matchedPattern.code);
remaining = remaining.substring(earliestPos + match[0].length);
} else {
// No more matches, add remaining text
xml += this.createRunXml(remaining);
break;
}
}
return xml;
}
/**
* Create a run (text segment) XML
*/
createRunXml(text, bold = false, italic = false, code = false) {
if (!text) return '';
const escapedText = this.escapeXml(text);
let propsXml = '<w:rPr>';
if (bold) propsXml += '<w:b/>';
if (italic) propsXml += '<w:i/>';
if (code) {
propsXml += '<w:rFonts w:ascii="Consolas" w:hAnsi="Consolas"/>';
propsXml += '<w:sz w:val="20"/>';
}
propsXml += '</w:rPr>';
return `<w:r>${propsXml}<w:t xml:space="preserve">${escapedText}</w:t></w:r>`;
}
/**
* Escape XML special characters
*/
escapeXml(text) {
return text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;');
}
}
module.exports = WordTemplateExporter;
+12
View File
@@ -0,0 +1,12 @@
# Test Double-Click File Opening
This is a test file to verify double-click functionality.
## Features to Test
- File should load automatically
- Content should display in editor
- Tab should show filename
- Preview should render markdown
**This text should be bold**
*This text should be italic*
+14
View File
@@ -0,0 +1,14 @@
# Test File Opening
This is a test file to verify that double-click file opening works correctly.
## Features
- **Bold text**
- *Italic text*
- `Code`
```javascript
console.log("Hello World");
```
This should appear when you double-click to open the file!
Binary file not shown.