MediaWiki:Common.js: Difference between revisions

Jump to navigation Jump to search
no edit summary
No edit summary
No edit summary
Line 1,260: Line 1,260:
   });
   });


   // --- PRINT HELPERS ---
   // ==========================
   // Warm the font cache in the *parent* document.
   // Softwear — Print (all pages)
  function preloadFontForPrint() {
  // ==========================
    var link = document.createElement("link");
  $(function () {
    link.rel = "preload";
     var DEBUG = true; // set false to silence console logs
    link.as = "font";
    link.type = "font/woff2";
    link.href = "/fonts/HALColant-TextRegular.woff2?v=20250820";
    link.crossOrigin = "anonymous";
    document.head.appendChild(link);
     // keep it; no need to remove
  }


  // Simple cache buster
    // 1) Warm the font cache in the *parent* document
  function cacheBust(url) {
    function preloadFontForPrint() {
    return url + (url.indexOf("?") > -1 ? "&" : "?") + "_=" + Date.now();
      var link = document.createElement("link");
  }
      link.rel = "preload";
      link.as = "font";
      link.type = "font/woff2";
      link.href = "/fonts/HALColant-TextRegular.woff2?v=20250820";
      link.crossOrigin = "anonymous";
      document.head.appendChild(link);
    }


  // --- tiny inline chooser for border preference ---
    // 2) Cache buster
  function ensurePrintChooser() {
    function cacheBust(url) {
    var $chooser = $("#print-chooser");
      return url + (url.indexOf("?") > -1 ? "&" : "?") + "_=" + Date.now();
    if ($chooser.length) return $chooser;
     }
 
    // MediaWiki-safe: use <div> + <a>, no <button> needed
    $chooser = $(
      '<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>"
    );
    $("#print-button").after($chooser);
     return $chooser;
  }


  // Kill any previous print handlers
    // 3) Create the little chooser under [print] if needed
  $(document).off(
    function ensurePrintChooser() {
    "click.print",
      var $chooser = $("#print-chooser");
    "#print-button, #print-with-border, #print-no-border, #print-chooser a.print-choice, #print-options a#print-with-border, #print-options a#print-no-border"
      if ($chooser.length) return $chooser;
  );


  // One handler for all the print UI
      // MediaWiki-safe: <div> + <a>, no <button>
  $(document).on(
      $chooser = $(
    "click.print",
        '<div id="print-chooser" class="print-chooser" style="display:none;">' +
    "#print-button, #print-with-border, #print-no-border, #print-chooser a.print-choice, #print-options a#print-with-border, #print-options a#print-no-border",
          '<a href="#" id="print-with-border" class="print-choice">border</a> ' +
    function (e) {
          '<a href="#" id="print-no-border" class="print-choice">no border</a>' +
      // Normalize the target to the closest <a> (covers the widget’s <a><button></button></a>)
          "</div>"
       var $anchor = $(e.target).closest("a");
      );
       if ($anchor.length) e.preventDefault();
       $("#print-button").after($chooser);
       return $chooser;
    }


      var rawId = this.id; // in case you clicked the element with the id directly
    // 4) Hide chooser utility (used by close handlers)
       var id = ($anchor.attr("id") || rawId || "").trim();
    function hidePrintUI() {
 
       $("#print-chooser").hide();
       console.log("[print] click:", {
       $("#show-article").removeClass("print-opts-open");
        target: e.target.tagName,
    }
        thisId: rawId,
        anchorId: id,
      });
 
      // Toggle chooser if main [print] clicked
      if (id === "print-button") {
        var $chooser = ensurePrintChooser();
        $chooser.toggle();


        var $article = $("#show-article");
    // 5) Main “choice” handler (called by the container click handler below)
        if ($chooser.is(":visible")) {
    function handlePrintChoice(id, $btn) {
          $article.addClass("print-opts-open");
        } else {
          $article.removeClass("print-opts-open");
        }
 
        console.log(
          "[print] toggled chooser; visible=",
          $chooser.is(":visible")
        );
        return; // don't start printing yet
      }
 
      // From here on it must be one of the two options
      if (id !== "print-with-border" && id !== "print-no-border") {
        console.warn("[print] unexpected click (ignoring). id=", id);
        return;
      }
 
      var $btn = $anchor.length ? $anchor : $(this);
       if ($btn.data("busy")) {
       if ($btn.data("busy")) {
         console.log("[print] busy, ignoring click");
         if (DEBUG) console.log("[print] busy, ignoring click");
         return;
         return;
       }
       }
Line 1,351: Line 1,313:


       var borderPref = id === "print-no-border" ? "without" : "with";
       var borderPref = id === "print-no-border" ? "without" : "with";
       console.log("[print] option chosen:", borderPref);
       if (DEBUG) console.log("[print] option chosen:", borderPref);


       preloadFontForPrint();
       preloadFontForPrint();
Line 1,357: Line 1,319:
       // Prefer local .print-only (Entry page)
       // Prefer local .print-only (Entry page)
       var localPrintOnly = $(".print-only").first();
       var localPrintOnly = $(".print-only").first();
       console.log("[print] local .print-only present:", localPrintOnly.length);
       if (DEBUG)
        console.log(
          "[print] local .print-only present:",
          localPrintOnly.length
        );


       if (localPrintOnly.length) {
       if (localPrintOnly.length) {
         console.log("[print] using local .print-only on this page");
         if (DEBUG) console.log("[print] using local .print-only on this page");
         $("#print-chooser").hide();
         hidePrintUI();
        $("#show-article").removeClass("print-opts-open");
         buildIframeAndPrint(localPrintOnly.prop("outerHTML"), borderPref, $btn);
         buildIframeAndPrint(localPrintOnly.prop("outerHTML"));
         return;
         return;
       }
       }
Line 1,370: Line 1,335:
       var title =
       var title =
         window.currentEntryTitle ||
         window.currentEntryTitle ||
         (mw.config && mw.config.get && mw.config.get("wgPageName"));
         (window.mw &&
       console.log("[print] currentEntryTitle / wgPageName:", title);
          mw.config &&
          mw.config.get &&
          mw.config.get("wgPageName"));
       if (DEBUG) console.log("[print] currentEntryTitle / wgPageName:", title);


       if (!title) {
       if (!title) {
Line 1,380: Line 1,348:
       }
       }


       var pageUrl = mw.util.getUrl(title);
       var pageUrl =
        window.mw && mw.util && mw.util.getUrl
          ? mw.util.getUrl(title)
          : "/wiki/" + String(title);
       var pageUrlFresh = cacheBust(pageUrl);
       var pageUrlFresh = cacheBust(pageUrl);
       console.log("[print] fetching page HTML:", pageUrlFresh);
       if (DEBUG) console.log("[print] fetching page HTML:", pageUrlFresh);


       $.get(pageUrlFresh)
       $.get(pageUrlFresh)
Line 1,388: Line 1,359:
           var $tmp = $("<div>").html(html);
           var $tmp = $("<div>").html(html);
           var $print = $tmp.find(".print-only").first();
           var $print = $tmp.find(".print-only").first();
           console.log("[print] .print-only in fetched page:", $print.length);
           if (DEBUG)
            console.log("[print] .print-only in fetched page:", $print.length);
           if (!$print.length) {
           if (!$print.length) {
             console.warn(
             console.warn(
Line 1,397: Line 1,369:
             return;
             return;
           }
           }
           $("#print-chooser").hide();
           hidePrintUI();
          $("#show-article").removeClass("print-opts-open");
           buildIframeAndPrint($print.prop("outerHTML"), borderPref, $btn);
           buildIframeAndPrint($print.prop("outerHTML"));
         })
         })
         .fail(function (xhr) {
         .fail(function (xhr) {
Line 1,410: Line 1,381:
           $("#print-button").data("busy", false);
           $("#print-button").data("busy", false);
         });
         });
    }
    // 6) Build a hidden iframe, inject CSS+HTML, and print
    function buildIframeAndPrint(printHtml, borderPref, $btn) {
      if (DEBUG) console.log("[print] buildIframeAndPrint: starting");


       // Helper scoped here so it can see borderPref and $btn
       // Hidden iframe
       function buildIframeAndPrint(printHtml) {
       var iframe = document.createElement("iframe");
        console.log("[print] buildIframeAndPrint: starting");
      iframe.style.position = "fixed";
        // Build hidden iframe
      iframe.style.right = "0";
        var iframe = document.createElement("iframe");
      iframe.style.bottom = "0";
        iframe.style.position = "fixed";
      iframe.style.width = "0";
        iframe.style.right = "0";
      iframe.style.height = "0";
        iframe.style.bottom = "0";
      iframe.style.border = "0";
        iframe.style.width = "0";
      document.body.appendChild(iframe);
        iframe.style.height = "0";
        iframe.style.border = "0";
        document.body.appendChild(iframe);


        var doc = iframe.contentDocument || iframe.contentWindow.document;
      var doc = iframe.contentDocument || iframe.contentWindow.document;
        doc.open();
      doc.open();
        doc.write(
      doc.write(
          '<!doctype html><html><head><meta charset="utf-8"><title>Print</title></head><body></body></html>'
        '<!doctype html><html><head><meta charset="utf-8"><title>Print</title></head><body></body></html>'
        );
      );
        doc.close();
      doc.close();


        // Ensure relative URLs (fonts/images) resolve inside iframe
      // Ensure relative URLs resolve
        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);


        // Inject PRINT CSS (cache-busted) and await it
      // Print CSS
        var printCssUrl =
      var printCssUrl =
          "/index.php?title=MediaWiki:Print.css&action=raw&ctype=text/css";
        "/index.php?title=MediaWiki:Print.css&action=raw&ctype=text/css";
        var printCssFresh = cacheBust(printCssUrl);
      var linkCss = doc.createElement("link");
      linkCss.rel = "stylesheet";
      linkCss.href = cacheBust(printCssUrl);


         var linkCss = doc.createElement("link");
      var cssLoaded = new Promise(function (resolve) {
         linkCss.rel = "stylesheet";
         linkCss.onload = function () {
         linkCss.href = printCssFresh;
          if (DEBUG) console.log("[print] print CSS loaded");
          resolve();
        };
         linkCss.onerror = function () {
          console.warn("[print] CSS failed to load");
          resolve();
         };
      });


        var cssLoaded = new Promise(function (resolve) {
      // Preload font inside iframe
          linkCss.onload = function () {
      var linkFont = doc.createElement("link");
            console.log("[print] print CSS loaded");
      linkFont.rel = "preload";
            resolve();
      linkFont.as = "font";
          };
      linkFont.type = "font/woff2";
          linkCss.onerror = function () {
      linkFont.href = "/fonts/HALColant-TextRegular.woff2?v=20250820";
            console.warn("[print] CSS failed to load");
      linkFont.crossOrigin = "anonymous";
            resolve();
          };
        });


        // Preload the font *inside* iframe
      doc.head.appendChild(linkFont);
        var linkFont = doc.createElement("link");
      doc.head.appendChild(linkCss);
        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);
      // Inject printable HTML
        doc.head.appendChild(linkCss);
      doc.body.innerHTML = printHtml;
      if (DEBUG) console.log("[print] injected print HTML");


        // Inject the printable HTML
      // Apply border preference to <html>
         doc.body.innerHTML = printHtml;
      (function () {
         console.log("[print] injected print HTML");
         var htmlEl = doc.documentElement;
        if (borderPref === "without") {
          htmlEl.classList
            ? htmlEl.classList.add("print-no-border")
            : (htmlEl.className += " print-no-border");
         } else {
          htmlEl.classList
            ? htmlEl.classList.remove("print-no-border")
            : (htmlEl.className = htmlEl.className.replace(
                /\bprint-no-border\b/g,
                ""
              ));
        }
        if (DEBUG) console.log("[print] borderPref applied:", borderPref);
      })();


        // Apply border preference (class on <html>)
      // Clean empty paragraphs
         (function applyBorderPreference() {
      (function () {
           var rootHtml = doc.documentElement;
         var ps = doc.querySelectorAll("#article-content p");
           if (borderPref === "without") {
        Array.prototype.forEach.call(ps, function (p) {
             rootHtml.classList
           var txt = (p.textContent || "").replace(/\u00a0/g, " ").trim();
              ? rootHtml.classList.add("print-no-border")
           var onlyBr =
              : (rootHtml.className += " print-no-border");
            p.children.length === 1 &&
           } else {
             p.firstElementChild &&
             rootHtml.classList
            p.firstElementChild.tagName === "BR";
               ? rootHtml.classList.remove("print-no-border")
           if (
              : (rootHtml.className = rootHtml.className.replace(
             (!txt &&
                  /\bprint-no-border\b/g,
               !p.querySelector("img, a, strong, em, span:not(:empty)")) ||
                  ""
            onlyBr
                ));
          ) {
            p.parentNode && p.parentNode.removeChild(p);
           }
           }
          console.log("[print] borderPref applied:", borderPref);
         });
         })();
         var root = doc.getElementById("article-content");
 
        if (root) {
         // Clean empty paragraphs
           Array.prototype.slice.call(root.childNodes).forEach(function (n) {
        (function () {
             if (n.nodeType === 3 && !n.textContent.replace(/\s+/g, "")) {
          var ps = doc.querySelectorAll("#article-content p");
               root.removeChild(n);
           Array.prototype.forEach.call(ps, function (p) {
             var txt = (p.textContent || "").replace(/\u00a0/g, " ").trim();
            var onlyBr =
              p.children.length === 1 && p.firstElementChild.tagName === "BR";
            if (
              (!txt &&
                !p.querySelector("img, a, strong, em, span:not(:empty)")) ||
              onlyBr
            ) {
               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);
              }
            });
          }
        })();


        // Inline tweaks
      // Small inline tweaks for print
        (function () {
      (function () {
          var css =
        var css =
            "@media print{" +
          "@media print{" +
            "  .article-description p,.article-reflection p,.article-external-reference p,.article-quote p{margin:0 0 1.2mm!important;}" +
          "  .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-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-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;}' +
          '  [class^="article-label-"]{margin-top:0!important;}' +
            '  .article-entry-number + [class^="article-label-"],' +
          '  .article-entry-number + [class^="article-label-"],' +
            '  .link-pdf + [class^="article-label-"],' +
          '  .link-pdf + [class^="article-label-"],' +
            '  .article-type + [class^="article-label-"],' +
          '  .article-type + [class^="article-label-"],' +
            '  .article-metadata + [class^="article-label-"],' +
          '  .article-metadata + [class^="article-label-"],' +
            '  .article-images + [class^="article-label-"],' +
          '  .article-images + [class^="article-label-"],' +
            '  .article-description + [class^="article-label-"],' +
          '  .article-description + [class^="article-label-"],' +
            '  .article-reflection + [class^="article-label-"],' +
          '  .article-reflection + [class^="article-label-"],' +
            '  .article-external-reference + [class^="article-label-"],' +
          '  .article-external-reference + [class^="article-label-"],' +
            '  .article-quote + [class^="article-label-"],' +
          '  .article-quote + [class^="article-label-"],' +
            '  .article-mod-line + [class^="article-label-"]{margin-top:0.9mm!important;}' +
          '  .article-mod-line + [class^="article-label-"]{margin-top:0.9mm!important;}' +
            "  .article-title-link{margin:0!important;padding:0!important;}" +
          "  .article-label-description + .article-description," +
            "  .article-title-link > *{margin:0!important;}" +
          "  .article-label-reflection + .article-reflection," +
            "  .link-pdf{margin-top:0!important;}" +
          "  .article-label-external-reference + .article-external-reference," +
            "  #article-content > :last-child{padding-bottom:0!important;}" +
          "  .article-label-quote + .article-quote," +
            "  #article-content > :last-child::after{content:none!important;}" +
          "  .article-label-modification-date + .article-modification-date{margin-top:0!important;}" +
            "}";
          "  .article-title-link{margin:0!important;padding:0!important;}" +
          var style = doc.createElement("style");
          "  .article-title-link > *{margin:0!important;}" +
          style.type = "text/css";
          "  .link-pdf{margin-top:0!important;}" +
          style.appendChild(doc.createTextNode(css));
          "  #article-content > :last-child{padding-bottom:0!important;}" +
          doc.head.appendChild(style);
          "  #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);
      })();


        // PDF-friendly links
      // PDF-friendly links
         var linkCssFix =
      (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}}";
           "@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}}";
        var styleFix = doc.createElement("style");
        styleFix.textContent = linkCssFix;
         doc.head.appendChild(styleFix);
         doc.head.appendChild(styleFix);


         (function () {
         var refs = doc.querySelectorAll(".article-external-reference a[href]");
          var refs = doc.querySelectorAll(
        refs.forEach(function (a) {
            ".article-external-reference a[href]"
          var txt = (a.textContent || "").trim();
          );
          var href = a.getAttribute("href") || "";
          refs.forEach(function (a) {
          var looksLongUrl = /^https?:\/\//i.test(txt) && txt.length > 60;
            var txt = (a.textContent || "").trim();
          if (looksLongUrl) {
            var href = a.getAttribute("href") || "";
            try {
            var looksLongUrl = /^https?:\/\//i.test(txt) && txt.length > 60;
              var u = new URL(href, doc.baseURI);
             if (looksLongUrl) {
              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";
        });
      })();
 
      // Wait helpers
      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 {
               try {
                 var u = new URL(href, doc.baseURI);
                 return img.decode().catch(function () {});
                var label =
               } catch (e) {}
                  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";
             return new Promise(function (res) {
             a.style.wordBreak = "normal";
              if (img.complete) return res();
            a.style.overflowWrap = "normal";
              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);
        });
      }


        // Wait helpers
      Promise.all([
        function waitImages() {
        cssLoaded,
          var imgs = [].slice.call(doc.images || []);
        waitImages(),
          if (!imgs.length) return Promise.resolve();
        waitFonts(1200),
          return Promise.all(
        waitSpecificFont(1200),
            imgs.map(function (img) {
        nextFrame(),
              if (img.decode) {
      ])
                try {
        .then(function () {
                  return img.decode().catch(function () {});
           if (DEBUG) console.log("[print] resources ready; calling print()");
                } catch (e) {}
           try {
              }
            // Filename via document.title
              return new Promise(function (res) {
            var entryNum = "";
                if (img.complete) return res();
             var numEl = doc.querySelector(".article-entry-number");
                img.onload = img.onerror = function () {
            if (numEl) {
                  res();
              var m = (numEl.textContent || "").match(/\d+/);
                };
              entryNum = m ? m[0] : "";
              });
            }
            })
            var desiredTitle =
          );
              (entryNum ? entryNum + "." : "") + "softwear.directory";
        }
        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([
             var oldIframeTitle = doc.title;
          cssLoaded,
            var oldParentTitle = document.title;
          waitImages(),
          waitFonts(1200),
          waitSpecificFont(1200),
          nextFrame(),
        ])
          .then(function () {
             console.log("[print] resources ready; calling print()");
            try {
              // 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;
            iframe.contentWindow.onafterprint = function () {
              var oldParentTitle = document.title;
               try {
                doc.title = oldIframeTitle;
                document.title = oldParentTitle;
              } catch (e) {}
              setTimeout(function () {
                if (iframe.parentNode) iframe.parentNode.removeChild(iframe);
              }, 100);
              $btn.data("busy", false);
              if (DEBUG) console.log("[print] afterprint cleanup done");
            };


              iframe.contentWindow.onafterprint = function () {
            doc.title = desiredTitle;
                try {
            document.title = desiredTitle;
                  doc.title = oldIframeTitle;
                  document.title = oldParentTitle;
                } catch (e) {}
                setTimeout(function () {
                  if (iframe.parentNode) iframe.parentNode.removeChild(iframe);
                }, 100);
                $btn.data("busy", false);
                console.log("[print] afterprint cleanup done");
              };


              doc.title = desiredTitle;
            iframe.contentWindow.focus();
              document.title = desiredTitle;
            iframe.contentWindow.print();


              iframe.contentWindow.focus();
            // Safety cleanup if onafterprint never fires
              iframe.contentWindow.print();
            setTimeout(function () {
 
              try {
              // Safety cleanup if onafterprint never fires
                doc.title = oldIframeTitle;
              setTimeout(function () {
                document.title = oldParentTitle;
                try {
              } catch (e) {}
                  doc.title = oldIframeTitle;
              if (iframe.parentNode) iframe.parentNode.removeChild(iframe);
                  document.title = oldParentTitle;
                } catch (e) {}
                if (iframe.parentNode) iframe.parentNode.removeChild(iframe);
                $btn.data("busy", false);
                console.log("[print] timeout cleanup done");
              }, 1000);
            } catch (err) {
              console.warn("[print] failed before print:", err);
               $btn.data("busy", false);
               $btn.data("busy", false);
             }
              if (DEBUG) console.log("[print] timeout cleanup done");
           })
             }, 1000);
          .catch(function (err) {
           } catch (err) {
             console.warn("[print] Promise chain error:", err);
             console.warn("[print] failed before print:", err);
             $btn.data("busy", false);
             $btn.data("busy", false);
           });
           }
        })
        .catch(function (err) {
          console.warn("[print] Promise chain error:", err);
          $btn.data("busy", false);
        });
    }
 
    // 7) Event wiring
    // Remove any prior print handlers so we don’t double-bind after AJAX nav etc.
    $(document).off("click.print");
 
    // Single tolerant handler bound to:
    // - #print-button    (to toggle chooser)
    // - #print-chooser    (so nested <a><button> clicks are caught)
    // - #print-options    (legacy container if you still render it)
    $(document).on(
      "click.print",
      "#print-button, #print-chooser, #print-options",
      function (e) {
        // If the main [print] (or a child of it) was clicked, toggle chooser
        if ($(e.target).closest("#print-button").length) {
          e.preventDefault();
          var $chooser = ensurePrintChooser();
          $chooser.toggle();
 
          $("#show-article").toggleClass(
            "print-opts-open",
            $chooser.is(":visible")
          );
 
          if (DEBUG)
            console.log(
              "[print] toggled chooser; visible=",
              $chooser.is(":visible")
            );
          return;
        }
 
        // Otherwise, check if one of the two choice anchors was clicked
        var anchor = e.target.closest("a#print-with-border, a#print-no-border");
        if (!anchor) return; // click inside container but not on a choice
 
        e.preventDefault();
        var id = anchor.id;
        if (DEBUG) console.log("[print] option anchor click:", { id });
        handlePrintChoice(id, $(anchor));
       }
       }
     }
    );
   );
 
    // 8) Also hide the chooser when the close button is clicked or ESC is pressed
    $(document).on("click.print", "#close-button", hidePrintUI);
    $(document).on("keydown.print", function (e) {
      if (e.key === "Escape") hidePrintUI();
     });
   });


   // Close modal with Close button
   // Close modal with Close button

Navigation menu