1/** 2 * IR Enhanced Alerts - Frontend Display JavaScript 3 * 4 * Handles alert dismissal, cookie management, and animations 5 */ 6 7(function($) { 8 'use strict'; 9 10 const IRAlerts = { 11 12 currentMessageIndex: 0, 13 totalMessages: 0, 14 messageRotationInterval: null, 15 currentModalIndex: 0, 16 totalModalMessages: 0, 17 18 /** 19 * Initialize alert handlers 20 */ 21 init: function() { 22 this.bindDismissHandlers(); 23 this.bindCloseHandlers(); 24 this.bindReopenHandler(); 25 this.bindBannerHandlers(); 26 this.initModalQueue(); 27 this.handleBodyScroll(); 28 }, 29 30 /** 31 * Bind dismiss button handlers 32 */ 33 bindDismissHandlers: function() { 34 $(document).on('click', '.ir-alert-dismiss', function(e) { 35 e.preventDefault(); 36 const alertId = $(this).data('alert-id'); 37 IRAlerts.dismissAlert(alertId); 38 }); 39 }, 40 41 /** 42 * Bind close (X) button handlers 43 */ 44 bindCloseHandlers: function() { 45 $(document).on('click', '.ir-alert-close, .ir-alert-banner-close', function(e) { 46 e.preventDefault(); 47 const alertId = $(this).data('alert-id'); 48 IRAlerts.dismissAlert(alertId); 49 }); 50 }, 51 52 /** 53 * Bind reopen button handler 54 */ 55 bindReopenHandler: function() { 56 $(document).on('click', '#ir-alerts-reopen-button', function(e) { 57 e.preventDefault(); 58 59 // Find all dismissed alerts in the DOM 60 const $dismissedAlerts = $('.ir-alert-dismissed'); 61 62 if ($dismissedAlerts.length === 0) { 63 // Alert was dismissed on previous page load, not in DOM 64 // Parse alert IDs from button data attribute and delete cookies 65 const alertData = $(this).data('alerts'); 66 if (alertData && alertData.length > 0) { 67 alertData.forEach(function(alert) { 68 IRAlerts.deleteCookie('ir_alert_dismissed_' + alert.id); 69 }); 70 } 71 72 // Reload page to render alerts fresh 73 location.reload(); 74 return; 75 } 76 77 // Alerts exist in DOM - show them instantly without reload 78 $dismissedAlerts.each(function() { 79 const alertId = $(this).data('alert-id'); 80 81 // Delete the dismiss cookie 82 IRAlerts.deleteCookie('ir_alert_dismissed_' + alertId); 83 84 // Remove dismissed state so the queue can rebuild cleanly 85 $(this).removeClass('ir-alert-dismissed').show(); 86 }); 87 88 IRAlerts.refreshModalQueue(); 89 90 // Hide the reopen button 91 IRAlerts.hideReopenButton(); 92 }); 93 }, 94 95 /** 96 * Dismiss an alert 97 */ 98 dismissAlert: function(alertId) { 99 // Get content hash from modal/banner data attribute 100 const $modal = $('#ir-alert-' + alertId); 101 const $banner = $('#ir-alert-banner-' + alertId); 102 const contentHash = $modal.attr('data-content-hash') || $banner.attr('data-content-hash') || '1'; 103 104 console.log('Dismissing alert ' + alertId + ' with hash: ' + contentHash); 105 106 // Set cookie to remember dismissal with content hash (24 hours) 107 // If content changes later, hash won't match and alert will show again 108 this.setCookie('ir_alert_dismissed_' + alertId, contentHash, 1); 109 110 console.log('Cookie set. Checking: ' + this.getCookie('ir_alert_dismissed_' + alertId)); 111 112 // Animate out and hide (but don't remove) modal 113 if ($modal.length) { 114 $modal.fadeOut(300, function() { 115 $(this).addClass('ir-alert-dismissed').hide(); 116 IRAlerts.refreshModalQueue(); 117 118 if ($('.ir-alert-dismissed').length > 0) { 119 IRAlerts.showReopenButton(); 120 } 121 }); 122 } 123 124 // Animate out and hide (but don't remove) banner 125 if ($banner.length) { 126 $banner.slideUp(300, function() { 127 $(this).addClass('ir-alert-dismissed').hide(); 128 IRAlerts.showReopenButton(); 129 }); 130 } 131 }, 132 133 /** 134 * Show the reopen button 135 */ 136 showReopenButton: function() { 137 if ($('#ir-alerts-reopen-button').length === 0) { 138 // Button doesn't exist, create it 139 const alertsData = []; 140 $('.ir-alert-dismissed').each(function() { 141 const alertId = $(this).data('alert-id'); 142 const alertTitle = $(this).find('.ir-alert-title, .ir-alert-banner-title').first().text(); 143 if (alertId) { 144 alertsData.push({ 145 id: alertId, 146 title: alertTitle 147 }); 148 } 149 }); 150 151 if (alertsData.length > 0) { 152 const buttonHtml = '<div id="ir-alerts-reopen-button" class="ir-reopen-btn" style="position:fixed;right:20px;bottom:20px;background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);color:white;padding:12px 20px;border-radius:50px;box-shadow:0 4px 15px rgba(0,0,0,0.2);cursor:pointer;z-index:999998;display:flex;align-items:center;gap:8px;font-weight:600;animation:ir-reopen-pulse 2s infinite;">' + 153 '<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/>
153</svg>' + 154 '<span>View Alerts</span>' + 155 '</div>'; 156 $('body').append(buttonHtml); 157 158 // Add animation styles if not already present 159 if (!$('#ir-reopen-animation-styles').length) { 160 $('head').append('<style id="ir-reopen-animation-styles">@keyframes ir-reopen-pulse{0%,100%{box-shadow:0 4px 15px rgba(0,0,0,0.2)}50%{box-shadow:0 4px 20px rgba(102,126,234,0.6)}}.ir-reopen-btn:hover{transform:translateY(-2px);box-shadow:0 6px 20px rgba(0,0,0,0.3)!important}</style>'); 161 } 162 } 163 } else { 164 $('#ir-alerts-reopen-button').fadeIn(300); 165 } 166 }, 167 168 /** 169 * Hide the reopen button 170 */ 171 hideReopenButton: function() { 172 $('#ir-alerts-reopen-button').fadeOut(300); 173 }, 174 175 /** 176 * Handle body scroll lock for modals 177 */ 178 handleBodyScroll: function() { 179 if ($('.ir-alert-modal:visible').length > 0) { 180 $('body').css('overflow', 'hidden'); 181 } 182 }, 183 184 /** 185 * Restore body scroll 186 */ 187 restoreBodyScroll: function() { 188 if ($('.ir-alert-modal:visible').length === 0) { 189 $('body').css('overflow', ''); 190 } 191 }, 192 193 /** 194 * Set up modal queue controls and initial active state 195 */ 196 initModalQueue: function() { 197 this.ensureModalQueueStyles(); 198 this.injectModalQueueControls(); 199 this.refreshModalQueue(); 200 }, 201 202 /** 203 * Add queue controls to modal templates when multiple messages are active 204 */ 205 injectModalQueueControls: function() { 206 const queueSize = $('.ir-alert-queue-modal[data-queue-index]').length; 207 208 if (queueSize <= 1) { 209 $('.ir-alert-modal-queue').remove(); 210 return; 211 } 212 213 $('.ir-alert-queue-modal').each(function() { 214 const $modal = $(this); 215 const $content = $modal.find('.ir-alert-content').first(); 216 217 if (!$content.length || $content.find('.ir-alert-modal-queue').length) { 218 return; 219 } 220 221 const controlsHtml = '<div class="ir-alert-modal-queue" aria-label="Alert queue navigation">' + 222 '<button class="ir-alert-modal-nav ir-alert-modal-nav-prev" type="button" aria-label="Previous alert">‹</button>' + 223 '<span class="ir-alert-modal-counter"><span class="ir-alert-modal-current">1</span> / <span class="ir-alert-modal-total">1</span></span>' + 224 '<button class="ir-alert-modal-nav ir-alert-modal-nav-next" type="button" aria-label="Next alert">›</button>' + 225 '</div>'; 226 227 $content.prepend(controlsHtml); 228 }); 229 }, 230 231 /** 232 * Recalculate the modal queue after dismiss/reopen actions 233 */ 234 refreshModalQueue: function() { 235 const $queueModals = $($('.ir-alert-queue-modal').filter(function() { 236 return !$(this).hasClass('ir-alert-dismissed'); 237 }).sort(function(a, b) { 238 return (parseInt($(a).attr('data-queue-index'), 10) || 0) - (parseInt($(b).attr('data-queue-index'), 10) || 0); 239 })); 240 241 this.totalModalMessages = $queueModals.length; 242 243 if (this.totalModalMessages === 0) { 244 $('.ir-alert-queue-modal').hide(); 245 this.restoreBodyScroll(); 246 return; 247 } 248 249 if (this.currentModalIndex >= this.totalModalMessages) { 250 this.currentModalIndex = 0; 251 } 252 253 this.injectModalQueueControls(); 254 this.showModalMessage(this.currentModalIndex); 255 }, 256 257 /** 258 * Move backwards/forwards through the modal queue 259 */ 260 navigateModal: function(direction) { 261 if (this.totalModalMessages <= 1) { 262 return; 263 } 264 265 this.currentModalIndex += direction; 266 267 if (this.currentModalIndex < 0) { 268 this.currentModalIndex = this.totalModalMessages - 1; 269 } else if (this.currentModalIndex >= this.totalModalMessages) { 270 this.currentModalIndex = 0; 271 } 272 273 this.showModalMessage(this.currentModalIndex); 274 }, 275 276 /** 277 * Show the selected modal in the queue and update its controls 278 */ 279 showModalMessage: function(index) {
280 const $queueModals = $($('.ir-alert-queue-modal').filter(function() { 281 return !$(this).hasClass('ir-alert-dismissed'); 282 }).sort(function(a, b) { 283 return (parseInt($(a).attr('data-queue-index'), 10) || 0) - (parseInt($(b).attr('data-queue-index'), 10) || 0); 284 })); 285 286 if (!$queueModals.length) { 287 this.restoreBodyScroll(); 288 return; 289 } 290 291 this.currentModalIndex = index; 292 293 $('.ir-alert-queue-modal').hide().removeClass('ir-alert-queue-active'); 294 295 const $activeModal = $queueModals.eq(index); 296 $activeModal.show().addClass('ir-alert-queue-active'); 297 298 $('.ir-alert-modal-queue').each(function() { 299 const $queue = $(this); 300 $queue.find('.ir-alert-modal-current').text(index + 1); 301 $queue.find('.ir-alert-modal-total').text($queueModals.length); 302 $queue.toggle($queueModals.length > 1); 303 }); 304 305 this.handleBodyScroll(); 306 }, 307 308 /** 309 * Add queue styles once, shared by all modal templates 310 */ 311 ensureModalQueueStyles: function() { 312 if ($('#ir-alert-modal-queue-styles').length) { 313 return; 314 } 315 316 $('head').append('<style id="ir-alert-modal-queue-styles">' + 317 '.ir-alert-modal-queue{display:flex;align-items:center;justify-content:flex-end;gap:12px;margin:0 0 20px 0}' + 318 '.ir-alert-modal-nav{width:34px;height:34px;border:1px solid #d8dee6;border-radius:999px;background:#fff;color:#1f2a37;font-size:22px;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .2s,border-color .2s,transform .2s}' + 319 '.ir-alert-modal-nav:hover{background:#f5f7fa;border-color:#b9c4d1;transform:translateY(-1px)}' + 320 '.ir-alert-modal-counter{font-size:14px;font-weight:600;color:#52606d;min-width:56px;text-align:center}' + 321 '@media (max-width:768px){.ir-alert-modal-queue{justify-content:center;margin-bottom:16px}}' + 322 '</style>'); 323 }, 324 325 /** 326 * Set cookie 327 */ 328 setCookie: function(name, value, days) { 329 let expires = ''; 330 if (days) { 331 const date = new Date(); 332 date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); 333 expires = '; expires=' + date.toUTCString(); 334 } 335 document.cookie = name + '=' + (value || '') + expires + '; path=/; SameSite=Lax'; 336 }, 337 338 /** 339 * Get cookie 340 */ 341 getCookie: function(name) { 342 const nameEQ = name + '='; 343 const ca = document.cookie.split(';'); 344 for (let i = 0; i < ca.length; i++) { 345 let c = ca[i]; 346 while (c.charAt(0) === ' ') c = c.substring(1, c.length); 347 if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length); 348 } 349 return null; 350 }, 351 352 /** 353 * Delete cookie 354 */ 355 deleteCookie: function(name) { 356 document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; SameSite=Lax'; 357 }, 358 359 /** 360 * Bind banner-specific handlers 361 */ 362 bindBannerHandlers: function() { 363 // Banner close button 364 $(document).on('click', '#ir-banner-close-btn', function(e) { 365 e.preventDefault(); 366 IRAlerts.dismissBanner(); 367 }); 368 369 // Banner slide-out tab (reopen banner) 370 $(document).on('click', '#ir-banner-slide-tab', function(e) { 371 e.preventDefault(); 372 IRAlerts.reopenBanner(); 373 }); 374 375 // Banner navigation 376 $(document).on('click', '.ir-banner-nav-prev', function(e) { 377 e.preventDefault(); 378 IRAlerts.navigateBanner(-1); 379 }); 380 381 $(document).on('click', '.ir-banner-nav-next', function(e) { 382 e.preventDefault(); 383 IRAlerts.navigateBanner(1); 384 }); 385 386 $(document).on('click', '.ir-alert-modal-nav-prev', function(e) { 387 e.preventDefault(); 388 IRAlerts.navigateModal(-1); 389 }); 390 391 $(document).on('click', '.ir-alert-modal-nav-next', function(e) { 392 e.preventDefault(); 393 IRAlerts.navigateModal(1); 394 }); 395 396 // Initialize message count and auto-rotation 397 this.totalMessages = $('.ir-banner-message').length; 398 if (this.totalMessages > 1) { 399 this.startMessageRotation(); 400 } 401 }, 402 403 /** 404 * Dismiss banner (slide out) 405 */
406 dismissBanner: function() { 407 const $banner = $('#ir-banner-container'); 408 409 // Slide out 410 $banner.addClass('slide-out'); 411 412 // Set cookie (single cookie for all banners) 413 this.setCookie('ir_banner_dismissed', '1', 1); 414 415 // After animation, show slide-out tab 416 setTimeout(function() { 417 IRAlerts.showBannerSlideTab(); 418 }, 400); 419 420 // Stop auto-rotation 421 this.stopMessageRotation(); 422 }, 423 424 /** 425 * Reopen banner (slide in) 426 */ 427 reopenBanner: function() { 428 // Delete dismiss cookie 429 this.deleteCookie('ir_banner_dismissed'); 430 431 // Hide the slide-out tab 432 $('#ir-banner-slide-tab').fadeOut(200); 433 434 // Slide banner back in 435 setTimeout(function() { 436 $('#ir-banner-container').removeClass('slide-out'); 437 438 // Restart auto-rotation if multiple messages 439 if (IRAlerts.totalMessages > 1) { 440 IRAlerts.startMessageRotation(); 441 } 442 }, 200); 443 }, 444 445 /** 446 * Show banner slide-out tab 447 */ 448 showBannerSlideTab: function() { 449 if ($('#ir-banner-slide-tab').length === 0) { 450 const tabHtml = '<div id="ir-banner-slide-tab" class="ir-banner-slide-tab">' + 451 '<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">' + 452 '<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/>' + 453 '</svg>' + 454 '<span>View Notices</span>' + 455 '</div>'; 456 $('body').append(tabHtml); 457 458 // Add styles if not already present 459 if (!$('#ir-banner-slide-tab-styles').length) { 460 $('head').append('<style id="ir-banner-slide-tab-styles">' + 461 '.ir-banner-slide-tab{position:fixed;right:180px;bottom:20px;background:linear-gradient(135deg,#f093fb 0%,#f5576c 100%);color:white;padding:12px 20px;border-radius:50px;box-shadow:0 4px 15px rgba(0,0,0,0.2);cursor:pointer;z-index:999998;display:flex;align-items:center;gap:8px;font-weight:600;transition:all 0.3s ease;animation:ir-banner-pulse 2s infinite}' + 462 '.ir-banner-slide-tab:hover{transform:translateY(-2px);box-shadow:0 6px 20px rgba(0,0,0,0.3)}' + 463 '@keyframes ir-banner-pulse{0%,100%{box-shadow:0 4px 15px rgba(0,0,0,0.2)}50%{box-shadow:0 4px 20px rgba(240,147,251,0.6)}}' + 464 '@media (max-width:768px){.ir-banner-slide-tab{right:15px;bottom:75px;padding:10px 16px;font-size:14px}}' + 465 '</style>'); 466 } 467 } else { 468 $('#ir-banner-slide-tab').fadeIn(300); 469 } 470 }, 471 472 /** 473 * Navigate between banner messages 474 */ 475 navigateBanner: function(direction) { 476 this.stopMessageRotation(); 477 478 this.currentMessageIndex += direction; 479 480 // Wrap around 481 if (this.currentMessageIndex < 0) { 482 this.currentMessageIndex = this.totalMessages - 1; 483 } else if (this.currentMessageIndex >= this.totalMessages) { 484 this.currentMessageIndex = 0; 485 } 486 487 this.showBannerMessage(this.currentMessageIndex); 488 this.startMessageRotation(); 489 }, 490 491 /** 492 * Show specific banner message 493 */ 494 showBannerMessage: function(index) { 495 const $message = $('.ir-banner-message[data-message-index="' + index + '"]'); 496 const gradient = $message.data('gradient'); 497 498 // Update active message 499 $('.ir-banner-message').removeClass('active'); 500 $message.addClass('active'); 501 502 // Update banner background color to match message type 503 if (gradient) { 504 $('#ir-banner-container').css('background', gradient); 505 } 506 507 // Update counter 508 $('.ir-banner-current').text(index + 1); 509 }, 510 511 /** 512 * Start auto-rotation of banner messages 513 */ 514 startMessageRotation: function() { 515 this.stopMessageRotation(); 516 517 if (this.totalMessages > 1) { 518 this.messageRotationInterval = setInterval(function() { 519 IRAlerts.navigateBanner(1); 520 }, 8000); // Rotate every 8 seconds 521 } 522 }, 523 524 /** 525 * Stop auto-rotation 526 */ 527 stopMessageRotation: function() { 528 if (this.messageRotationInterval) { 529 clearInterval(this.messageRotationInterval); 530 this.messageRotationInterval = null; 531 } 532 } 533 }; 534 535 // Initialize on document ready 536 $(document).ready(function() { 537 IRAlerts.init(); 538 }); 539 540 // Handle ESC key to dismiss modals 541 $(document).on('keydown', function(e) { 542 if (e.key === 'Escape' && $('.ir-alert-modal:visible').length > 0) { 543 const alertId = $('.ir-alert-modal:visible:first').data('alert-id');
544 IRAlerts.dismissAlert(alertId); 545 } 546 547 if (e.key === 'ArrowLeft' && $('.ir-alert-modal:visible').length > 0) { 548 IRAlerts.navigateModal(-1); 549 } 550 551 if (e.key === 'ArrowRight' && $('.ir-alert-modal:visible').length > 0) { 552 IRAlerts.navigateModal(1); 553 } 554 }); 555 556 // Handle overlay click to dismiss 557 $(document).on('click', '.ir-alert-overlay', function() { 558 const alertId = $(this).closest('.ir-alert-modal').data('alert-id'); 559 IRAlerts.dismissAlert(alertId); 560 }); 561 562})(jQuery);
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.