1(function ($, settings) { 2 $.fn.homelocalAvailabilityCalendar = function (options) { 3 const app = this; 4 const $container = $(app); 5 6 const selector = { 7 main: ".homelocal-availability-calendar", 8 calendarModal: "#homelocal-availability-calendar-modal", 9 dayCellSelector: ".homelocal-availability-calendar .week .day-of-month", 10 }; 11 12 let calendarSlider = null; 13 let cellHeight = null; 14 let activeMonthIndex = 0; 15 let calendarCells = {}; 16 // let initialized = false; 17 let currentDates = { 18 checkin: false, 19 checkout: false, 20 }; 21 22 options = $.extend({}, options); 23 options.max_num_days = parseInt(options.max_num_days, 10) || 365; 24 options.max_iterations = options.max_num_days + 62; 25 options.base_price = parseFloat(options.base_price) || 0.0; 26 settings.rate_multiplier = parseFloat(settings.rate_multiplier) || 1.0; 27 settings.start_of_week = parseInt(settings.start_of_week, 10) || 0; 28 29 const calendarUtils = new HomeLocalCalendarUtils( 30 homerunner_calendar_utils_settings, 31 { 32 min_stay: options.min_stay, 33 max_stay: options.max_stay, 34 max_num_days: options.max_num_days, 35 timezone: options.timezone, 36 }, 37 ); 38 39 const dayInRange = (day, minDay, maxDay) => { 40 return ( 41 new Date(day) >= new Date(minDay) && new Date(day) <= new Date(maxDay) 42 ); 43 }; 44 45 const debounce = (func, wait) => { 46 let timeout; 47 return function (...args) { 48 const context = this; 49 clearTimeout(timeout); 50 timeout = setTimeout(() => func.apply(context, args), wait); 51 }; 52 }; 53 54 const isModal = 55 options.type && options.type === "modal" && options.modal_selector; 56 57 app.getVisibleDates = () => { 58 const minDate = calendarUtils.minDate; 59 const maxDate = calendarUtils.maxDate; 60 61 let startDate = minDate 62 .plus({ month: activeMonthIndex - 1 }) 63 .startOf("month"); 64 65 let endDate = minDate 66 .plus({ month: activeMonthIndex + 2 }) 67 .endOf("month"); 68 69 if (endDate > maxDate.endOf("month")) { 70 endDate = maxDate.endOf("month"); 71 72 startDate = endDate.plus({ month: -3 }).startOf("month"); 73 } 74 75 if (startDate < minDate.startOf("month")) { 76 startDate = minDate.startOf("month"); 77 } 78 79 return { 80 start: startDate, 81 end: endDate, 82 }; 83 }; 84 85 app.updateCellSize = () => { 86 if ($container.find(".calendar:visible .calendar-table").length > 0) { 87 const width = $container 88 .find(".calendar:visible .calendar-table") 89 .width(); 90 cellHeight = width / 7; 91 $container.find(".calendar .calendar-table").each(function (i, table) { 92 $(table) 93 .find(".day") 94 .css({ 95 height: cellHeight + "px", 96 lineHeight: cellHeight + "px", 97 }); 98 }); 99 } 100 }; 101 102 app.isBelowMax = ({ year, month }) => { 103 return ( 104 year * 12 + month < 105 calendarUtils.maxDate.getFullYear() * 12 + 106 calendarUtils.maxDate.getMonth() + 107 1 108 ); 109 }; 110 111 app.isAboveMin = ({ year, month }) => { 112 return ( 113 year * 12 + month > 114 calendarUtils.minDate.getFullYear() * 12 + 115 calendarUtils.minDate.getMonth() - 116 1 117 ); 118 }; 119 120 app.addCalendar = ({ year, month }, show = false) => { 121 if ( 122 $container.find( 123 '.calendar[data-y="' + year + '"][data-m="' + month + '"]', 124 ).length < 1 125 ) { 126 const $el = $("<div/>", { 127 class: "calendar", 128 "data-y": year, 129 "data-m": month, 130 }).html(app.getCalendarHtml({ year, month })); 131 132 $el.appendTo($container.find(".calendars")); 133 134 setTimeout(app.updateCellSize, 1); 135 136 if (!show) { 137 $container 138 .find('.calendar[data-y="' + year + '"][data-m="' + month + '"]') 139 .hide(); 140 } 141 } 142 }; 143 144 app.monthLabel = (month) => { 145 return settings.months_label[month]; 146 }; 147 148 app.getCalendarHtml = ({ year, month }) => { 149 const zeroise = (input) => { 150 return input < 10 ? "0" + input : input; 151 }; 152 153 let html = 154 '<div class="calendar-heading">' + 155 app.monthLabel(month - 1) + 156 " " + 157 year + 158 "</div>"; 159 160 // Calendar content. Disable datepicker day name / number translation. 161 html += '<div class="calendar-table">'; 162 163 // Week days. 164 html += '<div class="week-days">'; 165 for (var i = 0; i <= 6; i++) { 166 const weekIndex = (i + settings.start_of_week) % 7; 167 html += '<div class="day-name">'; 168 html += settings.weekdays_label[weekIndex]; 169 html += "</div>"; 170 } 171 html += "</div>"; 172 173 var w = 0; // week day 174 var c = 1; // current date 175 176 let dayOfWeek = calendarUtils.getFirstWeekday(year, month); 177 178 if (settings.start_of_week) { 179 dayOfWeek = dayOfWeek - settings.start_of_week; 180 if (dayOfWeek < 0) { 181 dayOfWeek = 7 + dayOfWeek; 182 } 183 } 184 185 dayOfWeek = dayOfWeek % 7; 186 187 const daysInMonth = calendarUtils.getMonthDayCount(year, month); 188 189 // dates loop 190 for (var i = 0; i < 6 * 7; i++) { 191 if (w == 0) { 192 // first week's day 193 html += '<div class="week">'; 194 } 195 196 if (i < dayOfWeek) { 197 // previous month's day compared to active month. 198 html += '<div class="day day-placeholder 2"></div>'; 199 } else if (c > daysInMonth) { 200 // next month's day compared to active month. 201 html += '<div class="day day-placeholder 3"></div>'; 202 } else { 203 const date = `${year}-${zeroise(month)}-${zeroise(c)}`; 204 205 let style = ""; 206 if (cellHeight) { 207 style = ` height: ${cellHeight}px; line-height: ${cellHeight}px;`; 208 } 209 210 html += `<div class="day day-of-month" data-date="${date}" style="${style}"> 211 <span class="date">${c}</span> 212 <span class="note price"><span> 213 </div>`; 214 215 c++; 216 } 217 218 if (w == 7 - 1) { 219 // last week's day 220 html += "</div>"; 221 w = 0; 222 } else { 223 w++; 224 } 225 } 226 227 html += "</div>"; 228 return html; 229 }; 230 231 app.safelyProcessVisibleRange = (processFunction) => { 232 const visibleDates = app.getVisibleDates(); 233 234 const firstDate = visibleDates.start.toFormat("yyyy-MM-dd"); 235 const lastDate = visibleDates.end.toFormat("yyyy-MM-dd"); 236 237 app.safelyProcessDateRange(firstDate, lastDate, processFunction); 238 }; 239 240 app.safelyProcessDateRange = ( 241 firstDateStr, 242 lastDateStr, 243 processFunction, 244 ) => { 245 let loopCounter = 0; 246 let currDate = calendarUtils.getDateZero(firstDateStr); 247 let lastDate = calendarUtils.getDateZero(lastDateStr); 248 249 while (currDate <= lastDate && loopCounter < options.max_iterations) {
250 processFunction(currDate.toFormat("yyyy-MM-dd")); 251 252 loopCounter++; 253 currDate = currDate.plus({ day: 1 }); 254 } 255 256 if (loopCounter > options.max_iterations) { 257 console.warn( 258 "Calendar date range processing reached safety limit", 259 loopCounter, 260 ); 261 } 262 }; 263 264 app.updateCalendarsPricing = () => { 265 app.safelyProcessVisibleRange((date) => { 266 if (calendarCells[date]) { 267 // console.log(`Skipping date ${date} as it is already processed.`); 268 return; 269 } 270 271 let classnames = ["day", "day-of-month"]; 272 let showPrice = true; 273 const opts = calendarUtils.dateOptions(date); 274 275 if (calendarUtils.isToday(date)) { 276 classnames.push("today"); 277 } 278 279 if (calendarUtils.isUnavailable(date)) { 280 showPrice = false; 281 classnames.push("unavailable"); 282 } else if (calendarUtils.isTraped(date)) { 283 if (calendarUtils._checkinBooked(date)) { 284 classnames.push("checkin-booked"); 285 } else { 286 classnames.push("checkin-blocked"); 287 } 288 289 if (calendarUtils._checkoutBooked(date)) { 290 classnames.push("checkout-booked"); 291 } else { 292 classnames.push("checkout-blocked"); 293 } 294 295 classnames.push("trapped"); 296 } else { 297 if (calendarUtils._checkinBooked(date)) { 298 classnames.push("checkin-booked"); 299 } else if (!calendarUtils._canCheckin(date)) { 300 classnames.push("checkin-blocked"); 301 } else { 302 classnames.push("checkin-available"); 303 } 304 305 if (calendarUtils._checkoutBooked(date)) { 306 classnames.push("checkout-booked"); 307 } else if (!calendarUtils._canCheckout(date)) { 308 classnames.push("checkout-blocked"); 309 } else { 310 classnames.push("checkout-available"); 311 } 312 313 if ( 314 !calendarUtils._canCheckin(date) && 315 !calendarUtils._canCheckout(date) && 316 calendarUtils._canStay(date) 317 ) { 318 classnames.push("pass-through"); 319 } 320 } 321 322 // unique class names 323 classnames = classnames.filter( 324 (value, index, self) => self.indexOf(value) === index, 325 ); 326 327 let text = ""; 328 329 if (options.show_price === "1" && showPrice) { 330 if (opts && opts.price) { 331 text = $.homelocalPrice( 332 Math.round(opts.price * settings.rate_multiplier), 333 options.currency, 334 ); 335 } else { 336 text = $.homelocalPrice( 337 Math.round(options.base_price), 338 options.currency, 339 ); 340 } 341 } 342 343 calendarCells[date] = { 344 classnames: classnames, 345 text: text, 346 }; 347 348 const $cell = $container.find( 349 selector.dayCellSelector + '[data-date="' + date + '"]', 350 ); 351 $cell.attr("class", classnames.join(" ")); 352 $cell.find(".price").text(text); 353 }); 354 }; 355 356 app.updateCheckinState = () => { 357 const checkin = currentDates.checkin; 358 const selectableRange = calendarUtils.getSelectableRange(checkin); 359 360 $container.addClass("checkin-selected").removeClass("checkout-selected"); 361 362 app.safelyProcessVisibleRange((dateStr) => { 363 const $cell = $container.find( 364 selector.dayCellSelector + '[data-date="' + dateStr + '"]', 365 ); 366 367 let classnames = $cell.attr("class") 368 ? $cell.attr("class").split(" ") 369 : []; 370 371 // List of classnames to remove 372 const removeClasses = [ 373 "hover", 374 "selected", 375 "checkin", 376 "checkout", 377 "selectable-first", 378 "selectable-last", 379 ]; 380 381 // Filter out the unwanted classnames 382 classnames = classnames.filter((cls) => !removeClasses.includes(cls)); 383 384 if (dateStr === checkin) { 385 classnames.push("selected checkin"); 386 } else if ( 387 !selectableRange || 388 new Date(dateStr) < new Date(selectableRange.minDate) || 389 new Date(dateStr) > new Date(selectableRange.maxDate) 390 ) { 391 classnames.push("disabled"); 392 } else { 393 if (dateStr === selectableRange.maxDate) { 394 classnames.push("selectable selectable-last"); 395 } else if (dateStr === selectableRange.minDate) { 396 classnames.push("selectable selectable-first"); 397 } else if (calendarUtils.canCheckinout(checkin, dateStr).status) { 398 classnames.push("selectable"); 399 } else if (calendarUtils._canStay(dateStr)) { 400 classnames.push("selectable pass-through-temp"); 401 } 402 } 403 404 // unique class names 405 classnames = classnames.filter( 406 (value, index, self) => self.indexOf(value) === index, 407 ); 408 409 $cell.attr("class", classnames.join(" ")); 410 }); 411 }; 412 413 app.updateCheckinCheckoutState = () => { 414 const { checkin, checkout } = currentDates; 415 416 if (!checkin || !checkout) { 417 return; 418 } 419 420 $container.addClass("checkout-selected").removeClass("checkin-selected"); 421 422 const dayCellSelector = selector.main + " .week .day-of-month"; 423 const $dayCells = $container.find(dayCellSelector); 424 425 $dayCells.removeClass( 426 "disabled selectable selectable-first checkout hover checkin checkout selectable-last", 427 ); 428 429 app.safelyProcessDateRange(checkin, checkout, (dateStr) => { 430 if (dayInRange(dateStr, checkin, checkout)) { 431 const $cell = $container.find( 432 selector.dayCellSelector + '[data-date="' + dateStr + '"]', 433 ); 434 435 $cell.addClass("selected").removeClass("disabled pass-through-temp"); 436 437 if (checkin === dateStr) { 438 $cell.addClass("checkin"); 439 } else if (checkout === dateStr) { 440 $cell.addClass("checkout"); 441 } 442 } 443 }); 444 }; 445 446 let $widget = null; 447 448 app.maybeInitPicker = () => { 449 $widget = $(`.homelocal-booking-widget`); 450 if ($widget.length > 0) { 451 $container.addClass("calendar-picker"); 452 app.pickerInit(); 453 } 454 }; 455 456 app.pickerInit = () => { 457 const dayCellSelector = selector.main + " .week .day-of-month"; 458 const $dayCells = $container.find(dayCellSelector); 459 let isLoading = false;
460 461 $widget.on("updating", () => { 462 isLoading = true; 463 }); 464 $widget.on("available", () => { 465 isLoading = false; 466 }); 467 $widget.on("unavailable", () => { 468 isLoading = false; 469 }); 470 471 if ($("#homelocal-calendar-tip").length < 1) { 472 $("body") 473 .append(`<div id="homelocal-calendar-tip" class="homelocal-tip"> 474 <div class="tip-content"></div> 475 <div class="tip-arrow"></div> 476 </div>`); 477 } 478 479 let tipTimeout = null; 480 481 const showTip = ($day, tip) => { 482 $("#homelocal-calendar-tip .tip-content").html(tip); 483 484 // calculate placement based on element position in window 485 const placement = 486 $day.offset().top + $day.height() / 2 - $(window).scrollTop() < 487 $(window).height() / 3 488 ? "bottom" 489 : "top"; 490 491 $("#homelocal-calendar-tip").attr("data-placement", placement); 492 493 if (placement === "top" || placement === "bottom") { 494 $("#homelocal-calendar-tip").css({ 495 left: 496 $day.offset().left + 497 $day.width() / 2 - 498 $("#homelocal-calendar-tip").width() / 2, 499 }); 500 501 if (placement === "top") { 502 $("#homelocal-calendar-tip").css({ 503 top: 504 $day.offset().top - $("#homelocal-calendar-tip").height() - 10, 505 }); 506 } else if (placement === "bottom") { 507 $("#homelocal-calendar-tip").css({ 508 top: $day.offset().top + $day.height() + 10, 509 }); 510 } 511 } 512 513 $("#homelocal-calendar-tip").show(); 514 515 clearTimeout(tipTimeout); 516 tipTimeout = setTimeout(() => { 517 removeTip(); 518 }, 5000); 519 }; 520 521 const removeTip = () => { 522 $("#homelocal-calendar-tip").hide(); 523 clearTimeout(tipTimeout); 524 }; 525 526 const maybeHideTip = (e) => { 527 removeTip(); 528 }; 529 530 $(document).on("click", maybeHideTip); 531 532 const isSelectStart = () => 533 (currentDates.checkin && currentDates.checkout) || 534 (!currentDates.checkin && !currentDates.checkout); 535 536 // booking widget update should only happen once for a single property 537 // regardless of how many calendars are on the page. 538 window["homelocalCalendarWidgetUpdateTimer" + options.property_id] = null; 539 540 const setCheckinDay = (e, day, reset = false) => { 541 removeTip(); 542 543 if (day !== currentDates.checkin) { 544 currentDates.checkin = day; 545 546 if (reset) { 547 $(document).trigger("homelocal_booking_widget_reset"); 548 } 549 } 550 551 currentDates.checkout = false; 552 553 app.updateCheckinState(); 554 555 $(document).on("mouseover", dayCellSelector, onDayHover); 556 }; 557 558 const setCheckoutDay = (e, day) => { 559 removeTip(); 560 currentDates.checkout = day; 561 562 app.updateCheckinCheckoutState(); 563 564 $(document).off("mouseover", dayCellSelector, onDayHover); 565 566 if (currentDates.checkin && currentDates.checkout) { 567 $widget.find('input[name="checkin"]').val(currentDates.checkin); 568 $widget 569 .find('input[name="checkout"]') 570 .val(currentDates.checkout) 571 .trigger("change"); 572 } 573 }; 574 575 const dayClicked = (e) => { 576 const $day = $(e.currentTarget); 577 const dataDate = $day.attr("data-date"); 578 579 if (isLoading) { 580 showTip($day, settings.selection.availability_loading); 581 } else if (calendarUtils.isUnavailable(dataDate)) { 582 showTip($day, settings.selection.unavailable); 583 } else if (isSelectStart()) { 584 const checkinResult = calendarUtils.canCheckin(dataDate); 585 586 if ( 587 calendarUtils._checkinBooked(dataDate) && 588 !calendarUtils._canCheckin(dataDate) 589 ) { 590 showTip($day, settings.selection.booked); 591 } else if (checkinResult.status) { 592 $(document).trigger( 593 "homelocal_availability_calendar_set_checkin_" + 594 options.property_id, 595 [dataDate, true], 596 ); 597 } else { 598 showTip($day, checkinResult.message); 599 } 600 } else {
601 const checkoutResult = calendarUtils.canCheckinout( 602 currentDates.checkin, 603 dataDate, 604 ); 605 606 if ( 607 calendarUtils._checkoutBooked(dataDate) && 608 !calendarUtils._canCheckout(dataDate) 609 ) { 610 showTip($day, settings.selection.booked); 611 } else if (checkoutResult.status) { 612 $(document).trigger( 613 "homelocal_availability_calendar_set_checkout_" + 614 options.property_id, 615 [dataDate], 616 ); 617 } else { 618 showTip($day, checkoutResult.message); 619 } 620 } 621 622 return false; 623 }; 624 625 const onDayHover = (e) => { 626 const $day = $(e.currentTarget); 627 const hoverDate = $day.attr("data-date"); 628 const minmaxDate = calendarUtils.getSelectableRange( 629 currentDates.checkin, 630 ); 631 632 if (dayInRange(hoverDate, minmaxDate.minDate, minmaxDate.maxDate)) { 633 $dayCells.removeClass("checkout"); 634 635 $dayCells.each(function () { 636 const dataDate = $(this).attr("data-date"); 637 if ( 638 dayInRange(dataDate, currentDates.checkin, hoverDate) && 639 dayInRange(dataDate, minmaxDate.minDate, minmaxDate.maxDate) && 640 dataDate !== currentDates.checkin 641 ) { 642 $(this).addClass("hover"); 643 } else if ( 644 dayInRange( 645 dataDate, 646 currentDates.checkin, 647 calendarUtils.prevDay(minmaxDate.minDate), 648 ) && 649 dataDate !== currentDates.checkin 650 ) { 651 $(this).addClass("hover"); 652 } else { 653 $(this).removeClass("hover"); 654 } 655 }); 656 $container 657 .find(selector.dayCellSelector + ".hover:last") 658 .addClass("checkout"); 659 } 660 }; 661 662 const moveCalendar = () => { 663 const d = new Date(currentDates.checkin), 664 year = d.getFullYear(), 665 month = d.getMonth() + 1; 666 667 if ( 668 !$container 669 .find('.calendar[data-y="' + year + '"][data-m="' + month + '"]') 670 .hasClass("tns-slide-active") 671 ) { 672 const index = $container 673 .find('.calendar[data-y="' + year + '"][data-m="' + month + '"]') 674 .index(); 675 676 if (calendarSlider) { 677 calendarSlider.goTo(index); 678 } 679 } 680 }; 681 682 const resetSelection = () => { 683 removeTip(); 684 currentDates.checkin = false; 685 currentDates.checkout = false; 686 $container.removeClass("checkin-selected checkout-selected"); 687 $dayCells.removeClass( 688 "disabled selected selectable selectable-first checkout hover checkin checkout selectable-last pass-through-temp", 689 ); 690 }; 691 692 const widgetOnChange = (e) => { 693 currentDates.checkin = $widget 694 .find('input[name="checkin"]') 695 .val() 696 .valueOf(); 697 currentDates.checkout = $widget 698 .find('input[name="checkout"]') 699 .val() 700 .valueOf(); 701 if (currentDates.checkin && currentDates.checkout) { 702 moveCalendar(); 703 app.updateCheckinCheckoutState(); 704 } 705 }; 706 707 $container.on("click", dayCellSelector, dayClicked); 708 709 $widget.on("change", 'input[name="checkin"]', () => { 710 currentDates.checkin = $widget 711 .find('input[name="checkin"]') 712 .val() 713 .valueOf(); 714 if (currentDates.checkin && currentDates.checkout) { 715 moveCalendar(); 716 app.updateCheckinCheckoutState(); 717 } else { 718 $(document).trigger( 719 "homelocal_availability_calendar_set_checkin_" + 720 options.property_id, 721 [currentDates.checkin, false], 722 ); 723 } 724 }); 725 726 $widget.on("change", 'input[name="checkout"]', () => { 727 currentDates.checkout = $widget 728 .find('input[name="checkout"]') 729 .val() 730 .valueOf(); 731 732 if (currentDates.checkin && currentDates.checkout) {
733 moveCalendar(); 734 app.updateCheckinCheckoutState(); 735 736 $(document).off("mouseover", dayCellSelector, onDayHover); 737 } 738 }); 739 740 if ( 741 $widget.find('input[name="checkin"]').val() && 742 $widget.find('input[name="checkout"]').val() 743 ) { 744 widgetOnChange(); 745 } 746 747 // Disable dates without pricing information. 748 $dayCells.each(function () { 749 const dataDate = $(this).attr("data-date"); 750 if (!calendarUtils.dateOptions(dataDate)) { 751 $(this).addClass("unavailable"); 752 } 753 }); 754 755 $(document).on("show", selector.calendarModal, () => { 756 app.updateCellSize(); 757 removeTip(); 758 }); 759 760 // Third-party event listener. 761 $(document).on( 762 "homelocal_availability_calendar_reset_selection", 763 resetSelection, 764 ); 765 766 // New id based event listener. 767 $(document).on( 768 "homelocal_availability_calendar_reset_selection_" + 769 options.property_id, 770 resetSelection, 771 ); 772 $(document).on( 773 "homelocal_availability_calendar_set_checkin_" + options.property_id, 774 setCheckinDay, 775 ); 776 $(document).on( 777 "homelocal_availability_calendar_set_checkout_" + options.property_id, 778 setCheckoutDay, 779 ); 780 }; 781 782 app.renderCalendars = () => { 783 const startMonth = calendarUtils.minDate.startOf("month"); 784 const endMonth = calendarUtils.maxDate.startOf("month"); 785 786 const monthDiffCeiled = Math.ceil( 787 endMonth.diff(startMonth, "months").months, 788 ); 789 790 // Ensure at least 2 months are rendered. 791 const monthsToRender = Math.max(monthDiffCeiled + 1, 2); 792 793 for (let i = 0; i < monthsToRender; i++) { 794 const currentMonth = startMonth.plus({ months: i }); 795 app.addCalendar( 796 { 797 year: currentMonth.year, 798 month: currentMonth.month, 799 }, 800 true, 801 ); 802 } 803 }; 804 805 app.getLoadingSvg = () => { 806 return `<svg 807 role="img" 808 width="325" 809 height="396" 810 aria-labelledby="loading-aria" 811 viewBox="0 0 325 396" 812 preserveAspectRatio="none" 813> 814 <rect 815 x="0" 816 y="0" 817 width="100%" 818 height="100%" 819 clip-path="url(#clip-path)" 820 style='fill: url("#fill");' 821 ></rect> 822 <defs> 823 <clipPath id="clip-path"> 824 <rect x="235" y="80" rx="2" ry="2" width="45" height="45" /> 825 <rect x="62" y="53" rx="3" ry="3" width="13" height="12" /> 826 <rect x="107" y="53" rx="3" ry="3" width="13" height="12" /> 827 <rect x="154" y="53" rx="3" ry="3" width="13" height="12" /> 828 <rect x="204" y="53" rx="3" ry="3" width="13" height="12" /> 829 <rect x="247" y="53" rx="3" ry="3" width="13" height="12" /> 830 <rect x="294" y="53" rx="3" ry="3" width="13" height="12" /> 831 <rect x="282" y="80" rx="2" ry="2" width="45" height="45" /> 832 <rect x="282" y="127" rx="2" ry="2" width="45" height="45" /> 833 <rect x="282" y="174" rx="2" ry="2" width="45" height="45" /> 834 <rect x="282" y="221" rx="2" ry="2" width="45" height="45" /> 835 <rect x="282" y="268" rx="2" ry="2" width="45" height="45" /> 836 <rect x="235" y="127" rx="2" ry="2" width="45" height="45" /> 837 <rect x="188" y="127" rx="2" ry="2" width="45" height="45" /> 838 <rect x="141" y="127" rx="2" ry="2" width="45" height="45" /> 839 <rect x="94" y="127" rx="2" ry="2" width="45" height="45" /> 840 <rect x="47" y="127" rx="2" ry="2" width="45" height="45" /> 841 <rect x="0" y="127" rx="2" ry="2" width="45" height="45" /> 842 <rect x="0" y="174" rx="2" ry="2" width="45" height="45" /> 843 <rect x="0" y="221" rx="2" ry="2" width="45" height="45" /> 844 <rect x="0" y="268" rx="2" ry="2" width="45" height="45" /> 845 <rect x="0" y="315" rx="2" ry="2" width="45" height="45" /> 846 <rect x="47" y="268" rx="2" ry="2" width="45" height="45" /> 847 <rect x="94" y="268" rx="2" ry="2" width="45" height="45" /> 848 <rect x="141" y="268" rx="2" ry="2" width="45" height="45" /> 849 <rect x="235" y="268" rx="2" ry="2" width="45" height="45" /> 850 <rect x="235" y="221" rx="2" ry="2" width="45" height="45" /> 851 <rect x="188" y="221" rx="2" ry="2" width="45" height="45" /> 852 <rect x="141" y="221" rx="2" ry="2" width="45" height="45" /> 853 <rect x="94" y="221" rx="2" ry="2" width="45" height="45" /> 854 <rect x="47" y="221" rx="2" ry="2" width="45" height="45" /> 855 <rect x="47" y="174" rx="2" ry="2" width="45" height="45" /> 856 <rect x="94" y="174" rx="2" ry="2" width="45" height="45" /> 857 <rect x="141" y="174" rx="2" ry="2" width="45" height="45" /> 858 <rect x="188" y="174" rx="2" ry="2" width="45" height="45" /> 859 <rect x="235" y="174" rx="2" ry="2" width="45" height="45" /> 860 <rect x="188" y="268" rx="2" ry="2" width="45" height="45" /> 861 <rect x="100" y="10" rx="2" ry="2" width="125" height="22" /> 862 <rect x="16" y="52" rx="3" ry="3" width="13" height="12" /> 863 </clipPath> 864 <linearGradient id="fill"> 865 <stop 866 offset="0.599964" 867 stop-color="#f3f3f3" 868 stop-opacity="1" 869 > 870 <animate 871 attributeName="offset" 872 values="-2; -2; 1" 873 keyTimes="0; 0.25; 1" 874 dur="2s"
875 repeatCount="indefinite" 876 ></animate> 877 </stop> 878 <stop 879 offset="1.59996" 880 stop-color="#ecebeb" 881 stop-opacity="1" 882 > 883 <animate 884 attributeName="offset" 885 values="-1; -1; 2" 886 keyTimes="0; 0.25; 1" 887 dur="2s" 888 repeatCount="indefinite" 889 ></animate> 890 </stop> 891 <stop 892 offset="2.59996" 893 stop-color="#f3f3f3" 894 stop-opacity="1" 895 > 896 <animate 897 attributeName="offset" 898 values="0; 0; 3" 899 keyTimes="0; 0.25; 1" 900 dur="2s" 901 repeatCount="indefinite" 902 ></animate> 903 </stop> 904 </linearGradient> 905 </defs> 906</svg>`; 907 }; 908 909 app.loadPrice = (done = null) => { 910 $(document).trigger("homelocal_availability_calendar_loading", [ 911 options.property_id, 912 ]); 913 914 $container.addClass("hfse-loading"); 915 916 $.ajax({ 917 url: `${settings.wp_api}wp/v2/properties/${options.property_id}/calendar`, 918 type: "GET", 919 data: { 920 _: new Date().getTime(), 921 }, 922 beforeSend: function (xhr) { 923 if (settings.user_nonce) { 924 xhr.setRequestHeader("X-Homerunner-Nonce", settings.user_nonce); 925 } 926 }, 927 }) 928 .done((data) => { 929 if (typeof done === "function") { 930 done(data); 931 } 932 933 $(document).trigger("homelocal_availability_calendar_loaded", [ 934 options.property_id, 935 data, 936 ]); 937 }) 938 .always(() => { 939 $container.removeClass("hfse-loading"); 940 }); 941 }; 942 943 app.init = () => { 944 $container.append( 945 ` 946 ${app.getDiscountContent()} 947 <div class="local-time" style="display:none;"></div> 948 <div class="homelocal-availability-calendar-loader"> 949 <div class="calendar"> 950 ${app.getLoadingSvg()} 951 </div> 952 <div class="calendar"> 953 ${app.getLoadingSvg()} 954 </div> 955 </div> 956 <div class="notranslate homelocal-availability-calendar" style="display:none;"> 957 <div class="calendars"></div> 958 <button class="next-btn" type="button">${settings.next_icon_svg}</button> 959 <button class="prev-btn" type="button">${settings.prev_icon_svg}</button> 960 </div> 961 `, 962 ); 963 964 app.initCalendar(); 965 966 if (options.show_local_time === "1") { 967 app.initLocalTime(); 968 } 969 970 if (isModal) { 971 $(document).on("beforeshow", options.modal_selector, function () { 972 // if (!initialized) { 973 // app.initCalendar(); 974 // } 975 976 app.updateCellSize(); 977 }); 978 979 $(`${options.modal_selector} .hk-modal-body`).on( 980 "scroll", 981 debounce(app.onCalendarModalScroll, 50), 982 ); 983 } else { 984 // app.initCalendar(); 985 } 986 }; 987 988 app.getDiscountContent = () => { 989 if (options.show_discount_text === "1" && options.discount_text) { 990 return `<div class="discount-text-container"> 991 <span class="discount-icon"> 992 <svg width="30" height="30" viewBox="0 0 700 700" xmlns="http://www.w3.org/2000/svg"> 993 <g> 994 <path 995 d="m557.9 215.6c-9.7969-9.8008-15.398-23.277-15.398-37.102v-25.375c0-36.227-29.398-65.625-65.625-65.625h-25.375c-13.824 0-27.301-5.6016-37.102-15.398l-18.023-17.852c-25.551-25.551-67.199-25.551-92.75 0l-18.023 18.023c-9.8008 9.625-23.277 15.227-37.102 15.227h-25.375c-36.227 0-65.625 29.398-65.625 65.625v25.375c0 13.824-5.6016 27.301-15.398 37.102l-17.852 18.023c-25.551 25.551-25.551 67.199 0 92.75l18.023 18.023c9.625 9.8008 15.227 23.277 15.227 37.102v25.375c0 36.227 29.398 65.625 65.625 65.625h25.375c13.824 0 27.301 5.6016 37.102 15.398l18.023 18.023c12.773 12.777 29.574 19.078 46.375 19.078s33.602-6.4766 46.375-19.25l18.023-18.023c9.8008-9.625 23.277-15.227 37.102-15.227h25.375c36.227 0 65.625-29.398 65.625-65.625v-25.375c0-13.824 5.6016-27.301 15.398-37.102l18.023-18.023c25.551-25.551 25.551-67.199 0-92.75zm-6.8242 86.098-18.023 18.023c-16.449 16.453-25.551 38.328-25.551 61.777v25.375c0 16.977-13.824 30.625-30.625 30.625h-25.375c-23.449 0-45.324 9.1016-61.949 25.551l-18.023 18.023c-11.898 11.898-31.324 11.898-43.398 0l-18.023-18.023c-16.281-16.449-38.156-25.551-61.605-25.551h-25.375c-16.801 0-30.625-13.824-30.625-30.625v-25.375c0-23.273-9.1016-45.324-25.551-61.949l-18.023-18.023c-11.898-11.898-11.898-31.324 0-43.398l18.023-18.023c16.449-16.281 25.551-38.156 25.551-61.605v-25.375c0-16.801 13.824-30.625 30.625-30.625h25.375c23.449 0 45.324-9.1016 61.949-25.551l18.023-18.023c11.898-11.898 31.324-11.898 43.398 0l18.023 18.023c16.281 16.449 38.156 25.551 61.605 25.551h25.375c16.801 0 30.625 13.824 30.625 30.625v25.375c0 23.273 9.1016 45.324 25.551 61.949l18.023 18.023c11.902 11.727 11.902 31.328 0 43.227z" /> 996 <path 997 d="m425.07 180.07-175 175c-6.8242 6.8242-6.8242 17.852 0 24.676 3.5 3.5 7.875 5.25 12.426 5.25s8.9258-1.75 12.426-5.0742l175-175c6.8242-6.8242 6.8242-17.852 0-24.676s-18.027-7-24.852-0.17578z" /> 998 <path d="m455 350c0 19.328-15.672 35-35 35s-35-15.672-35-35 15.672-35 35-35 35 15.672 35 35" /> 999 <path d="m315 210c0 19.328-15.672 35-35 35s-35-15.672-35-35 15.672-35 35-35 35 15.672 35 35" /> 1000 </g> 1001 </svg> 1002 </span> 1003 <span class="discount-text"> 1004 ${options.discount_text} 1005 </span> 1006 </div>`; 1007 } 1008 1009 return ""; 1010 }; 1011 1012 app.initLocalTime = () => { 1013 app.updateLocalTime(); 1014 1015 $container.find(".local-time").show(); 1016 1017 setInterval(app.updateLocalTime, 60000); 1018 }; 1019 1020 app.updateLocalTime = () => { 1021 const localDate = calendarUtils.getDateNow(); 1022 $container.find(".local-time").html( 1023 `<span class="local-time-label">${settings.local_time_label}</span> 1024 <span class="local-time-datetime">
1024${localDate.toFormat( 1025 "dd LLL, hh:mm a", 1026 )}</span>`, 1027 ); 1028 }; 1029 1030 app.initCalendar = () => { 1031 initialized = true; 1032 1033 app.renderCalendars(); 1034 app.updateCellSize(); 1035 app.calendarSliderInit(); 1036 1037 $(window).on("resize", app.updateCellSize); 1038 1039 app.loadPrice((data) => { 1040 calendarUtils.setPriceData(data); 1041 app.updateCalendarsPricing(); 1042 app.updateCheckinCheckoutState(); 1043 1044 $container.find(".homelocal-availability-calendar-loader").hide(); 1045 $container.find(".homelocal-availability-calendar").show(); 1046 1047 app.updateCellSize(); 1048 app.maybeInitPicker(); 1049 }); 1050 }; 1051 1052 app.onCalendarModalScroll = () => { 1053 const monthHeight = cellHeight * 7; // 7 rows per month 1054 1055 const scrollTop = $( 1056 `${options.modal_selector} .hk-modal-body`, 1057 ).scrollTop(); 1058 1059 activeMonthIndex = Math.floor(scrollTop / monthHeight); 1060 app.updateCalendarsPricing(); 1061 app.updateCheckinCheckoutState(); 1062 }; 1063 1064 app.calendarSliderInit = () => { 1065 if (options.type && options.type === "modal") { 1066 const viewportWidth = $(window).width(); 1067 if (viewportWidth < 768) { 1068 if (calendarSlider) { 1069 calendarSlider.destroy(); 1070 calendarSlider = null; 1071 } 1072 return; 1073 } 1074 } 1075 1076 if (calendarSlider) { 1077 return; 1078 } 1079 1080 calendarSlider = tns({ 1081 container: `#${options.app_id} .calendars`, 1082 items: 1, 1083 gutter: 0, 1084 responsive: { 1085 768: { 1086 items: 2, 1087 gutter: 30, 1088 }, 1089 }, 1090 slideBy: 1, 1091 speed: 600, 1092 autoplay: false, 1093 mouseDrag: true, 1094 // preventActionWhenRunning: true, 1095 nav: false, 1096 loop: false, 1097 nextButton: `#${options.app_id} .next-btn`, 1098 prevButton: `#${options.app_id} .prev-btn`, 1099 }); 1100 1101 calendarSlider.events.on("transitionEnd", () => { 1102 activeMonthIndex = calendarSlider.getInfo().index; 1103 app.updateCalendarsPricing(); 1104 app.updateCheckinCheckoutState(); 1105 }); 1106 }; 1107 1108 app.init(); 1109 }; 1110})(jQuery, homerunner_availability_calendar_settings);
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.