1/** 2 * koi-coa.js â global COA modal (PDF only). 3 * 4 * Works with any element that has a [data-coa] attribute holding JSON: 5 * { name, pdf, pending } 6 * 7 * The modal no longer renders a transcribed summary of the certificate 8 * (product / lot / laboratory ID / report date / lab / headline purity / 9 * assay table / assay-count footer). It loads the PDF itself, so anything 10 * beyond `name`, `pdf` and `pending` in the payload is ignored. 11 * 12 * Capture-phase click listener so it runs BEFORE a card's inline 13 * onclick="location.href=â¦" and can block that navigation. 14 * 15 * The PDF URL is VERIFIED (same-origin HEAD) before it's loaded, so a 16 * missing/404 PDF never renders the site's 404 page inside the iframe â 17 * it hides the frame and shows a short note instead. 18 */ 19(function () { 20 'use strict'; 21 22 function $(id) { return document.getElementById(id); } 23 24 function setText(id, val, fallback) { 25 var el = $(id); 26 if (el) { el.textContent = val || fallback || 'â'; } 27 } 28 29 /* ---------------------------------------------------------------- * 30 * PDF frame 31 * ---------------------------------------------------------------- */ 32 33 /* Ensure a small "PDF not available" note exists inside the data area. */ 34 function pdfNote() { 35 var note = $('coaPdfMissing'); 36 if (!note) { 37 note = document.createElement('div'); 38 note.id = 'coaPdfMissing'; 39 note.className = 'mpdfhint'; 40 note.style.display = 'none'; 41 note.style.padding = '18px 4px'; 42 note.style.textAlign = 'center'; 43 note.textContent = 'The certificate PDF for this lot is being finalized and will appear here shortly.'; 44 var wrap = $('coaPdfWrap'); 45 if (wrap && wrap.parentNode) { wrap.parentNode.insertBefore(note, wrap.nextSibling); } 46 } 47 return note; 48 } 49 50 /* Show/hide the PDF frame + action buttons together. */ 51 function setPdfVisible(show, url, name) { 52 var frame = $('coaPdfFrame'), wrap = $('coaPdfWrap'), btns = document.querySelector('#coaModal .mcoabtns'); 53 var view = $('coaBtnView'), dl = $('coaBtnDl'), note = pdfNote(); 54 55 if (show) { 56 if (frame) { frame.src = url; } 57 if (wrap) { wrap.style.display = ''; } 58 if (btns) { btns.style.display = ''; } 59 if (view) { view.href = url; } 60 if (dl) { dl.href = url; dl.dataset.coaName = name || 'koi'; } 61 note.style.display = 'none'; 62 } else { 63 if (frame) { frame.src = 'about:blank'; } 64 if (wrap) { wrap.style.display = 'none'; } 65 if (btns) { btns.style.display = 'none'; } 66 note.style.display = ''; 67 } 68 } 69 70 /* Verify a same-origin URL is really a PDF before we trust it. */ 71 function verifyPdf(url, name) { 72 fetch(url, { method: 'HEAD' }) 73 .then(function (res) { 74 var ct = (res.headers.get('content-type') || '').toLowerCase(); 75 var okPdf = res.ok && ct.indexOf('pdf') !== -1; 76 setPdfVisible(okPdf, url, name); 77 }) 78 .catch(function () { setPdfVisible(false, url, name); }); 79 } 80 81 /* ---------------------------------------------------------------- * 82 * Open / close 83 * ---------------------------------------------------------------- */ 84 85 function openCOA(data) { 86 var modal = $('coaModal'); 87 if (!modal) { return; } 88 89 var pending = !!data.pending; 90 var has = $('coaHasData'); 91 var pend = $('coaPending'); 92 if (has) { has.style.display = pending ? 'none' : ''; } 93 if (pend) { pend.style.display = pending ? '' : 'none'; } 94 95 if (pending) { 96 setText('coaPendName', data.name, 'this compound'); 97 } else { 98 /* Reset frame first, then verify the URL before loading it. */ 99 setPdfVisible(false, '', data.name); 100 if (data.pdf) { 101 verifyPdf(data.pdf, data.name); 102 } 103 } 104 105 modal.classList.add('open'); 106 document.body.style.overflow = 'hidden'; 107 } 108 109 function closeCOA() { 110 var modal = $('coaModal'); 111 if (!modal) { return; } 112 modal.classList.remove('open'); 113 document.body.style.overflow = ''; 114 var frame = $('coaPdfFrame'); 115 if (frame) { frame.src = 'about:blank'; } // stop the PDF loading in the background 116 } 117 118 function downloadCOA(a) { 119 var nm = (a.dataset.coaName || 'koi') 120 .replace(/[^a-z0-9]+/gi, '-') 121 .toLowerCase() 122 .replace(/^-|-$/g, ''); 123 fetch(a.href) 124 .then(function (r) { if (!r.ok) { throw 0; } return r.blob(); }) 125 .then(function (b) { 126 var u = URL.createObjectURL(b); 127 var x = document.createElement('a'); 128 x.href = u; 129 x.download = nm + '-COA.pdf'; 130 document.body.appendChild(x); 131 x.click(); 132 x.remove(); 133 setTimeout(function () { URL.revokeObjectURL(u); }, 1500); 134 }) 135 .catch(function () { window.open(a.href, '_blank', 'noopener'); }); 136 } 137 138 /* ONE delegated listener, CAPTURE phase (the `true` at the end). */ 139 document.addEventListener('click', function (e) { 140 141 /* 1. Open â any element with [data-coa] */ 142 var btn = e.target.closest('[data-coa]'); 143 if (btn) { 144 e.preventDefault(); 145 e.stopPropagation(); 146 var data = {}; 147 try { 148 var raw = btn.getAttribute('data-coa'); 149 try { data = JSON.parse(raw); } catch (err2) { data = JSON.parse(decodeURIComponent(raw)); } 150 } catch (err) { return; } 151 openCOA(data); 152 return; 153 } 154 155 /* 2. Close â the â button */ 156 if (e.target.closest('[data-coa-close]')) { 157 e.stopPropagation(); 158 closeCOA(); 159 return; 160 } 161 162 /* 3. Close â click on the dark backdrop */ 163 var m = $('coaModal'); 164 if (m && m.classList.contains('open') && e.target === m) { 165 e.stopPropagation(); 166 closeCOA(); 167 return; 168 } 169 170 /* 4. Download button â blob download with a friendly filename */ 171 var d = e.target.closest('#coaBtnDl'); 172 if (d && d.href && d.getAttribute('href') !== '#') { 173 e.preventDefault(); 174 e.stopPropagation(); 175 downloadCOA(d); 176 } 177 }, true); // â capture phase: critical, do not remove 178 179 /* Esc closes the modal */ 180 document.addEventListener('keydown', function (e) { 181 if (e.key === 'Escape') { closeCOA(); } 182 }); 183})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.