Compare commits

..
1 Commits
Author SHA1 Message Date
amitwhandClaude Sonnet 4.5 b281255e90 Release v2.0.0: Major Feature Update
New Features:
• Export Profiles - Save and load export configurations
• Mermaid.js diagram support - Render flowcharts, sequence diagrams, and more
• Command Palette (Ctrl+Shift+P) - Quick access to all commands
• GitHub Light/Dark preview themes - Beautiful code preview styling
• Table Generator - Interactive table creation tool
• ASCII Art Generator - Create text banners, boxes, and templates
• Resizable Preview Pane - Drag divider to adjust editor/preview sizes
• Pop-out Preview Window - Open preview in separate window with live sync

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

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

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-13 19:40:30 +05:30
6 changed files with 1994 additions and 25 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "pan-converter",
"version": "1.9.2",
"version": "2.0.0",
"description": "Cross-platform Markdown editor and converter using Pandoc",
"main": "src/main.js",
"scripts": {
+198
View File
@@ -11,6 +11,8 @@
<link rel="stylesheet" href="styles-modern.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/codemirror.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css">
<!-- Mermaid.js for diagram rendering -->
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
</head>
<body>
<div class="container">
@@ -148,6 +150,19 @@
</div>
<div class="export-dialog-body">
<!-- Simple/Advanced Export Toggle -->
<!-- Export Profiles -->
<div class="export-section export-profiles">
<label>Export Profile:</label>
<div class="profile-controls">
<select id="export-profile-select">
<option value="">Custom Settings</option>
</select>
<button id="save-profile-btn" type="button" title="Save current settings as profile">💾 Save</button>
<button id="delete-profile-btn" type="button" title="Delete selected profile">🗑️ Delete</button>
</div>
<small class="export-help">Save and reuse your favorite export configurations</small>
</div>
<div class="export-section export-mode-toggle">
<label class="checkbox-label">
<input type="checkbox" id="advanced-export-toggle">
@@ -243,6 +258,37 @@
</div> <!-- End advanced-export-options -->
<!-- Basic Export Options (always visible) -->
<div class="export-section basic-options">
<label>Page Size & Orientation:</label>
<div class="form-row">
<label for="page-size">Size:</label>
<select id="page-size">
<option value="a4">A4 (210×297mm)</option>
<option value="a3">A3 (297×420mm)</option>
<option value="a5">A5 (148×210mm)</option>
<option value="b4">B4 (250×353mm)</option>
<option value="b5">B5 (176×250mm)</option>
<option value="letter">Letter (8.5×11in)</option>
<option value="legal">Legal (8.5×14in)</option>
<option value="tabloid">Tabloid (11×17in)</option>
<option value="custom">Custom...</option>
</select>
</div>
<div class="form-row">
<label for="page-orientation">Orientation:</label>
<select id="page-orientation">
<option value="portrait">Portrait</option>
<option value="landscape">Landscape</option>
</select>
</div>
<div id="custom-page-size" class="form-row" style="display: none;">
<label>Custom Size:</label>
<input type="text" id="custom-width" placeholder="Width (e.g., 210mm)" style="width: 45%; display: inline-block;">
<span style="display: inline-block; width: 5%; text-align: center;">×</span>
<input type="text" id="custom-height" placeholder="Height (e.g., 297mm)" style="width: 45%; display: inline-block;">
</div>
</div>
<div class="export-section basic-options">
<label>Quick Export Settings:</label>
<div class="checkbox-group">
@@ -258,6 +304,154 @@
</div>
</div>
<!-- Command Palette -->
<div id="command-palette" class="command-palette hidden">
<div class="command-palette-content">
<input type="text" id="command-search" placeholder="Type a command..." autofocus>
<div id="command-list" class="command-list"></div>
<div class="command-palette-footer">
<span>↑↓ Navigate</span>
<span>Enter Execute</span>
<span>Esc Close</span>
</div>
</div>
</div>
<!-- Table Generator Dialog -->
<div id="table-generator-dialog" class="export-dialog hidden">
<div class="export-dialog-content" style="max-width: 500px;">
<div class="export-dialog-header">
<h3>📊 Table Generator</h3>
<button id="table-dialog-close" title="Close">×</button>
</div>
<div class="export-dialog-body">
<div class="export-section">
<label for="table-rows">Number of Rows:</label>
<input type="number" id="table-rows" min="1" max="50" value="3" style="width: 100px;">
<small>Total rows including header (if enabled)</small>
</div>
<div class="export-section">
<label for="table-cols">Number of Columns:</label>
<input type="number" id="table-cols" min="1" max="20" value="3" style="width: 100px;">
</div>
<div class="export-section">
<label class="checkbox-label">
<input type="checkbox" id="table-has-header" checked>
Include Header Row
</label>
<small>First row will be formatted as table headers</small>
</div>
<div class="export-section">
<label for="table-alignment">Column Alignment:</label>
<select id="table-alignment">
<option value="left">Left</option>
<option value="center">Center</option>
<option value="right">Right</option>
</select>
<small>Applies to all columns</small>
</div>
<div class="export-section">
<label for="table-preview">Preview:</label>
<pre id="table-preview" style="background: #f5f5f5; padding: 10px; border-radius: 4px; font-size: 12px; max-height: 300px; overflow: auto;"></pre>
</div>
</div>
<div class="export-dialog-footer">
<button id="table-generate-preview" class="btn-secondary">Update Preview</button>
<button id="table-insert" class="btn-primary">Insert Table</button>
<button id="table-cancel" class="btn-secondary">Cancel</button>
</div>
</div>
</div>
<!-- ASCII Art Generator Dialog -->
<div id="ascii-art-dialog" class="export-dialog hidden">
<div class="export-dialog-content" style="max-width: 700px;">
<div class="export-dialog-header">
<h3>🎨 ASCII Art Generator</h3>
<button id="ascii-dialog-close" title="Close">×</button>
</div>
<div class="export-dialog-body">
<div class="export-section">
<label>Mode:</label>
<div style="display: flex; gap: 10px; margin-bottom: 10px;">
<button id="ascii-mode-text" class="btn-secondary ascii-mode-btn active">Text Banner</button>
<button id="ascii-mode-box" class="btn-secondary ascii-mode-btn">Box/Frame</button>
<button id="ascii-mode-templates" class="btn-secondary ascii-mode-btn">Templates</button>
</div>
</div>
<!-- Text Banner Mode -->
<div id="ascii-text-mode" class="ascii-mode-section">
<div class="export-section">
<label for="ascii-text-input">Text to Convert:</label>
<input type="text" id="ascii-text-input" placeholder="Enter your text..." maxlength="50">
</div>
<div class="export-section">
<label for="ascii-font-style">Style:</label>
<select id="ascii-font-style">
<option value="standard">Standard</option>
<option value="banner">Banner</option>
<option value="block">Block</option>
<option value="bubble">Bubble</option>
<option value="digital">Digital</option>
</select>
</div>
</div>
<!-- Box/Frame Mode -->
<div id="ascii-box-mode" class="ascii-mode-section hidden">
<div class="export-section">
<label for="ascii-box-text">Text Content:</label>
<textarea id="ascii-box-text" rows="3" placeholder="Enter text for the box..."></textarea>
</div>
<div class="export-section">
<label for="ascii-box-style">Box Style:</label>
<select id="ascii-box-style">
<option value="single">Single Line (─│┌┐└┘)</option>
<option value="double">Double Line (═║╔╗╚╝)</option>
<option value="rounded">Rounded (─│╭╮╰╯)</option>
<option value="bold">Bold (━┃┏┓┗┛)</option>
<option value="ascii">ASCII (+|-)</option>
</select>
</div>
<div class="export-section">
<label for="ascii-box-padding">Padding:</label>
<input type="number" id="ascii-box-padding" min="1" max="10" value="2" style="width: 80px;">
</div>
</div>
<!-- Templates Mode -->
<div id="ascii-templates-mode" class="ascii-mode-section hidden">
<div class="export-section">
<label>Select Template:</label>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px;">
<button class="btn-secondary ascii-template-btn" data-template="arrow-right">Arrow →</button>
<button class="btn-secondary ascii-template-btn" data-template="arrow-down">Arrow ↓</button>
<button class="btn-secondary ascii-template-btn" data-template="check">Checkmark ✓</button>
<button class="btn-secondary ascii-template-btn" data-template="divider">Divider</button>
<button class="btn-secondary ascii-template-btn" data-template="header">Header</button>
<button class="btn-secondary ascii-template-btn" data-template="flowchart">Flowchart</button>
</div>
</div>
</div>
<div class="export-section">
<label for="ascii-preview">Preview:</label>
<pre id="ascii-preview" style="background: #f5f5f5; padding: 15px; border-radius: 4px; font-size: 12px; max-height: 400px; overflow: auto; font-family: 'Courier New', monospace; line-height: 1.2;"></pre>
</div>
</div>
<div class="export-dialog-footer">
<button id="ascii-generate" class="btn-secondary">Generate Preview</button>
<button id="ascii-insert" class="btn-primary">Insert ASCII Art</button>
<button id="ascii-cancel" class="btn-secondary">Cancel</button>
</div>
</div>
</div>
<!-- Universal File Converter Dialog -->
<div id="universal-converter-dialog" class="export-dialog hidden">
<div class="export-dialog-content">
@@ -1049,7 +1243,11 @@
<textarea id="editor-1" class="editor-textarea"></textarea>
</div>
</div>
<div class="pane-resizer" id="pane-resizer-1" title="Drag to resize"></div>
<div id="preview-pane-1" class="pane">
<div class="preview-header">
<button class="preview-popout-btn" id="preview-popout-1" title="Pop out preview in new window"></button>
</div>
<div id="preview-1" class="preview-content"></div>
</div>
</div>
+200 -4
View File
@@ -78,6 +78,66 @@ let headerFooterSettings = {
}
};
// Page Size Definitions (in twentieths of a point for Word, mm/inches for Pandoc)
const PAGE_SIZES = {
a4: {
name: 'A4',
pandoc: 'a4',
word: { width: 11906, height: 16838 }, // 210×297mm
dimensions: '210×297mm'
},
a3: {
name: 'A3',
pandoc: 'a3',
word: { width: 16838, height: 23811 }, // 297×420mm
dimensions: '297×420mm'
},
a5: {
name: 'A5',
pandoc: 'a5',
word: { width: 8391, height: 11906 }, // 148×210mm
dimensions: '148×210mm'
},
b4: {
name: 'B4',
pandoc: 'b4',
word: { width: 14170, height: 20015 }, // 250×353mm
dimensions: '250×353mm'
},
b5: {
name: 'B5',
pandoc: 'b5',
word: { width: 9979, height: 14170 }, // 176×250mm
dimensions: '176×250mm'
},
letter: {
name: 'Letter',
pandoc: 'letter',
word: { width: 12240, height: 15840 }, // 8.5×11in
dimensions: '8.5×11in'
},
legal: {
name: 'Legal',
pandoc: 'legal',
word: { width: 12240, height: 20160 }, // 8.5×14in
dimensions: '8.5×14in'
},
tabloid: {
name: 'Tabloid',
pandoc: 'tabloid',
word: { width: 15840, height: 24480 }, // 11×17in
dimensions: '11×17in'
}
};
// Default page settings
let pageSettings = {
size: 'a4',
orientation: 'portrait',
customWidth: null,
customHeight: null
};
// Handle single instance lock for Windows file association
// When a file is double-clicked and the app is already running,
// Windows tries to start a second instance. We prevent this and
@@ -505,6 +565,23 @@ function createMenu() {
}
]
},
{
label: 'Tools',
submenu: [
{
label: 'Table Generator',
click: () => mainWindow.webContents.send('show-table-generator')
},
{
label: 'ASCII Art Generator',
click: () => mainWindow.webContents.send('show-ascii-generator')
},
{
label: 'Document Compare',
click: () => mainWindow.webContents.send('show-document-compare')
}
]
},
{
label: 'Help',
submenu: [
@@ -515,7 +592,7 @@ function createMenu() {
type: 'info',
title: 'About PanConverter',
message: 'PanConverter',
detail: 'A cross-platform Markdown editor and converter using Pandoc.\n\nVersion: 1.9.2\nAuthor: Amit Haridas\nEmail: amit.wh@gmail.com\nLicense: MIT\n\nFeatures:\n• Preserved ASCII art, charts, tables, flowcharts in exports with full-width background\n• Custom Headers & Footers for PDF, DOCX, ODT, and PowerPoint exports\n• Dynamic field support: $PAGE$, $TOTAL$, $DATE$, $TIME$, $TITLE$, $AUTHOR$, $FILENAME$\n• Logo/image embedding in headers and footers\n• Modern glassmorphism UI with gradient backgrounds\n• Enhanced PDF export via Word template with configurable start page\n• Configurable template settings (start page selection)\n• Streamlined PDF Editor UI (merge, split, compress, rotate, watermark, encrypt)\n• Universal File Converter (LibreOffice, ImageMagick, FFmpeg, Pandoc)\n• Windows Explorer context menu integration\n• Tabbed interface for multiple files\n• Advanced markdown editing with live preview\n• Real-time preview updates while typing\n• Full toolbar markdown editing functions\n• Enhanced PDF export with built-in Electron fallback\n• Enhanced Word export with template support (single file & batch)\n• File association support for .md files\n• Command-line interface for batch conversion\n• Advanced export options with templates and metadata\n• Batch file conversion with progress tracking\n• Improved preview typography and spacing\n• Adjustable font sizes via menu (Ctrl+Shift+Plus/Minus)\n• Complete theme support including Monokai fixes\n• Find & replace with match highlighting\n• Line numbers and auto-indentation\n• Export to multiple formats via Pandoc\n• PowerPoint & presentation export\n• Export tables to Excel/ODS spreadsheets\n• Document import & conversion\n• Table creation helper\n• 22 beautiful themes (including Dracula, Nord, Tokyo Night, Gruvbox, Ayu, Concrete, and more)\n• Undo/redo functionality\n• Live word count and statistics',
detail: 'A cross-platform Markdown editor and converter using Pandoc.\n\nVersion: 2.0.0\nAuthor: Amit Haridas\nEmail: amit.wh@gmail.com\nLicense: MIT\n\n✨ New in v2.0.0:\n• Export Profiles - Save and load export configurations\n• Mermaid.js diagram support - Render flowcharts, sequence diagrams, and more\n• Command Palette (Ctrl+Shift+P) - Quick access to all commands\n• GitHub Light/Dark preview themes - Beautiful code preview styling\n• Table Generator - Interactive table creation tool\n• ASCII Art Generator - Create text banners, boxes, and templates\n• Resizable Preview Pane - Drag divider to adjust editor/preview sizes\n• Pop-out Preview Window - Open preview in separate window with live sync\n\nCore Features:\n• Configurable page sizes (A3, A4, A5, B4, B5, Letter, Legal, Tabloid, Custom) with orientation support\n• Page size support for all export formats (PDF, DOCX, ODT, PowerPoint) and batch conversion\n• Preserved ASCII art, charts, tables, flowcharts in exports with full-width background\n• Custom Headers & Footers for PDF, DOCX, ODT, and PowerPoint exports\n• Dynamic field support: $PAGE$, $TOTAL$, $DATE$, $TIME$, $TITLE$, $AUTHOR$, $FILENAME$\n• Logo/image embedding in headers and footers\n• Modern glassmorphism UI with gradient backgrounds\n• Enhanced PDF export via Word template with configurable start page\n• Streamlined PDF Editor UI (merge, split, compress, rotate, watermark, encrypt)\n• Universal File Converter (LibreOffice, ImageMagick, FFmpeg, Pandoc)\n• Windows Explorer context menu integration\n• Tabbed interface for multiple files\n• Advanced markdown editing with live preview\n• Real-time preview updates while typing\n• Full toolbar markdown editing functions\n• Enhanced PDF export with built-in Electron fallback\n• Enhanced Word export with template support (single file & batch)\n• File association support for .md files\n• Command-line interface for batch conversion\n• Advanced export options with templates and metadata\n• Batch file conversion with progress tracking\n• Adjustable font sizes via menu (Ctrl+Shift+Plus/Minus)\n• 22 beautiful themes (including Dracula, Nord, Tokyo Night, Gruvbox, Ayu, Concrete, and more)\n• Find & replace with match highlighting\n• Line numbers and auto-indentation\n• Undo/redo functionality\n• Live word count and statistics',
buttons: ['OK']
});
}
@@ -672,6 +749,18 @@ ipcMain.on('save-header-footer-settings', (event, settings) => {
});
});
// Get current page settings
ipcMain.on('get-page-settings', (event) => {
event.reply('page-settings-data', pageSettings);
});
// Update page settings from export dialog
ipcMain.on('update-page-settings', (event, settings) => {
pageSettings = settings;
store.set('pageSettings', pageSettings);
console.log('Page settings updated:', pageSettings);
});
// Save header/footer logo image
// Browse for header/footer logo
ipcMain.on('browse-header-footer-logo', async (event, position) => {
@@ -794,6 +883,67 @@ function processDynamicFields(text, metadata = {}) {
}
// Add headers/footers to DOCX file using PizZip and docx4js
// Function to set page size in DOCX files
async function setDocxPageSize(docxPath) {
try {
const PizZip = require('pizzip');
// Read the DOCX file
const docxBuffer = fs.readFileSync(docxPath);
const zip = new PizZip(docxBuffer);
// Get document.xml
let documentXml = zip.file('word/document.xml').asText();
// Get page dimensions
let width, height;
const pageSize = PAGE_SIZES[pageSettings.size];
if (pageSize) {
width = pageSize.word.width;
height = pageSize.word.height;
} else if (pageSettings.customWidth && pageSettings.customHeight) {
// Parse custom dimensions (convert to twentieths of a point)
// Note: This is simplified - production code should handle various units
width = parseInt(pageSettings.customWidth) || 11906;
height = parseInt(pageSettings.customHeight) || 16838;
} else {
// Default to A4
width = 11906;
height = 16838;
}
// Swap dimensions for landscape
if (pageSettings.orientation === 'landscape') {
[width, height] = [height, width];
}
// Update all <w:pgSz> elements in section properties
const pgSzRegex = /<w:pgSz[^>]*\/>/g;
documentXml = documentXml.replace(pgSzRegex, () => {
return `<w:pgSz w:w="${width}" w:h="${height}" w:orient="${pageSettings.orientation}"/>`;
});
// If no pgSz found, add it to all sectPr elements
if (!pgSzRegex.test(documentXml)) {
const sectPrRegex = /<w:sectPr[^>]*>/g;
documentXml = documentXml.replace(sectPrRegex, (match) => {
return `${match}<w:pgSz w:w="${width}" w:h="${height}" w:orient="${pageSettings.orientation}"/>`;
});
}
// Save updated document.xml
zip.file('word/document.xml', documentXml);
// Write modified DOCX
const newDocxBuffer = zip.generate({ type: 'nodebuffer' });
fs.writeFileSync(docxPath, newDocxBuffer);
} catch (error) {
console.error('Failed to set DOCX page size:', error);
}
}
async function addHeaderFooterToDocx(docxPath, metadata = {}) {
if (!headerFooterSettings.enabled) return;
@@ -939,8 +1089,8 @@ async function exportWordWithTemplate() {
if (result.canceled) return;
// Create exporter instance with selected template and start page
const exporter = new WordTemplateExporter(wordTemplatePath, templateStartPage);
// Create exporter instance with selected template, start page, and page settings
const exporter = new WordTemplateExporter(wordTemplatePath, templateStartPage, pageSettings);
// Convert markdown to DOCX
await exporter.convert(content, result.filePath);
@@ -1444,6 +1594,16 @@ function exportWithPandoc(pandocCmd, outputFile, format) {
console.warn(`Pandoc stderr (non-fatal):`, stderr);
}
// Set page size for DOCX
if (format === 'docx') {
try {
await setDocxPageSize(outputFile);
console.log('Page size set for DOCX');
} catch (pageSizeError) {
console.error('Error setting page size for DOCX:', pageSizeError);
}
}
// Add headers/footers to DOCX if enabled
if (format === 'docx' && headerFooterSettings.enabled) {
try {
@@ -1461,6 +1621,16 @@ function exportWithPandoc(pandocCmd, outputFile, format) {
}
}
// Set page size for ODT
if (format === 'odt') {
try {
await setDocxPageSize(outputFile); // ODT has similar structure
console.log('Page size set for ODT');
} catch (pageSizeError) {
console.error('Error setting page size for ODT:', pageSizeError);
}
}
// Add headers/footers to ODT if enabled
if (format === 'odt' && headerFooterSettings.enabled) {
// ODT format is similar to DOCX in structure, we could implement this
@@ -2198,9 +2368,13 @@ function performBatchConversion(inputFolder, outputFolder, format, options) {
// Add PDF-specific options with header/footer support
if (format === 'pdf') {
const pdfEngine = options.pdfEngine || 'xelatex';
pandocCmd += ` --pdf-engine="${pdfEngine}"`;
pandocCmd += ` --pdf-engine=${pdfEngine}`;
if (options.geometry) pandocCmd += ` -V geometry:"${options.geometry}"`;
// Add monospace font settings for code blocks (ASCII art preservation)
pandocCmd += ' -V monofont="Consolas"';
pandocCmd += ' --highlight-style=tango';
// Add header/footer if enabled
if (headerFooterSettings.enabled) {
const filename = path.basename(inputFile, path.extname(inputFile));
@@ -2407,6 +2581,22 @@ function buildPandocCommand(content, format, outputPath) {
switch (format) {
case 'pdf':
command += ' --pdf-engine=xelatex --variable geometry:margin=1in';
// Add page size and orientation
const pageSize = PAGE_SIZES[pageSettings.size];
if (pageSize) {
command += ` -V geometry:papersize=${pageSize.pandoc}`;
} else if (pageSettings.customWidth && pageSettings.customHeight) {
// Custom page size
command += ` -V geometry:paperwidth=${pageSettings.customWidth}`;
command += ` -V geometry:paperheight=${pageSettings.customHeight}`;
}
// Add orientation
if (pageSettings.orientation === 'landscape') {
command += ' -V geometry:landscape';
}
// Add monospace font settings for code blocks (ASCII art preservation)
command += ' -V monofont="Consolas"';
command += ' --highlight-style=tango';
@@ -2495,6 +2685,12 @@ app.whenReady().then(() => {
headerFooterSettings = savedHFSettings;
}
// Load page size settings
const savedPageSettings = store.get('pageSettings', null);
if (savedPageSettings) {
pageSettings = savedPageSettings;
}
// Check for command line conversion requests
const args = process.argv.slice(2);
if (args.length >= 2 && (args[0] === '--convert' || args[0] === '--convert-to')) {
+1138
View File
File diff suppressed because it is too large Load Diff
+364 -9
View File
@@ -10,6 +10,10 @@ body {
height: 100vh;
}
.hidden {
display: none !important;
}
.container {
display: flex;
flex-direction: column;
@@ -156,7 +160,53 @@ body {
}
.pane:first-child {
border-right: 1px solid #ddd;
border-right: none;
}
/* Pane Resizer */
.pane-resizer {
width: 6px;
background: #e0e0e0;
cursor: col-resize;
position: relative;
flex-shrink: 0;
transition: background 0.2s;
}
.pane-resizer:hover {
background: #999;
}
.pane-resizer:active {
background: #666;
}
/* Preview Header */
.preview-header {
display: flex;
justify-content: flex-end;
align-items: center;
padding: 4px 8px;
background: #f6f8fa;
border-bottom: 1px solid #dfe2e5;
min-height: 32px;
}
.preview-popout-btn {
background: transparent;
border: 1px solid #d0d7de;
border-radius: 4px;
padding: 4px 8px;
cursor: pointer;
font-size: 14px;
color: #57606a;
transition: all 0.2s;
}
.preview-popout-btn:hover {
background: #f3f4f6;
border-color: #8c959f;
color: #24292f;
}
.editor-textarea {
@@ -207,10 +257,15 @@ body {
background: #fff;
}
#preview {
/* GitHub Light Theme for Preview (Default) */
#preview, [id^="preview-"], .preview-content {
max-width: 800px;
margin: 0 auto;
font-size: 15px;
padding: 20px;
background: #ffffff;
color: #24292f;
line-height: 1.6;
}
/* Status Bar */
@@ -251,22 +306,25 @@ body {
line-height: 1.6;
}
#preview code, .preview-content code {
#preview code, .preview-content code, [id^="preview-"] code {
padding: 0.2em 0.4em;
margin: 0;
font-size: 85%;
background-color: rgba(27,31,35,0.05);
border-radius: 3px;
font-family: 'SF Mono', Monaco, 'Courier New', monospace;
background-color: #f6f8fa;
border: 1px solid #d0d7de;
border-radius: 6px;
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
color: #24292f;
}
#preview pre, .preview-content pre {
#preview pre, .preview-content pre, [id^="preview-"] pre {
padding: 16px;
overflow: auto;
font-size: 85%;
line-height: 1.45;
background-color: #f6f8fa;
border-radius: 3px;
border: 1px solid #d0d7de;
border-radius: 6px;
margin-bottom: 16px;
}
@@ -289,6 +347,126 @@ body {
margin-bottom: 16px;
}
/* GitHub Dark Theme for Preview */
body.theme-dark #preview,
body.theme-dark [id^="preview-"],
body.theme-dark .preview-content {
background: #0d1117;
color: #c9d1d9;
}
body.theme-dark #preview h1,
body.theme-dark [id^="preview-"] h1,
body.theme-dark .preview-content h1 {
color: #c9d1d9;
border-bottom-color: #21262d;
}
body.theme-dark #preview h2,
body.theme-dark [id^="preview-"] h2,
body.theme-dark .preview-content h2 {
color: #c9d1d9;
border-bottom-color: #21262d;
}
body.theme-dark #preview h3,
body.theme-dark #preview h4,
body.theme-dark #preview h5,
body.theme-dark #preview h6,
body.theme-dark [id^="preview-"] h3,
body.theme-dark [id^="preview-"] h4,
body.theme-dark [id^="preview-"] h5,
body.theme-dark [id^="preview-"] h6,
body.theme-dark .preview-content h3,
body.theme-dark .preview-content h4,
body.theme-dark .preview-content h5,
body.theme-dark .preview-content h6 {
color: #c9d1d9;
}
body.theme-dark #preview p,
body.theme-dark [id^="preview-"] p,
body.theme-dark .preview-content p {
color: #c9d1d9;
}
body.theme-dark #preview code,
body.theme-dark [id^="preview-"] code,
body.theme-dark .preview-content code {
background-color: #161b22;
border-color: #30363d;
color: #c9d1d9;
}
body.theme-dark #preview pre,
body.theme-dark [id^="preview-"] pre,
body.theme-dark .preview-content pre {
background-color: #161b22;
border-color: #30363d;
}
body.theme-dark #preview blockquote,
body.theme-dark [id^="preview-"] blockquote,
body.theme-dark .preview-content blockquote {
color: #8b949e;
border-left-color: #3b434b;
}
body.theme-dark #preview a,
body.theme-dark [id^="preview-"] a,
body.theme-dark .preview-content a {
color: #58a6ff;
}
body.theme-dark #preview a:hover,
body.theme-dark [id^="preview-"] a:hover,
body.theme-dark .preview-content a:hover {
color: #79c0ff;
}
body.theme-dark #preview table,
body.theme-dark [id^="preview-"] table,
body.theme-dark .preview-content table {
border-color: #30363d;
}
body.theme-dark #preview table th,
body.theme-dark [id^="preview-"] table th,
body.theme-dark .preview-content table th {
background-color: #161b22;
color: #c9d1d9;
border-color: #30363d;
}
body.theme-dark #preview table td,
body.theme-dark [id^="preview-"] table td,
body.theme-dark .preview-content table td {
border-color: #30363d;
color: #c9d1d9;
}
body.theme-dark #preview hr,
body.theme-dark [id^="preview-"] hr,
body.theme-dark .preview-content hr {
background-color: #21262d;
border-color: #21262d;
}
body.theme-dark #preview ul,
body.theme-dark #preview ol,
body.theme-dark [id^="preview-"] ul,
body.theme-dark [id^="preview-"] ol,
body.theme-dark .preview-content ul,
body.theme-dark .preview-content ol {
color: #c9d1d9;
}
body.theme-dark #preview li,
body.theme-dark [id^="preview-"] li,
body.theme-dark .preview-content li {
color: #c9d1d9;
}
#preview ul, #preview ol, .preview-content ul, .preview-content ol {
padding-left: 2em;
margin-bottom: 16px;
@@ -326,10 +504,11 @@ body {
#preview table th, .preview-content table th {
font-weight: 600;
background-color: #f6f8fa;
}
#preview table tr:nth-child(2n), .preview-content table tr:nth-child(2n) {
background-color: #f6f8fa;
background-color: #ffffff;
}
/* Theme: Dark */
@@ -1516,6 +1695,42 @@ body.theme-github .line-numbers {
color: #586069;
}
/* Export Profiles Styles */
.export-profiles {
border-bottom: 1px solid #ddd;
padding-bottom: 15px;
margin-bottom: 15px;
}
.profile-controls {
display: flex;
gap: 8px;
align-items: center;
margin-top: 8px;
}
.profile-controls select {
flex: 1;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.profile-controls button {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
background: #f5f5f5;
cursor: pointer;
font-size: 13px;
transition: all 0.2s;
}
.profile-controls button:hover {
background: #e8e8e8;
}
/* Advanced Export Toggle Styles */
.export-mode-toggle {
border-bottom: 1px solid #ddd;
@@ -3446,3 +3661,143 @@ body[data-theme="dark"] .field-option:hover {
background: #0d6efd;
color: white;
}
/* Mermaid Diagram Styles */
.mermaid {
background: #f9f9f9;
padding: 20px;
border-radius: 8px;
margin: 20px 0;
text-align: center;
border: 1px solid #e0e0e0;
}
.mermaid svg {
max-width: 100%;
height: auto;
}
body.theme-dark .mermaid {
background: #2d2d2d;
border-color: #444;
}
body.theme-solarized .mermaid {
background: #fdf6e3;
border-color: #eee8d5;
}
body.theme-monokai .mermaid {
background: #2f2f2f;
border-color: #49483e;
}
body.theme-github .mermaid {
background: #f6f8fa;
border-color: #e1e4e8;
}
/* Command Palette Styles */
.command-palette {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
align-items: flex-start;
justify-content: center;
padding-top: 100px;
z-index: 10000;
}
.command-palette.hidden {
display: none;
}
.command-palette-content {
background: #fff;
border-radius: 12px;
width: 600px;
max-width: 90%;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
overflow: hidden;
}
#command-search {
width: 100%;
padding: 20px;
font-size: 18px;
border: none;
border-bottom: 1px solid #e0e0e0;
outline: none;
}
.command-list {
max-height: 400px;
overflow-y: auto;
}
.command-item {
padding: 12px 20px;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
transition: background 0.15s;
}
.command-item:hover,
.command-item.selected {
background: #f0f0f0;
}
.command-name {
font-weight: 500;
}
.command-shortcut {
font-size: 12px;
color: #666;
background: #f5f5f5;
padding: 3px 8px;
border-radius: 3px;
}
.command-palette-footer {
padding: 12px 20px;
background: #f9f9f9;
border-top: 1px solid #e0e0e0;
display: flex;
gap: 20px;
font-size: 12px;
color: #666;
}
/* Dark theme support for command palette */
body.theme-dark .command-palette-content {
background: #2d2d2d;
color: #f0f0f0;
}
body.theme-dark #command-search {
background: #2d2d2d;
color: #f0f0f0;
border-color: #444;
}
body.theme-dark .command-item:hover,
body.theme-dark .command-item.selected {
background: #3d3d3d;
}
body.theme-dark .command-shortcut {
background: #3d3d3d;
color: #ccc;
}
body.theme-dark .command-palette-footer {
background: #252525;
border-color: #444;
color: #999;
}
+91 -9
View File
@@ -10,9 +10,10 @@ const PizZip = require('pizzip');
const Docx = require('docx4js').default;
class WordTemplateExporter {
constructor(templatePath, startPage = 3) {
constructor(templatePath, startPage = 3, pageSettings = null) {
this.templatePath = templatePath || path.join(__dirname, '../word_template.docx');
this.startPage = startPage; // Which page to start inserting content
this.pageSettings = pageSettings; // Page size and orientation settings
}
/**
@@ -25,7 +26,12 @@ class WordTemplateExporter {
const zip = new PizZip(templateBuffer);
// Extract document.xml
const documentXml = zip.file('word/document.xml').asText();
let documentXml = zip.file('word/document.xml').asText();
// Set page size if settings provided
if (this.pageSettings) {
documentXml = this.setPageSize(documentXml);
}
// Parse markdown and generate Word XML
const newContentXml = this.markdownToWordXml(markdownContent);
@@ -47,6 +53,56 @@ class WordTemplateExporter {
}
}
/**
* Set page size in document XML
*/
setPageSize(documentXml) {
// Import PAGE_SIZES from main process (need to pass as parameter)
const PAGE_SIZES = {
a4: { width: 11906, height: 16838 },
a3: { width: 16838, height: 23811 },
a5: { width: 8391, height: 11906 },
b4: { width: 14170, height: 20015 },
b5: { width: 9979, height: 14170 },
letter: { width: 12240, height: 15840 },
legal: { width: 12240, height: 20160 },
tabloid: { width: 15840, height: 24480 }
};
let width, height;
const pageSize = PAGE_SIZES[this.pageSettings.size];
if (pageSize) {
width = pageSize.width;
height = pageSize.height;
} else {
// Default to A4
width = 11906;
height = 16838;
}
// Swap dimensions for landscape
if (this.pageSettings.orientation === 'landscape') {
[width, height] = [height, width];
}
// Update all <w:pgSz> elements in section properties
const pgSzRegex = /<w:pgSz[^>]*\/>/g;
let modifiedXml = documentXml.replace(pgSzRegex, () => {
return `<w:pgSz w:w="${width}" w:h="${height}" w:orient="${this.pageSettings.orientation}"/>`;
});
// If no pgSz found, add it to all sectPr elements
if (!pgSzRegex.test(documentXml)) {
const sectPrRegex = /<w:sectPr[^>]*>/g;
modifiedXml = modifiedXml.replace(sectPrRegex, (match) => {
return `${match}<w:pgSz w:w="${width}" w:h="${height}" w:orient="${this.pageSettings.orientation}"/>`;
});
}
return modifiedXml;
}
/**
* Insert markdown content after the specified page in the document
* @param {string} documentXml - The document XML
@@ -306,6 +362,7 @@ class WordTemplateExporter {
/**
* Create table XML from markdown table lines with template styling
* Uses full-width tables with equal column distribution matching template style
*/
createTableXml(tableLines) {
// Parse table
@@ -327,13 +384,19 @@ class WordTemplateExporter {
// Calculate number of columns
const numCols = Math.max(...rows.map(row => row.length));
// Calculate column width in twips (1440 twips = 1 inch)
// Assume standard page width of 9360 twips (6.5 inches usable)
const totalTableWidth = 9360;
const colWidth = Math.floor(totalTableWidth / numCols);
// Build table XML
let tableXml = '<w:tbl>';
// Table properties - use template table style
// Table properties - use template table style with full width
tableXml += `<w:tblPr>
<w:tblStyle w:val="TableGrid"/>
<w:tblW w:w="5000" w:type="pct"/>
<w:tblW w:w="0" w:type="auto"/>
<w:tblLayout w:type="fixed"/>
<w:tblLook w:val="04A0" w:firstRow="1" w:lastRow="0" w:firstColumn="0" w:lastColumn="0" w:noHBand="0" w:noVBand="1"/>
<w:tblBorders>
<w:top w:val="single" w:sz="8" w:space="0" w:color="F58220"/>
@@ -345,20 +408,22 @@ class WordTemplateExporter {
</w:tblBorders>
</w:tblPr>`;
// Table grid
// Table grid with explicit column widths
tableXml += '<w:tblGrid>';
for (let i = 0; i < numCols; i++) {
tableXml += '<w:gridCol/>';
tableXml += `<w:gridCol w:w="${colWidth}"/>`;
}
tableXml += '</w:tblGrid>';
// Table rows
rows.forEach((rowCells, rowIndex) => {
const isHeader = rowIndex === 0;
const isEvenRow = rowIndex % 2 === 0;
tableXml += '<w:tr>';
// Row properties for consistent height
tableXml += '<w:trPr><w:trHeight w:val="0" w:hRule="atLeast"/></w:trPr>';
// Pad row to have same number of columns
while (rowCells.length < numCols) {
rowCells.push('');
@@ -368,9 +433,14 @@ class WordTemplateExporter {
tableXml += '<w:tc>';
tableXml += '<w:tcPr>';
// Cell width
tableXml += `<w:tcW w:w="${colWidth}" w:type="dxa"/>`;
// Cell shading - orange for header, white for data rows
if (isHeader) {
tableXml += '<w:shd w:val="clear" w:color="FFFFFF" w:fill="F58220"/>';
tableXml += '<w:shd w:val="clear" w:color="auto" w:fill="F58220"/>';
} else {
tableXml += '<w:shd w:val="clear" w:color="auto" w:fill="FFFFFF"/>';
}
// Cell borders
@@ -380,12 +450,21 @@ class WordTemplateExporter {
'<w:bottom w:val="single" w:sz="8" w:space="0" w:color="F58220"/>' +
'<w:right w:val="single" w:sz="8" w:space="0" w:color="F58220"/>' +
'</w:tcBorders>';
// Cell margins for proper padding
tableXml += '<w:tcMar>' +
'<w:top w:w="80" w:type="dxa"/>' +
'<w:left w:w="120" w:type="dxa"/>' +
'<w:bottom w:w="80" w:type="dxa"/>' +
'<w:right w:w="120" w:type="dxa"/>' +
'</w:tcMar>';
tableXml += '</w:tcPr>';
// Cell content
tableXml += '<w:p>';
tableXml += '<w:pPr>';
tableXml += '<w:spacing w:before="100" w:after="100"/>';
tableXml += '<w:spacing w:before="0" w:after="0" w:line="240" w:lineRule="auto"/>';
tableXml += '</w:pPr>';
const runs = this.parseInlineFormatting(cellText);
@@ -407,6 +486,9 @@ class WordTemplateExporter {
tableXml += '</w:tbl>';
// Add spacing after table
tableXml += '<w:p><w:pPr><w:spacing w:before="120" w:after="0"/></w:pPr></w:p>';
return tableXml;
}