1import { determineProto } from './protoDeterminer.js'; 2var proto = determineProto(); 3 4const blogManager = { 5 config: { 6 postsPerPage: 12, 7 blogPostsEndpoint: `https://rails-admin.spillover.com/esites-api/v1/${proto}/site_posts?number_of_posts=1000`, 8 fetchedPostsLog: "Fetched blog posts:", 9 errorLoadingLog: "Error loading blog posts:", 10 postNotFoundLog: "Post not found.", 11 urlCopiedLog: "URL copied to clipboard:", 12 blogLinkNotification: "Blog Link Copied to Clipboard!", 13 shareHover: "Share blog to Facebook", 14 copyHover: "Copy blog link to clipboard", 15 }, 16 blogPosts: [], 17 selectedTags: [], 18 19 async fetchBlogPosts() { 20 try { 21 const response = await fetch(this.config.blogPostsEndpoint); 22 if (!response.ok) throw new Error("Network response was not ok"); 23 this.blogPosts = await response.json(); 24 console.log(this.config.fetchedPostsLog, this.blogPosts); 25 } catch (error) { 26 console.error(this.config.errorLoadingLog, error); 27 } 28 }, 29 30 getAllTags() { 31 const allTags = this.blogPosts.flatMap((post) => post.tags); // using this 32 return [...new Set(allTags)]; 33 }, 34 35 getTagsFromURL() { 36 const urlParams = new URLSearchParams(window.location.search); 37 const tagsParam = urlParams.get("tags"); 38 return tagsParam ? tagsParam.split(",") : []; 39 }, 40 41 updateCheckboxesBasedOnURLTags(tags) {
42 tags.forEach((tag) => { 43 const checkboxId = `tag${tag}Checkbox`; 44 const checkbox = document.getElementById(checkboxId); 45 if (checkbox) { 46 checkbox.checked = true; 47 } 48 }); 49 }, 50 51 renderBlogPosts(posts) { 52 const blogPostsContainer = document.getElementById("blogPostsContainer"); 53 blogPostsContainer.innerHTML = posts.map(this.generateBlogPostCard).join(""); 54 }, 55 56 generateTagChecklist() { 57 const tagChecklist = document.getElementById("tagChecklist"); 58 const uniqueTags = this.getAllTags().sort(); 59 60 if (uniqueTags.length === 0) { 61 $('#blogTags').hide(); 62 return; 63 } 64 65 tagChecklist ? tagChecklist.innerHTML = "" : null; 66 67 uniqueTags.forEach((tag) => { 68 const checkboxId = `tag${tag}Checkbox`; 69 70 const checkboxHtml = ` 71 <input 72 type="checkbox" 73 class="form-check-input tag-checkbox" 74 value="${tag}" 75 id="${checkboxId}" 76 onchange="blogManager.filterBlogs()" 77 > 78 `; 79 80 const labelHtml = ` 81 <label 82 class="form-check-label" 83 for="${checkboxId}" 84 >${tag}</label> 85 `; 86 87 const div = document.createElement("div"); 88 div.className = "form-check"; 89 div.innerHTML = checkboxHtml + labelHtml; 90 tagChecklist ? tagChecklist.appendChild(div) : null; 91 }); 92 }, 93 94 createSharePostLink(postID) { 95 const post = this.blogPosts.find((post) => post.id === postID); 96 97 if (!post) { 98 console.error("Post not found."); 99 return; 100 } 101 102 const encodedTitle = encodeURIComponent(post.headline); 103 const encodedUrl = encodeURIComponent(window.location.href.split("?")[0]); 104 105 const shareLink = `https://www.facebook.com/sharer/sharer.php?u=${encodedUrl}?post=${encodedTitle}`; 106 window.open(`${shareLink}`, "_blank"); 107 }, 108 109 copyBlogLinkToClipboard(postID) { 110 const post = this.blogPosts.find((post) => post.id === postID); 111 const encodedUrl = window.location.href.split("?")[0]; 112 const completeUrl = `${encodedUrl}?post=${post.headline}`; 113 114 navigator.clipboard 115 .writeText(completeUrl) 116 .then(() => { 117 console.log(this.config.urlCopiedLog, completeUrl); 118 119 // Show copy success notification 120 this.showNotification(this.config.blogLinkNotification); 121 }) 122 .catch((error) => { 123 console.error("Error copying URL to clipboard:", error); 124 }); 125 }, 126 127 showNotification(message) { 128 const createNotification = () => { 129 const notification = document.createElement("div"); 130 notification.className = "alert alert-success alert-dismissible fade show"; 131 notification.setAttribute("role", "alert"); 132 notification.innerHTML = ` 133 ${message} 134 <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> 135 `; 136 return notification; 137 }; 138 139 const notificationContainer = document.getElementById("notificationContainer"); 140 const modalNotificationContainer = document.getElementById("modalNotificationContainer"); 141 142 const notification = createNotification(); 143 144 if (notificationContainer) { 145 notificationContainer.appendChild(notification); 146 setTimeout(() => { 147 notificationContainer.removeChild(notification); 148 }, 3000); // Remove the notification after 3 seconds 149 } 150 151 if (modalNotificationContainer) { 152 const modalNotification = createNotification(); 153 modalNotificationContainer.appendChild(modalNotification); 154 setTimeout(() => { 155 modalNotificationContainer.removeChild(modalNotification); 156 }, 3000); // Remove the notification after 3 seconds 157 } 158 }, 159 160 generateBlogPostCard(post) { 161 const encodedTitle = encodeURIComponent(post.headline); 162 const shareHover = this.config.shareHover; 163 const copyHover = this.config.copyHover; 164 const sharePostLink = `blogManager.createSharePostLink(${post.id})`; 165 const copyBlogLink = `blogManager.copyBlogLinkToClipboard(${post.id})`; 166 const tagsHtml = post.tags.map((tag) => `<span class="blog-tag" onclick="blogManager.filterByTag('${tag}')">${tag}</span>`).join(", "); 167 168 return ` 169 <div class="col-lg-3 col-md-6 col-12" tabindex="0"> 170 <div class="uk-panel"> 171 <img src="${post.post_image}" class="blog-img" width="100%" height="100%" alt="${post.headline}"> 172 <div class="blog-top"> 173 <p class="blog-tags pointer">${tagsHtml}</p> 174 </div> 175 <div class="blog-bottom"> 176 <a href="?post=${encodedTitle}" class="title-wrapper"><p class="blog-title pointer truncate">${post.headline}</p></a> 177 <a href="?post=${encodedTitle}" class="badge badge-template blog-button">Read More</a> 178 <i class="fas fa-share ms-2 text-white pointer" data-bs-toggle="tooltip" data-bs-placement="bottom" title="${shareHover}" onclick="${sharePostLink}"></i> 179 <i class="fas fa-copy ms-2 text-white pointer" data-bs-toggle="tooltip" data-bs-placement="bottom" title="${copyHover}" onclick="${copyBlogLink}"></i> 180 </div> 181 </div> 182 </div> 183 `; 184 }, 185 186 showFullArticle(postTitle) { 187 const shareHover = this.config.shareHover; 188 const copyHover = this.config.copyHover; 189 const post = this.blogPosts.find((post) => post.headline === postTitle); 190 const fullArticleContainer = document.getElementById("fullArticleContent"); 191 const encodedTitle = encodeURIComponent(postTitle); 192 193 if (!post) { 194 console.error("Post not found."); 195 return; 196 } 197 198 const sharePostLink = `blogManager.createSharePostLink(${post.id})`; 199 const copyBlogLink = `blogManager.copyBlogLinkToClipboard(${post.id})`; 200 201 fullArticleContainer.innerHTML = ` 202 <div class="d-flex justify-content-end mt-3 mb-4">
203 <a class="fas fa-share ms-3 pointer fa-2xl" data-bs-toggle="tooltip" data-bs-placement="bottom" title="${shareHover}" onclick="${sharePostLink}"></a> 204 <a class="fas fa-copy ms-3 pointer fa-2xl" data-bs-toggle="tooltip" data-bs-placement="bottom" title="${copyHover}" onclick="${copyBlogLink}"></a> 205 </div> 206 <div class="row"> 207 <div class="col-md-8 offset-md-2"> 208 <div class="d-flex justify-content-center mb-3"> 209 <img src="${post.post_image}" class="img-fluid" alt="${post.headline}" /> 210 </div> 211 <div>${post.caption}</div> 212 ${post.referral_link ? ` 213 <div class="article-button"> 214 <a href="${post.referral_link}" target="_blank" class="btn btn-template">See More</a> 215 </div> 216 ` : ''} 217 </div> 218 </div> 219 `; 220 221 const modalTitle = document.getElementById("fullArticleModalLabel"); 222 modalTitle.textContent = post.headline; 223 224 const fullArticleModal = new bootstrap.Modal(document.getElementById("fullArticleModal")); 225 fullArticleModal.show(); 226 }, 227 228 calculateTotalPages(filteredPosts) { 229 // Inside the calculateTotalPages function 230 const totalPages = Math.ceil(filteredPosts.length / this.config.postsPerPage); 231 console.log("Total Pages:", totalPages); 232 233 return Math.ceil(filteredPosts.length / this.config.postsPerPage); 234 }, 235 236 filterBlogs() { 237 const selectedCheckboxes = document.querySelectorAll(".tag-checkbox:checked"); 238 this.selectedTags = Array.from(selectedCheckboxes).map((checkbox) => checkbox.value); 239 240 let filteredPosts = []; 241 if (this.selectedTags.length === 0) { 242 filteredPosts = this.blogPosts; 243 } else { 244 filteredPosts = this.blogPosts.filter((post) => { 245 return this.selectedTags.some((tag) => post.tags.includes(tag)); 246 }); 247 } 248 249 const totalPages = this.calculateTotalPages(filteredPosts); 250 this.renderPaginatedBlogPosts(1, filteredPosts); 251 this.renderPagination(totalPages, 1); 252 this.updateRemoveFiltersButtonVisibility(); 253 254 // Inside the filterBlogs function 255 console.log("Selected Tags:", this.selectedTags); 256 console.log("Filtered Posts:", filteredPosts); 257 }, 258 259 filterByTag(tag) { 260 const checkboxId = `tag${tag}Checkbox`; 261 const checkbox = document.getElementById(checkboxId); 262 263 if (checkbox) { 264 checkbox.checked = !checkbox.checked; 265 } 266 267 if (!this.selectedTags.includes(tag)) { 268 this.selectedTags.push(tag); 269 } else { 270 const index = this.selectedTags.indexOf(tag); 271 if (index > -1) { 272 this.selectedTags.splice(index, 1); 273 } 274 } 275 this.filterBlogs(); 276 }, 277 278 updateRemoveFiltersButtonVisibility() { 279 const button = document.getElementById("removeFiltersButton"); 280 if (this.selectedTags.length > 0) { 281 button.style.display = "inline-block"; 282 setTimeout(() => { 283 // Using a timeout ensures the CSS transition takes effect after the display property change 284 button.classList.add("visible"); 285 }, 50); 286 } else { 287 button.classList.remove("visible"); 288 // After the fade-out transition completes, hide the button 289 setTimeout(() => { 290 if (!button.classList.contains("visible")) { 291 // Ensure the button shouldn't be visible when the timeout completes 292 button.style.display = "none"; 293 } 294 }, 300); // This timeout duration should match the CSS transition duration 295 } 296 }, 297 298 removeFilters() { 299 this.selectedTags = []; 300 const checkboxes = document.querySelectorAll(".tag-checkbox"); 301 checkboxes.forEach((checkbox) => { 302 checkbox.checked = false; 303 }); 304 this.filterBlogs(); 305 }, 306 307 renderPaginatedBlogPosts(pageNumber, filteredPosts) { 308 // Before calling renderPaginatedBlogPosts 309 console.log("Page Number before renderPaginatedBlogPosts:", pageNumber); 310 console.log("Filtered Posts before renderPaginatedBlogPosts:", filteredPosts); 311 312 const startIndex = (pageNumber - 1) * this.config.postsPerPage; 313 const endIndex = startIndex + this.config.postsPerPage; 314 const paginatedPosts = filteredPosts.slice(startIndex, endIndex); 315 this.renderBlogPosts(paginatedPosts); 316 317 // Inside the renderPaginatedBlogPosts function 318 console.log("Page Number in renderPaginatedBlogPosts:", pageNumber); 319 console.log("Paginated Posts:", paginatedPosts); 320 }, 321 322 renderPagination(totalPages, currentPage) { 323 const paginationContainer = document.getElementById("pagination"); 324 325 let paginationHTML = ` 326 <ul class="pagination justify-content-center"> 327 <li class="page-item ${currentPage === 1 ? "disabled" : ""}"> 328 <button class="page-link page-button" data-page="${currentPage - 1}" ${currentPage === 1 ? "disabled" : ""}>Previous</button> 329 </li> 330 `; 331 332 for (let i = 1; i <= totalPages; i++) { 333 paginationHTML += ` 334 <li class="page-item ${currentPage === i ? "active" : ""}"> 335 <button class="page-link page-button" data-page="${i}">${i}</button> 336 </li> 337 `; 338 } 339 340 paginationHTML += ` 341 <li class="page-item ${currentPage === totalPages ? "disabled" : ""}"> 342 <button class="page-link page-button" data-page="${currentPage + 1}" ${currentPage === totalPages ? "disabled" : ""}>Next</button> 343 </li> 344 </ul> 345 `; 346 347 paginationContainer.innerHTML = totalPages <= 1 ? `` : paginationHTML; 348 349 const paginationButtons = document.querySelectorAll(".page-button"); 350 paginationButtons.forEach((button) => { 351 button.addEventListener("click", (event) => { 352 const pageNumber = parseInt(event.target.dataset.page); 353 354 // Update the paginated blog posts 355 this.filterAndPaginateBlogs(pageNumber); 356 }); 357 }); 358 }, 359 360 // Modify the filterBlogs function to handle both filtering and pagination 361 filterAndPaginateBlogs(pageNumber = 1) { 362 const selectedCheckboxes = document.querySelectorAll(".tag-checkbox:checked"); 363 this.selectedTags = Array.from(selectedCheckboxes).map((checkbox) => checkbox.value); 364 365 let filteredPosts = []; 366 if (this.selectedTags.length === 0) { 367 filteredPosts = this.blogPosts; 368 } else { 369 filteredPosts = this.blogPosts.filter((post) => { 370 return this.selectedTags.some((tag) => post.tags.includes(tag)); 371 }); 372 } 373 374 // Calculate the total pages for filtered posts 375 const totalPages = Math.ceil(filteredPosts.length / this.config.postsPerPage); 376 377 // Render the paginated blog posts and update pagination buttons 378 this.renderPaginatedBlogPosts(pageNumber, filteredPosts); 379 this.renderPagination(totalPages, pageNumber); // Update pagination buttons 380 this.setupReadFullArticleButtons(); 381 382 // Scroll back to the top of #blogPostsContainer 383 const blogPostsContainer = document.getElementById("blogPostsContainer"); 384 const offset = 250; 385 console.log(offset) 386 const elementPosition = blogPostsContainer.getBoundingClientRect().top + window.scrollY; 387 window.scrollTo({ 388 top: elementPosition - offset, 389 behavior: "smooth", 390 }); 391 }, 392 393 // Add an event listener to the checkboxes for filtering 394 addCheckboxEventListeners() { 395 const tagCheckboxes = document.querySelectorAll(".tag-checkbox");
396 tagCheckboxes.forEach((checkbox) => { 397 checkbox.addEventListener("change", () => { 398 this.filterAndPaginateBlogs(); // Update the filtered and paginated blogs when checkboxes change 399 }); 400 }); 401 }, 402 403 setupReadFullArticleButtons() { 404 const readFullArticleButtons = document.querySelectorAll(".read-full-article"); 405 readFullArticleButtons.forEach((button) => { 406 button.addEventListener("click", (event) => { 407 const postId = event.target.dataset.index; 408 this.showFullArticle(postId); 409 }); 410 }); 411 }, 412 413 async initializeBlog() { 414 this.generateBlogPostCard = this.generateBlogPostCard.bind(this); 415 this.addCheckboxEventListeners(); 416 await this.fetchBlogPosts(); 417 const totalPages = this.calculateTotalPages(this.blogPosts); 418 this.generateTagChecklist(); 419 this.renderPaginatedBlogPosts(1, this.blogPosts); 420 this.renderPagination(totalPages, 1); 421 this.setupReadFullArticleButtons(); 422 423 // Check for URL parameters 424 const urlParams = new URLSearchParams(window.location.search); 425 const postId = urlParams.get("post"); 426 const tags = urlParams.get("tags"); 427 428 if (postId) { 429 this.showFullArticle(postId); 430 } 431 432 if (tags) { 433 // Split tags by comma and filter the blogs 434 const tagArray = tags.split(","); 435 tagArray.forEach((tag) => { 436 const checkboxId = `tag${tag}Checkbox`; 437 const checkbox = document.getElementById(checkboxId); 438 if (checkbox) { 439 checkbox.checked = true; 440 } 441 }); 442 this.selectedTags = tagArray; 443 this.filterBlogs(); 444 } 445 446 var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); 447 var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) { 448 return new bootstrap.Tooltip(tooltipTriggerEl); 449 }); 450 }, 451}; 452 453if ($('#blogPostsContainer').length > 0) { 454 blogManager.initializeBlog(); 455}
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.