4,456
edits
No edit summary |
No edit summary |
||
Line 1,370: | Line 1,370: | ||
// Remove “empty” optional sections so they don’t leave gaps in print. | // Remove “empty” optional sections so they don’t leave gaps in print. | ||
(function | // --- Collapse empty sections & their labels, and tidy any leftover spacing --- | ||
function | (function tidyPrint(doc) { | ||
// | // 1) Utility: detect “visually empty” blocks | ||
var txt = ( | function isVisuallyEmpty(el) { | ||
if (!el) return true; | |||
// If it contains any real content, bail | |||
if ( | |||
el.querySelector("img, video, audio, iframe, svg, object, embed") | |||
) | |||
return false; | |||
if (el.querySelector("a[href]")) return false; | |||
// Remove <br> & empty <p> shells before checking text | |||
// (work on a shallow clone so we don't touch the live node yet) | |||
var clone = el.cloneNode(true); | |||
var i, | |||
brs = clone.getElementsByTagName("br"); | |||
for (i = brs.length - 1; i >= 0; i--) | |||
brs[i].parentNode.removeChild(brs[i]); | |||
// Remove <p> that are empty/whitespace only | |||
var ps = clone.getElementsByTagName("p"); | |||
for (i = ps.length - 1; i >= 0; i--) { | |||
var t = (ps[i].textContent || "") | |||
.replace(/\u00A0/g, " ") | |||
.replace(/\u200B/g, "") | |||
.replace(/\s+/g, " ") | |||
.trim(); | |||
if (!t) ps[i].parentNode.removeChild(ps[i]); | |||
} | |||
// Now inspect text | |||
var txt = (clone.textContent || "") | |||
.replace(/\u00A0/g, " ") // NBSP | .replace(/\u00A0/g, " ") // NBSP | ||
.replace(/\u200B/g, "") // zero-width space | .replace(/\u200B/g, "") // zero-width space | ||
.replace(/\s+/g, " ") // | .replace(/\s+/g, " ") // collapse spaces | ||
.trim(); | .trim(); | ||
return !txt; | |||
return !txt | |||
} | } | ||
function | // 2) Remove block + its label if empty | ||
var | function removePair(blockSel, labelSel) { | ||
if ( | var block = doc.querySelector(blockSel); | ||
// | if (block && isVisuallyEmpty(block)) { | ||
if ( | // Remove preceding label if it matches | ||
var prev = | if (labelSel) { | ||
if (prev && prev.matches && prev.matches( | var prev = block.previousElementSibling; | ||
if ( | |||
prev && | |||
prev.matches && | |||
prev.matches(labelSel) && | |||
prev.parentNode | |||
) { | |||
prev.parentNode.removeChild(prev); | |||
} | } | ||
} | } | ||
if ( | if (block.parentNode) block.parentNode.removeChild(block); | ||
} | } | ||
} | } | ||
// | // 3) Images wrapper: only keep if it actually has a usable <img src> | ||
(function () { | (function () { | ||
var | var wrap = doc.querySelector(".article-images"); | ||
if (! | if (!wrap) return; | ||
var imgs = | var imgs = wrap.getElementsByTagName("img"); | ||
var | var hasSrc = false, | ||
for ( | i; | ||
for (i = 0; i < imgs.length; i++) { | |||
var s = imgs[i].getAttribute("src"); | |||
if (s && /\S/.test(s)) { | |||
hasSrc = true; | |||
break; | break; | ||
} | } | ||
} | } | ||
if (! | if (!hasSrc && wrap.parentNode) wrap.parentNode.removeChild(wrap); | ||
})(); | })(); | ||
// | // 4) Optional sections | ||
removePair(".article-description", ".article-label-description"); | |||
removePair(".article-reflection", ".article-label-reflection"); | |||
removePair( | |||
".article-external-reference", | ".article-external-reference", | ||
".article-label-external-reference" | ".article-label-external-reference" | ||
); | ); | ||
removePair(".article-quote", ".article-label-quote"); | |||
// Metadata: if all values are empty | // 5) Metadata grid: drop if all values are empty | ||
(function () { | (function () { | ||
var meta = doc.querySelector(".article-metadata"); | var meta = doc.querySelector(".article-metadata"); | ||
if (!meta) return; | if (!meta) return; | ||
var | var vals = meta.querySelectorAll(".article-metadata-value"); | ||
var allEmpty = true; | var allEmpty = true, | ||
for ( | i; | ||
if (! | for (i = 0; i < vals.length; i++) { | ||
if (!isVisuallyEmpty(vals[i])) { | |||
allEmpty = false; | allEmpty = false; | ||
break; | break; | ||
} | } | ||
} | } | ||
if (allEmpty && meta.parentNode) { | if (allEmpty && meta.parentNode) meta.parentNode.removeChild(meta); | ||
})(); | |||
// 6) Title link row: remove the link bar if it ended up empty (no anchors) | |||
(function () { | |||
var bar = doc.querySelector(".link-pdf"); | |||
if (!bar) return; | |||
if (!bar.querySelector("a[href]") && bar.parentNode) | |||
bar.parentNode.removeChild(bar); | |||
})(); | |||
// 7) Remove any now-empty label that wasn’t paired (defensive) | |||
var strayLabels = doc.querySelectorAll( | |||
".article-label-description, .article-label-reflection, " + | |||
".article-label-external-reference, .article-label-quote" | |||
); | |||
for (var i = 0; i < strayLabels.length; i++) { | |||
if (strayLabels[i].parentNode) | |||
strayLabels[i].parentNode.removeChild(strayLabels[i]); | |||
} | |||
// 8) Kill truly empty paragraphs that may still add height | |||
var empties = doc.querySelectorAll("#article-content p"); | |||
for (var j = 0; j < empties.length; j++) { | |||
var text = (empties[j].textContent || "") | |||
.replace(/\u00A0/g, " ") | |||
.replace(/\u200B/g, "") | |||
.trim(); | |||
if ( | |||
!text && | |||
empties[j].children.length === 0 && | |||
empties[j].parentNode | |||
) { | |||
empties[j].parentNode.removeChild(empties[j]); | |||
} | } | ||
})(); | } | ||
// 9) Hide the hairline on the very last block to avoid a “phantom gap” | |||
var css = doc.createElement("style"); | |||
css.textContent = | |||
"@media print{#article-content>:last-child{padding-bottom:0!important;}" + | |||
"#article-content>:last-child::after{content:none!important;}}"; | |||
doc.head.appendChild(css); | |||
})(doc); | })(doc); | ||