1"use strict"; 2jQuery(document).ready(function ($) { 3 //Added active class in the eventin menu in dokan leftmenu 4 const _pathname = window.location.pathname.toString(); 5 const create_event = "/eventin/create_event/"; 6 const vendor_event = "/eventin/vendor_event/"; 7 if (_pathname.includes(create_event) || _pathname.includes(vendor_event)) { 8 $(".dokan-dashboard-menu").find("li.eventin").addClass("active"); 9 } 10 11 /*================================ 12 speaker slider 13 ===================================*/ 14 var $scope = $(".speaker_shortcode_slider"); 15 if ($scope.length > 0) { 16 $($scope).each(function () { 17 var $this = $(this); 18 speaker_sliders_pro($, $this); 19 }); 20 } 21 22 /*================================ 23 speaker slider 24 ===================================*/ 25 var $scope = $(".event-slider-shortcode"); 26 event_sliders_pro($, $scope); 27 28 /*================================ 29 Event calendar hover card 30 ===================================*/ 31 etn_calendar_tooltip_placement($); 32 33 /*================================ 34 Event accordion 35 ===================================*/ 36 37 $(".etn-content-item > .etn-accordion-heading").on("click", function (e) { 38 e.preventDefault(); 39 if ($(this).hasClass("active")) { 40 $(this).removeClass("active"); 41 $(this).siblings(".etn-acccordion-contents").slideUp(200); 42 $(".etn-content-item > .etn-accordion-heading i") 43 .removeClass("etn-minus") 44 .addClass("etn-plus"); 45 } else { 46 $(".etn-content-item > .etn-accordion-heading i") 47 .removeClass("etn-minus") 48 .addClass("etn-plus"); 49 $(this).find("i").removeClass("etn-plus").addClass("etn-minus"); 50 $(".etn-content-item > .etn-accordion-heading").removeClass("active"); 51 $(this).addClass("active"); 52 $(".etn-acccordion-contents").slideUp(200); 53 $(this).siblings(".etn-acccordion-contents").slideDown(200); 54 } 55 }); 56 57 /*================================ 58 // countdown 59 ===================================*/ 60 const main_block = $(".etn_count_down_block"); 61 if (main_block.length) { 62 count_down($, main_block); 63 } 64 65 /*================================ 66 // Advanced search toggle 67 ===================================*/ 68 if ($(".etn-filter-icon").length > 0) { 69 $(".etn-filter-icon").on("click", function () { 70 $(this).parents().find(".etn_event_inline_form_bottom").slideToggle(); 71 }); 72 } 73 74 /*================================ 75 // Certificate generate 76 ===================================*/ 77 const certificateButton = $(".etn-certificate-download"); 78 certificateButton.on("click", function (e) { 79 e.preventDefault(); 80 etnCreateCertificate(certificateButton); 81 }); 82 83 /*==================================================== 84 * // Event bulk attendee field value update script 85 * ====================================================*/ 86 $("#etn_bulk_attendee").on("change", function () { 87 const totalVariations = $(".etn-ticket-single-variation-details").length; 88 const submitButton = $(".attendee_submit"); 89 const isChecked = $(this).is(":checked"); 90 const checkbox = $(this); 91 checkbox.prop("checked", isChecked); 92 93 for ( 94 let variationIndex = 0; 95 variationIndex < totalVariations; 96 variationIndex++ 97 ) { 98 const totalTicketQuantity = checkbox.data(`total_ticket_quantity`); 99 100 for ( 101 let quantityIndex = 0; 102 quantityIndex < totalTicketQuantity; 103 quantityIndex++ 104 ) { 105 const element = quantityIndex + 1; 106 const nameElement = $( 107 `#ticket_${variationIndex}_attendee_name_${element}` 108 ); 109 const emailElement = $( 110 `#ticket_${variationIndex}_attendee_email_${element}` 111 ); 112 const phoneElement = $( 113 `#ticket_${variationIndex}_attendee_phone_${element}` 114 ); 115 const fields = [nameElement, emailElement, phoneElement]; 116 117 fields.forEach((field) => { 118 if (isChecked) { 119 field.removeClass("attendee_error"); 120 const fieldType = field.attr("type"); 121 if (fieldType === "email") { 122 emailElement.val(`attendee${element}@test.com`); 123 } else if (fieldType === "tel") { 124 field.val(`0123456789${element}`); 125 } else { 126 field.val(`Attendee ${element}`); 127 } 128 } else { 129 field.val("").addClass("attendee_error"); 130 } 131 }); 132 } 133 } 134 submitButton 135 .prop("disabled", !isChecked) 136 .toggleClass("attendee_submit_disable", !isChecked); 137 }); 138}); 139 140// etnCreateCertificate.js 141async function generateCertificateImage(element) { 142 // Wait for all images to load first 143 await Promise.all( 144 Array.from(element.querySelectorAll("img")).map((img) => { 145 if (img.complete) return Promise.resolve(); 146 return new Promise((resolve) => { 147 img.onload = resolve; 148 img.onerror = resolve; 149 }); 150 }) 151 ); 152 153 // Use html2canvas with proper config 154 const canvas = await window.html2canvas(element, { 155 useCORS: true, // Handle cross-origin images 156 allowTaint: true, // Allow loading cross-origin images 157 scrollX: 0, // Ensure proper positioning 158 scrollY: 0, 159 scale: 1.5, // Higher quality 160 logging: true, // Help debug issues 161 width: element.offsetWidth, 162 height: element.offsetHeight, 163 backgroundColor: null, // Preserve the element's own background (e.g. Elementor styles) 164 onclone: function(clonedDoc, clonedEl) { 165 // Inherit background from parent elements (e.g. Elementor section gradient) 166 // html2canvas only captures the target element's styles, so backgrounds 167 // applied to parent containers (like Elementor sections) would be lost. 168 var parent = element.parentElement; 169 while (parent && parent !== document.body) { 170 var parentStyle = window.getComputedStyle(parent); 171 var parentBgImage = parentStyle.backgroundImage; 172 var parentBgColor = parentStyle.backgroundColor; 173 var hasBgImage = parentBgImage && parentBgImage !== 'none'; 174 var hasBgColor = parentBgColor && 175 parentBgColor !== 'rgba(0, 0, 0, 0)' && 176 parentBgColor !== 'transparent'; 177 178 if (hasBgImage || hasBgColor) { 179 var elStyle = window.getComputedStyle(element); 180 var elBgImage = elStyle.backgroundImage; 181 182 if (hasBgImage) { 183 if (elBgImage && elBgImage !== 'none') { 184 // Layer element's background on top of parent's background 185 clonedEl.style.backgroundImage = elBgImage + ', ' + parentBgImage; 186 clonedEl.style.backgroundPosition = elStyle.backgroundPosition + ', center'; 187 clonedEl.style.backgroundSize = elStyle.backgroundSize + ', cover'; 188 } else { 189 clonedEl.style.backgroundImage = parentBgImage; 190 } 191 } 192 193 if (hasBgColor) { 194 clonedEl.style.backgroundColor = parentBgColor; 195 } 196 197 break; 198 } 199 parent = parent.parentElement; 200 } 201 } 202 }); 203 204 return canvas.toDataURL("image/png"); 205} 206 207/** 208 * Resolve the width the certificate was DESIGNED at. 209 * 210 * The certificate page is responsive (.etn-certificate-container is max-width:900px;width:100%), 211 * so the container reflows with the browser window. Because the PDF is produced by 212 * screenshotting that live layout, the same certificate yields a different PDF depen
212ding on how 213 * wide the visitor's window happens to be â text rewraps, multi-column rows stack, and the page 214 * grows taller. Capturing at the design width instead makes the output deterministic. 215 * 216 * Block templates carry the design width on the container block itself as an inline max-width 217 * (e.g. max-width:842px). The legacy template (.etn-pdf-content) has none â it is width:100% 218 * inside .etn-certificate-container, so its design width is that ancestor's max-width (900px). 219 * Walking up for the first finite max-width covers both and keeps tracking the stylesheet 220 * instead of hardcoding a number; 900 is the last resort and matches the certificate page's 221 * fixed viewport meta (see block-template-pdf-certificate.php). 222 */ 223function etnCertificateDesignWidth(element) { 224 const inlineMax = parseFloat(element.style.maxWidth); 225 if (!isNaN(inlineMax) && inlineMax > 0) return Math.round(inlineMax); 226 227 for (let node = element; node && node !== document.body; node = node.parentElement) { 228 const max = parseFloat(window.getComputedStyle(node).maxWidth); 229 if (!isNaN(max) && max > 0) return Math.round(max); 230 } 231 232 return 900; 233} 234 235async function etnCreateCertificate(downloadButton) { 236 'use strict'; 237 238 const jsPDF = window.jspdf.jsPDF; 239 240 if(downloadButton) downloadButton.addClass("loading"); 241 242 // Try both new and legacy selectors 243 const element = 244 document.querySelector(".etn-downloadable-container") || document.querySelector(".eventin-container-block") || 245 document.querySelector(".etn-pdf-content"); 246 if (!element) { 247 throw new Error("Certificate container not found"); 248 } 249 250 // Wait for all images to load 251 await Promise.all( 252 Array.from(element.querySelectorAll('img')).map((img) => { 253 if (img.complete) return Promise.resolve(); 254 return new Promise((resolve) => { 255 img.onload = resolve; 256 img.onerror = resolve; 257 }); 258 }) 259 ); 260 261 try { 262 263 // Pin the container to its design width for the duration of the capture so the PDF does 264 // not depend on the visitor's window size. Restored immediately after rasterizing, so the 265 // on-screen certificate stays responsive. We resize the live element rather than an 266 // offscreen clone on purpose: html2canvas copies <canvas> bitmaps (the QR code) from the 267 // element it is given, and cloneNode() would hand it blank canvases. 268 const designWidth = etnCertificateDesignWidth(element); 269 const previousInlineWidth = element.style.width; 270 const previousInlineMaxWidth = element.style.maxWidth; 271 272 element.style.width = designWidth + "px"; 273 element.style.maxWidth = "none"; 274 275 // Let the browser lay out at the pinned width before we measure it. 276 await new Promise((resolve) => 277 requestAnimationFrame(() => requestAnimationFrame(resolve)) 278 ); 279 280 let dataUrl; 281 let elementWidth; 282 let elementHeight; 283 284 try { 285 // Get dimensions from element, now that it is at its design width 286 const computedStyle = window.getComputedStyle(element); 287 elementWidth = element.offsetWidth || parseFloat(computedStyle.width); 288 elementHeight = element.offsetHeight || parseFloat(computedStyle.height); 289 290 dataUrl = await generateCertificateImage(element); 291 } finally { 292 element.style.width = previousInlineWidth; 293 element.style.maxWidth = previousInlineMaxWidth; 294 } 295 296 const img = new Image(); 297 298 return new Promise((resolve) => { 299 img.onload = () => { 300 301 const orientation = elementWidth > elementHeight ? "l" : "p"; 302 303 const doc = new jsPDF({ 304 orientation, 305 unit: "px", 306 format: "a4", 307 }); 308 309 const pageWidth = doc.internal.pageSize.getWidth(); 310 const pageHeight = doc.internal.pageSize.getHeight(); 311 312 const scale = Math.min( 313 (pageWidth - 20) / elementWidth, 314 (pageHeight - 20) / elementHeight 315 ); 316 317 const width = elementWidth * scale; 318 const height = elementHeight * scale; 319 320 const x = (pageWidth - width) / 2; 321 const y = (pageHeight - height) / 2; 322 323 doc.addImage(img, "PNG", x, y, width, height,undefined, "FAST"); 324 const filename = `document-${new Date().toISOString().slice(0, 10)}.pdf`; 325 doc.save(filename); 326 downloadButton.removeClass("loading"); 327 }; 328 329 img.onerror = (error) => { 330 console.error("Certificate image generation failed:", error); 331 downloadButton.removeClass("loading"); 332 resolve(false); 333 }; 334 335 img.src = dataUrl; 336 }); 337 } catch (error) { 338 console.error("Certificate generation failed:", error); 339 downloadButton.removeClass("loading"); 340 } 341} 342 343// Event Countdown Function 344 345function count_down($, $scope) { 346 const countdownContainer = $scope.find(".etn-event-countdown-wrap"); 347 if (countdownContainer.length) { 348 $scope.find(".etn-countdown-parent").countdown({ 349 date: countdownContainer.data("start-date"), 350 day: countdownContainer.data("date-texts").day, 351 days: countdownContainer.data("date-texts").days, 352 hour: countdownContainer.data("date-texts").hr, 353 hours: countdownContainer.data("date-texts").hrs, 354 minute: countdownContainer.data("date-texts").min,
355 minutes: countdownContainer.data("date-texts").mins, 356 second: countdownContainer.data("date-texts").sec, 357 seconds: countdownContainer.data("date-texts").secs, 358 hideOnComplete: true, 359 offset: countdownContainer.data("date-texts").offset, 360 }); 361 } 362} 363 364// print order details 365function etn_pro_pirnt_content_area(divContents) { 366 "use strict"; 367 var mywindow = window.open("", "PRINT", "height=400,width=800"); 368 mywindow.document.write( 369 '<style type="text/css">' + 370 ".woocommerce-column--1, .woocommerce-column--2{display:inline-block; float: none; width: 300px; vertical-align: top;} .woocommerce-table tr th{text-align:left; width: 300px; }" + 371 "</style>" 372 ); 373 374 var contentToPrint = 375 document.getElementsByClassName(divContents)[0].innerHTML; 376 contentToPrint = contentToPrint.split('<div class="extra-buttons">')[0]; 377 mywindow.document.write("</head><body >"); 378 mywindow.document.write(contentToPrint); 379 mywindow.document.write("</body></html>"); 380 mywindow.document.close(); // necessary for IE >= 10 381 mywindow.focus(); // necessary for IE >= 10*/ 382 mywindow.print(); 383 return true; 384} 385 386//download pdf 387function etn_pro_download_pdf() { 388 var contentToPrint = 389 document.getElementsByClassName("woocommerce-order")[0].innerHTML; 390 var source = contentToPrint.split('<div class="extra-buttons">')[0]; 391 var filename = "invoice"; 392 jQuery(".woocommerce-order").html(source); 393 var divToPrint = jQuery(".woocommerce-order")[0]; 394 395 // create custom canvas for a better resolution 396 var w = 1000; 397 var h = 1000; 398 var canvas = document.createElement("canvas"); 399 canvas.width = w * 5; 400 canvas.height = h * 5; 401 canvas.style.width = w + "px"; 402 canvas.style.height = h + "px"; 403 var context = canvas.getContext("2d"); 404 context.scale(5, 5); 405 406 html2canvas(divToPrint, { 407 scale: 4, 408 dpi: 288, 409 onrendered: function (canvas) { 410 var data = canvas.toDataURL("image/png", 1); 411 var docDefinition = { 412 content: [ 413 { 414 image: data, 415 width: 500, 416 }, 417 ], 418 }; 419 pdfMake.createPdf(docDefinition).download(filename + ".pdf"); 420 }, 421 }); 422 window.setTimeout(function () { 423 location.reload(); 424 }, 500); 425} 426 427function getSwiperOption({ count, space, autoplays }) { 428 return { 429 slidesPerView: count, 430 spaceBetween: space, 431 navigation: { 432 nextEl: ".swiper-button-next", 433 prevEl: ".swiper-button-prev", 434 }, 435 pagination: { 436 el: ".swiper-pagination", 437 clickable: true, 438 }, 439 autoplay: autoplays, 440 441 paginationClickable: true, 442 breakpoints: { 443 320: { 444 slidesPerView: 1, 445 }, 446 600: { 447 slidesPerView: 2, 448 }, 449 1024: { 450 slidesPerView: count, 451 }, 452 }, 453 }; 454} 455 456// speaker sliders pro 457function speaker_sliders_pro($, $scope) { 458 var $container = $scope.find(".etn-speaker-slider"); 459 var count = $container.data("count"); 460 var space = $container.data("space"); 461 var autoplay = $container.data("autoplay"); 462 var autoplays = autoplay == "yes" ? true : false; 463 464 if ($container.length > 0) { 465 $($container).each(function (index, element) { 466 var mySwiper = new Swiper( 467 element, 468 getSwiperOption({ count, space: 20, autoplays }) 469 ); 470 }); 471 } 472} 473 474// Event sliders pro 475function event_sliders_pro($, $scope) { 476 var $container = $scope.find(".etn-event-slider"); 477 var count = $container.data("count"); 478 var autoplay = $container.data("autoplay"); 479 var autoplays = autoplay == "yes" ? true : false; 480 if ($container.length > 0) { 481 $($container).each(function (index, element) { 482 var mySwiper = new Swiper( 483 element, 484 getSwiperOption({ count, space: 20, autoplays }) 485 ); 486 }); 487 } 488} 489 490 491// Places the hover card of a calendar event next to the event it belongs to. 492// 493// The card is a child of the event element, so the stylesheet could only 494// anchor it with `bottom: calc(100% + 14px)` â always above, always centred on 495// the event. That reads correctly for a month-view chip, but a timeGrid event 496// is as tall as its duration and only ~78px wide, so the card wants to sit 497// above a 500px-tall block (off the top of the page) and centred on a column
498// narrower than itself (off the side of it). Measure per hover instead: keep 499// the card above the event where it fits, drop it against the event's top edge 500// where it does not, and slide it back inside the viewport horizontally. 501function etn_calendar_tooltip_placement($) { 502 // Matches the spacing the month view has always had between card and event. 503 var GAP = 14; 504 // Never let the card touch the edge of the screen. 505 var EDGE = 8; 506 // Keep the arrow this far from the card's own corners. 507 var ARROW_INSET = 16; 508 // The card's own `z-index: 2000` gets it nowhere: .fc-event-main is a 509 // stacking context, so the card can only order itself inside its own event. 510 // Above that FullCalendar gives every event a harness, and every column's 511 // harnesses a shared .fc-timegrid-col-events at z-index 3 â the column 512 // wrappers all tie, so they paint in DOM order and Thursday covers a card 513 // that spills out of Wednesday. Lift both levels for the hovered event. 514 var RAISE = [ 515 ".fc-timegrid-event-harness", 516 ".fc-timegrid-col-events", 517 ".fc-daygrid-event-harness", 518 ".fc-daygrid-day-events", 519 ].join(","); 520 var raised = []; 521 522 function lower() { 523 raised.forEach(function (element) { 524 element.style.zIndex = element.getAttribute("data-etn-z") || ""; 525 element.removeAttribute("data-etn-z"); 526 }); 527 raised = []; 528 } 529 530 $(document).on( 531 "mouseenter", 532 ".events_calendar_standard .has-event, .events_calendar_list .has-event", 533 function () { 534 var tooltip = this.querySelector(".calendar-tooltip"); 535 536 if (!tooltip) { 537 return; 538 } 539 540 lower(); 541 542 for (var node = this.parentElement; node; node = node.parentElement) { 543 if (node.classList.contains("fc")) { 544 break; 545 } 546 547 if (node.matches(RAISE)) { 548 node.setAttribute("data-etn-z", node.style.zIndex); 549 node.style.zIndex = "2000"; 550 raised.push(node); 551 } 552 } 553 554 // .fc-event-main â null while the card is display:none, which is how the 555 // mobile breakpoint switches it off. Nothing to place in that case. 556 var anchor = tooltip.offsetParent; 557 558 if (!anchor) { 559 return; 560 } 561 562 // Measure against the stylesheet's own placement, not the previous 563 // hover's overrides. 564 tooltip.style.top = ""; 565 tooltip.style.bottom = ""; 566 tooltip.style.translate = ""; 567 tooltip.style.removeProperty("--etn-tooltip-arrow-left"); 568 tooltip.classList.remove("etn-tooltip-flipped"); 569 570 var height = tooltip.offsetHeight; 571 var width = tooltip.offsetWidth; 572 573 if (!height || !width) { 574 return; 575 } 576 577 var event = this.getBoundingClientRect(); 578 var anchorRect = anchor.getBoundingClientRect(); 579 var topLimit = EDGE; 580 var bottomLimit = window.innerHeight - EDGE; 581 582 if (event.top - GAP - height >= topLimit) { 583 // Room above: the month-view look, now in pixels rather than a 584 // percentage of however tall this particular event happens to be. 585 tooltip.style.bottom = anchorRect.height + GAP + "px"; 586 tooltip.style.top = "auto"; 587 } else { 588 // No room above. Hang the card off the event's TOP edge rather than 589 // its bottom â an all-day timeGrid event runs the height of the grid, 590 // and a card below it would be hundreds of pixels from the label the 591 // pointer is actually on. 592 var top = Math.min( 593 Math.max(event.top + GAP, topLimit), 594 Math.max(bottomLimit - height, topLimit) 595 ); 596 597 tooltip.style.top = top - anchorRect.top + "px"; 598 tooltip.style.bottom = "auto"; 599 tooltip.classList.add("etn-tooltip-flipped"); 600 } 601 602 // The card has a 300px minimum and a week-view column is narrower than 603 // that, so centring it on the event pushes it off screen in the first 604 // and last columns. `translate` rather than `transform`: the stylesheet 605 // animates the card in on `transform`, and the two compose. 606 var placed = tooltip.getBoundingClientRect(); 607 var shift = 0; 608 609 if (placed.right > window.innerWidth - EDGE) { 610 shift = window.innerWidth - EDGE - placed.right; 611 } 612 613 if (placed.left + shift < EDGE) { 614 shift = EDGE - placed.left; 615 } 616 617 if (shift) { 618 tooltip.style.translate = Math.round(shift) + "px"; 619 } 620 621 // Keep the arrow over the event now that the card may have moved off it. 622 // The arrow is drawn 3px left of its `left`, hence the offset. 623 var arrow = event.left + event.width / 2 - (placed.left + shift); 624 625 arrow = Math.min(Math.max(arrow, ARROW_INSET), width - ARROW_INSET); 626 tooltip.style.setProperty( 627 "--etn-tooltip-arrow-left", 628 Math.round(arrow) + 3 + "px" 629 ); 630 } 631 ); 632 633 $(document).on( 634 "mouseleave", 635 ".events_calendar_standard .has-event, .events_calendar_list .has-event", 636 lower 637 ); 638}
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.