Match the editor's spacing in PDF/DOCX/ODT and fix the toolbar dropdown
Exports now use the same typography as the Full Book editor — Georgia serif at 12pt, 1.7 line spacing, and a blank-line gap between paragraphs - so the on-screen editor and every exported format look consistent. DOCX/ ODT also gain a chapter break before each chapter after the first.
This commit is contained in:
parent
247f16baa5
commit
0df26d4ce6
6 changed files with 143 additions and 27 deletions
|
|
@ -96,17 +96,18 @@ const origManifest = fs.readFileSync(path.join(bookDir, "folio.json"));
|
|||
|
||||
const docxBuf = await mod.buildDocx(bookDir);
|
||||
const docxZip = await JSZip.loadAsync(docxBuf);
|
||||
const docxXml = await docxZip.file("word/document.xml").async("string");
|
||||
const docxXml = await docxZip.file("word/document.xml").async("string");
|
||||
const docxStyles = await docxZip.file("word/styles.xml").async("string");
|
||||
|
||||
check("docx is a zip (PK magic)", docxBuf.subarray(0, 2).toString("utf-8") === "PK");
|
||||
check("docx has [Content_Types].xml", !!docxZip.file("[Content_Types].xml"));
|
||||
check("docx has word/document.xml", !!docxZip.file("word/document.xml"));
|
||||
check("docx is a zip (PK magic)", docxBuf.subarray(0, 2).toString("utf-8") === "PK");
|
||||
check("docx has [Content_Types].xml", !!docxZip.file("[Content_Types].xml"));
|
||||
check("docx has word/document.xml", !!docxZip.file("word/document.xml"));
|
||||
|
||||
const stripTags = (xml) => xml.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
||||
const stripTags = (xml) => xml.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
||||
|
||||
const docxText = stripTags(docxXml);
|
||||
check("docx contains book title", docxText.includes("Office Test Book"));
|
||||
check("docx contains byline", docxText.includes("A book compiled with Folio._"));
|
||||
const docxText = stripTags(docxXml);
|
||||
check("docx contains book title", docxText.includes("Office Test Book"));
|
||||
check("docx contains byline", docxText.includes("A book compiled with Folio."));
|
||||
|
||||
const idxAlpha = docxText.indexOf("Alpha");
|
||||
const idxBeta = docxText.indexOf("Beta");
|
||||
|
|
@ -129,6 +130,15 @@ check("docx has heading 1", /w:val="Heading1"/.test(docxXml));
|
|||
check("docx has table", /<w:tbl>/.test(docxXml));
|
||||
check("docx has code font", /Consolas/.test(docxXml));
|
||||
check("docx has centered alignment", /w:val="center"/.test(docxXml));
|
||||
// Body font/size come from word/styles.xml (document defaults), mirroring the
|
||||
// editor's Georgia / 12pt choice.
|
||||
check("docx uses Georgia body font", /<w:rFonts w:ascii="Georgia"/.test(docxStyles));
|
||||
check("docx body size is 12pt (24 half-points)", /<w:sz w:val="24"\/>/.test(docxStyles));
|
||||
// Matches the editor: 1.7 line spacing and a blank line (408 twips) between
|
||||
// paragraphs. Attributes may be emitted in any order, so match loosely.
|
||||
check("docx has 1.7 line spacing", /<w:spacing[^>]*w:line="408"[^>]*w:lineRule="auto"[^>]*\/>/.test(docxXml));
|
||||
check("docx has blank-line paragraph spacing", /<w:spacing[^>]*w:after="408"/.test(docxXml));
|
||||
check("docx has chapter break before later chapters", (docxXml.match(/w:before="320"/g) || []).length >= 4);
|
||||
check("docx has callout shading", /w:fill="F5F2ED"/.test(docxXml));
|
||||
check("docx renders ==highlight== (no raw markup)", !docxText.includes("==highlight=="));
|
||||
check("docx renders **bold** (no raw markup)", !docxText.includes("**bold**"));
|
||||
|
|
|
|||
|
|
@ -204,6 +204,36 @@ const BOOK3_DIR = __BOOKDIR3_VALUE__;
|
|||
await new Promise((r) => setTimeout(r, 50));
|
||||
out.painterOk = cm.state.doc.toString() === "**src** **dst**";
|
||||
|
||||
// text color via the toolbar dropdown.
|
||||
cm.dispatch({ changes: { from: 0, to: cm.state.doc.length, insert: "" } });
|
||||
cm.dispatch({ changes: { from: 0, to: 0, insert: "color me" }, selection: { anchor: 0, head: 5 } });
|
||||
cm.focus();
|
||||
const colorToggle = document.querySelector('[data-dropdown="colorMenu"]');
|
||||
colorToggle?.click();
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
const colorMenuEl = document.getElementById("colorMenu");
|
||||
const colorRect = colorMenuEl?.getBoundingClientRect();
|
||||
out.colorMenuOpens =
|
||||
!!colorRect && !colorMenuEl?.classList.contains("hidden") && colorRect.bottom <= window.innerHeight + 1;
|
||||
// the dropdown is pinned to the viewport, so it must stay on screen even
|
||||
// though the toolbar itself is a horizontal scroll container
|
||||
out.colorMenuOnScreen = !!colorRect && colorRect.top >= 0 && colorRect.bottom <= window.innerHeight;
|
||||
document.querySelector('[data-cmd="color:#e11d48"]').click();
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
out.colorApplied = cm.state.doc.toString() === '<span style="color:#e11d48">color</span> me';
|
||||
|
||||
// custom color picker works
|
||||
cm.dispatch({ changes: { from: 0, to: cm.state.doc.length, insert: "" } });
|
||||
cm.dispatch({ changes: { from: 0, to: 0, insert: "pick me" }, selection: { anchor: 0, head: 4 } });
|
||||
cm.focus();
|
||||
colorToggle?.click();
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
const customInput = document.getElementById("customTextColor");
|
||||
customInput.value = "#0066cc";
|
||||
customInput.dispatchEvent(new Event("change", { bubbles: true }));
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
out.customColorApplied = cm.state.doc.toString() === '<span style="color:#0066cc">pick</span> me';
|
||||
|
||||
// leave some formatted content on disk for the autosave check
|
||||
cm.dispatch({
|
||||
changes: { from: 0, to: cm.state.doc.length, insert: "**Hello** World" },
|
||||
|
|
@ -859,6 +889,10 @@ const BOOK3_DIR = __BOOKDIR3_VALUE__;
|
|||
"highlightOk",
|
||||
"tableOk",
|
||||
"painterOk",
|
||||
"colorMenuOpens",
|
||||
"colorMenuOnScreen",
|
||||
"colorApplied",
|
||||
"customColorApplied",
|
||||
"formattingAutosaved",
|
||||
"fullBookShown",
|
||||
"assembledOk",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue