PageSourceSearch

https://www.salvador-dali.org/wp-content/themes/salvadordali-fundacio/js/pdf-modal.js?ver=1.2.0

js salvador-dali.org collected 2026-09-24 09:03:11 UTC 12,010 bytes, 397 lines download raw bytes

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.