1 2 3(function () { 4 'use strict'; 5 6 7 8 try { console.log('[TT-EnergyLabel] energy_label.js geladen - v1.1.48'); } catch (e) {} 9 10 if (typeof window.TT_ENERGY_LABEL_CONFIG === 'undefined') { 11 try { console.warn('[TT-EnergyLabel] ABBRUCH: window.TT_ENERGY_LABEL_CONFIG ist undefined'); } catch (e) {} 12 return; 13 } 14 15 const config = window.TT_ENERGY_LABEL_CONFIG; 16 const cache = new Map(); 17 const pending = new Map(); 18 const handled = new WeakSet(); 19 const handledTab = new Set(); 20 21 const log = (...args) => { 22 if (config.debug) { 23 console.log('[TT-EnergyLabel]', ...args); 24 } 25 }; 26 27 const warn = (...args) => { 28 console.warn('[TT-EnergyLabel]', ...args); 29 }; 30 31 function detectContext() { 32 const body = document.body; 33 if (!body) return 'all'; 34 35 if (body.classList.contains('product-detail') || 36 body.classList.contains('page-product') || 37 document.querySelector('.product-buy-box')) { 38 return 'detail'; 39 } 40 if (body.classList.contains('page-category') || 41 body.classList.contains('category-listing')) { 42 return 'category'; 43 } 44 if (body.classList.contains('page-cart')) return 'cart'; 45 if (body.classList.contains('page-wishlist')) return 'wishlist'; 46 if (body.classList.contains('page-comparelist')) return 'comparelist'; 47 return 'all'; 48 } 49 50 function findArticleId(element) { 51 let current = element; 52 for (let i = 0; i < 6 && current; i++) { 53 const id = current.dataset && ( 54 current.dataset.productId || 55 current.dataset.kartikel || 56 current.dataset.articleId 57 ); 58 if (id) return parseInt(id, 10); 59 60 const hidden = current.querySelector?.( 61 'input[name="a"], input[name="kArtikel"], input[name="article_id"]' 62 ); 63 if (hidden && hidden.value) { 64 return parseInt(hidden.value, 10); 65 } 66 current = current.parentElement; 67 } 68 69 const urlMatch = window.location.search.match(/[?&]a=(\d+)/); 70 if (urlMatch) return parseInt(urlMatch[1], 10); 71 72 return null; 73 } 74 75 function fetchLabelData(kArtikel) { 76 if (cache.has(kArtikel)) return Promise.resolve(cache.get(kArtikel)); 77 if (pending.has(kArtikel)) return pending.get(kArtikel); 78 79 const url = config.ajaxUrl + '?action=getLabel&kArtikel=' + kArtikel 80 + '&lang=' + encodeURIComponent(config.langIso || ''); 81 82 const promise = fetch(url, { 83 credentials: 'same-origin', 84 headers: { 'X-Requested-With': 'XMLHttpRequest' } 85 }) 86 .then(r => r.ok ? r.json() : null) 87 .then(data => { 88 cache.set(kArtikel, data); 89 pending.delete(kArtikel); 90 return data; 91 }) 92 .catch(err => { 93 log('Fetch error for', kArtikel, err); 94 pending.delete(kArtikel); 95 cache.set(kArtikel, null); 96 return null; 97 }); 98 99 pending.set(kArtikel, promise); 100 return promise; 101 } 102 103 function renderArrow(arrow, labelData) { 104 const sizeClass = 'tt-size-' + (config.arrowSize || 'medium'); 105 const dirClass = arrow.richtung === 'rechts' ? 'tt-dir-right' : 'tt-dir-left'; 106 107 const wrapper = document.createElement('span'); 108 wrapper.className = 'tt-energy-arrow-group'; 109 110 if (arrow.titel && arrow.titel !== '') { 111 const title = document.createElement('span'); 112 title.className = 'tt-arrow-title'; 113 title.textContent = arrow.titel; 114 wrapper.appendChild(title); 115 } 116 117 const arrowEl = document.createElement('button'); 118 arrowEl.type = 'button'; 119 arrowEl.className = 'tt-energy-arrow ' + sizeClass + ' ' + dirClass; 120 arrowEl.dataset.pos = arrow.position; 121 arrowEl.dataset.klasse = arrow.klasse; 122 arrowEl.dataset.skala = arrow.skala; 123 arrowEl.setAttribute('aria-label', labelData.accessibility.arrowAriaLabel); 124 arrowEl.setAttribute('title', labelData.accessibility.arrowAriaLabel); 125
126 const klasseEl = document.createElement('span'); 127 klasseEl.className = 'tt-arrow-class'; 128 klasseEl.textContent = arrow.klasse; 129 arrowEl.appendChild(klasseEl); 130 131 arrowEl.addEventListener('click', () => openLabelModal(labelData)); 132 133 wrapper.appendChild(arrowEl); 134 135 if (labelData.showScaleRange) { 136 const range = document.createElement('span'); 137 range.className = 'tt-scale-range'; 138 range.textContent = arrow.skalaVon + ' \u2013 ' + arrow.skalaBis; 139 range.setAttribute('aria-hidden', 'true'); 140 wrapper.appendChild(range); 141 } 142 143 return wrapper; 144 } 145 146 function renderDatasheetLink(labelData) { 147 if (!labelData.hasDatasheet) return null; 148 149 const link = document.createElement('button'); 150 link.type = 'button'; 151 link.className = 'tt-energy-datasheet-link'; 152 link.textContent = labelData.strings.datasheet_link; 153 link.setAttribute('aria-label', labelData.accessibility.datasheetAria); 154 link.setAttribute('title', labelData.accessibility.datasheetAria); 155 link.addEventListener('click', () => openDatasheetModal(labelData)); 156 return link; 157 } 158 159 let lastFocusedElement = null; 160 161 function getFocusableElements(modal) { 162 return modal.querySelectorAll( 163 'a[href], button:not([disabled]), input:not([disabled]), ' + 164 'select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])' 165 ); 166 } 167 168 function trapFocus(e, modal) { 169 if (e.key !== 'Tab') return; 170 const focusable = getFocusableElements(modal); 171 if (focusable.length === 0) return; 172 173 const first = focusable[0]; 174 const last = focusable[focusable.length - 1]; 175 176 if (e.shiftKey && document.activeElement === first) { 177 e.preventDefault(); 178 last.focus(); 179 } else if (!e.shiftKey && document.activeElement === last) { 180 e.preventDefault(); 181 first.focus(); 182 } 183 } 184 185 function buildModalShell(titleText, contentHtml, opts) { 186 opts = opts || {}; 187 const id = opts.id || 'tt-energy-modal-' + Date.now(); 188 const titleId = id + '-title'; 189 190 let modal = document.getElementById(opts.singletonId || ''); 191 if (!modal) { 192 modal = document.createElement('div'); 193 modal.id = opts.singletonId || id; 194 modal.className = 'tt-energy-modal'; 195 modal.setAttribute('role', 'dialog'); 196 modal.setAttribute('aria-modal', 'true'); 197 modal.setAttribute('aria-labelledby', titleId); 198 modal.innerHTML = '<div class="tt-energy-modal-backdrop" data-close="1"></div>' 199 + '<div class="tt-energy-modal-content">' 200 + '<div class="tt-energy-modal-header">' 201 + '<h2 class="tt-energy-modal-title" id="' + titleId + '"></h2>' 202 + '<button type="button" class="tt-energy-modal-close" aria-label="' + escapeAttr(opts.closeLabel || 'Schliessen') + '">' 203 + '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>' 204 + '</button>' 205 + '</div>' 206 + '<div class="tt-energy-modal-body"></div>' 207 + '</div>'; 208 document.body.appendChild(modal); 209 } 210 211 modal.querySelector('.tt-energy-modal-title').textContent = titleText; 212 modal.querySelector('.tt-energy-modal-close').setAttribute('aria-label', opts.closeLabel || 'Schliessen'); 213 modal.querySelector('.tt-energy-modal-body').innerHTML = contentHtml; 214 215 const closeFn = () => closeModal(modal); 216 modal.querySelector('.tt-energy-modal-close').onclick = closeFn; 217 modal.querySelector('.tt-energy-modal-backdrop').onclick = closeFn; 218 219 modal._keydownHandler = (e) => { 220 if (e.key === 'Escape') { 221 closeModal(modal); 222 } else { 223 trapFocus(e, modal); 224 } 225 }; 226 227 return modal; 228 } 229 230 function openModal(modal) { 231 lastFocusedElement = document.activeElement; 232 modal.classList.add('tt-open'); 233 document.addEventListener('keydown', modal._keydownHandler); 234 235 setTimeout(() => { 236 const closeBtn = modal.querySelector('.tt-energy-modal-close'); 237 if (closeBtn) closeBtn.focus(); 238 }, 50); 239 } 240 241 function closeModal(modal) { 242 modal.classList.remove('tt-open'); 243 if (modal._keydownHandler) { 244 document.removeEventListener('keydown', modal._keydownHandler); 245 } 246 247 if (lastFocusedElement && typeof lastFocusedElement.focus === 'function') { 248 lastFocusedElement.focus(); 249 } 250 } 251 252 function openLabelModal(labelData) { 253 if (!labelData.hasLabel) return; 254 255 let html = ''; 256 257 if (labelData.labelImage) { 258 html += '<picture>'; 259 if (labelData.labelImage.urlWebp) { 260 html += '<source srcset="' + escapeAttr(labelData.labelImage.urlWebp) + '" type="image/webp">'; 261 } 262 html += '<img class="tt-energy-modal-image" src="' + escapeAttr(labelData.labelImage.url) 263 + '" alt="' + escapeAttr(labelData.accessibility.imageAlt) + '">'; 264 html += '</picture>'; 265 } 266 267 if (labelData.eprel && labelData.eprelUrl) { 268 html += '<div class="tt-energy-modal-eprel">'; 269 if (labelData.showEprelQr) { 270 html += '<img class="tt-eprel-qr" alt="" src="https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=' 271 + encodeURIComponent(labelData.eprelUrl) + '">'; 272 } 273 html += '<div>' 274 + '<div style="font-size:13px;color:#666;margin-bottom:4px;">EPREL-Registriernummer</div>' 275 + '<div style="font-size:18px;font-weight:600;margin-bottom:6px;">' + escapeHtml(labelData.eprel) + '</div>' 276 + '<a class="tt-energy-eprel-link" href="' + escapeAttr(labelData.eprelUrl) + '" target="_blank" rel="noopener">' 277 + escapeHtml(labelData.strings.eprel_link) + ' ↗' 278 + '</a>' 279 + '</div>'; 280 html += '</div>'; 281 } 282 283 const modal = buildModalShell( 284 labelData.strings.label_modal, 285 html, 286 { singletonId: 'tt-energy-label-modal', closeLabel: labelData.strings.datasheet_close } 287 ); 288 openModal(modal); 289 } 290 291 292 293 294 295 296 function ttFrontendLog(level, message, context) { 297 try { 298 if (level === 'warn' || level === 'error') { 299 console.warn('[TT-PDF]', message, context || ''); 300 } else { 301 console.log('[TT-PDF]', message, context || ''); 302 } 303 } catch (e) {} 304 if (!config.frontendDebugLog) return; 305 try { 306 var logUrl = (config.pluginUrl || '') + 'frontend/tt_energylabel.php?action=log'; 307 fetch(logUrl, { 308 method: 'POST', 309 headers: { 'Content-Type': 'application/json' }, 310 body: JSON.stringify({ 311 level: level, 312 message: String(message).substring(0, 200), 313 context: Object.assign({
314 ua: navigator.userAgent.substring(0, 100), 315 url: location.pathname, 316 time: new Date().toISOString() 317 }, context || {}) 318 }), 319 credentials: 'same-origin', 320 keepalive: true 321 }).catch(function () { }); 322 } catch (e) { } 323 } 324 325 326 327 if (config.frontendDebugLog && !window._ttErrorHandlerInstalled) { 328 window._ttErrorHandlerInstalled = true; 329 window.addEventListener('error', function (e) { 330 ttFrontendLog('error', 'JS-Fehler: ' + (e.message || 'unknown'), { 331 file: (e.filename || '').split('/').slice(-2).join('/'), 332 line: e.lineno, 333 col: e.colno 334 }); 335 }); 336 window.addEventListener('unhandledrejection', function (e) { 337 ttFrontendLog('error', 'Unhandled Promise-Rejection: ' + (e.reason && e.reason.message ? e.reason.message : String(e.reason))); 338 }); 339 } 340 341 var pdfjsLoadPromise = null; 342 function loadPdfJsLib() { 343 if (pdfjsLoadPromise !== null) return pdfjsLoadPromise; 344 345 var base = (config.pluginUrl || '') + 'frontend/vendor/pdfjs/'; 346 var pdfJsUrl = base + 'pdf.min.js'; 347 var workerUrl = base + 'pdf.worker.min.js'; 348 349 ttFrontendLog('info', 'PDF.js wird geladen', { url: pdfJsUrl }); 350 351 pdfjsLoadPromise = new Promise(function (resolve, reject) { 352 353 function findLib() { 354 return window.pdfjsLib 355 || window['pdfjs-dist/build/pdf'] 356 || (window.pdfjs && window.pdfjs.getDocument ? window.pdfjs : null); 357 } 358 359 var existing = findLib(); 360 if (existing) { 361 ttFrontendLog('info', 'PDF.js bereits geladen, Worker konfigurieren'); 362 if (existing.GlobalWorkerOptions) { 363 existing.GlobalWorkerOptions.workerSrc = workerUrl; 364 } 365 resolve(existing); 366 return; 367 } 368 369 var s = document.createElement('script'); 370 s.src = pdfJsUrl; 371 s.async = false; 372 s.onload = function () { 373 var lib = findLib(); 374 ttFrontendLog('info', 'PDF.js Script geladen', { 375 libFound: !!lib, 376 hasGetDoc: !!(lib && lib.getDocument), 377 version: lib && lib.version ? lib.version : 'unknown' 378 }); 379 if (!lib || typeof lib.getDocument !== 'function') { 380 reject(new Error('PDF.js geladen, aber getDocument fehlt')); 381 return; 382 } 383 if (lib.GlobalWorkerOptions) { 384 lib.GlobalWorkerOptions.workerSrc = workerUrl; 385 } 386 resolve(lib); 387 }; 388 s.onerror = function () { 389 ttFrontendLog('error', 'PDF.js Script konnte nicht geladen werden', { url: pdfJsUrl }); 390 reject(new Error('PDF.js-Skript nicht erreichbar: ' + pdfJsUrl)); 391 }; 392 document.head.appendChild(s); 393 }); 394 return pdfjsLoadPromise; 395 } 396 397 var pdfBufferCache = new Map(); 398 function fetchPdfBuffer(pdfUrl) { 399 if (pdfBufferCache.has(pdfUrl)) return pdfBufferCache.get(pdfUrl); 400 ttFrontendLog('info', 'Fetche PDF-Bytes', { url: pdfUrl.substring(0, 200) }); 401 var p = fetch(pdfUrl, { credentials: 'same-origin' }) 402 .then(function (resp) { 403 ttFrontendLog('info', 'PDF-Response erhalten', { 404 status: resp.status, 405 ok: resp.ok, 406 type: resp.headers.get('Content-Type') 407 }); 408 if (!resp.ok) throw new Error('HTTP ' + resp.status); 409 return resp.arrayBuffer(); 410 }) 411 .then(function (buf) { 412 ttFrontendLog('info', 'PDF-Bytes empfangen', { bytes: buf.byteLength }); 413 return buf; 414 }); 415 pdfBufferCache.set(pdfUrl, p); 416 return p; 417 } 418 419 var pdfViewerStates = new Map(); 420 function initPdfState(pdfUrl) { 421 var existing = pdfViewerStates.get(pdfUrl); 422 if (existing && existing.pdf) return Promise.resolve(existing); 423 return loadPdfJsLib().then(function (lib) { 424 return fetchPdfBuffer(pdfUrl).then(function (buf) { 425 ttFrontendLog('info', 'PDF.js getDocument startet'); 426 return lib.getDocument({ 427 data: buf.slice(0), 428 disableAutoFetch: true, 429 disableStream: true, 430 isEvalSupported: false 431 }).promise; 432 }); 433 }).then(function (pdf) { 434 ttFrontendLog('info', 'PDF geladen', { pages: pdf.numPages }); 435 var state = { 436 pdf: pdf, 437 numPages: pdf.numPages, 438 pageCache: new Map(), 439 currentPage: 1 440 }; 441 pdfViewerStates.set(pdfUrl, state); 442 return state; 443 }); 444 } 445 446 function renderPdfPage(state, pageNum, target, scale) { 447 scale = scale || 1.5; 448 pageNum = Math.max(1, Math.min(state.numPages, pageNum)); 449 var key = pageNum + '@' + scale; 450 if (state.pageCache.has(key)) { 451 var cachedImg = document.createElement('img'); 452 cachedImg.src = state.pageCache.get(key); 453 cachedImg.alt = 'Datenblatt Seite ' + pageNum; 454 cachedImg.className = 'tt-energy-modal-pdf-img'; 455 target.innerHTML = ''; 456 target.appendChild(cachedImg); 457 return Promise.resolve(); 458 } 459 return state.pdf.getPage(pageNum).then(function (page) { 460 var viewport = page.getViewport({ scale: scale }); 461 var canvas = document.createElement('canvas'); 462 var ctx = canvas.getContext('2d'); 463 canvas.width = viewport.width; 464 canvas.height = viewport.height; 465 return page.render({ canvasContext: ctx, viewport: viewport }).promise 466 .then(function () { 467 try { 468 var dataUrl = canvas.toDataURL('image/jpeg', 0.85); 469 state.pageCache.set(key, dataUrl); 470 var img = document.createElement('img'); 471 img.src = dataUrl; 472 img.alt = 'Datenblatt Seite ' + pageNum; 473 img.className = 'tt-energy-modal-pdf-img'; 474 target.innerHTML = ''; 475 target.appendChild(img); 476 } catch (e) { 477 canvas.className = 'tt-energy-modal-pdf-img'; 478 target.innerHTML = ''; 479 target.appendChild(canvas); 480 } 481 }); 482 }); 483 } 484 485 function updateViewerControls(modal, state) { 486 var pageInfo = modal.querySelector('.tt-energy-modal-pdf-pageinfo'); 487 var prevBtn = modal.querySelector('.tt-energy-modal-pdf-prev'); 488 var nextBtn = modal.querySelector('.tt-energy-modal-pdf-next'); 489 if (pageInfo) pageInfo.textContent = 'Seite ' + state.currentPage + ' / ' + state.numPages; 490 if (prevBtn) prevBtn.disabled = (state.currentPage <= 1); 491 if (nextBtn) nextBtn.disabled = (state.currentPage >= state.numPages); 492 493 494 var nav = modal.querySelector('.tt-energy-modal-pdf-nav'); 495 if (nav) nav.style.display = (state.numPages > 1) ? '' : 'none'; 496 } 497 498 function showPdfError(target, message) { 499 target.innerHTML = '' 500 + '<div class="tt-energy-modal-pdf-error">' 501 + '<svg viewBox="0 0 64 80" xmlns="http://www.w3.org/2000/svg" style="width:48px;height:60px;" aria-hidden="true">' 502 + '<rect x="2" y="2" width="60" height="76" rx="4" fill="#fff" stroke="#dc2626" stroke-width="2"/>' 503 + '<rect x="2" y="2" width="60" height="18" rx="4" fill="#dc2626"/>' 504 + '<text x="32" y="14" text-anchor="middle" font-family="Arial,sans-serif" font-size="10" font-weight="bold" fill="#fff">PDF</text>' 505 + '</svg>' 506 + '<p>Vorschau nicht verfügbar.<br><small style="opacity:0.7;">' + escapeHtml(message) + '</small><br>Datenblatt unten herunterladen.</p>' 507 + '</div>'; 508 } 509 510 function setPdfStatus(target, message) { 511 512 var loadingDiv = target.querySelector('.tt-energy-modal-pdf-loading >
512 div:last-child'); 513 if (loadingDiv) loadingDiv.textContent = message; 514 } 515 516 function openDatasheetModal(labelData) { 517 ttFrontendLog('info', 'openDatasheetModal aufgerufen', { 518 hasDatasheet: !!labelData.hasDatasheet, 519 url: labelData.datasheet ? labelData.datasheet.url : null 520 }); 521 if (!labelData.hasDatasheet) return; 522 523 var url = labelData.datasheet.url; 524 var html = '' 525 + '<div class="tt-energy-modal-pdf-wrap">' 526 + '<div class="tt-energy-modal-pdf-toolbar">' 527 + '<div class="tt-energy-modal-pdf-nav">' 528 + '<button type="button" class="tt-energy-modal-pdf-prev" disabled>' 529 + '<span aria-hidden="true">‹</span> Zurück' 530 + '</button>' 531 + '<span class="tt-energy-modal-pdf-pageinfo">Seite 1</span>' 532 + '<button type="button" class="tt-energy-modal-pdf-next" disabled>' 533 + 'Weiter <span aria-hidden="true">›</span>' 534 + '</button>' 535 + '</div>' 536 + '<a class="tt-energy-modal-pdf-dlbtn" href="' + escapeAttr(url) + '" target="_blank" rel="noopener" download>' 537 + '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="width:14px;height:14px;"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>' 538 + '<span>' + escapeHtml(labelData.strings.datasheet_dl) + '</span>' 539 + '</a>' 540 + '</div>' 541 + '<div class="tt-energy-modal-pdf-page">' 542 + '<div class="tt-energy-modal-pdf-loading">' 543 + '<div class="tt-energy-modal-pdf-spinner" aria-hidden="true"></div>' 544 + '<div>Initialisiereâ¦</div>' 545 + '</div>' 546 + '</div>' 547 + '</div>'; 548 549 var modal = buildModalShell( 550 labelData.strings.datasheet_modal, 551 html, 552 { singletonId: 'tt-energy-datasheet-modal', closeLabel: labelData.strings.datasheet_close } 553 ); 554 openModal(modal); 555 556 var pageContainer = modal.querySelector('.tt-energy-modal-pdf-page'); 557 if (!pageContainer) { 558 ttFrontendLog('warn', 'pageContainer nicht gefunden'); 559 return; 560 } 561 562 setPdfStatus(pageContainer, 'Lade PDF.js-Bibliothekâ¦'); 563 ttFrontendLog('info', 'Step 1/4: Lade PDF.js'); 564 565 loadPdfJsLib().then(function (lib) { 566 setPdfStatus(pageContainer, 'PDF.js geladen, lade PDF-Dateiâ¦'); 567 ttFrontendLog('info', 'Step 2/4: PDF.js OK, fetche PDF', { version: lib.version || '?' }); 568 return fetchPdfBuffer(url).then(function (buf) { 569 setPdfStatus(pageContainer, 'PDF geladen (' + Math.round(buf.byteLength/1024) + ' KB), öffneâ¦'); 570 ttFrontendLog('info', 'Step 3/4: PDF gefetched, öffne via getDocument'); 571 return lib.getDocument({ 572 data: buf.slice(0), 573 disableAutoFetch: true, 574 disableStream: true, 575 isEvalSupported: false 576 }).promise; 577 }); 578 }).then(function (pdf) { 579 setPdfStatus(pageContainer, 'Rendere Seite 1 von ' + pdf.numPages + 'â¦'); 580 ttFrontendLog('info', 'Step 4/4: PDF geöffnet, rendere Seite 1', { pages: pdf.numPages }); 581 var state = { 582 pdf: pdf, 583 numPages: pdf.numPages, 584 pageCache: new Map(), 585 currentPage: 1 586 }; 587 pdfViewerStates.set(url, state); 588 updateViewerControls(modal, state); 589 return renderPdfPage(state, 1, pageContainer, 1.5).then(function () { 590 ttFrontendLog('info', 'Seite 1 erfolgreich gerendert'); 591 var prevBtn = modal.querySelector('.tt-energy-modal-pdf-prev'); 592 var nextBtn = modal.querySelector('.tt-energy-modal-pdf-next'); 593 if (prevBtn) prevBtn.addEventListener('click', function () { 594 if (state.currentPage > 1) { 595 state.currentPage--; 596 updateViewerControls(modal, state); 597 renderPdfPage(state, state.currentPage, pageContainer, 1.5); 598 } 599 }); 600 if (nextBtn) nextBtn.addEventListener('click', function () { 601 if (state.currentPage < state.numPages) { 602 state.currentPage++; 603 updateViewerControls(modal, state); 604 renderPdfPage(state, state.currentPage, pageContainer, 1.5); 605 } 606 }); 607 }); 608 }).catch(function (err) {
609 var errMsg = err && err.message ? err.message : String(err); 610 ttFrontendLog('error', 'PDF-Viewer-Fehler: ' + errMsg); 611 showPdfError(pageContainer, errMsg); 612 }); 613 } 614 615 function tryInsertTab(labelData) { 616 if (!labelData.displayModes.includes('tab')) return; 617 if (!labelData.tabContent || labelData.tabContent.trim() === '') return; 618 if (handledTab.has(labelData.kArtikel)) return; 619 620 const tabList = document.querySelector( 621 '#product-tabs ul.nav-tabs, .product-detail-tabs .nav-tabs, ' + 622 '.product-detail .nav-tabs, ul.nav-tabs[role="tablist"]' 623 ); 624 const panelContainer = document.querySelector( 625 '#product-tabs .tab-content, .product-detail-tabs .tab-content, ' + 626 '.product-detail .tab-content' 627 ); 628 629 if (!tabList || !panelContainer) { 630 log('Keine Tab-Leiste auf der Seite gefunden, Tab-Modus uebersprungen'); 631 return; 632 } 633 634 const tabId = 'tt-energy-tab-' + labelData.kArtikel; 635 const panelId = 'tt-energy-tab-panel-' + labelData.kArtikel; 636 637 if (document.getElementById(tabId)) { 638 handledTab.add(labelData.kArtikel); 639 return; 640 } 641 642 const li = document.createElement('li'); 643 li.className = 'nav-item'; 644 li.setAttribute('role', 'presentation'); 645 li.innerHTML = '<a class="nav-link" id="' + tabId + '" href="#' + panelId + '"' 646 + ' role="tab" aria-controls="' + panelId + '" aria-selected="false" data-toggle="tab">' 647 + escapeHtml(labelData.strings.datasheet_tab) 648 + '</a>'; 649 650 const items = tabList.querySelectorAll('li'); 651 const pos = labelData.tabPosition; 652 653 if (pos === 'last' || items.length === 0) { 654 tabList.appendChild(li); 655 } else { 656 const targetIdx = parseInt(pos, 10); 657 if (!isNaN(targetIdx) && targetIdx >= 1 && targetIdx <= items.length) { 658 tabList.insertBefore(li, items[targetIdx - 1]); 659 } else { 660 tabList.appendChild(li); 661 } 662 } 663 664 const panel = document.createElement('div'); 665 panel.className = 'tab-pane fade'; 666 panel.id = panelId; 667 panel.setAttribute('role', 'tabpanel'); 668 panel.setAttribute('aria-labelledby', tabId); 669 670 671 672 673 var datasheetHtml = '<div class="tt-energy-tab-content">' + labelData.tabContent + '</div>'; 674 if (labelData.hasDatasheet && labelData.datasheet && labelData.datasheet.url) { 675 var dlLabel = labelData.strings && labelData.strings.datasheet_dl 676 ? labelData.strings.datasheet_dl : 'Datenblatt herunterladen'; 677 datasheetHtml += 678 '<div class="tt-energy-tab-pdfviewer" data-pdf-url="' + escapeAttr(labelData.datasheet.url) + '" data-rendered="0">' + 679 ' <div class="tt-energy-modal-pdf-toolbar">' + 680 ' <div class="tt-energy-modal-pdf-nav">' + 681 ' <button type="button" class="tt-energy-modal-pdf-prev" disabled>' + 682 ' <span aria-hidden="true">‹</span> Zurück' + 683 ' </button>' + 684 ' <span class="tt-energy-modal-pdf-pageinfo">Seite 1</span>' + 685 ' <button type="button" class="tt-energy-modal-pdf-next" disabled>' + 686 ' Weiter <span aria-hidden="true">›</span>' + 687 ' </button>' + 688 ' </div>' + 689 ' <a class="tt-energy-modal-pdf-dlbtn" href="' + escapeAttr(labelData.datasheet.url) + '" target="_blank" rel="noopener" download>' + 690 ' <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="width:14px;height:14px;"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>' + 691 ' <span>' + escapeHtml(dlLabel) + '</span>' + 692 ' </a>' + 693 ' </div>' + 694 ' <div class="tt-energy-modal-pdf-page">' + 695 ' <div class="tt-energy-modal-pdf-loading">' + 696 ' <div class="tt-energy-modal-pdf-spinner" aria-hidden="true"></div>' + 697 ' <div>Initialisiereâ¦</div>' + 698 ' </div>' + 699 ' </div>' + 700 '</div>'; 701 } 702 panel.innerHTML = datasheetHtml; 703 panelContainer.appendChild(panel); 704 705 const link = document.getElementById(tabId); 706 link.addEventListener('click', (e) => { 707 e.preventDefault(); 708 709 tabList.querySelectorAll('.nav-link').forEach(t => { 710 t.classList.remove('active'); 711 t.setAttribute('aria-selected', 'false'); 712 }); 713 panelContainer.querySelectorAll('.tab-pane').forEach(p => { 714 p.classList.remove('active', 'show', 'in'); 715 }); 716 717 link.classList.add('active'); 718 link.setAttribute('aria-selected', 'true'); 719 panel.classList.add('active', 'show', 'in'); 720 721 wireDatasheetTriggers(panel, labelData); 722 723 triggerTabPdfViewer(panel, labelData); 724 }); 725 726 wireDatasheetTriggers(panel, labelData); 727 728 handledTab.add(labelData.kArtikel); 729 log('Tab eingefuegt für Artikel', labelData.kArtikel); 730 } 731 732 function triggerTabPdfViewer(panel, labelData) { 733 var viewer = panel.querySelector('.tt-energy-tab-pdfviewer[data-
733pdf-url]'); 734 if (!viewer) return; 735 if (viewer.dataset.rendered === '1') return; 736 viewer.dataset.rendered = '1'; 737 738 var url = viewer.dataset.pdfUrl; 739 var pageContainer = viewer.querySelector('.tt-energy-modal-pdf-page'); 740 if (!pageContainer) return; 741 742 ttFrontendLog('info', 'Tab-PDF-Viewer startet', { url: url }); 743 setPdfStatus(pageContainer, 'Lade PDF.js-Bibliothekâ¦'); 744 745 loadPdfJsLib().then(function (lib) { 746 setPdfStatus(pageContainer, 'PDF.js geladen, lade PDF-Dateiâ¦'); 747 ttFrontendLog('info', 'Tab-PDF Step 2: fetche PDF'); 748 return fetchPdfBuffer(url).then(function (buf) { 749 setPdfStatus(pageContainer, 'PDF geladen (' + Math.round(buf.byteLength/1024) + ' KB), öffneâ¦'); 750 return lib.getDocument({ 751 data: buf.slice(0), 752 disableAutoFetch: true, 753 disableStream: true, 754 isEvalSupported: false 755 }).promise; 756 }); 757 }).then(function (pdf) { 758 setPdfStatus(pageContainer, 'Rendere Seite 1 von ' + pdf.numPages + 'â¦'); 759 ttFrontendLog('info', 'Tab-PDF Step 4: rendere Seite 1', { pages: pdf.numPages }); 760 var state = { 761 pdf: pdf, 762 numPages: pdf.numPages, 763 pageCache: new Map(), 764 currentPage: 1 765 }; 766 pdfViewerStates.set(url, state); 767 updateViewerControls(viewer, state); 768 return renderPdfPage(state, 1, pageContainer, 1.5).then(function () { 769 ttFrontendLog('info', 'Tab-PDF: Seite 1 erfolgreich gerendert'); 770 var prevBtn = viewer.querySelector('.tt-energy-modal-pdf-prev'); 771 var nextBtn = viewer.querySelector('.tt-energy-modal-pdf-next'); 772 if (prevBtn) prevBtn.addEventListener('click', function () { 773 if (state.currentPage > 1) { 774 state.currentPage--; 775 updateViewerControls(viewer, state); 776 renderPdfPage(state, state.currentPage, pageContainer, 1.5); 777 } 778 }); 779 if (nextBtn) nextBtn.addEventListener('click', function () { 780 if (state.currentPage < state.numPages) { 781 state.currentPage++; 782 updateViewerControls(viewer, state); 783 renderPdfPage(state, state.currentPage, pageContainer, 1.5); 784 } 785 }); 786 }); 787 }).catch(function (err) { 788 var errMsg = err && err.message ? err.message : String(err); 789 ttFrontendLog('error', 'Tab-PDF-Viewer-Fehler: ' + errMsg); 790 showPdfError(pageContainer, errMsg); 791 }); 792 } 793 794 function wireDatasheetTriggers(container, labelData) { 795 const triggers = container.querySelectorAll('.tt-energy-datasheet-link, .tt-btn-datasheet'); 796 triggers.forEach(t => { 797 if (t.dataset.ttBound) return; 798 t.dataset.ttBound = '1'; 799 t.addEventListener('click', (e) => { 800 801 if (t.tagName === 'BUTTON') { 802 e.preventDefault(); 803 openDatasheetModal(labelData); 804 } else if (t.tagName === 'A') { 805 e.preventDefault(); 806 openDatasheetModal(labelData); 807 } 808 }); 809 }); 810 } 811 812 function processSelectors(rootElement) { 813 const context = detectContext(); 814 const contextSelectors = (config.selectors[context] || []); 815 const globalSelectors = (config.selectors['all'] || []); 816 const selectors = [...contextSelectors, ...globalSelectors]; 817 818 if (selectors.length === 0) { 819 log('Keine Selektoren für Kontext', context); 820 return; 821 } 822 823 log('Verarbeite', selectors.length, 'Selektoren in Kontext', context); 824 825 selectors.forEach(sel => { 826 let elements; 827 try { 828 elements = rootElement.querySelectorAll(sel.selector); 829 } catch (err) { 830 log('Ungueltiger Selektor:', sel.selector, err); 831 return; 832 } 833
834 elements.forEach(el => { 835 if (handled.has(el)) return; 836 837 const kArtikel = findArticleId(el); 838 if (!kArtikel) { 839 log('Keine Artikel-ID für', sel.selector, el); 840 return; 841 } 842 843 handled.add(el); 844 845 fetchLabelData(kArtikel).then(data => { 846 if (!data || !data.arrows || data.arrows.length === 0) return; 847 848 if (sel.type === 'arrow') { 849 const wrapper = document.createElement('span'); 850 wrapper.className = 'tt-energy-label-wrapper'; 851 data.arrows.forEach(arrow => { 852 wrapper.appendChild(renderArrow(arrow, data)); 853 }); 854 insertElement(el, wrapper, sel.method); 855 } else if (sel.type === 'datasheet') { 856 857 if (data.displayModes && data.displayModes.includes('inline')) { 858 const link = renderDatasheetLink(data); 859 if (link) insertElement(el, link, sel.method); 860 } 861 } 862 863 if (context === 'detail' && data.displayModes && data.displayModes.includes('tab')) { 864 tryInsertTab(data); 865 } 866 }); 867 }); 868 }); 869 } 870 871 function insertElement(target, newElement, method) { 872 switch (method) { 873 case 'before': target.parentNode?.insertBefore(newElement, target); break; 874 case 'after': target.parentNode?.insertBefore(newElement, target.nextSibling); break; 875 case 'prepend': target.insertBefore(newElement, target.firstChild); break; 876 case 'append': target.appendChild(newElement); break; 877 case 'replace': target.innerHTML = ''; target.appendChild(newElement); break; 878 default: target.parentNode?.insertBefore(newElement, target.nextSibling); 879 } 880 } 881 882 function escapeHtml(s) { 883 return String(s).replace(/[&<>"]/g, c => ({'&':'&','<':'<','>':'>','"':'"'}[c])); 884 } 885 function escapeAttr(s) { 886 return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); 887 } 888 889 890 891 function getDetailArticleId() { 892 893 const urlMatch = window.location.search.match(/[?&]a=(\d+)/); 894 if (urlMatch) { 895 return parseInt(urlMatch[1], 10); 896 } 897 898 const buyInput = document.querySelector( 899 'form[id^="buy_form"] input[name="a"], form#buy_form input[name="a"], input[name="a"][type="hidden"]' 900 ); 901 if (buyInput && buyInput.value) { 902 return parseInt(buyInput.value, 10); 903 } 904 905 const dataNode = document.querySelector( 906 '[data-product-id], [data-kartikel], [data-article-id]' 907 ); 908 if (dataNode) { 909 const id = dataNode.dataset.productId || dataNode.dataset.kartikel || dataNode.dataset.articleId; 910 if (id) return parseInt(id, 10); 911 } 912 913 return null; 914 } 915 916 function isDetailPage() { 917 const body = document.body; 918 if (!body) return false; 919 if (body.classList.contains('product-detail') || 920 body.classList.contains('page-product')) { 921 return true; 922 } 923 924 if (document.querySelector('form[id^="buy_form"]')) return true; 925 926 if (/[?&]a=\d+/.test(window.location.search)) return true; 927 return false; 928 } 929 930 function autoInjectDetail() { 931 if (!isDetailPage()) { 932 log('Auto-Detail: keine Detailseite'); 933 return; 934 } 935 936 const kArtikel = getDetailArticleId(); 937 if (!kArtikel) { 938 log('Auto-Detail: Keine Artikel-ID gefunden'); 939 return; 940 } 941 log('Auto-Detail: kArtikel=' + kArtikel); 942 943 944 var buyForm = document.querySelector('form[id^="buy_form"]'); 945 946 const arrowCandidates = [ 947 '.product-detail-price', 948 '.product-detail .price-tile', 949 '.product-detail .product-price', 950 '.price-tile.price-detail', 951 '.price-tile-price', 952 '.price-tile', 953 '.product-buy-box .price', 954 '.product-buy-box .price-wrapper', 955 '.price-wrapper .price', 956 '.price-wrapper', 957 '.product-price', 958 '.h-price', 959 'span.price', 960 '.detail-price-tile', 961 'form[id^="buy_form"] .price', 962 '#main-content .price' 963 ]; 964 965 const datasheetCandidates = [ 966 '.product-attribute-list', 967 '.product-attributes', 968 '.product-details-list', 969 '.product-info-box', 970 '.product-buy-box', 971 '.product-detail .product-attributes', 972 '.product-detail h1, h1.h-product-title', 973 'h1[itemprop="name"]' 974 ]; 975 976 function findFirstMatch(candidates, label) { 977 for (const sel of candidates) { 978 try { 979 const el = document.querySelector(sel); 980 if (el) { 981 log('Auto-Detail: ' + label + '-Anker gefunden: ' + sel); 982 return el; 983 } 984 } catch (e) {} 985 } 986 log('Auto-Detail: ' + label + '-Anker NICHT gefunden, getestet:', candidates); 987 return null; 988 } 989 990 let arrowAnchor = findFirstMatch(arrowCandidates, 'Pfeil'); 991 let dsAnchor = findFirstMatch(datasheetCandidates, 'Datenblatt'); 992 993 if (!arrowAnchor) { 994 arrowAnchor = document.querySelector('h1[itemprop="name"], .product-detail h1, main h1, article h1'); 995 if (arrowAnchor) { 996 log('Auto-Detail: Fallback auf H1', arrowAnchor); 997 } 998 } 999 1000 if (!arrowAnchor && buyForm) { 1001 arrowAnchor = buyForm; 1002 log('Auto-Detail: Fallback auf buy_form', buyForm); 1003 } 1004 1005 if (!arrowAnchor) { 1006 arrowAnchor = document.querySelector('h1, h2'); 1007 if (arrowAnchor) { 1008 log('Auto-Detail: letzter Fallback auf erstes Heading', arrowAnchor); 1009 } 1010 } 1011 1012 if (!arrowAnchor && !dsAnchor) { 1013 warn('Auto-Detail: kein Anker auf der Detailseite gefunden. Selektoren-Tab pruefen.'); 1014 return; 1015 } 1016 1017 fetchLabelData(kArtikel).then(data => { 1018 if (!data) { 1019 warn('Auto-Detail: AJAX gab null zurueck für kArtikel=' + kArtikel + '. Mglw. ist Plugin-Frontend-Endpoint nicht erreichbar oder Artikel hat keine Energielabel-Daten/Demo-Konfiguration.'); 1020 return; 1021 } 1022 if (!data.arrows || data.arrows.length === 0) { 1023 warn('Auto-Detail: keine Pfeil-Daten für kArtikel=' + kArtikel + '. Demo-Eintrag in xplugin_tt_energy_label_demo pruefen.'); 1024 return; 1025 } 1026 log('Auto-Detail: Daten erhalten, Pfeile=' + data.arrows.length, data); 1027 1028 if (arrowAnchor && !handled.has(arrowAnchor) && 1029 !arrowAnchor.parentNode.querySelector('.tt-energy-label-wrapper')) { 1030 handled.add(arrowAnchor); 1031 const wrapper = document.createElement('span'); 1032 wrapper.className = 'tt-energy-label-wrapper tt-energy-auto';
1033 data.arrows.forEach(arrow => { 1034 wrapper.appendChild(renderArrow(arrow, data)); 1035 }); 1036 arrowAnchor.parentNode.insertBefore(wrapper, arrowAnchor.nextSibling); 1037 log('Auto-Detail: Pfeil eingefuegt nach', arrowAnchor); 1038 } 1039 1040 if (dsAnchor && data.displayModes && data.displayModes.includes('inline') && 1041 !handled.has(dsAnchor) && 1042 !dsAnchor.parentNode.querySelector('.tt-energy-datasheet-link.tt-energy-auto')) { 1043 handled.add(dsAnchor); 1044 const link = renderDatasheetLink(data); 1045 if (link) { 1046 link.classList.add('tt-energy-auto'); 1047 dsAnchor.parentNode.insertBefore(link, dsAnchor.nextSibling); 1048 log('Auto-Detail: Datenblatt-Link eingefuegt'); 1049 } 1050 } 1051 1052 if (data.displayModes && data.displayModes.includes('tab')) { 1053 tryInsertTab(data); 1054 } 1055 }); 1056 } 1057 1058 function init() { 1059 1060 1061 1062 log('Init - JS-Modus: nur Klick-Handler, keine DOM-Manipulation'); 1063 1064 initModalTabSwitcher(); 1065 initBackendRenderedPdfViewers(); 1066 } 1067 1068 function initBackendRenderedPdfViewers() { 1069 1070 ttFrontendLog('info', 'energy_label.js Init - suche Backend-PDF-Viewer im DOM'); 1071 1072 function findReadyViewers() { 1073 var all = document.querySelectorAll('.tt-energy-tab-pdfviewer[data-pdf-url][data-rendered="0"]'); 1074 var ready = []; 1075 for (var i = 0; i < all.length; i++) { 1076 if (all[i].offsetParent !== null) { 1077 ready.push(all[i]); 1078 } 1079 } 1080 return ready; 1081 } 1082 1083 document.addEventListener('click', function (e) { 1084 var target = e.target; 1085 while (target && target !== document) { 1086 if (target.classList && target.classList.contains('nav-link')) { 1087 1088 setTimeout(function () { 1089 var ready = findReadyViewers(); 1090 ttFrontendLog('info', 'Tab-Click: ' + ready.length + ' bereite Viewer gefunden'); 1091 for (var i = 0; i < ready.length; i++) triggerBackendPdfViewer(ready[i]); 1092 }, 100); 1093 return; 1094 } 1095 target = target.parentNode; 1096 } 1097 }, true); 1098 1099 document.addEventListener('shown.bs.modal', function () { 1100 setTimeout(function () { 1101 var ready = findReadyViewers(); 1102 ttFrontendLog('info', 'Modal-Shown: ' + ready.length + ' bereite Viewer gefunden'); 1103 for (var i = 0; i < ready.length; i++) triggerBackendPdfViewer(ready[i]); 1104 }, 100); 1105 }, true); 1106 1107 var initial = findReadyViewers(); 1108 ttFrontendLog('info', 'Initial-Scan: ' + initial.length + ' bereite Viewer gefunden'); 1109 for (var i = 0; i < initial.length; i++) triggerBackendPdfViewer(initial[i]); 1110 1111 1112 1113 1114 var pollCount = 0; 1115 var pollInterval = setInterval(function () { 1116 pollCount++; 1117 var anyViewer = document.querySelector('.tt-energy-tab-pdfviewer[data-pdf-url][data-rendered="0"]'); 1118 if (!anyViewer) { 1119 1120 clearInterval(pollInterval); 1121 return; 1122 } 1123 var ready = findReadyViewers(); 1124 if (ready.length > 0) { 1125 1126 ttFrontendLog('info', 'Polling #' + pollCount + ': ' + ready.length + ' bereite Viewer gefunden'); 1127 for (var i = 0; i < ready.length; i++) triggerBackendPdfViewer(ready[i]); 1128 } 1129 1130 if (pollCount >= 20) clearInterval(pollInterval); 1131 }, 500); 1132 } 1133 1134 function triggerBackendPdfViewer(viewer) { 1135 if (!viewer || viewer.dataset.rendered === '1') return; 1136 viewer.dataset.rendered = '1'; 1137 1138 var url = viewer.dataset.pdfUrl; 1139 var pageContainer = viewer.querySelector('.tt-energy-modal-pdf-page'); 1140 if (!pageContainer) { 1141 ttFrontendLog('warn', 'Backend-PDF-Viewer ohne page-Container'); 1142 return; 1143 } 1144 1145 ttFrontendLog('info', 'Backend-PDF-Viewer startet', { url: url }); 1146 setPdfStatus(pageContainer, 'Lade PDF.js-Bibliothekâ¦'); 1147 1148 loadPdfJsLib().then(function (lib) { 1149 setPdfStatus(pageContainer, 'PDF.js geladen, lade PDF-Dateiâ¦'); 1150 return fetchPdfBuffer(url).then(function (buf) { 1151 setPdfStatus(pageContainer, 'PDF geladen (' + Math.round(buf.byteLength/1024) + ' KB), öffneâ¦'); 1152 return lib.getDocument({ 1153 data: buf.slice(0), 1154 disableAutoFetch: true, 1155 disableStream: true, 1156 isEvalSupported: false 1157 }).promise; 1158 }); 1159 }).then(function (pdf) { 1160 setPdfStatus(pageContainer, 'Rendere Seite 1 von ' + pdf.numPages + 'â¦'); 1161 ttFrontendLog('info', 'Backend-PDF: rendere Seite 1', { pages: pdf.numPages }); 1162 var state = { 1163 pdf: pdf, 1164 numPages: pdf.numPages, 1165 pageCache: new Map(), 1166 currentPage: 1 1167 }; 1168 pdfViewerStates.set(url, state); 1169 updateViewerControls(viewer, state); 1170 return renderPdfPage(state, 1, pageContainer, 1.5).then(function () { 1171 ttFrontendLog('info', 'Backend-PDF: Seite 1 erfolgreich gerendert'); 1172 var prevBtn = viewer.querySelector('.tt-energy-modal-pdf-prev'); 1173 var nextBtn = viewer.querySelector('.tt-energy-modal-pdf-next'); 1174 if (prevBtn) prevBtn.addEventListener('click', function () { 1175 if (state.currentPage > 1) { 1176 state.currentPage--; 1177 updateViewerControls(viewer, state); 1178 renderPdfPage(state, state.currentPage, pageContainer, 1.5); 1179 } 1180 }); 1181 if (nextBtn) nextBtn.addEventListener('click', function () { 1182 if (state.currentPage < state.numPages) { 1183 state.currentPage++; 1184 updateViewerControls(viewer, state); 1185 renderPdfPage(state, state.currentPage, pageContainer, 1.5); 1186 } 1187 }); 1188 }); 1189 }).catch(function (err) {
1190 var errMsg = err && err.message ? err.message : String(err); 1191 ttFrontendLog('error', 'Backend-PDF-Viewer-Fehler: ' + errMsg); 1192 showPdfError(pageContainer, errMsg); 1193 }); 1194 } 1195 1196 function initModalTabSwitcher() { 1197 document.addEventListener('click', function(e) { 1198 1199 var arrow = e.target.closest('.tt-energy-arrow'); 1200 var caption = e.target.closest('.tt-energy-arrow-caption'); 1201 var dsLink = e.target.closest('.tt-energy-datasheet-link'); 1202 if (!arrow && !caption && !dsLink) return; 1203 1204 var trigger = dsLink || caption || arrow; 1205 1206 var modalId = trigger.getAttribute('data-tt-modal-id') || ''; 1207 if (!modalId) { 1208 var href = trigger.getAttribute('data-target') 1209 || trigger.getAttribute('data-bs-target') 1210 || trigger.getAttribute('href') || ''; 1211 if (href.charAt(0) === '#') modalId = href.substring(1); 1212 } 1213 if (!modalId) return; 1214 1215 var modal = document.getElementById(modalId); 1216 1217 var targetTab = arrow && !caption && !dsLink ? 'label' : 'sheet'; 1218 1219 if (!modal) { 1220 1221 if (dsLink) return; 1222 e.preventDefault(); 1223 return; 1224 } 1225 1226 1227 if (dsLink) { 1228 e.preventDefault(); 1229 1230 1231 openModal(modalId); 1232 } 1233 1234 1235 setTimeout(function() { 1236 activateModalTab(modalId, targetTab); 1237 }, 30); 1238 }); 1239 } 1240 1241 function openModal(modalId) { 1242 var modal = document.getElementById(modalId); 1243 if (!modal) return; 1244 1245 if (window.bootstrap && typeof window.bootstrap.Modal === 'function') { 1246 try { window.bootstrap.Modal.getOrCreateInstance(modal).show(); return; } catch (e) {} 1247 } 1248 1249 if (window.jQuery && typeof window.jQuery.fn.modal === 'function') { 1250 try { window.jQuery(modal).modal('show'); return; } catch (e) {} 1251 } 1252 1253 modal.classList.add('show'); 1254 modal.style.display = 'block'; 1255 modal.removeAttribute('aria-hidden'); 1256 modal.setAttribute('aria-modal', 'true'); 1257 document.body.classList.add('modal-open'); 1258 } 1259 1260 function activateModalTab(modalId, which) { 1261 1262 var tabBtn = document.getElementById(modalId + '-tab-' + which); 1263 var tabPane = document.getElementById(modalId + '-pane-' + which); 1264 if (!tabBtn || !tabPane) return; 1265 1266 if (window.bootstrap && typeof window.bootstrap.Tab === 'function') { 1267 try { new window.bootstrap.Tab(tabBtn).show(); return; } catch (e) {} 1268 } 1269 1270 if (window.jQuery && typeof window.jQuery.fn.tab === 'function') { 1271 try { window.jQuery(tabBtn).tab('show'); return; } catch (e) {} 1272 } 1273 1274 var modal = document.getElementById(modalId); 1275 if (!modal) return; 1276 var tabs = modal.querySelectorAll('.nav-link'); 1277 var panes = modal.querySelectorAll('.tab-pane'); 1278 for (var i = 0; i < tabs.length; i++) { 1279 tabs[i].classList.remove('active'); 1280 tabs[i].setAttribute('aria-selected', 'false'); 1281 } 1282 for (var j = 0; j < panes.length; j++) { 1283 panes[j].classList.remove('active', 'show'); 1284 } 1285 tabBtn.classList.add('active'); 1286 tabBtn.setAttribute('aria-selected', 'true'); 1287 tabPane.classList.add('active', 'show'); 1288 } 1289 1290 if (document.readyState === 'loading') { 1291 document.addEventListener('DOMContentLoaded', init); 1292 } else { 1293 init(); 1294 } 1295})();
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.