1/** 2 * Elementor PDF Smart Viewer JavaScript 3 * 4 * @package ElementorPDFSmartViewer 5 * @version 1.0.4 6 */ 7 8(function($) { 9 'use strict'; 10 11 class PDFSmartViewer { 12 constructor(container) { 13 this.container = container; 14 this.canvas = container.querySelector('.epsv-canvas'); 15 16 // Check if canvas exists and get context 17 if (!this.canvas) { 18 console.error('Canvas element not found'); 19 return; 20 } 21 22 this.ctx = this.canvas.getContext('2d'); 23 if (!this.ctx) { 24 console.error('Canvas 2D context not supported'); 25 return; 26 } 27 28 this.pdfUrl = container.getAttribute('data-pdf-url'); 29 this.widgetId = container.getAttribute('data-widget-id'); 30 this.initialZoom = parseFloat(container.getAttribute('data-initial-zoom')) || 1; 31 32 // Control visibility settings 33 this.showZoomControls = container.getAttribute('data-show-zoom') === 'true'; 34 this.showFullscreen = container.getAttribute('data-show-fullscreen') === 'true'; 35 this.showDownload = container.getAttribute('data-show-download') === 'true'; 36 this.showPrint = container.getAttribute('data-show-print') === 'true'; 37 this.showPageInfo = container.getAttribute('data-show-page-info') === 'true'; 38 39 // State variables 40 this.pdfDoc = null; 41 this.currentPage = 1; 42 this.currentZoom = this.initialZoom; 43 this.isRendering = false; 44 this.pendingPage = null; 45 this.isFullscreen = false; 46 this.currentRenderTask = null; 47 this.hasRenderedFirstPage = false; 48 49 // DOM elements 50 this.loadingEl = container.querySelector('.epsv-loading'); 51 this.errorEl = container.querySelector('.epsv-error'); 52 this.zoomLevelEl = container.querySelector('.epsv-zoom-level'); 53 this.pageCurrentEl = container.querySelector('.epsv-page-current'); 54 this.pageTotalEl = container.querySelector('.epsv-page-total'); 55 56 // Buttons 57 this.prevBtn = container.querySelector('.epsv-prev'); 58 this.nextBtn = container.querySelector('.epsv-next'); 59 this.zoomInBtn = container.querySelector('.epsv-zoom-in'); 60 this.zoomOutBtn = container.querySelector('.epsv-zoom-out'); 61 this.fullscreenBtn = container.querySelector('.epsv-fullscreen'); 62 this.downloadBtn = container.querySelector('.epsv-download'); 63 this.printBtn = container.querySelector('.epsv-print'); 64 65 this.init(); 66 } 67 68 init() { 69 if (!this.canvas || !this.ctx) { 70 this.showError('Canvas not available'); 71 return; 72 } 73 74 if (!this.pdfUrl) { 75 this.showError('No PDF URL provided'); 76 return; 77 } 78 79 // Pre-hide canvas for first page to prevent any blink 80 this.canvas.style.visibility = 'hidden'; 81 this.canvas.style.opacity = '0'; 82 83 this.bindEvents(); 84 this.loadPDF(); 85 } 86 87 bindEvents() { 88 // Navigation events 89 if (this.prevBtn) { 90 this.prevBtn.addEventListener('click', (e) => { 91 e.preventDefault(); 92 this.previousPage(); 93 }); 94 } 95 96 if (this.nextBtn) { 97 this.nextBtn.addEventListener('click', (e) => { 98 e.preventDefault(); 99 this.nextPage(); 100 }); 101 } 102 103 // Zoom events 104 if (this.zoomInBtn) { 105 this.zoomInBtn.addEventListener('click', (e) => { 106 e.preventDefault(); 107 this.zoomIn(); 108 }); 109 } 110 111 if (this.zoomOutBtn) { 112 this.zoomOutBtn.addEventListener('click', (e) => { 113 e.preventDefault(); 114 this.zoomOut(); 115 }); 116 } 117 118 // Fullscreen events 119 if (this.fullscreenBtn) { 120 this.fullscreenBtn.addEventListener('click', (e) => { 121 e.preventDefault(); 122 this.toggleFullscreen(); 123 }); 124 } 125 126 // Download events 127 if (this.downloadBtn) { 128 this.downloadBtn.addEventListener('click', (e) => { 129 e.preventDefault(); 130 this.downloadPDF(); 131 }); 132 } 133 134 // Print events 135 if (this.printBtn) { 136 this.printBtn.addEventListener('click', (e) => { 137 e.preventDefault(); 138 this.printPDF(); 139 }); 140 } 141 142 // Keyboard events 143 document.addEventListener('keydown', (e) => { 144 if (!this.container.contains(document.activeElement) && !this.isFullscreen) { 145 return; 146 } 147 148 switch(e.key) { 149 case 'ArrowLeft': 150 e.preventDefault(); 151 this.previousPage(); 152 break; 153 case 'ArrowRight': 154 e.preventDefault(); 155 this.nextPage(); 156 break; 157 case '+': 158 case '=': 159 e.preventDefault(); 160 this.zoomIn(); 161 break; 162 case '-': 163 e.preventDefault(); 164 this.zoomOut(); 165 break; 166 case 'f': 167 case 'F': 168 e.preventDefault(); 169 this.toggleFullscreen(); 170 break; 171 case 'Escape': 172 if (this.isFullscreen) { 173 e.preventDefault(); 174 this.exitFullscreen(); 175 } 176 break; 177 } 178 }); 179 180 // Resize events 181 this.debouncedResize = this.debounce(() => { 182 this.renderCurrentPage(); 183 }, 250); 184 185 window.addEventListener('resize', this.debouncedResize); 186 187 // Fullscreen change events 188 document.addEventListener('fullscreenchange', () => { 189 this.handleFullscreenChange(); 190 }); 191 } 192 193 loadPDF() { 194 this.showLoading(); 195 196 // Reset first page rendering flag 197 this.hasRenderedFirstPage = false;
198 199 // Ensure canvas is initially visible (will be hidden during first page render if needed) 200 this.canvas.style.visibility = 'visible'; 201 this.canvas.style.opacity = '1'; 202 203 // Pre-initialize canvas context for incognito mode fix 204 this.preInitializeCanvas(); 205 206 // Set PDF.js worker path 207 if (typeof pdfjsLib !== 'undefined') { 208 pdfjsLib.GlobalWorkerOptions.workerSrc = pdfsmvif_ajax.plugin_url + 'assets/js/pdf.worker.min.js'; 209 210 // Check if it's a remote URL that might have CORS issues 211 const isRemoteUrl = this.isRemoteUrl(this.pdfUrl); 212 213 if (isRemoteUrl) { 214 this.loadPDFWithFallback(); 215 } else { 216 this.loadPDFDirect(); 217 } 218 } else { 219 this.showError('PDF.js library not loaded'); 220 } 221 } 222 223 preInitializeCanvas() { 224 // Pre-initialize canvas context to prevent incognito mode issues 225 if (this.canvas && this.ctx) { 226 try { 227 // Force canvas context to be ready 228 this.canvas.width = 1; 229 this.canvas.height = 1; 230 this.ctx.clearRect(0, 0, 1, 1); 231 232 // Test context operations 233 this.ctx.save(); 234 this.ctx.restore(); 235 236 // Reset canvas 237 this.canvas.width = 0; 238 this.canvas.height = 0; 239 } catch (e) { 240 console.warn('Canvas pre-initialization failed:', e); 241 } 242 } 243 } 244 245 isRemoteUrl(url) { 246 try { 247 const urlObj = new URL(url); 248 const currentOrigin = window.location.origin; 249 return urlObj.origin !== currentOrigin; 250 } catch (e) { 251 return false; 252 } 253 } 254 255 loadPDFDirect() { 256 pdfjsLib.getDocument(this.pdfUrl).promise 257 .then((pdf) => { 258 this.pdfDoc = pdf; 259 this.hideLoading(); 260 this.updatePageTotal(); 261 262 // Small delay to ensure everything is ready for first render in incognito mode 263 setTimeout(() => { 264 this.renderCurrentPage(); 265 this.updateControls(); 266 }, 50); 267 }) 268 .catch((error) => { 269 console.error('Error loading PDF directly:', error); 270 this.showError('Failed to load PDF. Please check the URL or file.'); 271 }); 272 } 273 274 loadPDFWithFallback() { 275 // For remote URLs, try direct loading first (some servers allow it) 276 277 pdfjsLib.getDocument(this.pdfUrl).promise 278 .then((pdf) => { 279 this.pdfDoc = pdf; 280 this.hideLoading(); 281 this.updatePageTotal(); 282 283 // Small delay to ensure everything is ready for first render in incognito mode 284 setTimeout(() => { 285 this.renderCurrentPage(); 286 this.updateControls(); 287 }, 50); 288 }) 289 .catch((directError) => { 290 console.error('Error loading PDF directly:', directError); 291 292 // If direct loading fails, try the proxy 293 const proxyUrl = this.getProxyUrl(this.pdfUrl); 294 295 pdfjsLib.getDocument(proxyUrl).promise 296 .then((pdf) => { 297 this.pdfDoc = pdf; 298 this.hideLoading(); 299 this.updatePageTotal(); 300 301 // Small delay to ensure everything is ready for first render in incognito mode 302 setTimeout(() => { 303 this.renderCurrentPage(); 304 this.updateControls(); 305 }, 50); 306 }) 307 .catch((proxyError) => { 308 console.error('Error loading PDF via proxy:', proxyError); 309 310 // If both PDF.js methods fail, try iframe fallback 311 this.loadPDFWithIframe(); 312 }); 313 }); 314 } 315 316 loadPDFWithIframe() { 317 // Hide the canvas and show download link instead 318 if (this.canvas) { 319 this.canvas.style.display = 'none'; 320 } 321 322 // Create a download link for external PDFs 323 const downloadContainer = document.createElement('div'); 324 downloadContainer.className = 'epsv-external-pdf'; 325 downloadContainer.style.cssText = `
326 text-align: center; 327 padding: 40px 20px; 328 background: #f8f9fa; 329 border: 2px dashed #dee2e6; 330 border-radius: 8px; 331 margin: 20px 0; 332 `; 333 334 const icon = document.createElement('div'); 335 icon.innerHTML = 'ð'; 336 icon.style.cssText = ` 337 font-size: 48px; 338 margin-bottom: 20px; 339 `; 340 341 const title = document.createElement('h3'); 342 title.textContent = 'External PDF Document'; 343 title.style.cssText = ` 344 margin: 0 0 15px 0; 345 color: #495057; 346 font-size: 18px; 347 `; 348 349 const description = document.createElement('p'); 350 description.textContent = 'This PDF is hosted on an external server. Due to security restrictions, it cannot be displayed directly in the viewer.'; 351 description.style.cssText = ` 352 margin: 0 0 20px 0; 353 color: #6c757d; 354 line-height: 1.5; 355 `; 356 357 const downloadBtn = document.createElement('a'); 358 downloadBtn.href = this.pdfUrl; 359 downloadBtn.target = '_blank'; 360 downloadBtn.textContent = 'Open PDF in New Tab'; 361 downloadBtn.className = 'button button-primary'; 362 downloadBtn.style.cssText = ` 363 display: inline-block; 364 padding: 12px 24px; 365 background: #0073aa; 366 color: white; 367 text-decoration: none; 368 border-radius: 4px; 369 font-weight: 500; 370 transition: background 0.3s; 371 `; 372 373 downloadBtn.addEventListener('mouseenter', () => { 374 downloadBtn.style.background = '#005a87'; 375 }); 376 377 downloadBtn.addEventListener('mouseleave', () => { 378 downloadBtn.style.background = '#0073aa'; 379 }); 380 381 // Add elements to container 382 downloadContainer.appendChild(icon); 383 downloadContainer.appendChild(title); 384 downloadContainer.appendChild(description); 385 downloadContainer.appendChild(downloadBtn); 386 387 // Add to the viewer container 388 const viewerContainer = this.canvas.parentElement; 389 viewerContainer.appendChild(downloadContainer); 390 391 // Hide loading 392 this.hideLoading(); 393 394 // Hide controls since we're showing a download link 395 if (this.container.querySelector('.epsv-controls')) { 396 this.container.querySelector('.epsv-controls').style.display = 'none'; 397 } 398 } 399 400 getProxyUrl(pdfUrl) { 401 // Create proxy URL with nonce for security 402 const proxyUrl = new URL(pdfsmvif_ajax.ajax_url); 403 proxyUrl.searchParams.set('action', 'pdfsmvif_proxy_pdf'); 404 proxyUrl.searchParams.set('url', encodeURIComponent(pdfUrl)); 405 proxyUrl.searchParams.set('nonce', pdfsmvif_ajax.proxy_nonce); 406 return proxyUrl.toString(); 407 } 408 409 renderCurrentPage() { 410 if (!this.pdfDoc) return; 411 412 // If a render is in progress or being cancelled, queue the latest request 413 if (this.isRendering || this.currentRenderTask) { 414 this.pendingPage = this.currentPage; 415 this.pendingZoom = this.currentZoom; 416 // If a render task exists, cancel it (if not already cancelling) 417 if (this.currentRenderTask && !this.currentRenderTask._cancelling) { 418 this.currentRenderTask._cancelling = true; 419 this.currentRenderTask.cancel(); 420 } 421 return; 422 } 423 424 this.isRendering = true; 425 this.pendingPage = null; 426 this.pendingZoom = null; 427 428 // IMMEDIATELY hide canvas for first page to prevent any blink 429 if (this.currentPage === 1 && !this.hasRenderedFirstPage) { 430 this.canvas.style.visibility = 'hidden'; 431 this.canvas.style.opacity = '0'; 432 } 433 434 this.pdfDoc.getPage(this.currentPage).then((page) => { 435 const containerWidth = this.canvas.parentElement.clientWidth; 436 const viewport = page.getViewport({ scale: 1 }); 437 const scale = (containerWidth / viewport.width) * this.currentZoom; 438 const scaledViewport = page.getViewport({ scale }); 439 440 // Comprehensive fix for incognito mode upside-down first page issue 441 if (this.currentPage === 1 && !this.hasRenderedFirstPage) { 442 this.renderFirstPageWithFix(page, scaledViewport); 443 } else { 444 this.renderPageNormally(page, scaledViewport); 445 } 446 }).catch((error) => { 447 if (typeof console !== 'undefined' && console.error) { 448 console.error('Error getting page:', error); 449 } 450 this.isRendering = false;
451 this.currentRenderTask = null; 452 this.showError('Failed to load page'); 453 }); 454 } 455 456 renderFirstPageWithFix(page, scaledViewport) { 457 // Hide canvas during first page rendering to prevent blink 458 this.canvas.style.visibility = 'hidden'; 459 460 // Safety timeout to ensure canvas becomes visible even if something goes wrong 461 const safetyTimeout = setTimeout(() => { 462 this.canvas.style.visibility = 'visible'; 463 this.canvas.style.opacity = '1'; 464 console.warn('Safety timeout: Canvas made visible after 3 seconds'); 465 }, 3000); 466 467 // Multiple strategies to fix incognito mode upside-down issue 468 const attemptRender = (attempt = 0) => { 469 if (attempt >= 3) { 470 // Final fallback - render normally but keep hidden until ready 471 this.renderPageNormallyWithVisibilityControl(page, scaledViewport); 472 return; 473 } 474 475 // Strategy 1: Force canvas reset and context recreation 476 this.canvas.width = scaledViewport.width; 477 this.canvas.height = scaledViewport.height; 478 this.ctx = this.canvas.getContext('2d'); 479 480 // Strategy 2: Clear canvas completely 481 this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); 482 483 // Strategy 3: Force a small delay to ensure context is ready 484 setTimeout(() => { 485 try { 486 // Strategy 4: Test context with a simple operation 487 this.ctx.save(); 488 this.ctx.restore(); 489 490 // Strategy 5: Render with explicit viewport settings 491 const renderContext = { 492 canvasContext: this.ctx, 493 viewport: scaledViewport, 494 transform: null // Force PDF.js to use default transform 495 }; 496 497 this.currentRenderTask = page.render(renderContext); 498 499 this.currentRenderTask.promise.then(() => { 500 // Clear safety timeout 501 clearTimeout(safetyTimeout); 502 503 // Show canvas only after successful render 504 this.canvas.style.visibility = 'visible'; 505 this.canvas.style.opacity = '1'; 506 507 this.isRendering = false; 508 this.currentRenderTask = null; 509 this.hasRenderedFirstPage = true; 510 this.updatePageInfo(); 511 this.updateControls(); 512 513 // If a render is pending, start it now 514 if (this.pendingPage !== null) { 515 this.currentPage = this.pendingPage; 516 this.currentZoom = this.pendingZoom || this.currentZoom; 517 this.pendingPage = null; 518 this.pendingZoom = null; 519 this.renderCurrentPage(); 520 } 521 }).catch((error) => { 522 // Clear safety timeout 523 clearTimeout(safetyTimeout); 524 525 if (error && error.name !== 'RenderingCancelled') { 526 console.error('First page render error, retrying:', error); 527 // Retry with next attempt 528 attemptRender(attempt + 1); 529 } else { 530 this.isRendering = false;
531 this.currentRenderTask = null; 532 // Show canvas even on cancellation 533 this.canvas.style.visibility = 'visible'; 534 this.canvas.style.opacity = '1'; 535 } 536 }); 537 538 } catch (e) { 539 console.error('Context error, retrying:', e); 540 attemptRender(attempt + 1); 541 } 542 }, 50 + (attempt * 25)); // Increasing delay with each attempt 543 }; 544 545 attemptRender(); 546 } 547 548 renderPageNormallyWithVisibilityControl(page, scaledViewport) { 549 // Safety timeout to ensure canvas becomes visible 550 const safetyTimeout = setTimeout(() => { 551 this.canvas.style.visibility = 'visible'; 552 this.canvas.style.opacity = '1'; 553 console.warn('Safety timeout: Canvas made visible after 2 seconds (fallback)'); 554 }, 2000); 555 556 // Set canvas dimensions 557 this.canvas.width = scaledViewport.width; 558 this.canvas.height = scaledViewport.height; 559 560 // Clear the canvas 561 this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); 562 563 // Render the page 564 const renderContext = { 565 canvasContext: this.ctx, 566 viewport: scaledViewport 567 }; 568 569 this.currentRenderTask = page.render(renderContext); 570 571 this.currentRenderTask.promise.then(() => { 572 // Clear safety timeout 573 clearTimeout(safetyTimeout); 574 575 // Show canvas after successful render 576 this.canvas.style.visibility = 'visible'; 577 this.canvas.style.opacity = '1'; 578 579 this.isRendering = false; 580 this.currentRenderTask = null; 581 this.hasRenderedFirstPage = true; 582 this.updatePageInfo(); 583 this.updateControls(); 584 585 // If a render is pending, start it now 586 if (this.pendingPage !== null) { 587 this.currentPage = this.pendingPage; 588 this.currentZoom = this.pendingZoom || this.currentZoom; 589 this.pendingPage = null; 590 this.pendingZoom = null; 591 this.renderCurrentPage(); 592 } 593 }).catch((error) => { 594 // Clear safety timeout 595 clearTimeout(safetyTimeout); 596 597 // Show canvas even on error 598 this.canvas.style.visibility = 'visible'; 599 this.canvas.style.opacity = '1'; 600 601 this.isRendering = false; 602 this.currentRenderTask = null; 603 // Only show error if it's not a cancellation 604 if (error && error.name !== 'RenderingCancelled') { 605 if (typeof console !== 'undefined' && console.error) { 606 console.error('Error rendering page:', error); 607 } 608 this.showError('Failed to render page'); 609 } 610 // If a render is pending, start it now 611 if (this.pendingPage !== null) { 612 this.currentPage = this.pendingPage; 613 this.currentZoom = this.pendingZoom || this.currentZoom; 614 this.pendingPage = null; 615 this.pendingZoom = null; 616 this.renderCurrentPage(); 617 } 618 }); 619 } 620 621 renderPageNormally(page, scaledViewport) { 622 // Ensure canvas is visible for subsequent pages 623 this.canvas.style.visibility = 'visible'; 624 625 // Set canvas dimensions 626 this.canvas.width = scaledViewport.width; 627 this.canvas.height = scaledViewport.height; 628 629 // Clear the canvas 630 this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); 631 632 // Render the page 633 const renderContext = { 634 canvasContext: this.ctx, 635 viewport: scaledViewport 636 }; 637 638 this.currentRenderTask = page.render(renderContext); 639 640 this.currentRenderTask.promise.then(() => { 641 this.isRendering = false;
642 this.currentRenderTask = null; 643 this.updatePageInfo(); 644 this.updateControls(); 645 646 // Mark that we've rendered the first page successfully 647 if (this.currentPage === 1) { 648 this.hasRenderedFirstPage = true; 649 } 650 651 // If a render is pending, start it now 652 if (this.pendingPage !== null) { 653 this.currentPage = this.pendingPage; 654 this.currentZoom = this.pendingZoom || this.currentZoom; 655 this.pendingPage = null; 656 this.pendingZoom = null; 657 this.renderCurrentPage(); 658 } 659 }).catch((error) => { 660 this.isRendering = false; 661 this.currentRenderTask = null; 662 // Only show error if it's not a cancellation 663 if (error && error.name !== 'RenderingCancelled') { 664 if (typeof console !== 'undefined' && console.error) { 665 console.error('Error rendering page:', error); 666 } 667 this.showError('Failed to render page'); 668 } 669 // If a render is pending, start it now 670 if (this.pendingPage !== null) { 671 this.currentPage = this.pendingPage; 672 this.currentZoom = this.pendingZoom || this.currentZoom; 673 this.pendingPage = null; 674 this.pendingZoom = null; 675 this.renderCurrentPage(); 676 } 677 }); 678 } 679 680 previousPage() { 681 if (this.currentPage <= 1) return; 682 this.currentPage--; 683 this.renderCurrentPage(); 684 } 685 686 nextPage() { 687 if (!this.pdfDoc || this.currentPage >= this.pdfDoc.numPages) return; 688 this.currentPage++; 689 this.renderCurrentPage(); 690 } 691 692 zoomIn() { 693 this.currentZoom = Math.min(this.currentZoom * 1.25, 3); 694 this.updateZoomLevel(); 695 this.renderCurrentPage(); 696 } 697 698 zoomOut() { 699 this.currentZoom = Math.max(this.currentZoom / 1.25, 0.25); 700 this.updateZoomLevel(); 701 this.renderCurrentPage(); 702 } 703 704 toggleFullscreen() { 705 if (this.isFullscreen) { 706 this.exitFullscreen(); 707 } else { 708 this.enterFullscreen(); 709 } 710 } 711 712 enterFullscreen() { 713 if (this.container.requestFullscreen) { 714 this.container.requestFullscreen(); 715 } else if (this.container.webkitRequestFullscreen) { 716 this.container.webkitRequestFullscreen(); 717 } else if (this.container.msRequestFullscreen) { 718 this.container.msRequestFullscreen(); 719 } 720 } 721 722 exitFullscreen() { 723 if (document.exitFullscreen) { 724 document.exitFullscreen(); 725 } else if (document.webkitExitFullscreen) { 726 document.webkitExitFullscreen(); 727 } else if (document.msExitFullscreen) { 728 document.msExitFullscreen(); 729 } 730 } 731 732 handleFullscreenChange() { 733 this.isFullscreen = !!document.fullscreenElement; 734 this.container.classList.toggle('epsv-fullscreen', this.isFullscreen); 735 736 if (this.isFullscreen) { 737 this.renderCurrentPage(); 738 } 739 } 740 741 downloadPDF() { 742 const link = document.createElement('a'); 743 link.href = this.pdfUrl; 744 link.download = this.getFileNameFromUrl(this.pdfUrl); 745 document.body.appendChild(link); 746 link.click(); 747 document.body.removeChild(link); 748 } 749 750 printPDF() { 751 const printWindow = window.open(this.pdfUrl, '_blank'); 752 if (printWindow) { 753 printWindow.onload = function() { 754 printWindow.print(); 755 }; 756 } 757 } 758 759 updatePageInfo() { 760 if (this.pageCurrentEl) { 761 this.pageCurrentEl.textContent = this.currentPage; 762 } 763 } 764 765 updatePageTotal() { 766 if (this.pageTotalEl && this.pdfDoc) { 767 this.pageTotalEl.textContent = this.pdfDoc.numPages; 768 } 769 } 770 771 updateZoomLevel() { 772 if (this.zoomLevelEl) { 773 this.zoomLevelEl.textContent = Math.round(this.currentZoom * 100) + '%'; 774 } 775 } 776 777 updateControls() { 778 // Update navigation buttons 779 if (this.prevBtn) { 780 this.prevBtn.disabled = this.currentPage <= 1; 781 } 782 783 if (this.nextBtn) { 784 this.nextBtn.disabled = !this.pdfDoc || this.currentPage >= this.pdfDoc.numPages; 785 } 786 787 // Update zoom buttons 788 if (this.zoomInBtn) { 789 this.zoomInBtn.disabled = this.currentZoom >= 3; 790 } 791 792 if (this.zoomOutBtn) { 793 this.zoomOutBtn.disabled = this.currentZoom <= 0.25; 794 } 795 } 796 797 showLoading() { 798 if (this.loadingEl) { 799 this.loadingEl.style.display = 'block'; 800 } 801 if (this.errorEl) { 802 this.errorEl.style.display = 'none'; 803 } 804 } 805 806 hideLoading() { 807 if (this.loadingEl) { 808 this.loadingEl.style.display = 'none'; 809 } 810 } 811 812 showError(message) { 813 this.hideLoading(); 814 if (this.errorEl) { 815 const span = this.errorEl.querySelector('span'); 816 if (span) { 817 span.textContent = message; 818 } 819 this.errorEl.style.display = 'block'; 820 } 821 } 822 823 getFileNameFromUrl(url) { 824 try { 825 const urlObj = new URL(url); 826 const pathname = urlObj.pathname; 827 const filename = pathname.substring(pathname.lastIndexOf('/') + 1); 828 return filename || 'document.pdf'; 829 } catch (e) { 830 return 'document.pdf'; 831 } 832 } 833 834 debounce(func, wait) { 835 let timeout;
836 return function executedFunction(...args) { 837 clearTimeout(timeout); 838 timeout = setTimeout(() => { 839 func.apply(this, args); 840 }, wait); 841 }; 842 } 843 844 destroy() { 845 // Cancel any ongoing render task 846 if (this.currentRenderTask) { 847 this.currentRenderTask.cancel(); 848 this.currentRenderTask = null; 849 } 850 851 // Remove event listeners 852 if (this.debouncedResize) { 853 window.removeEventListener('resize', this.debouncedResize); 854 } 855 856 // Clear any pending operations 857 this.isRendering = false; 858 this.pendingPage = null; 859 } 860 } 861 862 // Initialize viewers when DOM is ready 863 $(document).ready(function() { 864 const containers = document.querySelectorAll('.epsv-container'); 865 containers.forEach(container => { 866 new PDFSmartViewer(container); 867 }); 868 }); 869 870 // Re-initialize on Elementor frontend init 871 $(window).on('elementor/frontend/init', function() { 872 elementorFrontend.hooks.addAction('frontend/element_ready/global', function($scope) { 873 const containers = $scope.find('.epsv-container'); 874 containers.each(function() { 875 if (!this.epsvInstance) { 876 this.epsvInstance = new PDFSmartViewer(this); 877 } 878 }); 879 }); 880 }); 881 882})(jQuery);
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.