1(function () { 2 const modal = document.getElementById('pdf-modal'); 3 const viewer = document.getElementById('pdfjs-viewer'); 4 const frame = document.getElementById('pdf-frame'); 5 const pageInfo = document.getElementById('pdf-page-info'); 6 const prevPageBtn = document.querySelector('[data-pdf-prev-page]'); 7 const nextPageBtn = document.querySelector('[data-pdf-next-page]'); 8 const zoomInBtn = document.querySelector('[data-pdf-zoom-in]'); 9 const zoomOutBtn = document.querySelector('[data-pdf-zoom-out]'); 10 const rotateLeftBtn = document.querySelector('[data-pdf-rotate-left]'); 11 const rotateRightBtn = document.querySelector('[data-pdf-rotate-right]'); 12 const fullscreenBtn = document.querySelector('[data-pdf-fullscreen]'); 13 const downloadBtn = document.querySelector('[data-pdf-download]'); 14 if (!modal) return; 15 16 const closeButtons = modal.querySelectorAll('[data-close-pdf]'); 17 let renderToken = 0; 18 let activeLoadingTask = null; 19 let activeDocument = null; 20 let currentPdfUrl = ''; 21 let zoomLevel = 1; 22 let rotation = 0; 23 let currentPage = 1; 24 let totalPages = 1; 25 26 function isModalFullscreen() { 27 const fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement; 28 return !!fullscreenElement && (fullscreenElement === modal || modal.contains(fullscreenElement)); 29 } 30 31 function updateFullscreenButtonState() { 32 if (!fullscreenBtn) return; 33 34 const isFullscreen = isModalFullscreen(); 35 const enterLabel = fullscreenBtn.dataset.labelEnter || 'Enter fullscreen'; 36 const exitLabel = fullscreenBtn.dataset.labelExit || 'Exit fullscreen'; 37 const buttonLabel = isFullscreen ? exitLabel : enterLabel; 38 const enterIcon = fullscreenBtn.querySelector('[data-fullscreen-enter-icon]'); 39 const exitIcon = fullscreenBtn.querySelector('[data-fullscreen-exit-icon]'); 40 41 fullscreenBtn.setAttribute('aria-label', buttonLabel); 42 fullscreenBtn.setAttribute('title', buttonLabel); 43 fullscreenBtn.classList.toggle('is-fullscreen', isFullscreen); 44 } 45 46 function isPdfUrl(url) { 47 if (!url) return false; 48 try { 49 const parsed = new URL(url, window.location.href); 50 return parsed.pathname.toLowerCase().endsWith('.pdf'); 51 } catch (err) { 52 return /\.pdf($|[?#])/i.test(url); 53 } 54 } 55 56 function hydratePdfLinks() { 57 document.querySelectorAll('a[href]').forEach((link) => { 58 if (link.hasAttribute('download') || link.dataset.noPdfModal === '1') return; 59 60 const href = link.getAttribute('href') || ''; 61 if (!isPdfUrl(href)) return; 62 63 link.classList.add('js-pdf-viewer'); 64 if (!link.dataset.pdf) { 65 link.dataset.pdf = href; 66 } 67 }); 68 } 69 70 function openModal(pdfUrl) { 71 currentPdfUrl = pdfUrl; 72 zoomLevel = 1; 73 rotation = 0; 74 currentPage = 1; 75 totalPages = 1; 76 updateToolbarState(); 77 updateFullscreenButtonState(); 78 79 modal.classList.add('is-open'); 80 modal.setAttribute('aria-hidden', 'false'); 81 document.body.classList.add('pdf-modal-open'); 82 renderPdfInModal(pdfUrl); 83 } 84 85 function updateToolbarState() { 86 if (pageInfo) { 87 pageInfo.textContent = currentPage + ' / ' + totalPages; 88 } 89 90 if (prevPageBtn) { 91 prevPageBtn.disabled = currentPage <= 1; 92 } 93 94 if (nextPageBtn) { 95 nextPageBtn.disabled = currentPage >= totalPages; 96 } 97 98 if (zoomOutBtn) { 99 zoomOutBtn.disabled = zoomLevel <= 0.6; 100 } 101 102 if (zoomInBtn) { 103 zoomInBtn.disabled = zoomLevel >= 2.8; 104 } 105 } 106 107 function updateCurrentPageFromScroll() { 108 if (!viewer) return; 109 const pages = viewer.querySelectorAll('.pdf-modal__page'); 110 if (!pages.length) return; 111 112 const scrollTop = viewer.scrollTop; 113 let bestPage = 1; 114 let bestDistance = Number.POSITIVE_INFINITY; 115 116 pages.forEach((pageEl) => { 117 const pageNumber = Number(pageEl.dataset.pageNumber || '1'); 118 const distance = Math.abs(pageEl.offsetTop - scrollTop - 12); 119 if (distance < bestDistance) { 120 bestDistance = distance; 121 bestPage = pageNumber; 122 } 123 }); 124 125 if (bestPage !== currentPage) { 126 currentPage = bestPage; 127 updateToolbarState(); 128 } 129 } 130 131 function scrollToPage(pageNumber) { 132 if (!viewer) return; 133 const safePage = Math.max(1, Math.min(totalPages, pageNumber)); 134 const target = viewer.querySelector('[data-page-number="' + safePage + '"]'); 135 if (!target) return; 136 137 target.scrollIntoView({ behavior: 'smooth', block: 'start' }); 138 currentPage = safePage; 139 updateToolbarState(); 140 } 141 142 function setStatusMessage(message) { 143 if (!viewer) return; 144 viewer.innerHTML = ''; 145 const status = document.createElement('p'); 146 status.className = 'pdf-modal__status'; 147 status.textContent = message; 148 viewer.appendChild(status); 149 } 150 151 function activateIframeFallback(pdfUrl) { 152 if (!frame) return; 153 modal.classList.add('pdf-modal--fallback'); 154 frame.src = pdfUrl + '#toolbar=1&navpanes=0&scrollbar=1'; 155 if (pageInfo) { 156 pageInfo.textContent = 'Browser viewer mode'; 157 } 158 } 159 160 function clearFallbackIframe() { 161 modal.classList.remove('pdf-modal--fallback'); 162 if (frame) { 163 frame.src = ''; 164 } 165 } 166 167 function clearPdfRenderState() { 168 renderToken += 1; 169 if (activeLoadingTask && typeof activeLoadingTask.destroy === 'function') { 170 activeLoadingTask.destroy(); 171 } 172 activeLoadingTask = null; 173 174 if (activeDocument && typeof activeDocument.destroy === 'function') { 175 activeDocument.destroy(); 176 } 177 activeDocument = null; 178 179 clearFallbackIframe(); 180 181 if (viewer) { 182 viewer.innerHTML = ''; 183 viewer.scrollTop = 0; 184 } 185 186 currentPdfUrl = ''; 187 currentPage = 1; 188 totalPages = 1; 189 zoomLevel = 1; 190 rotation = 0; 191 updateToolbarState(); 192 } 193 194 async function renderPdfInModal(pdfUrl) { 195 const token = ++renderToken; 196 clearFallbackIframe(); 197 198 if (!viewer || !window.pdfjsLib) { 199 activateIframeFallback(pdfUrl); 200 return; 201 } 202 203 if (window.pdfModalConfig && window.pdfModalConfig.workerSrc) { 204 window.pdfjsLib.GlobalWorkerOptions.workerSrc = window.pdfModalConfig.workerSrc; 205 } 206 207 setStatusMessage('Loading PDF...'); 208 209 try { 210 activeLoadingTask = window.pdfjsLib.getDocument({ url: pdfUrl }); 211 const pdf = await activeLoadingTask.promise; 212 213 if (token !== renderToken) {
214 pdf.destroy(); 215 return; 216 } 217 218 activeDocument = pdf; 219 viewer.innerHTML = ''; 220 totalPages = pdf.numPages; 221 currentPage = 1; 222 updateToolbarState(); 223 224 for (let pageNumber = 1; pageNumber <= pdf.numPages; pageNumber += 1) { 225 const page = await pdf.getPage(pageNumber); 226 if (token !== renderToken) return; 227 228 const wrapper = document.createElement('div'); 229 wrapper.className = 'pdf-modal__page'; 230 wrapper.dataset.pageNumber = String(pageNumber); 231 232 const canvas = document.createElement('canvas'); 233 canvas.className = 'pdf-modal__canvas'; 234 wrapper.appendChild(canvas); 235 viewer.appendChild(wrapper); 236 237 const unscaledViewport = page.getViewport({ scale: 1, rotation: rotation }); 238 const availableWidth = Math.max(viewer.clientWidth - 40, 300); 239 const baseScale = availableWidth / unscaledViewport.width; 240 const viewport = page.getViewport({ scale: baseScale * zoomLevel, rotation: rotation }); 241 242 const outputScale = window.devicePixelRatio || 1; 243 canvas.width = Math.floor(viewport.width * outputScale); 244 canvas.height = Math.floor(viewport.height * outputScale); 245 canvas.style.width = Math.floor(viewport.width) + 'px'; 246 canvas.style.height = Math.floor(viewport.height) + 'px'; 247 248 const renderContext = { 249 canvasContext: canvas.getContext('2d'), 250 viewport: viewport, 251 transform: outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null, 252 }; 253 254 await page.render(renderContext).promise; 255 } 256 257 updateToolbarState(); 258 } catch (err) { 259 activateIframeFallback(pdfUrl); 260 } 261 } 262 263 function toggleFullscreen() { 264 if (!isModalFullscreen()) { 265 const requestFullscreen = modal.requestFullscreen || modal.webkitRequestFullscreen; 266 if (requestFullscreen) { 267 requestFullscreen.call(modal).catch((err) => { 268 console.warn('Fullscreen request failed:', err); 269 }); 270 } 271 } else { 272 const exitFullscreen = document.exitFullscreen || document.webkitExitFullscreen; 273 if (exitFullscreen) { 274 exitFullscreen.call(document); 275 } 276 } 277 } 278 279 function closeModal() { 280 if (isModalFullscreen()) { 281 const exitFullscreen = document.exitFullscreen || document.webkitExitFullscreen; 282 283 if (exitFullscreen) { 284 Promise.resolve(exitFullscreen.call(document)).finally(function () { 285 closeModal(); 286 }); 287 return; 288 } 289 } 290 291 clearPdfRenderState(); 292 modal.classList.remove('is-open'); 293 modal.setAttribute('aria-hidden', 'true'); 294 document.body.classList.remove('pdf-modal-open'); 295 updateFullscreenButtonState(); 296 } 297 298 hydratePdfLinks(); 299 300 // Keep matching links updated if Gutenberg or other scripts inject content later. 301 const observer = new MutationObserver(hydratePdfLinks); 302 observer.observe(document.body, { childList: true, subtree: true }); 303 304 function handlePdfClick(e) { 305 const link = e.target.closest('a.js-pdf-viewer'); 306 if (!link) return; 307 308 const pdfUrl = link.dataset.pdf || link.getAttribute('href') || ''; 309 if (!isPdfUrl(pdfUrl)) return; 310 311 e.preventDefault(); 312 e.stopPropagation(); 313 if (typeof e.stopImmediatePropagation === 'function') { 314 e.stopImmediatePropagation(); 315 } 316 317 openModal(pdfUrl); 318 } 319 320 // Capture phase ensures we intercept before other scripts that may open a new tab. 321 document.addEventListener('click', handlePdfClick, true); 322 323 if (viewer) { 324 viewer.addEventListener('scroll', updateCurrentPageFromScroll, { passive: true }); 325 } 326 327 if (prevPageBtn) { 328 prevPageBtn.addEventListener('click', function () { 329 scrollToPage(currentPage - 1); 330 }); 331 } 332 333 if (nextPageBtn) { 334 nextPageBtn.addEventListener('click', function () { 335 scrollToPage(currentPage + 1); 336 }); 337 } 338 339 if (zoomInBtn) { 340 zoomInBtn.addEventListener('click', function () { 341 if (!activeDocument || !currentPdfUrl) return; 342 zoomLevel = Math.min(2.8, zoomLevel + 0.2); 343 updateToolbarState(); 344 renderPdfInModal(currentPdfUrl); 345 }); 346 } 347 348 if (zoomOutBtn) { 349 zoomOutBtn.addEventListener('click', function () { 350 if (!activeDocument || !currentPdfUrl) return; 351 zoomLevel = Math.max(0.6, zoomLevel - 0.2); 352 updateToolbarState(); 353 renderPdfInModal(currentPdfUrl); 354 }); 355 } 356 357 if (rotateLeftBtn) { 358 rotateLeftBtn.addEventListener('click', function () { 359 if (!activeDocument || !currentPdfUrl) return; 360 rotation = (rotation + 270) % 360; 361 renderPdfInModal(currentPdfUrl); 362 }); 363 } 364 365 if (rotateRightBtn) { 366 rotateRightBtn.addEventListener('click', function () { 367 if (!activeDocument || !currentPdfUrl) return; 368 rotation = (rotation + 90) % 360; 369 renderPdfInModal(currentPdfUrl); 370 }); 371 } 372 373 if (fullscreenBtn) { 374 fullscreenBtn.addEventListener('click', toggleFullscreen); 375 } 376 377 document.addEventListener('fullscreenchange', updateFullscreenButtonState); 378 document.addEventListener('webkitfullscreenchange', updateFullscreenButtonState); 379 updateFullscreenButtonState(); 380 381 if (downloadBtn) { 382 downloadBtn.addEventListener('click', function () { 383 if (!currentPdfUrl) return; 384 const link = document.createElement('a'); 385 link.href = currentPdfUrl; 386 link.download = ''; 387 document.body.appendChild(link); 388 link.click(); 389 document.body.removeChild(link); 390 }); 391 } 392 393 closeButtons.forEach((btn) => btn.addEventListener('click', closeModal)); 394 document.addEventListener('keydown', (e) => { 395 if (e.key === 'Escape' && modal.classList.contains('is-open')) closeModal(); 396 }); 397})();
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.