mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-03 02:11:07 +05:30
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8e0748037 | ||
|
|
cb98549db8 | ||
|
|
ff651e9b21 | ||
|
|
e013e5fd69 | ||
|
|
f7362bec46 |
+19
-33
File diff suppressed because one or more lines are too long
@@ -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.9.0
|
||||
**Current Version**: v1.9.2
|
||||
**Author**: Amit Haridas (amit.wh@gmail.com)
|
||||
**License**: MIT
|
||||
**Repository**: https://github.com/amitwh/pan-converter
|
||||
@@ -113,7 +113,77 @@ gh release create v1.2.1 --title "Title" --notes "Release notes" \
|
||||
|
||||
## Feature Implementation Guide
|
||||
|
||||
### v1.9.0 Custom Headers & Footers (Latest)
|
||||
### v1.9.1 ASCII Art & Code Block Preservation (Latest)
|
||||
|
||||
#### 🎨 Preserved ASCII Art in Exports
|
||||
**ASCII Art, Charts, Tables & Flowcharts Now Export Exactly as Previewed** (`src/wordTemplateExporter.js`, `src/main.js`)
|
||||
|
||||
**Problem Solved:**
|
||||
- Previously, ASCII art, flowcharts, and code blocks were being corrupted during export to Word and PDF
|
||||
- Line breaks, spacing, and monospace alignment were lost
|
||||
- Box-drawing characters and diagrams became unreadable
|
||||
|
||||
**Solution Implemented:**
|
||||
1. **Enhanced Word Template Exporter** (`src/wordTemplateExporter.js:259-289`)
|
||||
- Code blocks now render each line as a separate paragraph with exact spacing
|
||||
- `white-space: pre` and `word-wrap: normal` prevent text wrapping
|
||||
- Monospace font (Consolas) with consistent 18pt size
|
||||
- Light gray background (#F5F5F5) for visual distinction
|
||||
- `<w:keepLines/>` and `<w:wordWrap w:val="0"/>` preserve exact formatting
|
||||
|
||||
2. **Improved ASCII Art Detection** (`src/wordTemplateExporter.js:410-456`)
|
||||
- Extended Unicode box-drawing character support (╭╮╯╰ rounded corners, heavy box characters)
|
||||
- Additional arrow characters (↔↕⇒⇐⇓⇑)
|
||||
- More ASCII patterns detected (+---+---+, <---->, etc.)
|
||||
- Better markdown table vs ASCII art differentiation
|
||||
|
||||
3. **Enhanced PDF Export** (`src/main.js:1266-1268`, `src/main.js:1345-1347`, `src/main.js:2410-2412`)
|
||||
- Added `-V monofont="Consolas"` for monospace code blocks
|
||||
- Added `--highlight-style=tango` for syntax highlighting
|
||||
- Applied to main export, fallback export, and CLI conversion
|
||||
|
||||
4. **Electron PDF Fallback** (`src/main.js:1613-1637`)
|
||||
- Enhanced CSS for `<pre>` and `<code>` elements
|
||||
- `white-space: pre` prevents wrapping
|
||||
- `font-family: Consolas, Monaco, 'Courier New', monospace`
|
||||
- Print media query preserves formatting when printing
|
||||
|
||||
5. **HTML Export** (`src/main.js:1525-1548`)
|
||||
- Same CSS improvements as Electron PDF
|
||||
- Code blocks maintain exact preview appearance
|
||||
|
||||
**Key CSS Properties for ASCII Preservation:**
|
||||
```css
|
||||
pre {
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
font-family: Consolas, Monaco, 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
pre code {
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
display: block;
|
||||
}
|
||||
```
|
||||
|
||||
**Word XML Properties for ASCII Preservation:**
|
||||
```xml
|
||||
<w:pPr>
|
||||
<w:spacing w:line="240" w:lineRule="exact"/>
|
||||
<w:keepLines/>
|
||||
<w:wordWrap w:val="0"/>
|
||||
</w:pPr>
|
||||
<w:rPr>
|
||||
<w:rFonts w:ascii="Consolas" w:hAnsi="Consolas" w:cs="Consolas"/>
|
||||
<w:noProof/>
|
||||
</w:rPr>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### v1.9.0 Custom Headers & Footers
|
||||
|
||||
#### 📄 Comprehensive Header & Footer System
|
||||
**Custom Headers & Footers for Standard Exports** (`src/main.js:701-853`, `src/index.html:929-1042`, `src/renderer.js:2560-2747`, `src/styles.css:3162-3432`)
|
||||
@@ -1109,7 +1179,7 @@ if (!gotTheLock) {
|
||||
|
||||
## Pending Tasks & Future Enhancements
|
||||
|
||||
No pending tasks at this time. All planned v1.9.0 features have been completed.
|
||||
No pending tasks at this time. All planned v1.9.1 features have been completed.
|
||||
|
||||
### Potential Future Enhancements
|
||||
- Image embedding in headers/footers (partially implemented - logo upload UI ready)
|
||||
@@ -1119,19 +1189,19 @@ No pending tasks at this time. All planned v1.9.0 features have been completed.
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: October 28, 2025
|
||||
**Claude Assistant**: Development completed for v1.9.0 with Custom Headers & Footers!
|
||||
**Last Updated**: December 12, 2025
|
||||
**Claude Assistant**: Development completed for v1.9.1 with ASCII Art Preservation!
|
||||
|
||||
### v1.9.0 - Latest Release Summary
|
||||
- **Custom Headers & Footers**: Professional headers/footers for PDF, DOCX, ODT, and PowerPoint exports
|
||||
- **Dynamic Field Support**: $PAGE$, $TOTAL$, $DATE$, $TIME$, $TITLE$, $AUTHOR$, $FILENAME$
|
||||
- **Logo Upload UI**: Ready for image embedding in headers/footers
|
||||
- **Three-Column Layout**: Left/Center/Right positioning for headers and footers
|
||||
- **Batch Support**: Full header/footer integration in batch converter
|
||||
- **Persistent Settings**: Configurations saved across sessions
|
||||
- **Dark Theme Support**: Complete styling for all 22 themes
|
||||
### v1.9.1 - Latest Release Summary
|
||||
- **ASCII Art Preservation**: Code blocks, flowcharts, tables, and ASCII art now export exactly as shown in preview
|
||||
- **Enhanced Code Block Rendering**: Each line rendered separately to preserve exact spacing and alignment
|
||||
- **Monospace Font Support**: Consolas font used consistently across all export formats
|
||||
- **Improved ASCII Detection**: Extended Unicode box-drawing and arrow character support
|
||||
- **PDF Export Enhancement**: Added monofont and highlight-style options for better code rendering
|
||||
- **HTML/Electron PDF Fix**: Enhanced CSS to prevent text wrapping in code blocks
|
||||
|
||||
### Previous Releases
|
||||
- v1.9.0: Custom Headers & Footers for PDF, DOCX, ODT, PowerPoint exports
|
||||
- v1.8.3: Streamlined PDF Editor UI & Configurable Template Settings
|
||||
- v1.8.2: Enhanced PDF Export & Print Fix
|
||||
- v1.8.1: Streamlined PDF Editor UI
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pan-converter",
|
||||
"version": "1.9.0",
|
||||
"version": "1.9.2",
|
||||
"description": "Cross-platform Markdown editor and converter using Pandoc",
|
||||
"main": "src/main.js",
|
||||
"scripts": {
|
||||
|
||||
+2
-2
@@ -964,7 +964,7 @@
|
||||
</div>
|
||||
<div class="hf-logo-row">
|
||||
<label>Header Logo/Image:</label>
|
||||
<input type="file" id="header-logo" accept="image/*">
|
||||
<button id="header-logo-browse" class="browse-btn">Browse...</button>
|
||||
<button id="header-logo-clear" class="clear-btn">Clear</button>
|
||||
<span id="header-logo-preview" class="logo-preview"></span>
|
||||
</div>
|
||||
@@ -992,7 +992,7 @@
|
||||
</div>
|
||||
<div class="hf-logo-row">
|
||||
<label>Footer Logo/Image:</label>
|
||||
<input type="file" id="footer-logo" accept="image/*">
|
||||
<button id="footer-logo-browse" class="browse-btn">Browse...</button>
|
||||
<button id="footer-logo-clear" class="clear-btn">Clear</button>
|
||||
<span id="footer-logo-preview" class="logo-preview"></span>
|
||||
</div>
|
||||
|
||||
+212
-6
@@ -5,6 +5,15 @@ const { exec } = require('child_process');
|
||||
const { PDFDocument, rgb, degrees, StandardFonts } = require('pdf-lib');
|
||||
const WordTemplateExporter = require('./wordTemplateExporter');
|
||||
|
||||
// Add MiKTeX to PATH for LaTeX support
|
||||
if (process.platform === 'win32') {
|
||||
const miktexPath = 'C:\\Program Files\\MiKTeX\\miktex\\bin\\x64';
|
||||
if (fs.existsSync(miktexPath)) {
|
||||
process.env.PATH = `${miktexPath};${process.env.PATH}`;
|
||||
console.log('[MAIN] Added MiKTeX to PATH:', miktexPath);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the system Pandoc path
|
||||
function getPandocPath() {
|
||||
// Pandoc is expected to be in the system's PATH.
|
||||
@@ -506,7 +515,7 @@ function createMenu() {
|
||||
type: 'info',
|
||||
title: 'About PanConverter',
|
||||
message: 'PanConverter',
|
||||
detail: 'A cross-platform Markdown editor and converter using Pandoc.\n\nVersion: 1.9.0\nAuthor: Amit Haridas\nEmail: amit.wh@gmail.com\nLicense: MIT\n\nFeatures:\n• Custom Headers & Footers for PDF, DOCX, ODT, and PowerPoint exports\n• Dynamic field support: $PAGE$, $TOTAL$, $DATE$, $TIME$, $TITLE$, $AUTHOR$, $FILENAME$\n• Logo/image embedding in headers and footers\n• Modern glassmorphism UI with gradient backgrounds\n• Enhanced PDF export via Word template with configurable start page\n• Configurable template settings (start page selection)\n• Streamlined PDF Editor UI (merge, split, compress, rotate, watermark, encrypt)\n• Universal File Converter (LibreOffice, ImageMagick, FFmpeg, Pandoc)\n• Windows Explorer context menu integration\n• Tabbed interface for multiple files\n• Advanced markdown editing with live preview\n• Real-time preview updates while typing\n• Full toolbar markdown editing functions\n• Enhanced PDF export with built-in Electron fallback\n• Enhanced Word export with template support (single file & batch)\n• File association support for .md files\n• Command-line interface for batch conversion\n• Advanced export options with templates and metadata\n• Batch file conversion with progress tracking\n• Improved preview typography and spacing\n• Adjustable font sizes via menu (Ctrl+Shift+Plus/Minus)\n• Complete theme support including Monokai fixes\n• Find & replace with match highlighting\n• Line numbers and auto-indentation\n• Export to multiple formats via Pandoc\n• PowerPoint & presentation export\n• Export tables to Excel/ODS spreadsheets\n• Document import & conversion\n• Table creation helper\n• 22 beautiful themes (including Dracula, Nord, Tokyo Night, Gruvbox, Ayu, Concrete, and more)\n• Undo/redo functionality\n• Live word count and statistics',
|
||||
detail: 'A cross-platform Markdown editor and converter using Pandoc.\n\nVersion: 1.9.2\nAuthor: Amit Haridas\nEmail: amit.wh@gmail.com\nLicense: MIT\n\nFeatures:\n• Preserved ASCII art, charts, tables, flowcharts in exports with full-width background\n• Custom Headers & Footers for PDF, DOCX, ODT, and PowerPoint exports\n• Dynamic field support: $PAGE$, $TOTAL$, $DATE$, $TIME$, $TITLE$, $AUTHOR$, $FILENAME$\n• Logo/image embedding in headers and footers\n• Modern glassmorphism UI with gradient backgrounds\n• Enhanced PDF export via Word template with configurable start page\n• Configurable template settings (start page selection)\n• Streamlined PDF Editor UI (merge, split, compress, rotate, watermark, encrypt)\n• Universal File Converter (LibreOffice, ImageMagick, FFmpeg, Pandoc)\n• Windows Explorer context menu integration\n• Tabbed interface for multiple files\n• Advanced markdown editing with live preview\n• Real-time preview updates while typing\n• Full toolbar markdown editing functions\n• Enhanced PDF export with built-in Electron fallback\n• Enhanced Word export with template support (single file & batch)\n• File association support for .md files\n• Command-line interface for batch conversion\n• Advanced export options with templates and metadata\n• Batch file conversion with progress tracking\n• Improved preview typography and spacing\n• Adjustable font sizes via menu (Ctrl+Shift+Plus/Minus)\n• Complete theme support including Monokai fixes\n• Find & replace with match highlighting\n• Line numbers and auto-indentation\n• Export to multiple formats via Pandoc\n• PowerPoint & presentation export\n• Export tables to Excel/ODS spreadsheets\n• Document import & conversion\n• Table creation helper\n• 22 beautiful themes (including Dracula, Nord, Tokyo Night, Gruvbox, Ayu, Concrete, and more)\n• Undo/redo functionality\n• Live word count and statistics',
|
||||
buttons: ['OK']
|
||||
});
|
||||
}
|
||||
@@ -664,8 +673,59 @@ ipcMain.on('save-header-footer-settings', (event, settings) => {
|
||||
});
|
||||
|
||||
// Save header/footer logo image
|
||||
// Browse for header/footer logo
|
||||
ipcMain.on('browse-header-footer-logo', async (event, position) => {
|
||||
try {
|
||||
const result = await dialog.showOpenDialog(mainWindow, {
|
||||
title: `Select ${position.charAt(0).toUpperCase() + position.slice(1)} Logo/Image`,
|
||||
filters: [
|
||||
{ name: 'Images', extensions: ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'svg', 'webp'] }
|
||||
],
|
||||
properties: ['openFile']
|
||||
});
|
||||
|
||||
if (!result.canceled && result.filePaths.length > 0) {
|
||||
const filePath = result.filePaths[0];
|
||||
|
||||
// Copy image to userData directory for persistent storage
|
||||
const userDataPath = app.getPath('userData');
|
||||
const logoDir = path.join(userDataPath, 'logos');
|
||||
|
||||
// Create logos directory if it doesn't exist
|
||||
if (!fs.existsSync(logoDir)) {
|
||||
fs.mkdirSync(logoDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Generate unique filename
|
||||
const ext = path.extname(filePath);
|
||||
const filename = `${position}_${Date.now()}${ext}`;
|
||||
const destPath = path.join(logoDir, filename);
|
||||
|
||||
// Copy file
|
||||
fs.copyFileSync(filePath, destPath);
|
||||
|
||||
// Update settings
|
||||
if (position === 'header') {
|
||||
headerFooterSettings.header.logo = destPath;
|
||||
} else if (position === 'footer') {
|
||||
headerFooterSettings.footer.logo = destPath;
|
||||
}
|
||||
|
||||
event.reply('header-footer-logo-saved', { position, path: destPath });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Logo browse error:', error);
|
||||
dialog.showErrorBox('Logo Error', `Failed to select logo: ${error.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('save-header-footer-logo', async (event, { position, filePath }) => {
|
||||
try {
|
||||
if (!filePath) {
|
||||
dialog.showErrorBox('Logo Error', 'Failed to save logo: The "path" argument must be of type string. Received undefined');
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy image to userData directory for persistent storage
|
||||
const userDataPath = app.getPath('userData');
|
||||
const logoDir = path.join(userDataPath, 'logos');
|
||||
@@ -675,6 +735,12 @@ ipcMain.on('save-header-footer-logo', async (event, { position, filePath }) => {
|
||||
fs.mkdirSync(logoDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Verify source file exists
|
||||
if (!fs.existsSync(filePath)) {
|
||||
dialog.showErrorBox('Logo Error', `Source file not found: ${filePath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate unique filename
|
||||
const ext = path.extname(filePath);
|
||||
const filename = `${position}_${Date.now()}${ext}`;
|
||||
@@ -692,6 +758,7 @@ ipcMain.on('save-header-footer-logo', async (event, { position, filePath }) => {
|
||||
|
||||
event.reply('header-footer-logo-saved', { position, path: destPath });
|
||||
} catch (error) {
|
||||
console.error('Logo save error:', error);
|
||||
dialog.showErrorBox('Logo Error', `Failed to save logo: ${error.message}`);
|
||||
}
|
||||
});
|
||||
@@ -1196,6 +1263,42 @@ function performExportWithOptions(format, options) {
|
||||
pandocCmd += ` --pdf-engine="${pdfEngine}"`;
|
||||
if (options.geometry) pandocCmd += ` -V geometry:"${options.geometry}"`;
|
||||
|
||||
// Add monospace font settings for code blocks (ASCII art preservation)
|
||||
pandocCmd += ' -V monofont="Consolas"';
|
||||
pandocCmd += ' --highlight-style=tango';
|
||||
|
||||
// Add header/footer if enabled
|
||||
if (headerFooterSettings.enabled) {
|
||||
const filename = currentFile ? path.basename(currentFile, path.extname(currentFile)) : 'document';
|
||||
const metadata = { filename, title: filename, author: '' };
|
||||
|
||||
const headerLeft = processDynamicFields(headerFooterSettings.header.left, metadata);
|
||||
const headerCenter = processDynamicFields(headerFooterSettings.header.center, metadata);
|
||||
const headerRight = processDynamicFields(headerFooterSettings.header.right, metadata);
|
||||
const footerLeft = processDynamicFields(headerFooterSettings.footer.left, metadata);
|
||||
const footerCenter = processDynamicFields(headerFooterSettings.footer.center, metadata);
|
||||
const footerRight = processDynamicFields(headerFooterSettings.footer.right, metadata);
|
||||
|
||||
// Create LaTeX header
|
||||
const latexHeader = `
|
||||
\\usepackage{fancyhdr}
|
||||
\\pagestyle{fancy}
|
||||
\\fancyhf{}
|
||||
\\lhead{${headerLeft.replace(/\\/g, '\\\\')}}
|
||||
\\chead{${headerCenter.replace(/\\/g, '\\\\')}}
|
||||
\\rhead{${headerRight.replace(/\\/g, '\\\\')}}
|
||||
\\lfoot{${footerLeft.replace(/\\/g, '\\\\')}}
|
||||
\\cfoot{${footerCenter.replace(/[$]PAGE[$]/g, '\\\\thepage').replace(/[$]TOTAL[$]/g, '\\\\pageref{LastPage}').replace(/\\/g, '\\\\')}}
|
||||
\\rfoot{${footerRight.replace(/\\/g, '\\\\')}}
|
||||
\\renewcommand{\\headrulewidth}{0.4pt}
|
||||
\\renewcommand{\\footrulewidth}{0.4pt}
|
||||
`;
|
||||
const headerFile = path.join(require('os').tmpdir(), `header_export_${Date.now()}.tex`);
|
||||
fs.writeFileSync(headerFile, latexHeader, 'utf-8');
|
||||
pandocCmd += ` --include-in-header="${headerFile}"`;
|
||||
pandocCmd += ' --variable header-includes="\\\\usepackage{lastpage}"';
|
||||
}
|
||||
|
||||
// Try with specified PDF engine
|
||||
exec(pandocCmd, (error) => {
|
||||
if (error) {
|
||||
@@ -1209,6 +1312,15 @@ function performExportWithOptions(format, options) {
|
||||
} else if (format === 'docx') {
|
||||
pandocCmd += ' -t docx';
|
||||
exportWithPandoc(pandocCmd, outputFile, format);
|
||||
} else if (format === 'pptx') {
|
||||
// Add PowerPoint footer if enabled
|
||||
if (headerFooterSettings.enabled && headerFooterSettings.footer.center) {
|
||||
const filename = currentFile ? path.basename(currentFile, path.extname(currentFile)) : 'document';
|
||||
const metadata = { filename, title: filename, author: '' };
|
||||
const footerText = processDynamicFields(headerFooterSettings.footer.center, metadata);
|
||||
pandocCmd += ` --variable footer="${footerText}"`;
|
||||
}
|
||||
exportWithPandoc(pandocCmd, outputFile, format);
|
||||
} else {
|
||||
// Generic export for other formats
|
||||
exportWithPandoc(pandocCmd, outputFile, format);
|
||||
@@ -1230,9 +1342,45 @@ function tryPdfFallback(inputFile, outputFile, engines, index, options, lastErro
|
||||
const engine = engines[index];
|
||||
let pandocCmd = `${getPandocPath()} "${inputFile}" --pdf-engine=${engine} -o "${outputFile}"`;
|
||||
|
||||
// Add monospace font settings for code blocks (ASCII art preservation)
|
||||
pandocCmd += ' -V monofont="Consolas"';
|
||||
pandocCmd += ' --highlight-style=tango';
|
||||
|
||||
// Add geometry if specified
|
||||
if (options.geometry) pandocCmd = pandocCmd.replace(` -o `, ` -V geometry:"${options.geometry}" -o `);
|
||||
|
||||
// Add header/footer if enabled
|
||||
if (headerFooterSettings.enabled) {
|
||||
const filename = path.basename(inputFile, path.extname(inputFile));
|
||||
const metadata = { filename, title: filename, author: options.metadata?.author || '' };
|
||||
|
||||
const headerLeft = processDynamicFields(headerFooterSettings.header.left, metadata);
|
||||
const headerCenter = processDynamicFields(headerFooterSettings.header.center, metadata);
|
||||
const headerRight = processDynamicFields(headerFooterSettings.header.right, metadata);
|
||||
const footerLeft = processDynamicFields(headerFooterSettings.footer.left, metadata);
|
||||
const footerCenter = processDynamicFields(headerFooterSettings.footer.center, metadata);
|
||||
const footerRight = processDynamicFields(headerFooterSettings.footer.right, metadata);
|
||||
|
||||
// Create LaTeX header for fancyhdr
|
||||
const latexHeader = `
|
||||
\\usepackage{fancyhdr}
|
||||
\\usepackage{lastpage}
|
||||
\\pagestyle{fancy}
|
||||
\\fancyhf{}
|
||||
\\lhead{${headerLeft.replace(/\\/g, '\\\\')}}
|
||||
\\chead{${headerCenter.replace(/\\/g, '\\\\')}}
|
||||
\\rhead{${headerRight.replace(/\\/g, '\\\\')}}
|
||||
\\lfoot{${footerLeft.replace(/\\/g, '\\\\')}}
|
||||
\\cfoot{${footerCenter.replace(/\$PAGE\$/g, '\\\\thepage').replace(/\$TOTAL\$/g, '\\\\pageref{LastPage}').replace(/\\/g, '\\\\')}}
|
||||
\\rfoot{${footerRight.replace(/\\/g, '\\\\')}}
|
||||
\\renewcommand{\\headrulewidth}{0.4pt}
|
||||
\\renewcommand{\\footrulewidth}{0.4pt}
|
||||
`;
|
||||
const headerFile = path.join(require('os').tmpdir(), `header_fallback_${Date.now()}.tex`);
|
||||
fs.writeFileSync(headerFile, latexHeader, 'utf-8');
|
||||
pandocCmd += ` --include-in-header="${headerFile}"`;
|
||||
}
|
||||
|
||||
// Add all other options
|
||||
if (options.template && options.template !== 'default') {
|
||||
pandocCmd += ` --template="${options.template}"`;
|
||||
@@ -1354,7 +1502,7 @@ function exportToHTML(outputFile) {
|
||||
const marked = require('marked');
|
||||
const markdownContent = fs.readFileSync(currentFile, 'utf8');
|
||||
const htmlContent = marked.parse(markdownContent);
|
||||
|
||||
|
||||
const fullHtml = `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
@@ -1374,21 +1522,37 @@ function exportToHTML(outputFile) {
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
/* Inline code */
|
||||
code {
|
||||
background: #f4f4f4;
|
||||
padding: 2px 4px;
|
||||
border-radius: 3px;
|
||||
font-family: Consolas, Monaco, 'Courier New', monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
/* Code blocks - critical for ASCII art preservation */
|
||||
pre {
|
||||
background: #f4f4f4;
|
||||
background: #f5f5f5;
|
||||
padding: 1em;
|
||||
border-radius: 5px;
|
||||
overflow-x: auto;
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
font-family: Consolas, Monaco, 'Courier New', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
border: 1px solid #e0e0e0;
|
||||
margin: 1em 0;
|
||||
}
|
||||
pre code {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
display: block;
|
||||
}
|
||||
blockquote {
|
||||
border-left: 4px solid #ddd;
|
||||
@@ -1420,6 +1584,18 @@ function exportToHTML(outputFile) {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
@media print {
|
||||
pre {
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
overflow: visible;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
pre code {
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -1442,7 +1618,7 @@ function exportToPDFElectron(outputFile) {
|
||||
const marked = require('marked');
|
||||
const markdownContent = fs.readFileSync(currentFile, 'utf8');
|
||||
const htmlContent = marked.parse(markdownContent);
|
||||
|
||||
|
||||
const fullHtml = `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
@@ -1462,21 +1638,38 @@ function exportToPDFElectron(outputFile) {
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
/* Inline code */
|
||||
code {
|
||||
background: #f4f4f4;
|
||||
padding: 2px 4px;
|
||||
border-radius: 3px;
|
||||
font-family: Consolas, Monaco, 'Courier New', monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
/* Code blocks - critical for ASCII art preservation */
|
||||
pre {
|
||||
background: #f4f4f4;
|
||||
background: #f5f5f5;
|
||||
padding: 1em;
|
||||
border-radius: 5px;
|
||||
overflow-x: auto;
|
||||
overflow-x: visible;
|
||||
overflow-y: visible;
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
font-family: Consolas, Monaco, 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
border: 1px solid #e0e0e0;
|
||||
margin: 1em 0;
|
||||
}
|
||||
pre code {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
display: block;
|
||||
}
|
||||
blockquote {
|
||||
border-left: 4px solid #ddd;
|
||||
@@ -1503,6 +1696,16 @@ function exportToPDFElectron(outputFile) {
|
||||
}
|
||||
@media print {
|
||||
body { padding: 20px; }
|
||||
pre {
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
overflow: visible;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
pre code {
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
@@ -2204,6 +2407,9 @@ function buildPandocCommand(content, format, outputPath) {
|
||||
switch (format) {
|
||||
case 'pdf':
|
||||
command += ' --pdf-engine=xelatex --variable geometry:margin=1in';
|
||||
// Add monospace font settings for code blocks (ASCII art preservation)
|
||||
command += ' -V monofont="Consolas"';
|
||||
command += ' --highlight-style=tango';
|
||||
|
||||
// Add header/footer if enabled
|
||||
if (headerFooterSettings.enabled) {
|
||||
|
||||
+6
-14
@@ -2658,16 +2658,9 @@ function saveHeaderFooterSettings() {
|
||||
closeHeaderFooterDialog();
|
||||
}
|
||||
|
||||
// Handle logo file selection
|
||||
function handleLogoSelection(position) {
|
||||
const input = document.getElementById(`${position}-logo`);
|
||||
|
||||
if (input.files && input.files[0]) {
|
||||
const filePath = input.files[0].path;
|
||||
|
||||
// Send to main process to save
|
||||
ipcRenderer.send('save-header-footer-logo', { position, filePath });
|
||||
}
|
||||
// Handle logo browsing - ask main process to show open dialog
|
||||
function browseForLogo(position) {
|
||||
ipcRenderer.send('browse-header-footer-logo', position);
|
||||
}
|
||||
|
||||
// Handle logo saved confirmation
|
||||
@@ -2678,7 +2671,6 @@ ipcRenderer.on('header-footer-logo-saved', (event, { position, path }) => {
|
||||
// Clear logo
|
||||
function clearLogo(position) {
|
||||
ipcRenderer.send('clear-header-footer-logo', position);
|
||||
document.getElementById(`${position}-logo`).value = '';
|
||||
document.getElementById(`${position}-logo-preview`).textContent = '';
|
||||
}
|
||||
|
||||
@@ -2721,9 +2713,9 @@ document.querySelectorAll('.field-insert-btn').forEach(btn => {
|
||||
});
|
||||
});
|
||||
|
||||
// Logo file inputs
|
||||
document.getElementById('header-logo').addEventListener('change', () => handleLogoSelection('header'));
|
||||
document.getElementById('footer-logo').addEventListener('change', () => handleLogoSelection('footer'));
|
||||
// Logo browse buttons
|
||||
document.getElementById('header-logo-browse').addEventListener('click', () => browseForLogo('header'));
|
||||
document.getElementById('footer-logo-browse').addEventListener('click', () => browseForLogo('footer'));
|
||||
|
||||
// Logo clear buttons
|
||||
document.getElementById('header-logo-clear').addEventListener('click', () => clearLogo('header'));
|
||||
|
||||
@@ -3312,6 +3312,22 @@ body.printing-no-styles .preview-content pre,
|
||||
background: var(--button-hover, #5a6268);
|
||||
}
|
||||
|
||||
.browse-btn {
|
||||
padding: 6px 12px;
|
||||
background: var(--accent-color, #007bff);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
transition: background 0.2s ease;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.browse-btn:hover {
|
||||
background: var(--accent-hover, #0056b3);
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
padding: 6px 12px;
|
||||
background: var(--danger-color, #dc3545);
|
||||
|
||||
+55
-26
@@ -253,23 +253,42 @@ class WordTemplateExporter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create code block XML
|
||||
* Create code block XML - renders each line as separate paragraph
|
||||
* to preserve exact formatting like in preview (monospace, no wrapping)
|
||||
* Background shading applied at paragraph level to extend full width
|
||||
*/
|
||||
createCodeBlockXml(code) {
|
||||
const escapedCode = this.escapeXml(code);
|
||||
const lines = code.split('\n');
|
||||
let xml = '';
|
||||
|
||||
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>`;
|
||||
lines.forEach((line, index) => {
|
||||
const escapedLine = this.escapeXml(line);
|
||||
|
||||
// Each line gets its own paragraph with exact spacing and no wrapping
|
||||
// Shading (shd) is at paragraph level so background extends full width
|
||||
xml += `<w:p>
|
||||
<w:pPr>
|
||||
<w:pStyle w:val="Normal"/>
|
||||
<w:shd w:val="clear" w:color="auto" w:fill="F5F5F5"/>
|
||||
<w:spacing w:before="${index === 0 ? '120' : '0'}" w:after="${index === lines.length - 1 ? '120' : '0'}" w:line="240" w:lineRule="exact"/>
|
||||
<w:ind w:left="284" w:right="284"/>
|
||||
<w:jc w:val="left"/>
|
||||
<w:keepLines/>
|
||||
<w:wordWrap w:val="0"/>
|
||||
</w:pPr>
|
||||
<w:r>
|
||||
<w:rPr>
|
||||
<w:rFonts w:ascii="Consolas" w:hAnsi="Consolas" w:cs="Consolas"/>
|
||||
<w:sz w:val="18"/>
|
||||
<w:szCs w:val="18"/>
|
||||
<w:noProof/>
|
||||
</w:rPr>
|
||||
<w:t xml:space="preserve">${escapedLine}</w:t>
|
||||
</w:r>
|
||||
</w:p>`;
|
||||
});
|
||||
|
||||
return xml;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -397,15 +416,22 @@ class WordTemplateExporter {
|
||||
isAsciiArt(line) {
|
||||
// Don't treat markdown tables as ASCII art
|
||||
if (line.trim().startsWith('|') && line.trim().endsWith('|')) {
|
||||
return false;
|
||||
// Check if it's a proper markdown table (has multiple cells)
|
||||
const cells = line.split('|').filter(c => c.trim());
|
||||
if (cells.length >= 2) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Common ASCII art characters
|
||||
// Common ASCII art characters (Unicode box drawing and symbols)
|
||||
const asciiArtChars = [
|
||||
'─', '│', '┌', '┐', '└', '┘', '├', '┤', '┬', '┴', '┼', // Box drawing
|
||||
'═', '║', '╔', '╗', '╚', '╝', '╠', '╣', '╦', '╩', '╬', // Double box
|
||||
'▲', '▼', '◄', '►', '♦', '●', '○', '■', '□', // Shapes
|
||||
'↓', '→', '←', '↑', // Arrows
|
||||
'╭', '╮', '╯', '╰', // Rounded corners
|
||||
'▲', '▼', '◄', '►', '♦', '●', '○', '■', '□', '◆', '◇', // Shapes
|
||||
'↓', '→', '←', '↑', '↔', '↕', '⇒', '⇐', '⇓', '⇑', // Arrows
|
||||
'┃', '━', '┏', '┓', '┗', '┛', '┣', '┫', '┳', '┻', '╋', // Heavy box
|
||||
'░', '▒', '▓', '█', // Shading
|
||||
];
|
||||
|
||||
// Check for box drawing characters
|
||||
@@ -415,14 +441,18 @@ class WordTemplateExporter {
|
||||
|
||||
// Check for ASCII box patterns with regular characters
|
||||
const asciiPatterns = [
|
||||
/^\s*\+[-=_]+\+/, // +-----+
|
||||
/^\s*\+[-=_+]+\+/, // +-----+ or +=====+
|
||||
/^\s*\|[-=_]{3,}\|/, // |-----|
|
||||
/^\s*[-=_]{5,}$/, // -----
|
||||
/^\s*\+[-]+\+[-]+\+/, // +---+---+
|
||||
/^\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]
|
||||
/^\s*\[[^\]]+\]\s*$/, // [Step in brackets]
|
||||
/^\s*<[-=]+>/, // <----> or <====>
|
||||
/^\s*\/[-_\\\/]+\//, // /----/
|
||||
/^\s*\*[-=\*]+\*/, // *----*
|
||||
];
|
||||
|
||||
return asciiPatterns.some(pattern => pattern.test(line));
|
||||
@@ -430,6 +460,7 @@ class WordTemplateExporter {
|
||||
|
||||
/**
|
||||
* Create ASCII art XML with monospace font
|
||||
* Background shading applied at paragraph level to extend full width
|
||||
*/
|
||||
createAsciiArtXml(asciiContent) {
|
||||
// Split ASCII art into individual lines and create a paragraph for each
|
||||
@@ -437,10 +468,13 @@ class WordTemplateExporter {
|
||||
let xml = '';
|
||||
|
||||
lines.forEach((line, index) => {
|
||||
// Shading (shd) is at paragraph level so background extends full width
|
||||
xml += `<w:p>
|
||||
<w:pPr>
|
||||
<w:pStyle w:val="Normal"/>
|
||||
<w:shd w:val="clear" w:color="auto" w:fill="F5F5F5"/>
|
||||
<w:spacing w:before="${index === 0 ? '120' : '0'}" w:after="${index === lines.length - 1 ? '120' : '0'}" w:line="240" w:lineRule="exact"/>
|
||||
<w:ind w:left="0" w:right="0"/>
|
||||
<w:ind w:left="284" w:right="284"/>
|
||||
<w:jc w:val="left"/>
|
||||
<w:keepLines/>
|
||||
<w:wordWrap w:val="0"/>
|
||||
@@ -453,7 +487,6 @@ class WordTemplateExporter {
|
||||
if (hasArrow) {
|
||||
// Split line into parts and color arrows red
|
||||
let remainingLine = line;
|
||||
let processedText = '';
|
||||
|
||||
while (remainingLine.length > 0) {
|
||||
let foundArrow = false;
|
||||
@@ -479,7 +512,6 @@ class WordTemplateExporter {
|
||||
<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>
|
||||
@@ -494,7 +526,6 @@ class WordTemplateExporter {
|
||||
<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>
|
||||
@@ -510,7 +541,6 @@ class WordTemplateExporter {
|
||||
<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>
|
||||
@@ -526,7 +556,6 @@ class WordTemplateExporter {
|
||||
<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>
|
||||
|
||||
Reference in New Issue
Block a user