From 4f74b03324be229c7f42e3194f8d8f4235953e2e Mon Sep 17 00:00:00 2001 From: Twest2 Date: Mon, 15 Jun 2026 14:46:39 -0500 Subject: [PATCH] fix docx toc and callouts --- exporters/docx.js | 42 +++++++++-------------------- tests/unit/exporters-binary.test.js | 9 ++++--- 2 files changed, 17 insertions(+), 34 deletions(-) diff --git a/exporters/docx.js b/exporters/docx.js index c21469e..bf845ea 100644 --- a/exporters/docx.js +++ b/exporters/docx.js @@ -7,7 +7,6 @@ const { escapeXml } = require('../core/util'); const { encodePng } = require('../core/png'); const { guideSlug, renderAllImages, LEVEL_LABEL, stepContentGroups, codeBlockText } = require('./common'); const { guideMetaLines, guideSummary } = require('./document-layout'); -const raster = require('../core/raster'); /** * DOCX exporter: WordprocessingML built directly (no dependency), one @@ -67,24 +66,20 @@ function drawing(relId, widthPx, heightPx, maxWidthTwips) { ``; } -function calloutIcon(level) { - const img = raster.createImage(24, 24, [0, 0, 0, 0]); - const fill = { - info: [37, 99, 235, 255], - success: [16, 185, 129, 255], - warn: [245, 158, 11, 255], - error: [239, 68, 68, 255], - }[level] || [37, 99, 235, 255]; - raster.fillOval(img, 1, 1, 22, 22, fill); - const glyph = level === 'success' ? 'v' : level === 'warn' ? '!' : level === 'error' ? 'x' : 'i'; - raster.drawTextCentered(img, 12, 13, glyph, 12, [255, 255, 255, 255]); - return img; -} - function pageBreak() { return p(''); } +function tocFieldParagraph() { + return p([ + '', + ' TOC \\o "1-3" \\h \\z \\u ', + '', + 'Update contents in Word', + '', + ].join('')); +} + function headingStyleForDepth(depth) { return `Heading${Math.min(3, depth + 1)}`; } @@ -184,16 +179,6 @@ function exportDocx(ast, outDir, template = {}) { rels.push(``); - const iconRelIds = {}; - for (const level of ['info', 'success', 'warn', 'error']) { - const icon = calloutIcon(level); - const relId = ++relCounter; - iconRelIds[level] = relId; - const name = `callout-${level}.png`; - media.push({ name, data: encodePng(icon) }); - rels.push(``); - } - const body = []; body.push(p( run(ast.guide.title, { bold: true, size: 48 }), @@ -210,18 +195,15 @@ function exportDocx(ast, outDir, template = {}) { run('Contents', { bold: true, size: 28 }), '' )); - body.push(p( - `Update contents in Word` - )); + body.push(tocFieldParagraph()); body.push(pageBreak()); } const emitTextBlock = (tb) => { const style = LEVEL_STYLE[tb.level] || LEVEL_STYLE.info; - const iconRelId = iconRelIds[tb.level] || iconRelIds.info; const label = `${LEVEL_LABEL[tb.level] || 'Note'}${tb.title ? `: ${tb.title}` : ''}`; body.push(p( - `${drawing(iconRelId, 16, 16, 240)}${run(label, { bold: true, size: 20, color: style.color })}${tb.descriptionText ? run('\n' + tb.descriptionText, { size: 20, color: '1F2937' }) : ''}`, + `${run(label, { bold: true, size: 20, color: style.color })}${tb.descriptionText ? run('\n' + tb.descriptionText, { size: 20, color: '1F2937' }) : ''}`, `` )); }; diff --git a/tests/unit/exporters-binary.test.js b/tests/unit/exporters-binary.test.js index 9c2acea..3c9896c 100644 --- a/tests/unit/exporters-binary.test.js +++ b/tests/unit/exporters-binary.test.js @@ -296,15 +296,15 @@ test('DOCX export: valid OPC package, well-formed XML, resolvable image rels', ( // Every relationship target exists in the package, every embed has a rel. const relsXml = entries.get('word/_rels/document.xml.rels').toString('utf8'); const relTargets = [...relsXml.matchAll(/Target="([^"]+)"/g)].map((m) => m[1]); - assert.equal(relTargets.length, 8); + assert.equal(relTargets.length, 4); assert.ok(relTargets.includes('settings.xml')); assert.ok(relTargets.includes('styles.xml')); const mediaTargets = relTargets.filter((target) => target.startsWith('media/')); - assert.equal(mediaTargets.length, 6); + assert.equal(mediaTargets.length, 2); const iconTargets = mediaTargets.filter((target) => target.includes('callout-')); const imageTargets = mediaTargets.filter((target) => target.includes('image')); - assert.equal(iconTargets.length, 4); + assert.equal(iconTargets.length, 0); assert.equal(imageTargets.length, 2); for (const target of iconTargets) { @@ -323,7 +323,8 @@ test('DOCX export: valid OPC package, well-formed XML, resolvable image rels', ( for (const id of embeds) { assert.ok(relIds.includes(id), `missing relationship for ${id}`); } - assert.ok(docXml.includes('TOC \\o "1-3" \\h \\z \\u')); + assert.ok(docXml.includes('w:fldChar w:fldCharType="begin" w:dirty="true"')); + assert.ok(docXml.includes('TOC \\o "1-3" \\h \\z \\u')); assert.ok(docXml.includes('w:pStyle w:val="Heading1"')); assert.ok(docXml.includes('w:pStyle w:val="Heading2"')); assert.ok(docXml.includes('w:outlineLvl w:val="0"'));