|
|
Line 1,263: |
Line 1,263: |
| /* ---------- Softwear PRINT (scoped, ES5-safe) ---------- */ | | /* ---------- Softwear PRINT (scoped, ES5-safe) ---------- */ |
|
| |
|
| /* helpers */ | | /* ========= Softwear Print — ES5, jQuery only ========= */ |
| function swPrintPreloadFont() {
| | (function ($) { |
| var link = document.createElement("link");
| | // ---- helpers ---- |
| link.rel = "preload";
| | function preloadFontForPrint() { |
| link.as = "font";
| | var link = document.createElement("link"); |
| link.type = "font/woff2";
| | link.rel = "preload"; |
| link.href = "/fonts/HALColant-TextRegular.woff2?v=20250820";
| | link.as = "font"; |
| link.crossOrigin = "anonymous";
| | link.type = "font/woff2"; |
| document.head.appendChild(link);
| | link.href = "/fonts/HALColant-TextRegular.woff2?v=20250820"; |
| }
| | link.crossOrigin = "anonymous"; |
| | | document.head.appendChild(link); |
| function swPrintCacheBust(url) {
| |
| return url + (url.indexOf("?") > -1 ? "&" : "?") + "_=" + Date.now();
| |
| }
| |
| | |
| function swEnsurePrintChooser() {
| |
| var $chooser = jQuery("#print-chooser");
| |
| if ($chooser.length) return $chooser;
| |
| $chooser = jQuery(
| |
| '<div id="print-chooser" class="print-chooser" style="display:none;">' +
| |
| '<a href="#" id="print-with-border" class="print-choice">border</a> ' +
| |
| '<a href="#" id="print-no-border" class="print-choice">no border</a>' +
| |
| "</div>"
| |
| );
| |
| jQuery("#print-button").after($chooser);
| |
| return $chooser;
| |
| }
| |
| | |
| function swHidePrintUI() {
| |
| jQuery("#print-chooser").hide(); | |
| jQuery("#show-article").removeClass("print-opts-open");
| |
| }
| |
| | |
| /* core: build iframe and print */
| |
| function swBuildIframeAndPrint(printHtml, borderPref, $btn) {
| |
| // iframe
| |
| var iframe = document.createElement("iframe");
| |
| iframe.style.position = "fixed";
| |
| iframe.style.right = "0";
| |
| iframe.style.bottom = "0";
| |
| iframe.style.width = "0";
| |
| iframe.style.height = "0";
| |
| iframe.style.border = "0";
| |
| document.body.appendChild(iframe);
| |
| | |
| var doc = iframe.contentDocument || iframe.contentWindow.document;
| |
| doc.open();
| |
| doc.write(
| |
| '<!doctype html><html><head><meta charset="utf-8"><title>Print</title></head><body></body></html>' | |
| );
| |
| doc.close();
| |
| | |
| // make relative URLs resolve
| |
| var base = doc.createElement("base");
| |
| base.href = location.origin + "/";
| |
| doc.head.appendChild(base);
| |
| | |
| // print.css
| |
| var linkCss = doc.createElement("link");
| |
| linkCss.rel = "stylesheet";
| |
| linkCss.href = swPrintCacheBust(
| |
| "/index.php?title=MediaWiki:Print.css&action=raw&ctype=text/css" | |
| );
| |
| | |
| var cssLoaded = new Promise(function (resolve) {
| |
| linkCss.onload = function () {
| |
| resolve();
| |
| };
| |
| linkCss.onerror = function () {
| |
| resolve();
| |
| };
| |
| });
| |
| | |
| // font preload (inside iframe)
| |
| var linkFont = doc.createElement("link");
| |
| linkFont.rel = "preload";
| |
| linkFont.as = "font";
| |
| linkFont.type = "font/woff2";
| |
| linkFont.href = "/fonts/HALColant-TextRegular.woff2?v=20250820";
| |
| linkFont.crossOrigin = "anonymous";
| |
| | |
| doc.head.appendChild(linkFont);
| |
| doc.head.appendChild(linkCss);
| |
| | |
| // inject HTML
| |
| doc.body.innerHTML = printHtml;
| |
| | |
| // border preference -> <html> class
| |
| (function () {
| |
| var htmlEl = doc.documentElement; | |
| if (borderPref === "without") {
| |
| if (htmlEl.classList) htmlEl.classList.add("print-no-border");
| |
| else if (
| |
| (" " + htmlEl.className + " ").indexOf(" print-no-border ") === -1
| |
| ) {
| |
| htmlEl.className += " print-no-border";
| |
| }
| |
| } else {
| |
| if (htmlEl.classList) htmlEl.classList.remove("print-no-border");
| |
| else
| |
| htmlEl.className = htmlEl.className.replace(
| |
| /\bprint-no-border\b/g,
| |
| ""
| |
| );
| |
| }
| |
| })();
| |
| | |
| // clean empty paragraphs
| |
| (function () {
| |
| var ps = doc.querySelectorAll("#article-content p");
| |
| Array.prototype.forEach.call(ps, function (p) {
| |
| var txt = (p.textContent || "").replace(/\u00a0/g, " ").trim();
| |
| var onlyBr =
| |
| p.children.length === 1 &&
| |
| p.firstElementChild &&
| |
| p.firstElementChild.tagName === "BR";
| |
| if (
| |
| (!txt && !p.querySelector("img, a, strong, em, span:not(:empty)")) ||
| |
| onlyBr
| |
| ) {
| |
| if (p.parentNode) p.parentNode.removeChild(p);
| |
| }
| |
| });
| |
| var root = doc.getElementById("article-content");
| |
| if (root) {
| |
| Array.prototype.slice.call(root.childNodes).forEach(function (n) {
| |
| if (n.nodeType === 3 && !n.textContent.replace(/\s+/g, "")) {
| |
| root.removeChild(n);
| |
| }
| |
| });
| |
| }
| |
| })();
| |
| | |
| // small inline tweaks
| |
| (function () {
| |
| var css =
| |
| "@media print{" +
| |
| " .article-description p,.article-reflection p,.article-external-reference p,.article-quote p{margin:0 0 1.2mm!important;}" +
| |
| " .article-description p:last-child,.article-reflection p:last-child,.article-external-reference p:last-child,.article-quote p:last-child{margin-bottom:0!important;}" +
| |
| " .article-entry-number,.link-pdf,.article-type,.article-metadata,.article-images,.article-description,.article-reflection,.article-external-reference,.article-quote,.article-mod-line{padding-bottom:1mm!important;}" +
| |
| ' [class^\\"article-label-\\"]{margin-top:0!important;}' +
| |
| ' .article-entry-number + [class^\\"article-label-\\"],' +
| |
| ' .link-pdf + [class^\\"article-label-\\"],' +
| |
| ' .article-type + [class^\\"article-label-\\"],' +
| |
| ' .article-metadata + [class^\\"article-label-\\"],' +
| |
| ' .article-images + [class^\\"article-label-\\"],' +
| |
| ' .article-description + [class^\\"article-label-\\"],' +
| |
| ' .article-reflection + [class^\\"article-label-\\"],' +
| |
| ' .article-external-reference + [class^\\"article-label-\\"],' +
| |
| ' .article-quote + [class^\\"article-label-\\"],' +
| |
| ' .article-mod-line + [class^\\"article-label-\\"]{margin-top:0.9mm!important;}' +
| |
| " .article-label-description + .article-description," +
| |
| " .article-label-reflection + .article-reflection," +
| |
| " .article-label-external-reference + .article-external-reference," +
| |
| " .article-label-quote + .article-quote," +
| |
| " .article-label-modification-date + .article-modification-date{margin-top:0!important;}" +
| |
| " .article-title-link{margin:0!important;padding:0!important;}" +
| |
| " .article-title-link > *{margin:0!important;}" +
| |
| " .link-pdf{margin-top:0!important;}" +
| |
| " #article-content > :last-child{padding-bottom:0!important;}" +
| |
| " #article-content > :last-child::after{content:none!important;}" +
| |
| "}";
| |
| var style = doc.createElement("style");
| |
| style.type = "text/css";
| |
| style.appendChild(doc.createTextNode(css));
| |
| doc.head.appendChild(style);
| |
| })();
| |
| | |
| // link tweaks
| |
| (function () {
| |
| var styleFix = doc.createElement("style");
| |
| styleFix.textContent =
| |
| "@media print {.article-external-reference a,.link-pdf a{white-space:nowrap!important;word-break:normal!important;overflow-wrap:normal!important;text-decoration:underline}.article-external-reference{overflow-wrap:anywhere;word-break:break-word}a[href]{position:relative}}";
| |
| doc.head.appendChild(styleFix);
| |
| var refs = doc.querySelectorAll(".article-external-reference a[href]");
| |
| Array.prototype.forEach.call(refs, function (a) {
| |
| var txt = (a.textContent || "").trim();
| |
| var href = a.getAttribute("href") || "";
| |
| var looksLongUrl = /^https?:\/\//i.test(txt) && txt.length > 60;
| |
| if (looksLongUrl) {
| |
| try {
| |
| var u = new URL(href, doc.baseURI);
| |
| var label =
| |
| u.hostname + (u.pathname.replace(/\/$/, "") ? u.pathname : "");
| |
| if (label.length > 40) label = label.slice(0, 37) + "…";
| |
| a.textContent = label;
| |
| } catch (e) {
| |
| a.textContent = "Link";
| |
| }
| |
| }
| |
| a.style.whiteSpace = "nowrap";
| |
| a.style.wordBreak = "normal";
| |
| a.style.overflowWrap = "normal";
| |
| });
| |
| })();
| |
| | |
| // waits
| |
| function waitImages() {
| |
| var imgs = [].slice.call(doc.images || []);
| |
| if (!imgs.length) return Promise.resolve();
| |
| return Promise.all(
| |
| imgs.map(function (img) {
| |
| if (img.decode) {
| |
| try {
| |
| return img.decode().catch(function () {});
| |
| } catch (e) {}
| |
| }
| |
| return new Promise(function (res) {
| |
| if (img.complete) return res();
| |
| img.onload = img.onerror = function () {
| |
| res();
| |
| };
| |
| });
| |
| })
| |
| );
| |
| }
| |
| function waitFonts(timeoutMs) {
| |
| if (!doc.fonts || !doc.fonts.ready) return Promise.resolve();
| |
| var ready = doc.fonts.ready;
| |
| var t = new Promise(function (res) {
| |
| setTimeout(res, timeoutMs || 1200);
| |
| });
| |
| return Promise.race([ready, t]);
| |
| }
| |
| function waitSpecificFont(timeoutMs) {
| |
| if (!doc.fonts || !doc.fonts.load) return Promise.resolve();
| |
| var p = Promise.all([
| |
| doc.fonts.load('400 16px "HALColant-TextRegular"'),
| |
| doc.fonts.load('normal 16px "HALColant-TextRegular"'),
| |
| ]);
| |
| var t = new Promise(function (res) {
| |
| setTimeout(res, timeoutMs || 1200);
| |
| });
| |
| return Promise.race([p, t]);
| |
| }
| |
| function nextFrame() {
| |
| return new Promise(function (res) {
| |
| (iframe.contentWindow.requestAnimationFrame || setTimeout)(res, 0);
| |
| });
| |
| }
| |
| | |
| Promise.all([
| |
| cssLoaded,
| |
| waitImages(),
| |
| waitFonts(1200),
| |
| waitSpecificFont(1200),
| |
| nextFrame(),
| |
| ])
| |
| .then(function () {
| |
| // filename via document.title
| |
| var entryNum = "";
| |
| var numEl = doc.querySelector(".article-entry-number");
| |
| if (numEl) {
| |
| var m = (numEl.textContent || "").match(/\d+/);
| |
| entryNum = m ? m[0] : "";
| |
| }
| |
| var desiredTitle =
| |
| (entryNum ? entryNum + "." : "") + "softwear.directory";
| |
| var oldIframeTitle = doc.title;
| |
| var oldParentTitle = document.title;
| |
| | |
| iframe.contentWindow.onafterprint = function () {
| |
| try {
| |
| doc.title = oldIframeTitle;
| |
| document.title = oldParentTitle;
| |
| } catch (e) {}
| |
| setTimeout(function () {
| |
| if (iframe.parentNode) iframe.parentNode.removeChild(iframe);
| |
| }, 100);
| |
| if ($btn && $btn.length) $btn.data("busy", false);
| |
| };
| |
| | |
| doc.title = desiredTitle;
| |
| document.title = desiredTitle;
| |
| | |
| iframe.contentWindow.focus();
| |
| iframe.contentWindow.print();
| |
| | |
| // safety cleanup
| |
| setTimeout(function () {
| |
| try {
| |
| doc.title = oldIframeTitle;
| |
| document.title = oldParentTitle;
| |
| } catch (e) {}
| |
| if (iframe.parentNode) iframe.parentNode.removeChild(iframe);
| |
| if ($btn && $btn.length) $btn.data("busy", false);
| |
| }, 1000);
| |
| })
| |
| .catch(function () {
| |
| if ($btn && $btn.length) $btn.data("busy", false);
| |
| });
| |
| }
| |
| | |
| /* decide source & kick print */
| |
| function swHandlePrintChoice(id, $btn) {
| |
| if ($btn && $btn.data("busy")) return;
| |
| if ($btn && $btn.length) $btn.data("busy", true);
| |
| | |
| var borderPref = id === "print-no-border" ? "without" : "with";
| |
| swPrintPreloadFont();
| |
| | |
| // prefer local .print-only (Entry page)
| |
| var localPrintOnly = jQuery(".print-only").first();
| |
| if (localPrintOnly.length) {
| |
| swHidePrintUI();
| |
| swBuildIframeAndPrint(localPrintOnly.prop("outerHTML"), borderPref, $btn);
| |
| return;
| |
| } | | } |
|
| |
|
| // otherwise fetch by title (modal/home list flow) | | function cacheBust(url) { |
| var title =
| | return url + (url.indexOf("?") > -1 ? "&" : "?") + "_=" + Date.now(); |
| window.currentEntryTitle ||
| |
| (window.mw && mw.config && mw.config.get && mw.config.get("wgPageName")); | |
| if (!title) {
| |
| window.print();
| |
| if ($btn && $btn.length) $btn.data("busy", false);
| |
| return;
| |
| } | | } |
|
| |
|
| var pageUrl = | | function ensurePrintChooser() { |
| window.mw && mw.util && mw.util.getUrl
| | var $chooser = $("#print-chooser"); |
| ? mw.util.getUrl(title)
| | if ($chooser.length) return $chooser; |
| : "/wiki/" + String(title);
| |
| jQuery
| |
| .get(swPrintCacheBust(pageUrl))
| |
| .done(function (html) {
| |
| var $tmp = jQuery("<div>").html(html);
| |
| var $print = $tmp.find(".print-only").first();
| |
| if (!$print.length) {
| |
| window.print();
| |
| if ($btn && $btn.length) $btn.data("busy", false);
| |
| return;
| |
| }
| |
| swHidePrintUI();
| |
| swBuildIframeAndPrint($print.prop("outerHTML"), borderPref, $btn);
| |
| })
| |
| .fail(function () {
| |
| window.print();
| |
| jQuery("#print-button").data("busy", false);
| |
| });
| |
| }
| |
|
| |
|
| /* wiring (namespaced) */
| | $chooser = $( |
| jQuery(document).off("click.swprint");
| | '<div id="print-chooser" class="print-chooser" style="display:none;margin-top:6px;">' + |
| jQuery(document).on(
| | '<a href="#" id="print-with-border" class="print-choice">[with border]</a> ' + |
| "click.swprint",
| | '<a href="#" id="print-no-border" class="print-choice">[no border]</a>' + |
| "#print-button, #print-chooser, #print-options",
| | "</div>" |
| function (e) {
| |
| // click on the main [print]
| |
| if (jQuery(e.target).closest("#print-button").length) {
| |
| e.preventDefault();
| |
| var $chooser = swEnsurePrintChooser();
| |
| $chooser.toggle();
| |
| jQuery("#show-article").toggleClass(
| |
| "print-opts-open",
| |
| $chooser.is(":visible")
| |
| );
| |
| return;
| |
| }
| |
| // click on a choice link
| |
| var $choice = jQuery(e.target).closest(
| |
| "a#print-with-border, a#print-no-border"
| |
| ); | | ); |
| if (!$choice.length) return; | | $("#print-button").after($chooser); |
| e.preventDefault();
| | return $chooser; |
| swHandlePrintChoice($choice.attr("id"), $choice); | |
| } | | } |
| );
| |
|
| |
| // --- extra safety bindings for Entry page / widgets ---
| |
|
| |
|
| // 1) Directly bind the anchors (works even if container delegation misses)
| | function hideChooser() { |
| jQuery(document).on(
| | $("#print-chooser").hide(); |
| "click.swprintChoice",
| | $("#show-article").removeClass("print-opts-open"); |
| "a#print-with-border, a#print-no-border, #print-with-border, #print-no-border",
| |
| function (e) { | |
| console.log("[print] direct anchor handler fired:", this.id); | |
| e.preventDefault();
| |
| swHandlePrintChoice(this.id, jQuery(this)); | |
| } | | } |
| );
| |
|
| |
|
| // 2) If using the Button widget (<a id=...><button>...</button></a>),
| | // Remove any previous versions |
| // capture clicks on the <button> and route via its parent <a>.
| | $(document).off("click.print"); |
| jQuery(document).on(
| |
| "click.swprintChoiceBtn",
| |
| "#print-options button",
| |
| function (e) {
| |
| var $a = jQuery(this).closest("a[id]");
| |
| if (!$a.length) return;
| |
| console.log(
| |
| "[print] widget button handler fired; parent anchor id:",
| |
| $a.attr("id")
| |
| );
| |
| e.preventDefault();
| |
| swHandlePrintChoice($a.attr("id"), $a);
| |
| }
| |
| );
| |
|
| |
|
| // also hide choices on ESC; your close-button handler already hides them
| | // One handler for all the print UI |
| jQuery(document).on("keydown.swprint", function (e) {
| | $(document).on( |
| if (e && e.keyCode === 27) swHidePrintUI();
| | "click.print", |
| });
| | "#print-button, #print-chooser a.print-choice, #print-with-border, #print-no-border", |
| | function (e) { |
| | var $anchor = $(e.target).closest("a"); |
| | if ($anchor.length) e.preventDefault(); |
|
| |
|
| /* ---------- /Softwear PRINT ---------- */
| | var rawId = this.id; |
| | var id = ($anchor.attr("id") || rawId || "").trim(); |
|
| |
|
| /* === SW PRINT: native fallback for Entry pages (ES5-safe) === */
| | // 1) Toggle the chooser when [print] is clicked |
| (function () {
| | if (id === "print-button") { |
| // prevent double-binding
| | var $chooser = ensurePrintChooser(); |
| if (window.__swPrintNativeBound) {
| | var show = !$chooser.is(":visible"); |
| return;
| | $chooser.css({ display: show ? "block" : "none" }); |
| }
| | $("#show-article")[show ? "addClass" : "removeClass"]( |
| window.__swPrintNativeBound = true;
| | "print-opts-open" |
| | | ); |
| // ---- tiny helpers ----
| | return; |
| function ensurePrintChooserNative() {
| |
| var chooser = document.getElementById("print-chooser");
| |
| if (!chooser) {
| |
| chooser = document.createElement("div");
| |
| chooser.id = "print-chooser";
| |
| chooser.className = "print-chooser";
| |
| chooser.style.display = "none";
| |
| chooser.innerHTML =
| |
| '<a href="#" id="print-with-border" class="print-choice">border</a> ' + | |
| '<a href="#" id="print-no-border" class="print-choice">no border</a>';
| |
| var printBtn = document.getElementById("print-button");
| |
| if (printBtn && printBtn.parentNode) {
| |
| printBtn.parentNode.insertBefore(chooser, printBtn.nextSibling); | |
| } | | } |
| }
| |
| return chooser;
| |
| }
| |
|
| |
|
| function toggleChooser(show) {
| | // 2) Must be one of the options |
| var chooser = ensurePrintChooserNative();
| | if (id !== "print-with-border" && id !== "print-no-border") { |
| var want =
| | return; |
| typeof show === "boolean" ? show : chooser.style.display === "none"; | |
| chooser.style.display = want ? "block" : "none";
| |
| var article = document.getElementById("show-article");
| |
| if (article) {
| |
| if (want) { | |
| article.classList.add("print-opts-open");
| |
| } else {
| |
| article.classList.remove("print-opts-open");
| |
| }
| |
| }
| |
| if (window.console && console.log) {
| |
| console.log("[swprint-native] chooser", want ? "shown" : "hidden");
| |
| }
| |
| }
| |
| | |
| function safeCacheBust(url) {
| |
| try {
| |
| if (typeof cacheBust === "function") {
| |
| return cacheBust(url);
| |
| }
| |
| } catch (e) {
| |
| /* ignore */
| |
| }
| |
| var sep = url.indexOf("?") > -1 ? "&" : "?";
| |
| return url + sep + "_=" + new Date().getTime();
| |
| }
| |
| | |
| // ---- main flow: start printing with preference ----
| |
| function swStartPrint(borderPref) {
| |
| if (window.console && console.log) {
| |
| console.log("[swprint-native] start with pref:", borderPref);
| |
| }
| |
| | |
| try {
| |
| if (typeof preloadFontForPrint === "function") {
| |
| preloadFontForPrint(); | |
| } | | } |
| } catch (e) {}
| |
|
| |
|
| // 1) Prefer local .print-only (Entry page content already on page)
| | var $btn = $anchor.length ? $anchor : $(this); |
| var local = document.querySelector(".print-only");
| | if ($btn.data("busy")) return; |
| if (local) {
| | $btn.data("busy", true); |
| if (window.console && console.log) { | |
| console.log("[swprint-native] using local .print-only");
| |
| }
| |
| toggleChooser(false);
| |
| buildIframeAndPrint(local.outerHTML, borderPref); | |
| return;
| |
| }
| |
|
| |
|
| // 2) Otherwise, fetch by title (modal / people page flow)
| | var borderPref = id === "print-no-border" ? "without" : "with"; |
| var title = null;
| | preloadFontForPrint(); |
| if (
| |
| typeof window.currentEntryTitle === "string" &&
| |
| window.currentEntryTitle
| |
| ) {
| |
| title = window.currentEntryTitle;
| |
| } else if (
| |
| window.mw &&
| |
| mw.config &&
| |
| typeof mw.config.get === "function"
| |
| ) {
| |
| title = mw.config.get("wgPageName"); | |
| }
| |
|
| |
|
| if (
| | // Prefer local .print-only (Entry pages) |
| !title || | | var $localPrintOnly = $(".print-only").first(); |
| !(window.mw && mw.util && typeof mw.util.getUrl === "function")
| | if ($localPrintOnly.length) { |
| ) {
| | hideChooser(); |
| if (window.console && console.warn) { | | buildIframeAndPrint( |
| console.warn( | | $localPrintOnly.prop("outerHTML"), |
| "[swprint-native] no title/mw.util; fallback to window.print()" | | borderPref, |
| | $btn |
| ); | | ); |
| | return; |
| } | | } |
| window.print();
| |
| return;
| |
| }
| |
|
| |
|
| var url = mw.util.getUrl(title);
| | // Fallback: fetch by title (modal/home/person pages) |
| url = safeCacheBust(url);
| | var title = |
| if (window.console && console.log) {
| | window.currentEntryTitle || |
| console.log("[swprint-native] fetching:", url);
| | (mw.config && mw.config.get && mw.config.get("wgPageName")); |
| }
| | if (!title) { |
| | window.print(); |
| | $btn.data("busy", false); |
| | return; |
| | } |
|
| |
|
| // Use fetch if present; fall back to XHR
| | var pageUrlFresh = cacheBust(mw.util.getUrl(title)); |
| if (window.fetch) {
| | $.get(pageUrlFresh) |
| fetch(url, { credentials: "same-origin" }) | | .done(function (html) { |
| .then(function (r) { | | var $tmp = $("<div>").html(html); |
| return r.text(); | | var $print = $tmp.find(".print-only").first(); |
| })
| | if (!$print.length) { |
| .then(function (html) {
| | window.print(); |
| handleFetchedHtml(html, borderPref); | | $btn.data("busy", false); |
| | return; |
| | } |
| | hideChooser(); |
| | buildIframeAndPrint($print.prop("outerHTML"), borderPref, $btn); |
| }) | | }) |
| .catch(function (err) { | | .fail(function () { |
| if (window.console && console.warn) {
| |
| console.warn(
| |
| "[swprint-native] fetch failed; window.print()",
| |
| err
| |
| );
| |
| }
| |
| window.print(); | | window.print(); |
| | $("#print-button").data("busy", false); |
| }); | | }); |
| } else {
| |
| var xhr = new XMLHttpRequest();
| |
| xhr.open("GET", url, true);
| |
| xhr.withCredentials = true;
| |
| xhr.onreadystatechange = function () {
| |
| if (xhr.readyState === 4) {
| |
| if (xhr.status >= 200 && xhr.status < 300) {
| |
| handleFetchedHtml(xhr.responseText, borderPref);
| |
| } else {
| |
| if (window.console && console.warn) {
| |
| console.warn(
| |
| "[swprint-native] XHR failed; window.print()",
| |
| xhr.status
| |
| );
| |
| }
| |
| window.print();
| |
| }
| |
| }
| |
| };
| |
| xhr.send(null);
| |
| } | | } |
| } | | ); |
|
| |
|
| function handleFetchedHtml(html, borderPref) { | | // Also hide the chooser when closing the card |
| var tmp = document.createElement("div");
| | $(document).on("click.print", "#close-button", function () { |
| tmp.innerHTML = html;
| | hideChooser(); |
| var printOnly = tmp.querySelector(".print-only");
| | }); |
| if (!printOnly) {
| |
| if (window.console && console.warn) {
| |
| console.warn(
| |
| "[swprint-native] no .print-only in fetched page; window.print()"
| |
| );
| |
| }
| |
| window.print();
| |
| return;
| |
| }
| |
| toggleChooser(false); | |
| buildIframeAndPrint(printOnly.outerHTML, borderPref);
| |
| } | |
| | |
| // ---- iframe build + print (ES5) ----
| |
| function buildIframeAndPrint(printHtml, borderPref) {
| |
| if (window.console && console.log) {
| |
| console.log("[swprint-native] buildIframeAndPrint()");
| |
| }
| |
|
| |
|
| | // ---- iframe + print ---- |
| | function buildIframeAndPrint(printHtml, borderPref, $btn) { |
| | // Hidden iframe |
| var iframe = document.createElement("iframe"); | | var iframe = document.createElement("iframe"); |
| iframe.style.position = "fixed"; | | iframe.style.position = "fixed"; |
Line 1,863: |
Line 1,402: |
| doc.close(); | | doc.close(); |
|
| |
|
| | // Base for relative URLs |
| var base = doc.createElement("base"); | | var base = doc.createElement("base"); |
| base.href = location.origin + "/"; | | base.href = location.origin + "/"; |
| doc.head.appendChild(base); | | doc.head.appendChild(base); |
|
| |
|
| var cssUrl = | | // Print CSS |
| "/index.php?title=MediaWiki:Print.css&action=raw&ctype=text/css";
| |
| cssUrl = safeCacheBust(cssUrl);
| |
| | |
| var linkCss = doc.createElement("link"); | | var linkCss = doc.createElement("link"); |
| linkCss.rel = "stylesheet"; | | linkCss.rel = "stylesheet"; |
| linkCss.href = cssUrl; | | linkCss.href = cacheBust( |
| | "/index.php?title=MediaWiki:Print.css&action=raw&ctype=text/css" |
| | ); |
|
| |
|
| var cssLoaded = new Promise(function (resolve) { | | var cssLoaded = new Promise(function (resolve) { |
| linkCss.onload = function () { | | linkCss.onload = function () { |
| if (window.console && console.log) {
| |
| console.log("[swprint-native] print CSS loaded");
| |
| }
| |
| resolve(); | | resolve(); |
| }; | | }; |
| linkCss.onerror = function () { | | linkCss.onerror = function () { |
| if (window.console && console.warn) {
| |
| console.warn("[swprint-native] print CSS failed");
| |
| }
| |
| resolve(); | | resolve(); |
| }; | | }; |
| }); | | }); |
|
| |
|
| | // Preload font inside iframe |
| var linkFont = doc.createElement("link"); | | var linkFont = doc.createElement("link"); |
| linkFont.rel = "preload"; | | linkFont.rel = "preload"; |
Line 1,900: |
Line 1,434: |
| doc.head.appendChild(linkCss); | | doc.head.appendChild(linkCss); |
|
| |
|
| // content | | // Content |
| doc.body.innerHTML = printHtml; | | doc.body.innerHTML = printHtml; |
|
| |
|
| // border pref | | // Apply border preference on <html> |
| var root = doc.documentElement; | | var rootHtml = doc.documentElement; |
| if (borderPref === "without") { | | if (borderPref === "without") { |
| root.classList.add("print-no-border"); | | if (rootHtml.classList) rootHtml.classList.add("print-no-border"); |
| | else rootHtml.className += " print-no-border"; |
| } else { | | } else { |
| root.classList.remove("print-no-border"); | | if (rootHtml.classList) rootHtml.classList.remove("print-no-border"); |
| }
| | else |
| if (window.console && console.log) {
| | rootHtml.className = rootHtml.className.replace( |
| console.log("[swprint-native] borderPref applied:", borderPref);
| | /\bprint-no-border\b/g, |
| | "" |
| | ); |
| } | | } |
|
| |
|
| // minimal inline tweaks | | // Minimal inline tweaks |
| var style = doc.createElement("style"); | | var tweaks = doc.createElement("style"); |
| style.type = "text/css"; | | tweaks.textContent = |
| style.appendChild(
| | "@media print{" + |
| doc.createTextNode( | | " .article-description p,.article-reflection p,.article-external-reference p,.article-quote p{margin:0 0 1.2mm!important;}" + |
| "@media print{.article-description p,.article-reflection p,.article-external-reference p,.article-quote p{margin:0 0 1.2mm!important}.article-description p:last-child,.article-reflection p:last-child,.article-external-reference p:last-child,.article-quote p:last-child{margin-bottom:0!important}.article-entry-number,.link-pdf,.article-type,.article-metadata,.article-images,.article-description,.article-reflection,.article-external-reference,.article-quote,.article-mod-line{padding-bottom:1mm!important}[class^='article-label-']{margin-top:0!important}.article-entry-number+[class^='article-label-'],.link-pdf+[class^='article-label-'],.article-type+[class^='article-label-'],.article-metadata+[class^='article-label-'],.article-images+[class^='article-label-'],.article-description+[class^='article-label-'],.article-reflection+[class^='article-label-'],.article-external-reference+[class^='article-label-'],.article-quote+[class^='article-label-'],.article-mod-line+[class^='article-label-']{margin-top:0.9mm!important}.article-title-link{margin:0!important;padding:0!important}.article-title-link>*{margin:0!important}.link-pdf{margin-top:0!important}#article-content>:last-child{padding-bottom:0!important}#article-content>:last-child::after{content:none!important}}"
| | " .article-description p:last-child,.article-reflection p:last-child,.article-external-reference p:last-child,.article-quote p:last-child{margin-bottom:0!important;}" + |
| ) | | " .article-entry-number,.link-pdf,.article-type,.article-metadata,.article-images,.article-description,.article-reflection,.article-external-reference,.article-quote,.article-mod-line{padding-bottom:1mm!important;}" + |
| );
| | " .article-title-link{margin:0!important;padding:0!important;}" + |
| doc.head.appendChild(style); | | " .article-title-link > *{margin:0!important;}" + |
| | " .link-pdf{margin-top:0!important;}" + |
| | " #article-content > :last-child{padding-bottom:0!important;}" + |
| | " #article-content > :last-child::after{content:none!important;}" + |
| | "}"; |
| | doc.head.appendChild(tweaks); |
|
| |
|
| var styleFix = doc.createElement("style"); | | // Wait for resources, then print |
| styleFix.type = "text/css";
| | function waitImages() { |
| styleFix.appendChild(
| | var imgs = [].slice.call(doc.images || []); |
| doc.createTextNode(
| | if (!imgs.length) return Promise.resolve(); |
| "@media print {.article-external-reference a,.link-pdf a{white-space:nowrap!important;word-break:normal!important;overflow-wrap:normal!important;text-decoration:underline}.article-external-reference{overflow-wrap:anywhere;word-break:break-word}a[href]{position:relative}}"
| | return Promise.all( |
| ) | | imgs.map(function (img) { |
| );
| | if (img.decode) { |
| doc.head.appendChild(styleFix);
| | try { |
| | | return img.decode().catch(function () {}); |
| // wait helpers | | } catch (e) {} |
| function nextFrame(resolve) { | | } |
| (iframe.contentWindow.requestAnimationFrame || setTimeout)(resolve, 0); | | return new Promise(function (res) { |
| | if (img.complete) return res(); |
| | img.onload = img.onerror = function () { |
| | res(); |
| | }; |
| | }); |
| | }) |
| | ); |
| | } |
| | function waitFonts(timeoutMs) { |
| | if (!doc.fonts || !doc.fonts.ready) return Promise.resolve(); |
| | var ready = doc.fonts.ready; |
| | var t = new Promise(function (res) { |
| | setTimeout(res, timeoutMs || 1200); |
| | }); |
| | return Promise.race([ready, t]); |
| | } |
| | function waitSpecificFont(timeoutMs) { |
| | if (!doc.fonts || !doc.fonts.load) return Promise.resolve(); |
| | var p = Promise.all([ |
| | doc.fonts.load('400 16px "HALColant-TextRegular"'), |
| | doc.fonts.load('normal 16px "HALColant-TextRegular"'), |
| | ]); |
| | var t = new Promise(function (res) { |
| | setTimeout(res, timeoutMs || 1200); |
| | }); |
| | return Promise.race([p, t]); |
| | } |
| | function nextFrame() { |
| | return new Promise(function (res) { |
| | (iframe.contentWindow.requestAnimationFrame || setTimeout)(res, 0); |
| | }); |
| } | | } |
| var waitNextFrame = new Promise(function (resolve) {
| |
| nextFrame(resolve);
| |
| });
| |
| var waitFonts =
| |
| doc.fonts && doc.fonts.ready
| |
| ? Promise.race([
| |
| doc.fonts.ready,
| |
| new Promise(function (r) {
| |
| setTimeout(r, 1200);
| |
| }),
| |
| ])
| |
| : Promise.resolve();
| |
|
| |
| Promise.all([cssLoaded, waitFonts, waitNextFrame]).then(function () {
| |
| if (window.console && console.log) {
| |
| console.log("[swprint-native] resources ready; print()");
| |
| }
| |
|
| |
| var numEl = doc.querySelector(".article-entry-number");
| |
| var entryNum = "";
| |
| if (numEl) {
| |
| var m = (numEl.textContent || "").match(/\d+/);
| |
| entryNum = m ? m[0] : "";
| |
| }
| |
| var desiredTitle =
| |
| (entryNum ? entryNum + "." : "") + "softwear.directory";
| |
|
| |
|
| var oldIframeTitle = doc.title; | | Promise.all([ |
| var oldParentTitle = document.title; | | cssLoaded, |
| | | waitImages(), |
| iframe.contentWindow.onafterprint = function () { | | waitFonts(1200), |
| | waitSpecificFont(1200), |
| | nextFrame(), |
| | ]) |
| | .then(function () { |
| try { | | try { |
| doc.title = oldIframeTitle; | | // Filename via entry number |
| document.title = oldParentTitle; | | var entryNum = ""; |
| } catch (e) {}
| | var numEl = doc.querySelector(".article-entry-number"); |
| setTimeout(function () {
| | if (numEl) { |
| if (iframe.parentNode) { | | var m = (numEl.textContent || "").match(/\d+/); |
| iframe.parentNode.removeChild(iframe); | | entryNum = m ? m[0] : ""; |
| } | | } |
| }, 100);
| | var desiredTitle = |
| if (window.console && console.log) {
| | (entryNum ? entryNum + "." : "") + "softwear.directory"; |
| console.log("[swprint-native] afterprint cleanup"); | | |
| }
| | var oldIframeTitle = doc.title; |
| };
| | var oldParentTitle = document.title; |
|
| |
|
| doc.title = desiredTitle;
| | iframe.contentWindow.onafterprint = function () { |
| document.title = desiredTitle;
| | try { |
| | doc.title = oldIframeTitle; |
| | document.title = oldParentTitle; |
| | } catch (e) {} |
| | setTimeout(function () { |
| | if (iframe.parentNode) iframe.parentNode.removeChild(iframe); |
| | }, 100); |
| | $btn.data("busy", false); |
| | }; |
|
| |
|
| iframe.contentWindow.focus();
| | doc.title = desiredTitle; |
| iframe.contentWindow.print();
| | document.title = desiredTitle; |
|
| |
|
| // safety cleanup if onafterprint never fires
| | iframe.contentWindow.focus(); |
| setTimeout(function () {
| | iframe.contentWindow.print(); |
| try {
| |
| doc.title = oldIframeTitle; | |
| document.title = oldParentTitle;
| |
| } catch (e) {}
| |
| if (iframe.parentNode) {
| |
| iframe.parentNode.removeChild(iframe); | |
| }
| |
| if (window.console && console.log) {
| |
| console.log("[swprint-native] timeout cleanup");
| |
| }
| |
| }, 1500);
| |
| });
| |
| }
| |
|
| |
|
| // ---- bind native clicks ----
| | // Safety cleanup |
| var printBtn = document.getElementById("print-button");
| | setTimeout(function () { |
| if (printBtn) {
| | try { |
| printBtn.addEventListener(
| | doc.title = oldIframeTitle; |
| "click",
| | document.title = oldParentTitle; |
| function (e) {
| | } catch (e) {} |
| if (e && e.preventDefault) { | | if (iframe.parentNode) iframe.parentNode.removeChild(iframe); |
| e.preventDefault(); | | $btn.data("busy", false); |
| | }, 1000); |
| | } catch (err) { |
| | $btn.data("busy", false); |
| } | | } |
| if (window.console && console.log) {
| | }) |
| console.log("[swprint-native] print-button clicked");
| | .catch(function () { |
| }
| | $btn.data("busy", false); |
| toggleChooser();
| | }); |
| }, | |
| false
| |
| );
| |
| } | | } |
| | })(jQuery); |
|
| |
|
| function onChoice(e) {
| | /* ---------- /Softwear PRINT ---------- */ |
| var t = e.target || e.srcElement;
| |
| var a = null;
| |
| // emulate closest("a") in ES5
| |
| while (t && t !== document) {
| |
| if (t.tagName && t.tagName.toLowerCase() === "a") {
| |
| a = t;
| |
| break;
| |
| }
| |
| t = t.parentNode;
| |
| }
| |
| if (!a) {
| |
| return;
| |
| }
| |
| var id = a.id || "";
| |
| if (id !== "print-with-border" && id !== "print-no-border") {
| |
| return;
| |
| }
| |
| if (e && e.preventDefault) {
| |
| e.preventDefault();
| |
| }
| |
| var pref = id === "print-no-border" ? "without" : "with";
| |
| if (window.console && console.log) {
| |
| console.log("[swprint-native] choice clicked:", pref);
| |
| }
| |
| swStartPrint(pref);
| |
| }
| |
| | |
| // bind existing anchors (if already in DOM)
| |
| var aWith = document.getElementById("print-with-border");
| |
| if (aWith) {
| |
| aWith.addEventListener("click", onChoice, false);
| |
| }
| |
| var aNo = document.getElementById("print-no-border");
| |
| if (aNo) {
| |
| aNo.addEventListener("click", onChoice, false);
| |
| }
| |
| | |
| // and delegate for dynamically created chooser
| |
| document.addEventListener("click", onChoice, false);
| |
| | |
| if (window.console && console.log) {
| |
| console.log("[swprint-native] fallback bound");
| |
| }
| |
| })();
| |
| | |
| //ENDEND
| |
|
| |
|
| // Close modal with Close button | | // Close modal with Close button |