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>
This commit is contained in:
2025-10-26 23:54:00 +05:30
co-authored by Claude
parent 922e74d671
commit 8be2651b0e
10 changed files with 444 additions and 11 deletions
+41 -1
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() {
@@ -237,6 +238,7 @@ function createMenu() {
{ label: 'HTML', click: () => exportFile('html') },
{ label: 'PDF', click: () => exportFile('pdf') },
{ 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') },
@@ -467,7 +469,7 @@ function createMenu() {
type: 'info',
title: 'About PanConverter',
message: 'PanConverter',
detail: 'A cross-platform Markdown editor and converter using Pandoc.\n\nVersion: 1.7.8\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.7.9\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',
buttons: ['OK']
});
}
@@ -533,6 +535,44 @@ function showBatchConversionDialog() {
mainWindow.webContents.send('show-batch-dialog');
}
// 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
const exporter = new WordTemplateExporter();
// 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}`);
}
}
// Universal File Converter integration
function showUniversalConverterDialog() {
mainWindow.webContents.send('show-universal-converter-dialog');
+331
View File
@@ -0,0 +1,331 @@
/**
* Word Template Exporter
* Converts Markdown to DOCX using custom Word templates
* Based on Template_exporter functionality
*/
const { Document, Packer, Paragraph, TextRun, AlignmentType, HeadingLevel, Table, TableRow, TableCell, WidthType, BorderStyle } = require('docx');
const fs = require('fs');
const path = require('path');
class WordTemplateExporter {
constructor() {
this.inCodeBlock = false;
this.inList = false;
this.listType = null;
}
/**
* Convert markdown to Word document with template
* @param {string} markdownContent - The markdown content to convert
* @param {string} outputPath - Where to save the DOCX file
* @param {string} templatePath - Optional template path (for future implementation)
*/
async convert(markdownContent, outputPath, templatePath = null) {
// Parse markdown and create document elements
const children = this.parseMarkdown(markdownContent);
const doc = new Document({
numbering: {
config: [
{
reference: 'numbering',
levels: [
{
level: 0,
format: 'decimal',
text: '%1.',
alignment: AlignmentType.LEFT
}
]
},
{
reference: 'bullets',
levels: [
{
level: 0,
format: 'bullet',
text: '\u2022',
alignment: AlignmentType.LEFT
}
]
}
]
},
sections: [{
properties: {},
children: children
}]
});
// Write to file
const buffer = await Packer.toBuffer(doc);
fs.writeFileSync(outputPath, buffer);
return outputPath;
}
parseMarkdown(content) {
const lines = content.split('\n');
const elements = [];
let i = 0;
while (i < lines.length) {
const line = lines[i];
// Skip empty lines
if (!line.trim()) {
elements.push(new Paragraph({ text: '' }));
i++;
continue;
}
// Code blocks
if (line.trim().startsWith('```')) {
const codeLines = [];
i++;
while (i < lines.length && !lines[i].trim().startsWith('```')) {
codeLines.push(lines[i]);
i++;
}
elements.push(this.createCodeBlock(codeLines.join('\n')));
i++;
continue;
}
// Headers
if (line.trim().startsWith('#')) {
elements.push(this.createHeading(line));
i++;
continue;
}
// Blockquotes
if (line.trim().startsWith('>')) {
elements.push(this.createQuote(line));
i++;
continue;
}
// Ordered lists
if (/^\s*\d+\.\s+/.test(line)) {
elements.push(this.createListItem(line, true));
i++;
continue;
}
// Unordered lists
if (/^\s*[-*+]\s+/.test(line)) {
elements.push(this.createListItem(line, false));
i++;
continue;
}
// Horizontal rule
if (/^[-*_]{3,}$/.test(line.trim())) {
elements.push(this.createHorizontalRule());
i++;
continue;
}
// Tables
if (line.includes('|')) {
const tableLines = [line];
i++;
while (i < lines.length && lines[i].includes('|')) {
tableLines.push(lines[i]);
i++;
}
const table = this.createTable(tableLines);
if (table) elements.push(table);
continue;
}
// Normal paragraph
elements.push(this.createParagraph(line));
i++;
}
return elements;
}
createHeading(line) {
const level = line.match(/^#+/)[0].length;
const text = line.replace(/^#+\s*/, '').trim();
const headingLevels = {
1: HeadingLevel.HEADING_1,
2: HeadingLevel.HEADING_2,
3: HeadingLevel.HEADING_3,
4: HeadingLevel.HEADING_4,
5: HeadingLevel.HEADING_5,
6: HeadingLevel.HEADING_6
};
return new Paragraph({
text: text,
heading: headingLevels[Math.min(level, 6)]
});
}
createParagraph(line) {
const runs = this.parseInlineFormatting(line);
return new Paragraph({ children: runs });
}
createQuote(line) {
const text = line.replace(/^>\s*/, '').trim();
const runs = this.parseInlineFormatting(text);
return new Paragraph({
children: runs,
indent: { left: 720 }, // 0.5 inch indent
italics: true
});
}
createListItem(line, numbered) {
const text = line.replace(/^\s*(\d+\.|-|\*|\+)\s+/, '').trim();
const runs = this.parseInlineFormatting(text);
return new Paragraph({
children: runs,
numbering: {
reference: numbered ? 'numbering' : 'bullets',
level: 0
}
});
}
createCodeBlock(code) {
return new Paragraph({
children: [
new TextRun({
text: code,
font: 'Consolas',
size: 18 // 9pt
})
],
shading: {
fill: 'F5F5F5'
}
});
}
createHorizontalRule() {
return new Paragraph({
text: '_'.repeat(50),
alignment: AlignmentType.CENTER
});
}
createTable(tableLines) {
// Skip separator lines
const rows = tableLines.filter(line => !line.match(/^\s*\|[\s\-:]+\|\s*$/));
if (rows.length === 0) return null;
const tableRows = rows.map((line, index) => {
const cells = line.split('|').filter(cell => cell.trim()).map(cell => cell.trim());
return new TableRow({
children: cells.map(cellText => {
const runs = this.parseInlineFormatting(cellText);
return new TableCell({
children: [new Paragraph({ children: runs })],
shading: index === 0 ? { fill: 'E7E6E6' } : undefined,
width: { size: 100 / cells.length, type: WidthType.PERCENTAGE }
});
})
});
});
return new Table({
rows: tableRows,
width: { size: 100, type: WidthType.PERCENTAGE }
});
}
parseInlineFormatting(text) {
const runs = [];
let pos = 0;
// Patterns for inline formatting
const patterns = [
{ regex: /\*\*\*(.+?)\*\*\*/g, bold: true, italics: true },
{ regex: /\*\*(.+?)\*\*/g, bold: true },
{ regex: /\*(.+?)\*/g, italics: true },
{ regex: /`(.+?)`/g, code: true },
{ regex: /\[([^\]]+)\]\(([^\)]+)\)/g, link: true }
];
// Simple implementation: find all matches and create runs
let segments = [{ text: text, format: {} }];
for (const pattern of patterns) {
const newSegments = [];
for (const segment of segments) {
if (segment.format.processed) {
newSegments.push(segment);
continue;
}
let lastIndex = 0;
const matches = [...segment.text.matchAll(pattern.regex)];
if (matches.length === 0) {
newSegments.push(segment);
continue;
}
matches.forEach(match => {
// Add text before match
if (match.index > lastIndex) {
newSegments.push({
text: segment.text.substring(lastIndex, match.index),
format: segment.format
});
}
// Add formatted text
const format = { ...segment.format, processed: true };
if (pattern.bold) format.bold = true;
if (pattern.italics) format.italics = true;
if (pattern.code) {
format.font = 'Consolas';
format.size = 20;
}
newSegments.push({
text: match[1],
format: format
});
lastIndex = match.index + match[0].length;
});
// Add remaining text
if (lastIndex < segment.text.length) {
newSegments.push({
text: segment.text.substring(lastIndex),
format: segment.format
});
}
}
segments = newSegments;
}
// Convert segments to TextRun objects
return segments.map(seg => new TextRun({
text: seg.text,
bold: seg.format.bold,
italics: seg.format.italics,
font: seg.format.font,
size: seg.format.size
}));
}
}
module.exports = WordTemplateExporter;