PageSourceSearch

https://www.hartelwickfederation.org.uk/elwick/wp-content/themes/i…/frontend/js/alert-display.js?ver=1.0.0

js hartelwickfederation.org.uk collected 2026-09-29 10:42:20 UTC 21,307 bytes, 562 lines download raw bytes

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">&lsaquo;</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">&rsaquo;</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.