Compare commits

..
2 Commits
Author SHA1 Message Date
amitwh 94906a068a chore(release): bump version to 4.4.5 2026-06-30 13:57:34 +05:30
amitwh e72b863362 fix(word): strip HTML style blocks and alignment divs from DOCX input
Pre-process markdown before Word/DOCX export to remove raw HTML artifacts

(<style> blocks, HTML comments, and <div align=...> tags) that were

visible in the generated document. Applies to single and batch DOCX exports

via both Pandoc and WordTemplateExporter paths.
2026-06-30 13:57:29 +05:30
9 changed files with 145 additions and 11 deletions
+1 -1
View File
@@ -6,7 +6,7 @@
Electron desktop app for Markdown editing and universal file conversion powered by Pandoc. Cross-platform (Win/macOS/Linux). Features: multi-tab editor with live preview, 25+ themes, PDF viewer/editor (merge/split/compress/rotate/watermark/password), export to 20+ formats (PDF/DOCX/ODT/EPUB/HTML/LaTeX/RTF/PPTX), batch conversion, syntax highlighting, diagram support (Mermaid), Git integration, and a plugin system. Electron desktop app for Markdown editing and universal file conversion powered by Pandoc. Cross-platform (Win/macOS/Linux). Features: multi-tab editor with live preview, 25+ themes, PDF viewer/editor (merge/split/compress/rotate/watermark/password), export to 20+ formats (PDF/DOCX/ODT/EPUB/HTML/LaTeX/RTF/PPTX), batch conversion, syntax highlighting, diagram support (Mermaid), Git integration, and a plugin system.
- **Version:** 4.4.3 - **Version:** 4.4.5
- **License:** MIT - **License:** MIT
- **App ID:** `com.concreteinfo.markdownconverter` - **App ID:** `com.concreteinfo.markdownconverter`
+1 -1
View File
@@ -162,4 +162,4 @@ Amit Haridas (amit.wh@gmail.com)
## Version ## Version
v4.4.4 v4.4.5
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "markdown-converter", "name": "markdown-converter",
"version": "4.4.4", "version": "4.4.5",
"description": "Professional Markdown editor and universal file converter with PDF editing, batch processing, and syntax highlighting", "description": "Professional Markdown editor and universal file converter with PDF editing, batch processing, and syntax highlighting",
"main": "src/main.js", "main": "src/main.js",
"scripts": { "scripts": {
+56 -4
View File
@@ -2562,7 +2562,23 @@ function performExportWithOptions(format, options) {
// Use pandoc for export with advanced options // Use pandoc for export with advanced options
let pandocCmd = `${getPandocPath()} "${currentFile}" -o "${outputFile}"`; let inputFile = currentFile;
let tempInputFile = null;
// Pre-process markdown for Word output so that <style> blocks, HTML
// comments, and alignment <div> tags do not appear as visible text.
if (format === 'docx') {
const content = fs.readFileSync(currentFile, 'utf-8');
const cleanedContent = WordTemplateExporter.preprocessMarkdownForWordExport(content);
tempInputFile = path.join(
require('os').tmpdir(),
`mc_export_${Date.now()}_${path.basename(currentFile)}`
);
fs.writeFileSync(tempInputFile, cleanedContent, 'utf-8');
inputFile = tempInputFile;
}
let pandocCmd = `${getPandocPath()} "${inputFile}" -o "${outputFile}"`;
// Add template if specified // Add template if specified
if (options.template && options.template !== 'default') { if (options.template && options.template !== 'default') {
@@ -2657,7 +2673,15 @@ function performExportWithOptions(format, options) {
}); });
} else if (format === 'docx') { } else if (format === 'docx') {
pandocCmd += ' -t docx'; pandocCmd += ' -t docx';
exportWithPandoc(pandocCmd, outputFile, format); exportWithPandoc(pandocCmd, outputFile, format, () => {
if (tempInputFile) {
try {
fs.unlinkSync(tempInputFile);
} catch {
// Ignore cleanup errors
}
}
});
} else if (format === 'pptx') { } else if (format === 'pptx') {
// Add PowerPoint footer if enabled // Add PowerPoint footer if enabled
if (headerFooterSettings.enabled && headerFooterSettings.footer.center) { if (headerFooterSettings.enabled && headerFooterSettings.footer.center) {
@@ -2859,7 +2883,7 @@ function showExportSuccess(outputFile) {
} }
// Helper function to export with pandoc (general) - uses runPandocCmd for safety // Helper function to export with pandoc (general) - uses runPandocCmd for safety
function exportWithPandoc(pandocCmd, outputFile, format) { function exportWithPandoc(pandocCmd, outputFile, format, onComplete) {
runPandocCmd(pandocCmd, async (error, stdout, stderr) => { runPandocCmd(pandocCmd, async (error, stdout, stderr) => {
if (error) { if (error) {
console.error(`Pandoc error for ${format}:`, error); console.error(`Pandoc error for ${format}:`, error);
@@ -2924,6 +2948,9 @@ function exportWithPandoc(pandocCmd, outputFile, format) {
} }
showExportSuccess(outputFile); showExportSuccess(outputFile);
} }
if (typeof onComplete === 'function') {
onComplete(error);
}
}); });
} }
@@ -3749,7 +3776,23 @@ async function performBatchConversion(inputFolder, outputFolder, format, options
} }
// Build pandoc command for other formats // Build pandoc command for other formats
let pandocCmd = `${getPandocPath()} "${inputFile}" -o "${outputFile}"`; let pandocInputFile = inputFile;
let batchTempInputFile = null;
// Pre-process markdown for Word output so that <style> blocks, HTML
// comments, and alignment <div> tags do not appear as visible text.
if (format === 'docx') {
const content = fs.readFileSync(inputFile, 'utf-8');
const cleanedContent = WordTemplateExporter.preprocessMarkdownForWordExport(content);
batchTempInputFile = path.join(
require('os').tmpdir(),
`mc_batch_export_${Date.now()}_${path.basename(inputFile)}`
);
fs.writeFileSync(batchTempInputFile, cleanedContent, 'utf-8');
pandocInputFile = batchTempInputFile;
}
let pandocCmd = `${getPandocPath()} "${pandocInputFile}" -o "${outputFile}"`;
// Add template if specified // Add template if specified
if (options.template && options.template !== 'default') { if (options.template && options.template !== 'default') {
@@ -3850,6 +3893,15 @@ async function performBatchConversion(inputFolder, outputFolder, format, options
// Execute conversion (using runPandocCmd for safety) // Execute conversion (using runPandocCmd for safety)
runPandocCmd(pandocCmd, async (error, _stdout, stderr) => { runPandocCmd(pandocCmd, async (error, _stdout, stderr) => {
// Clean up temporary pre-processed input file
if (batchTempInputFile) {
try {
fs.unlinkSync(batchTempInputFile);
} catch {
// Ignore cleanup errors
}
}
if (error) { if (error) {
console.error(`Batch: Failed to convert ${path.basename(inputFile)}:`, error.message, stderr); console.error(`Batch: Failed to convert ${path.basename(inputFile)}:`, error.message, stderr);
} else { } else {
+1 -1
View File
@@ -1,6 +1,6 @@
/** /**
* MarkdownConverter Renderer Process * MarkdownConverter Renderer Process
* @version 4.4.4 * @version 4.4.5
*/ */
const { ipcRenderer } = require('electron'); const { ipcRenderer } = require('electron');
+1 -1
View File
@@ -1,6 +1,6 @@
/** /**
* ModalManager - Unified modal system with accessibility support * ModalManager - Unified modal system with accessibility support
* @version 4.4.4 * @version 4.4.5
*/ */
class ModalManager { class ModalManager {
#modal; #modal;
+22 -1
View File
@@ -15,6 +15,26 @@ class WordTemplateExporter {
this.pageSettings = pageSettings; // Page size and orientation settings this.pageSettings = pageSettings; // Page size and orientation settings
} }
/**
* Strip HTML artifacts that Pandoc / Word cannot render and that would
* otherwise appear as visible text in DOCX output.
* Removes HTML comments, <style> blocks, and alignment <div> tags.
*/
static preprocessMarkdownForWordExport(markdown) {
if (typeof markdown !== 'string') return markdown;
return (
markdown
// HTML comments (multi-line)
.replace(/<!--[\s\S]*?-->/g, '')
// <style> blocks (case-insensitive, multi-line)
.replace(/<style\b[\s\S]*?<\/style>/gi, '')
// Opening <div align="..."> tags
.replace(/<div\b[^>]*?\balign\s*=\s*["'][^"']*["'][^>]*>/gi, '')
// Closing </div> tags
.replace(/<\/div\s*>/gi, '')
);
}
/** /**
* Convert markdown to Word document using template * Convert markdown to Word document using template
*/ */
@@ -33,7 +53,8 @@ class WordTemplateExporter {
} }
// Parse markdown and generate Word XML // Parse markdown and generate Word XML
const newContentXml = this.markdownToWordXml(markdownContent); const cleanedContent = WordTemplateExporter.preprocessMarkdownForWordExport(markdownContent);
const newContentXml = this.markdownToWordXml(cleanedContent);
// Insert new content after the specified start page // Insert new content after the specified start page
const modifiedXml = this.insertContentAfterPage(documentXml, newContentXml, this.startPage); const modifiedXml = this.insertContentAfterPage(documentXml, newContentXml, this.startPage);
+1 -1
View File
@@ -16,6 +16,6 @@ describe('Project version consistency', () => {
}); });
test('README contains current version string', () => { test('README contains current version string', () => {
expect(readme).toContain('v4.4.4'); expect(readme).toContain(`v${packageJson.version}`);
}); });
}); });
+61
View File
@@ -0,0 +1,61 @@
/**
* Tests for WordTemplateExporter
*/
const WordTemplateExporter = require('../src/wordTemplateExporter');
describe('WordTemplateExporter.preprocessMarkdownForWordExport', () => {
test('removes HTML style blocks', () => {
const input = `<style>
<!-- sneh-a4-print v1 -->
@media print { body { font-size: 8pt; } }
</style>
# Heading
`;
const output = WordTemplateExporter.preprocessMarkdownForWordExport(input);
expect(output).not.toContain('<style');
expect(output).not.toContain('</style>');
expect(output).not.toContain('sneh-a4-print');
expect(output).not.toContain('@media print');
expect(output).toContain('# Heading');
});
test('removes HTML comments outside style blocks', () => {
const input = `<!-- comment -->
Hello world
`;
const output = WordTemplateExporter.preprocessMarkdownForWordExport(input);
expect(output).not.toContain('<!--');
expect(output).not.toContain('-->');
expect(output).toContain('Hello world');
});
test('removes alignment div tags', () => {
const input = `<div align="center">
Centered content
</div>
`;
const output = WordTemplateExporter.preprocessMarkdownForWordExport(input);
expect(output).not.toContain('<div align="center">');
expect(output).not.toContain('</div>');
expect(output).toContain('Centered content');
});
test('preserves regular markdown content', () => {
const input = `# Title
| A | B |
|---|---|
| 1 | 2 |
`;
const output = WordTemplateExporter.preprocessMarkdownForWordExport(input);
expect(output).toBe(input);
});
test('handles content with no HTML artifacts', () => {
const input = 'Plain text paragraph.';
const output = WordTemplateExporter.preprocessMarkdownForWordExport(input);
expect(output).toBe(input);
});
});