1/** 2 * Hydrates Recently Viewed Trips via a cacheable, cookie-less request. 3 * Only renders anything if there is at least one trip to show. 4 */ 5(function (Drupal, once) { 6 "use strict"; 7 8 const RVT_TITLE = "Recently Viewed Trips"; 9 const INIT_RETRY_MS = 50; 10 const INIT_RETRY_MAX = 60; 11 12 function getSkeletonCardCount() { 13 return window.matchMedia("(min-width: 960px)").matches ? 3 : 1; 14 } 15 16 function getCookie(name) { 17 const parts = document.cookie.split("; "); 18 for (let i = 0; i < parts.length; i++) { 19 const [k, v] = parts[i].split("="); 20 if (k === name) return v || ""; 21 } 22 return ""; 23 } 24 25 function skeletonTripCardHtml() { 26 return ` 27 <div class="skeleton-trip-card"> 28 <div class="skeleton-trip-card__image"></div> 29 <div class="skeleton-trip-card__info-1"></div> 30 <div class="skeleton-trip-card__info-2"></div> 31 <div class="skeleton-trip-card__info-3"></div> 32 <div class="skeleton-trip-card__bottom-wrapper"> 33 <div class="skeleton-trip-card__price"></div> 34 <div class="skeleton-trip-card__button"></div> 35 </div> 36 </div> 37 `; 38 } 39 40 function renderLoadingShell(el) { 41 const skeletons = Array.from({ length: getSkeletonCardCount() }, skeletonTripCardHtml).join(""); 42 el.innerHTML = ` 43 <section class="recently-viewed-trips" data-rvt-section=""> 44 <hr /> 45 <h2 class="recently-viewed-trips__title">${RVT_TITLE}</h2> 46 <div class="recently-viewed-trips__cards"> 47 <div class="view view-id-recently_viewed_trips view-display-id-trip_cards"> 48 <div class="view__content"> 49 <div class="view--unformatted carousel-loading">${skeletons}</div> 50 </div> 51 </div> 52 </div> 53 <div class="recently-viewed-trips__modal-container"></div> 54 </section> 55 `; 56 } 57 58 function swiperReady() { 59 return typeof window.Swiper === "function"; 60 } 61 62 function carouselInitialized() { 63 return !!document.querySelector( 64 ".recently-viewed-trips__cards .view--unformatted.swiper-initialized" 65 ); 66 } 67 68 function revealCardsWithoutCarousel() { 69 document 70 .querySelectorAll(".recently-viewed-trips .trip-card__card") 71 .forEach((card) => { 72 card.style.opacity = "1"; 73 }); 74 } 75 76 function initRvtCarousel() { 77 const run = () => { 78 if (!Drupal.recently_viewed_trips || !swiperReady()) { 79 return false; 80 } 81 if (carouselInitialized()) { 82 return true; 83 } 84 try { 85 Drupal.recently_viewed_trips.init(); 86 if (Drupal.behaviors.card_list && Drupal.behaviors.card_list.init) { 87 Drupal.behaviors.card_list.init(); 88 } 89 } catch (e) { 90 return false; 91 } 92 return carouselInitialized(); 93 }; 94 95 if (run()) { 96 return; 97 } 98 99 let attempts = 0; 100 const retry = window.setInterval(() => { 101 if (run() || ++attempts >= INIT_RETRY_MAX) { 102 window.clearInterval(retry); 103 // Avoid a permanent blank block if Swiper never attaches. 104 if (!carouselInitialized()) { 105 revealCardsWithoutCarousel(); 106 } 107 } 108 }, INIT_RETRY_MS); 109 } 110 111 function hydrateRvtSlots(context) { 112 const slots = once("bkrRvtWidget", "#rvt-slot", context); 113 slots.forEach((el) => { 114 const endpoint = el.getAttribute("data-rvt-endpoint"); 115 if (!endpoint) return; 116 117 const cookieName = 118 el.getAttribute("data-rvt-cookie") || "recently_viewed_trips"; 119 const rv = getCookie(cookieName) || ""; 120 121 // If no cookie, remove the slot and bail (nothing to render). 122 if (!rv.trim()) { 123 el.remove(); 124 return; 125 } 126 127 renderLoadingShell(el); 128 129 const url = endpoint + "?rv=" + encodeURIComponent(rv); 130 131 fetch(url, { credentials: "same-origin", cache: "no-store" }) 132 .then((res) => (res.ok ? res.text() : "")) 133 .then((html) => { 134 // If empty response, remove the slot and bail. 135 if (!html || !html.trim()) { 136 el.remove(); 137 return; 138 } 139 140 const cardsContainer = el.querySelector(".recently-viewed-trips__cards"); 141 if (!cardsContainer) { 142 el.remove(); 143 return; 144 } 145 146 cardsContainer.innerHTML = html; 147 initRvtCarousel(); 148 }) 149 .catch(() => { 150 // On error, remove the slot to avoid empty chrome. 151 el.remove(); 152 }); 153 }); 154 } 155 156 Drupal.behaviors.bkrRvtWidget = { 157 attach(context) { 158 hydrateRvtSlots(context); 159 }, 160 }; 161 162 // Self-bootstrap: this file is loaded via a bare <script defer> (not a Drupal 163 // library) so it can register after core/footer attachBehaviors has already 164 // run â common after Azure auth return (?check_logged_in=1). once() keeps a 165 // later attach() call from double-hydrating. 166 if (document.querySelector("#rvt-slot")) { 167 hydrateRvtSlots(document); 168 } 169})(Drupal, once);
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.