PageSourceSearch

https://klimafolkemoedet.dk/wp-content/themes/hello-elementor-child/js/custom.js?ver=2.0.138

js klimafolkemoedet.dk collected 2026-10-03 20:34:19 UTC 32,321 bytes, 756 lines download raw bytes

1function haveCommonItems(arr1, arr2) {
2    // Check if both arrays are defined
3    if (!arr1 || !arr2) {
4        return false;
5    }
6
7    // Use the some method to check if any element in arr1 exists in arr2
8    return arr1.some(item => arr2.includes(item));
9}
10
11function removeItem(arr, itemToRemove) {
12    // Check if the array is undefined or null
13    if (arr === undefined || arr === null) {
14        console.log("Array is undefined or null.");
15        return arr;
16    }
17
18    // Check if the array is empty
19    if (arr.length === 0) {
20        console.log("Array is empty.");
21        return arr;
22    }
23
24    // Use filter to create a new array without the specified item
25    return arr.filter(item => item !== itemToRemove);
26}
27
28jQuery(document).ready(function($) {
29    $('.paneldeltager-container .description-container').expander({
30        slicePoint: 150,  // default is 100
31        expandText: 'Læs mere →', // default is 'read more'
32        userCollapseText: 'Luk ⤫'  // default is 'read less'
33    });
34
35    var Shuffle = window.Shuffle;
36
37    // Only initialize Shuffle if the container exists
38    if ($('.shuffle-container').length > 0) {
39        var myShuffle = new Shuffle(document.querySelector('.shuffle-container'), {
40            itemSelector: '.shuffle-item',
41            sizer: '.shuffle-item',
42            delimiter: ',',
43            buffer: 1,
44        });
45
46        $('.filterizr-filter li').on('click', function(evt) {
47            var filter = $(this).attr("data-filter");
48            
49            if (filter) {
50                myShuffle.filter(filter);
51            }
52        });
53
54        let selections = {
55            themes: [],
56            roles: [],
57            ages: [],
58        };
59
60        function isAgeInRange(age, range) {
61            if (range === 'all') return true;
62            
63            const ageNum = parseInt(age);
64            if (range === '15-35') return ageNum >= 15 && ageNum <= 35;
65            if (range === '36-49') return ageNum >= 36 && ageNum <= 49;
66            if (range === '50+') return ageNum >= 50;
67            
68            return false;
69        }
70
71        function custom_filter() {
72            myShuffle.filter(element => {
73                if (selections.themes.length == 0 && selections.roles.length == 0 && selections.ages.length == 0) {
74                    return true;
75                }
76
77                var themes = JSON.parse(element.getAttribute("data-themes"));
78                var roles = JSON.parse(element.getAttribute("data-roles"));
79                var age = element.getAttribute("data-age");
80
81                var hasTheme = selections.themes.length == 0 ? true : haveCommonItems(themes, selections.themes);
82                var hasRole = selections.roles.length == 0 ? true : haveCommonItems(roles, selections.roles);
83                var hasAge = selections.ages.length == 0 ? true : selections.ages.some(range => isAgeInRange(age, range));
84
85                return (hasTheme && hasRole && hasAge);
86            });
87        }
88        
89        custom_filter();
90
91        $("li.fltr-controls").on("click", function() {
92            var checkbox = $(this).find("input[type=checkbox]");
93            checkbox.prop("checked", !checkbox.prop("checked"));
94        });
95
96        $("input[name^=themes]").on("change", function() {
97            var filter = $(this).attr("data-filter");
98            var label = $(this).closest("label");
99            var active = $(this).is(":checked");
100
101            if (filter == "all") {
102                selections.themes = [];
103                $(this).closest(".filterizr-filter").find("label").removeClass("filtr-active");
104                custom_filter();
105                return;
106            }
107
108            if (active) {
109                selections.themes.push(filter);  
110                label.addClass("filtr-active");
111            } else {
112                selections.themes = removeItem(selections.themes, filter);
113                label.removeClass("filtr-active");
114            }
115            
116            custom_filter();
117        });
118
119        // Age filter handler
120        $("input[name=age]").on("change", function() {
121            var filter = $(this).attr("data-filter");
122            var label = $(this).closest("label");
123            
124            $(".age-filter-container").find("label").removeClass("filtr-active");
125            
126            if (filter === "all") {
127                selections.ages = [];
128                // No addClass for "all"
129            } else {
130                selections.ages = [filter];
131                label.addClass("filtr-active");
132            }
133            custom_filter();
134        });
135
136        // Roles filter handler
137        $(".roles-filter-container input[type=radio]").on("change", function() {
138            var filter = $(this).attr("data-filter");
139            var label = $(this).closest("label");
140            
141            // Force radio button state reset
142            $(".roles-filter-container input[type=radio]").prop("checked", false);
143            $(this).prop("checked", true);
144            
145            // Reset visual states
146            $(".roles-filter-container").find("label").removeClass("filtr-active");
147            
148            if ($(this).attr("name") === "all") {
149                selections.roles = [];
150                // No addClass for "all"
151            } else {
152                selections.roles = [filter];
153                label.addClass("filtr-active");
154            }
155            
156            custom_filter();
157        });
158    }
159
160    /**
161     * Character limit for form fields with submission prevention
162     * Simplified approach: Allow typing beyond limit but prevent submission
163     * Only for tcode_event post type
164     */
165    jQuery(document).ready(function($) {
166        // Constants
167        const CHAR_LIMIT_TITLE = 40;
168        const CHAR_LIMIT_CONTENT = 400;
169        
170        // Track fields with excess text
171        let fieldsWithExcess = [];
172        
173        // Helper function to check if we're on an event form
174        function isEventForm($element) {
175            // Check if we're on an event edit page or form
176            return (
177                $element.closest('.e-loop-item.tcode_event').length > 0 || 
178                $element.closest('.acf-form[data-post-type="tcode_event"]').length > 0 ||
179                $element.closest('form.frontend-form').length > 0 ||  // Added this line
180                $element.closest('.fea-modal.edit-modal').length > 0 || // Added this line
181                $('body').hasClass('post-type-tcode_event') ||
182                // Check for event form by looking for specific event fields
183                $element.closest('form').find('[data-name="event_tema"], [data-name="arrangementstype"]').length > 0
184            );
185        }
186        
187        // Helper function to check if we're on a bod form - to explicitly exclude
188        function isBoderForm($element) {
189            return (
190                $element.closest('.e-loop-item.bod').length > 0 || 
191                $element.closest('.acf-form[data-post-type="bod"]').length > 0 ||
192                $('body').hasClass('post-type-bod')
193            );
194        }
195        
196        // Function to initialize character counter for regular inputs
197        function initInputCharCounter() {
198            // Title field
199            $('.acf-field[data-name="fea_post_title"] input[type="text"]').each(function() {
200                const $field = $(this);
201                
202                // Skip if already initialized
203                if ($field.data('char-counter-init')) return;
204                
205                // Skip if on a bod form
206                if (isBoderForm($field)) return;
207                
208                // Skip if not in an event form
209                if (!isEventForm($field)) return;
210                
211                $field.data('char-counter-init', true);
212                
213                // Remove any existing counters to avoid duplicates
214                $field.next('.char-counter-container').remove();
215                
216                const $counterContainer = $('<div class="char-counter-container"></div>');
217                const $counter = $('<div class="char-counter"></div>');
218                $counterContainer.append($counter);
219                $field.after($counterContainer);
220                
221                $field.on('input change keyup', function() {
222                    const text = $(this).val();
223                    const currentLength = text.length;
224                    
225                    // Update counter
226                    $counter.text(currentLength + '/' + CHAR_LIMIT_TITLE);
227                    
228                    // Update styling and track excess
229                    const fieldId = 'title-' + ($(this).attr('id') || $(this).attr('name') || Math.random().toString(36).substring(2, 9));
230                    
231                    if (currentLength > CHAR_LIMIT_TITLE) {
232                        $counter.removeClass('normal warning').addClass('exceeded');
233                        $field.addClass('field-with-excess');
234                        
235                        // Add to excess tracking
236                        if (!fieldsWithExcess.includes(fieldId)) {
237                            fieldsWithExcess.push(fieldId);
238                        }
239                    } else if (currentLength >= CHAR_LIMIT_TITLE * 0.9) {
240                        $counter.removeClass('normal exceeded').addClass('warning');
241                        $field.removeClass('field-with-excess');
242                        
243                        // Remove from excess tracking
244                        fieldsWithExcess = fieldsWithExcess.filter(id => id !== fieldId);
245                    } else {
246                        $counter.removeClass('warning exceeded').addClass('normal');
247                        $field.removeClass('field-with-excess');
248                        
249                        // Remove from excess tracking
250                        fieldsWithExcess = fieldsWithExcess.filter(id => id !== fieldId);
251                    }
252                    
253                    // Update form submission state
254                    updateFormSubmissionState();
255                });
256                
257                // Initial update
258                $field.trigger('input');
259            });
260        }
261        
262        // Function to initialize TinyMCE character counter
263        function initTinyMCECharCounter() {
264            if (typeof tinyMCE === 'undefined' || !tinyMCE.editors || !tinyMCE.editors.length) {
265                return;
266            }
267            
268            // Loop through all TinyMCE editors
269            tinyMCE.editors.forEach(function(editor) {
270                // Skip if already initialized
271                if (editor.hasCharCounter) return;
272                
273                const $editorElement = $('#' + editor.id);
274                
275                // Skip if on a bod form
276                if (isBoderForm($editorElement)) return;
277                
278                // Skip if not in an event form
279                if (!isEventForm($editorElement)) return;
280                
281                const $editorContainer = $editorElement.closest('.wp-editor-container');
282                
283                // Remove any existing counters to avoid duplicates
284                $editorContainer.next('.char-counter-container').remove();
285                $editorContainer.find('.char-counter-container').remove();
286                
287                // Create counter element
288                const $counterContainer = $('<div class="char-counter-container"></div>');
289                const $counter = $('<div class="char-counter"></div>');
290                $counterContainer.append($counter);
291                $editorContainer.after($counterContainer);
292                
293                editor.hasCharCounter = true;
294                
295                // Function to count characters and update counter
296                function updateCounter() {
297                    // Get plain text content (strips HTML tags)
298                    const content = editor.getContent({format: 'text'});
299                    const length = content.length;
300                    
301                    // Always update counter text - ensure it's visible initially
302                    $counter.text(length + '/' + CHAR_LIMIT_CONTENT);
303                    
304                    // Update styling and track excess
305                    const fieldId = 'editor-' + editor.id;
306                    
307                    if (length > CHAR_LIMIT_CONTENT) {
308                        $counter.removeClass('normal warning').addClass('exceeded');
309                        $editorContainer.addClass('field-with-excess');
310                        
311                        // Add to excess tracking
312                        if (!fieldsWithExcess.includes(fieldId)) {
313                            fieldsWithExcess.push(fieldId);
314                        }
315                    } else if (length >= CHAR_LIMIT_CONTENT * 0.9) {
316                        $counter.removeClass('normal exceeded').addClass('warning');
317                        $editorContainer.removeClass('field-with-excess');
318                        
319                        // Remove from excess tracking
320                        fieldsWithExcess = fieldsWithExcess.filter(id => id !== fieldId);
321                    } else {
322                        $counter.removeClass('warning exceeded').addClass('normal');
323                        $editorContainer.removeClass('field-with-excess');
324                        
325                        // Remove from excess tracking
326                        fieldsWithExcess = fieldsWithExcess.filter(id => id !== fieldId);
327                    }
328                    
329                    // Update form submission state
330                    updateFormSubmissionState();
331                }
332                
333                // Add event listeners
334                editor.on('keyup change SetContent', function() {
335                    setTimeout(updateCounter, 100);
336                });
337                
338                // Initial update - make sure it runs
339                editor.on('init', function() {
340                    setTimeout(updateCounter, 500);
341                });
342                
343                // If editor is already initialized, run the counter immediately
344                if (editor.initialized) {
345                    setTimeout(updateCounter, 100);
346                }
347            });
348        }
349        
350        // Returns true if THIS form's own title/content fields exceed the limits.
351        // Computed fresh from the form's current fields so that one form (or a
352        // legacy over-limit event elsewhere on the page) can never lock another.
353        function formHasExcessText($form) {
354            let hasExcess = false;
355
356            // Title fields in this form
357            $form.find('.acf-field[data-name="fea_post_title"] input[type="text"]').each(function() {
358                if (($(this).val() || '').length > CHAR_LIMIT_TITLE) {
359                    hasExcess = true;
360                }
361            });
362
363            // WYSIWYG/content fields in this form
364            $form.find('.acf-field-wysiwyg textarea.wp-editor-area, textarea.wp-editor-area').each(function() {
365                const id = $(this).attr('id');
366                let len;
367                if (id && typeof tinyMCE !== 'undefined' && tinyMCE.get(id) && !tinyMCE.get(id).isHidden()) {
368                    // Visual mode: live content lives in the TinyMCE instance, not the textarea
369                    len = tinyMCE.get(id).getContent({format: 'text'}).length;
370                } else {
371                    // Text mode or no editor: fall back to the raw textarea value
372                    len = ($(this).val() || '').length;
373                }
374                if (len > CHAR_LIMIT_CONTENT) {
375                    hasExcess = true;
376                }
377            });
378
379            return hasExcess;
380        }
381
382        // Function to update form submission state
383        function updateFormSubmissionState() {
384            // Find only event forms - explicitly exclude bod forms
385            const $forms = $('.acf-form, form.frontend-form').filter(function() {
386                return isEventForm($(this)) && !isBoderForm($(this));
387            });
388
389            $forms.each(function() {
390                const $form = $(this);
391
392                // Target the specific button class from your co
392debase - UPDATED SELECTOR
393                const $submitBtn = $form.find('.fea-submit-button, button[type="submit"], input[type="submit"], button[data-state="publish"]');
394
395                // Remove any existing warning message
396                $form.find('.excess-text-warning').remove();
397
398                // Evaluate ONLY this form's own fields - never a shared global array
399                if (formHasExcessText($form)) {
400                    // Disable submit button - use both prop and attr for maximum compatibility
401                    $submitBtn.prop('disabled', true).attr('disabled', 'disabled').addClass('disabled');
402
403                    // Add warning message before the submit button
404                    const $warningMsg = $('<div class="excess-text-warning">Tilpas mængden af tekst i felterne øverst for at kunne gemme</div>');
405
406                    // Find the submit button container
407                    const $submitContainer = $submitBtn.closest('.fea-submit-buttons, .acf-form-submit');
408                    if ($submitContainer.length) {
409                        $submitContainer.before($warningMsg);
410                    } else {
411                        $submitBtn.before($warningMsg);
412                    }
413
414                    // Also prevent form submission via click handler - re-check at click time
415                    $submitBtn.off('click.charLimit').on('click.charLimit', function(e) {
416                        if (formHasExcessText($form)) {
417                            e.preventDefault();
418                            e.stopPropagation();
419
420                            // Scroll to the first field with excess within this form
421                            const $firstExcessField = $form.find('.field-with-excess').first();
422                            if ($firstExcessField.length) {
423                                $('html, body').animate({
424                                    scrollTop: $firstExcessField.offset().top - 100
425                                }, 500);
426                            }
427
428                            return false;
429                        }
430                    });
431                } else {
432                    // Enable submit button - remove both prop and attr
433                    $submitBtn.prop('disabled', false).removeAttr('disabled').removeClass('disabled');
434
435                    // Remove click handler
436                    $submitBtn.off('click.charLimit');
437                }
438            });
439        }
440        
441        // Initialize on page load
442        initInputCharCounter();
443        initTinyMCECharCounter();
444        
445        // Initialize after AJAX completions
446        $(document).ajaxComplete(function() {
447            setTimeout(function() {
448                initInputCharCounter();
449                initTinyMCECharCounter();
450            }, 300);
451        });
452        
453        // For Elementor frontend
454        if (typeof elementorFrontend !== 'undefined') {
455            elementorFrontend.hooks.addAction('frontend/element_ready/global', function() {
456                setTimeout(function() {
457                    initInputCharCounter();
458                    initTinyMCECharCounter();
459                }, 300);
460            });
461        }
462        
463        // Monitor DOM changes for dynamically added fields
464        if (typeof MutationObserver !== 'undefined') {
465            const observer = new MutationObserver(function(mutations) {
466                let shouldInit = false;
467                
468                mutations.forEach(function(mutation) {
469                    if (mutation.addedNodes.length) {
470                        for (let i = 0; i < mutation.addedNodes.length; i++) {
471                            const node = mutation.addedNodes[i];
472                            if (node.nodeType === 1 && (
473                                $(node).hasClass('acf-field-wysiwyg') ||
474                                $(node).find('.acf-field-wysiwyg').length ||
475                                $(node).hasClass('wp-editor-container') ||
476                                $(node).find('.wp-editor-container').length ||
477                                $(node).is('input') ||
478                                $(node).find('input').length ||
479                                $(node).hasClass('fea-submit-button') ||
480                                $(node).find('.fea-submit-button').length
481                            )) {
482                                shouldInit = true;
483                                break;
484                            }
485                        }
486                    }
487                });
488                
489                if (shouldInit) {
490                    setTimeout(function() {
491                        initInputCharCounter();
492                        initTinyMCECharCounter();
493                    }, 300);
494                }
495            });
496            
497            observer.observe(document.body, { childList: true, subtree: true });
498        }
499        
500        // Also add form submit handler to catch any submissions that might b
500ypass button disabling
501        $(document).on('submit', function(e) {
502            const $form = $(e.target);
503            // Only apply to event forms
504            if (!isEventForm($form) || isBoderForm($form)) {
505                return;
506            }
507
508            // Evaluate ONLY the submitted form's own fields
509            if (formHasExcessText($form)) {
510                e.preventDefault();
511                e.stopPropagation();
512
513                // Scroll to the first field with excess within this form
514                const $firstExcessField = $form.find('.field-with-excess').first();
515                if ($firstExcessField.length) {
516                    $('html, body').animate({
517                        scrollTop: $firstExcessField.offset().top - 100
518                    }, 500);
519                }
520
521                return false;
522            }
523        });
524
525        // Add a specific handler for modal buttons
526        $(document).on('click', '.fea-modal button.fea-submit-button', function(e) {
527            const $form = $(this).closest('.acf-form, form.frontend-form, form');
528            // Only apply to event forms - bod forms are not limited
529            if (!isEventForm($form) || isBoderForm($form)) {
530                return;
531            }
532
533            // Evaluate ONLY this button's own form fields
534            if (formHasExcessText($form)) {
535                e.preventDefault();
536                e.stopPropagation();
537
538                // Scroll to the first field with excess within this form
539                const $firstExcessField = $form.find('.field-with-excess').first();
540                if ($firstExcessField.length) {
541                    $('html, body').animate({
542                        scrollTop: $firstExcessField.offset().top - 100
543                    }, 500);
544                }
545
546                return false;
547            }
548        });
549        
550        // Clean up any existing counters on bod forms
551        function removeCountersFromBoderForms() {
552            $('.acf-form').each(function() {
553                if (isBoderForm($(this))) {
554                    $(this).find('.char-counter-container').remove();
555                    $(this).find('.field-with-excess').removeClass('field-with-excess');
556                    $(this).find('.excess-text-warning').remove();
557                }
558            });
559        }
560        
561        // Run cleanup on page load and after AJAX
562        removeCountersFromBoderForms();
563        $(document).ajaxComplete(removeCountersFromBoderForms);
564    });
565
566    /**
567     * Event search functionality
568     */
569    if ($('.tcode-event-schedule').length > 0) {
570        // Initialize search state
571        var isSearchActive = false;
572        
573        // Store original click handlers
574        var originalDayClickHandler;
575        var originalLocationClickHandler;
576        
577        // Search functionality
578        $('#event-search').on('keyup', function() {
579            var searchText = $(this).val().toLowerCase();
580            
581            if (searchText.length > 0) {
582                // Enter search mode
583                if (!isSearchActive) {
584                    isSearchActive = true;
585                    
586                    // Disable date and location click handlers by adding a search-active class
587                    $('.scheduled-days, .scheduled-locations').addClass('search-active');
588                    
589                    // Add a temporary click handler to prevent default behavior
590                    $('.scheduled-day, .scheduled-location').on('click.searchMode', function(e) {
591                        if (isSearchActive) {
592                            e.preventDefault();
593                            e.stopPropagation();
594                            return false;
595                        }
596                    });
597                }
598                
599                // Make all events visible
600                $('.scheduled-event').show().removeClass('event-hidden').addClass('event-visible');
601                $('.scheduled-events').removeClass('hidden');
602                
603                // Filter events based on search text
604                var matchCount = 0;
605                $('.scheduled-event').each(function() {
606                    var $event = $(this);
607                    var eventTitle = $event.find('.event-title').text().toLowerCase();
608                    var eventContent = $event.find('.event-content').text().toLowerCase();
609                    var eventArtists = $event.find('.tcode-es-artist-title').text().toLowerCase();
610                    var panelists = $event.find('.paneldeltagere').text().toLowerCase();
611                    var moderator = $event.find('.moderator').text().toLowerCase();
612                    var organisation = $event.find('.organisation').text().toLowerCase();
613                    // Add theme search - look for the alt attribute in the theme image
614                    var eventTheme = $event.find('.imgContainer img').attr('alt') || '';
615                    eventTheme = eventTheme.toLowerCase();
616                    
617                    if (eventTitle.indexOf(searchText) > -1 || 
618                        eventContent.indexOf(searchText) > -1 ||
619                        eventArtists.indexOf(searchText) > -1 ||
620                        panelists.indexOf(searchText) > -1 ||
621                        moderator.indexOf(searchText) > -1 ||
622                        organisation.indexOf(searchText) > -1 ||
623                        eventTheme.indexOf(searchText) > -1) {
624                        $event.show();
625                        matchCount++;
626                    } else {
627                        $event.hide();
628                    }
629                });
630                
631                // Show "no results" message if needed
632                $('.no-search-results').remove();
633                if (matchCount === 0) {ª
634                    $('.scheduled-events.desktop').append('<div class="no-search-results">Ingen resultater fundet. Prøv en anden søgning.</div>');
635                }
636            } else {
637                // Exit search mode
638                isSearchActive = false;
639                
640                // Remove search results message
641                $('.no-search-results').remove();
642                
643                // Re-enable date and location click handlers
644                $('.scheduled-days, .scheduled-locations').removeClass('search-active');
645                $('.scheduled-day, .scheduled-location').off('click.searchMode');
646                
647                // Reset to default view by triggering a click on the first day
648                $('.scheduled-day').first().trigger('click');
649            }
650        });        
651        // Make search button functional
652        $('.promagnifier').on('click', function() {
653            $('#event-search').trigger('keyup');
654        });
655        
656    }
657
658    /**
659     * Show/hide description for bod elements
660     */
661    // Function to initialize bod elements
662    function initBodElements() {
663        // Hide all bod-desc elements initially
664        $('.bod-desc').hide();
665        
666        // Reset all buttons to their default state
667        $('.bod-showdesc').removeClass('active');
668        $('.bod-showdesc .htmega_button_txt').text('');
669        $('.bod-showdesc .htmega_button_icon i').removeClass('fa-chevron-up').addClass('fa-chevron-down');
670    }
671    
672    // Call the function when the document is ready
673    initBodElements();
674    
675    // Use event delegation for the click handler to work with dynamically loaded content
676    $(document).on('click', '.bod-showdesc', function(e) {
677        e.preventDefault();
678        
679        // Find the common parent container - going up to the main widget-wrap that contains everything
680        var $mainContainer = $(this).closest('.elementor-widget-wrap').parent().parent().parent().parent();
681        
682        // Find the bod-desc within this main container
683        var $desc = $mainContainer.find('.bod-desc');
684        
685        // Toggle the description with a slide animation
686        if ($desc.is(':visible')) {
687            $desc.slideUp(300);
688            $(this).removeClass('active');
689            // Update the button text
690            $(this).find('.htmega_button_txt').text('');
691            $(this).find('.htmega_button_icon i').removeClass('fa-chevron-up').addClass('fa-chevron-down');
692        } else {
693            $desc.slideDown(300);
694            $(this).addClass('active');
695            // Update the button text
696            $(this).find('.htmega_button_txt').text('');
697            $(this).find('.htmega_button_icon i').removeClass('fa-chevron-down').addClass('fa-chevron-up');
698        }
699    });
700    
701    // Listen for various events that might indicate content has been updated
702    $(document).ajaxComplete(function(event, xhr, settings) {
703        setTimeout(initBodElements, 100); // Slight delay to ensure DOM is updated
704    });
705    
706    // For Elementor-specific events
707    jQuery(window).on('elementor/frontend/init', function() {
708        elementorFrontend.hooks.addAction('frontend/element_ready/global', function() {
709            setTimeout(initBodElements, 100);
710        });
711    });
712
713    // Also run on document ready as a fallback
714    jQuery(document).ready(function() {
715        setTimeout(initBodElements, 100);
716    });
717    
718    // Monitor DOM changes to detect when new content is added
719    // This is a more aggressive approach but should catch all dynamic content updates
720    if (typeof MutationObserver !== 'undefined') {
721        var observer = new MutationObserver(function(mutations) {
722            var shouldInit = false;
723            
724            mutations.forEach(function(mutation) {
725                // Check if new nodes were added
726                if (mutation.addedNodes.length) {
727                    // Check if any of the added nodes contain our target elements
728                    for (var i = 0; i < mutation.addedNodes.length; i++) {
729                        var node = mutation.addedNodes[i];
730                        if (node.nodeType === 1 && (
731                            $(node).hasClass('bod-desc') || 
732                            $(node).find('.bod-desc').length || 
733                            $(node).hasClass('bod-showdesc') || 
734                            $(node).find('.bod-showdesc').length)) {
735                            shouldInit = true;
736                            break;
737                        }
738                    }
739                }
740            });
741            
742            if (shouldInit) {
743                setTimeout(initBodElements, 100);
744            }
745        });
746        
747        // Start observing the document with the configured parameters
748        observer.observe(document.body, { childList: true, subtree: true });
749    }
750    
751    // Additional event listeners for common filter/pagination events
752    $(document).on('click', '.filterizr-filter li, .fltr-control
752s, input[name^=themes], input[name=age], .roles-filter-container input[type=radio]', function() {
753        // Wait for the filter to complete
754        setTimeout(initBodElements, 300);
755    });
756});

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.