mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-10-01 09:19:34 +05:30
Replaces the two native OS dialogs used to configure the DOCX "Enhanced"
export template (an open-file picker + a message-box question) with a
single in-app modal that shows the currently active template state, per
Task 18's original audit finding that this state was invisible until a
user thought to reopen the menu. Consolidates the "Select Word
Template..."/"Template Settings..." menu items into one "Word Template
Settings..." entry wired to the new dialog; Browse still uses the native
file picker since there is genuinely no bundled folder of templates to
enumerate (confirmed by investigation — see task-18-report.md).
Also fixes a related dangling-reference bug: WordTemplateExporter's
hardcoded default template path (word_template.docx) was deleted from
the repo in an earlier commit, but the code still tried to read it and
threw ENOENT whenever no custom template was selected. convert() now
degrades gracefully by generating a minimal, valid DOCX shell (styles +
numbering matching what markdownToWordXml() already references) instead
of crashing, and the new dialog surfaces this state honestly ("using
default formatting, no default template is bundled") rather than
implying a working default exists.
Out of scope, per explicit instruction: bundling fabricated starter
.docx templates to populate a literal multi-item gallery (rejected as
disproportionate/fake-content scope), and an EPUB template gallery (no
EPUB template mechanism exists anywhere in this codebase to build one
for).
Amit Haridas
1110 lines
36 KiB
JavaScript
1110 lines
36 KiB
JavaScript
/**
|
|
* Word Template Exporter
|
|
* Loads word_template.docx, preserves first 2 pages (cover + TOC),
|
|
* and adds markdown content starting from page 3 using template styles
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const PizZip = require('pizzip');
|
|
|
|
class WordTemplateExporter {
|
|
/**
|
|
* Path used when no explicit template is configured. Historically a
|
|
* `word_template.docx` shipped at the repo root, but it was removed from
|
|
* the project (see git history) while this fallback reference was not
|
|
* updated, so this path is not guaranteed to exist on disk — callers
|
|
* must not assume it does. Exposed as a static so the UI layer (main.js)
|
|
* can check availability without duplicating the path logic.
|
|
*/
|
|
static getDefaultTemplatePath() {
|
|
return path.join(__dirname, '../word_template.docx');
|
|
}
|
|
|
|
constructor(templatePath, startPage = 3, pageSettings = null) {
|
|
this.templatePath = templatePath || WordTemplateExporter.getDefaultTemplatePath();
|
|
this.startPage = startPage; // Which page to start inserting content
|
|
this.pageSettings = pageSettings; // Page size and orientation settings
|
|
}
|
|
|
|
/**
|
|
* Whether a real, readable template file exists at this.templatePath.
|
|
* When false, convert() degrades gracefully to a minimal generated
|
|
* document instead of throwing ENOENT.
|
|
*/
|
|
hasTemplateFile() {
|
|
return !!this.templatePath && fs.existsSync(this.templatePath);
|
|
}
|
|
|
|
/**
|
|
* 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 <div> tags (including
|
|
* alignment attributes). Code blocks and inline code spans are preserved.
|
|
*/
|
|
static preprocessMarkdownForWordExport(markdown) {
|
|
if (typeof markdown !== 'string') return markdown;
|
|
|
|
// Split markdown into code-block and non-code-block segments so we do
|
|
// not strip HTML that the author intentionally placed inside fences.
|
|
const segments = [];
|
|
let inCodeBlock = false;
|
|
let currentLines = [];
|
|
|
|
const flush = () => {
|
|
if (currentLines.length === 0) return;
|
|
segments.push({
|
|
type: inCodeBlock ? 'code' : 'text',
|
|
text: currentLines.join('\n'),
|
|
});
|
|
currentLines = [];
|
|
};
|
|
|
|
for (const line of markdown.split('\n')) {
|
|
if (/^\s*```/.test(line)) {
|
|
if (inCodeBlock) {
|
|
currentLines.push(line);
|
|
flush();
|
|
inCodeBlock = false;
|
|
} else {
|
|
flush();
|
|
currentLines.push(line);
|
|
inCodeBlock = true;
|
|
}
|
|
} else {
|
|
currentLines.push(line);
|
|
}
|
|
}
|
|
flush();
|
|
|
|
const stripArtifacts = (text) => {
|
|
// Protect inline code spans so HTML inside backticks is preserved.
|
|
const codeSpans = [];
|
|
const protectedText = text.replace(/`[^`]+`/g, (match) => {
|
|
codeSpans.push(match);
|
|
return ` |