mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-03 02:11:07 +05:30
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94906a068a | ||
|
|
e72b863362 |
@@ -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.
|
||||
|
||||
- **Version:** 4.4.3
|
||||
- **Version:** 4.4.5
|
||||
- **License:** MIT
|
||||
- **App ID:** `com.concreteinfo.markdownconverter`
|
||||
|
||||
|
||||
@@ -162,4 +162,4 @@ Amit Haridas (amit.wh@gmail.com)
|
||||
|
||||
## Version
|
||||
|
||||
v4.4.4
|
||||
v4.4.5
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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",
|
||||
"main": "src/main.js",
|
||||
"scripts": {
|
||||
|
||||
+56
-4
@@ -2562,7 +2562,23 @@ function performExportWithOptions(format, 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
|
||||
if (options.template && options.template !== 'default') {
|
||||
@@ -2657,7 +2673,15 @@ function performExportWithOptions(format, options) {
|
||||
});
|
||||
} else if (format === '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') {
|
||||
// Add PowerPoint footer if enabled
|
||||
if (headerFooterSettings.enabled && headerFooterSettings.footer.center) {
|
||||
@@ -2859,7 +2883,7 @@ function showExportSuccess(outputFile) {
|
||||
}
|
||||
|
||||
// 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) => {
|
||||
if (error) {
|
||||
console.error(`Pandoc error for ${format}:`, error);
|
||||
@@ -2924,6 +2948,9 @@ function exportWithPandoc(pandocCmd, outputFile, format) {
|
||||
}
|
||||
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
|
||||
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
|
||||
if (options.template && options.template !== 'default') {
|
||||
@@ -3850,6 +3893,15 @@ async function performBatchConversion(inputFolder, outputFolder, format, options
|
||||
|
||||
// Execute conversion (using runPandocCmd for safety)
|
||||
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) {
|
||||
console.error(`Batch: Failed to convert ${path.basename(inputFile)}:`, error.message, stderr);
|
||||
} else {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* MarkdownConverter Renderer Process
|
||||
* @version 4.4.4
|
||||
* @version 4.4.5
|
||||
*/
|
||||
|
||||
const { ipcRenderer } = require('electron');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* ModalManager - Unified modal system with accessibility support
|
||||
* @version 4.4.4
|
||||
* @version 4.4.5
|
||||
*/
|
||||
class ModalManager {
|
||||
#modal;
|
||||
|
||||
@@ -15,6 +15,26 @@ class WordTemplateExporter {
|
||||
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
|
||||
*/
|
||||
@@ -33,7 +53,8 @@ class WordTemplateExporter {
|
||||
}
|
||||
|
||||
// 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
|
||||
const modifiedXml = this.insertContentAfterPage(documentXml, newContentXml, this.startPage);
|
||||
|
||||
@@ -16,6 +16,6 @@ describe('Project version consistency', () => {
|
||||
});
|
||||
|
||||
test('README contains current version string', () => {
|
||||
expect(readme).toContain('v4.4.4');
|
||||
expect(readme).toContain(`v${packageJson.version}`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user