PageSourceSearch

https://alfredcurrier.com/wp-content/plugins/cm-alpha/ui-modals/js/scripts.js?ver=1790301847

js alfredcurrier.com collected 2026-09-25 02:05:13 UTC 28,988 bytes, 644 lines download raw bytes

1/** cm-alpha/ui-modals/js/scripts.js
2 * CM UI Modals JavaScript
3 */
4
5jQuery(document).ready(function($) {
6    window.cmActiveModals = new Set();
7
8    // Track custom modal containers
9    window.cmModalContainers = {};
10    
11    // Global modal close callback system
12    window.cmModalCloseCallbacks = [];
13    window.cmModalSpecificCallbacks = {};
14    
15    // Global variables for YouTube size tracking
16    let hasCustomYouTubeSize = false;
17    let youtubeCustomDimensions = null;
18    
19    // Function to process include files (JS, CSS, PHP)
20    function processIncludeFiles(modalId, settings) {
21        const includeFiles = settings.include_files;
22        if (!includeFiles) return;
23        
24        const $contentContainer = $(`#${modalId}-container .cm-modal-content`);
25        
26        // Parse comma-separated list of files
27        const files = includeFiles.split(',').map(file => file.trim());
28        
29        // Track loaded resources
30        const loadedResources = {
31            css: [],
32            js: [],
33            php: []
34        };
35        
36        // Process each file based on its extension
37        files.forEach(file => {
38            const extension = file.split('.').pop().toLowerCase();
39            
40            switch (extension) {
41                case 'css':
42                    // Load CSS file
43                    if (!loadedResources.css.includes(file)) {
44                        const linkElement = document.createElement('link');
45                        linkElement.rel = 'stylesheet';
46                        linkElement.href = file.startsWith('/') ? file : ajaxurl.replace('admin-ajax.php', '') + file;
47                        linkElement.className = `cm-modal-${modalId}-css`;
48                        document.head.appendChild(linkElement);
49                        loadedResources.css.push(file);
50                    }
51                    break;
52                    
53                case 'js':
54                    // Load JavaScript file
55                    if (!loadedResources.js.includes(file)) {
56                        const scriptElement = document.createElement('script');
57                        scriptElement.src = file.startsWith('/') ? file : ajaxurl.replace('admin-ajax.php', '') + file;
58                        scriptElement.className = `cm-modal-${modalId}-js`;
59                        document.body.appendChild(scriptElement);
60                        loadedResources.js.push(file);
61                    }
62                    break;
63                    
64                case 'php':
65                    // Load PHP file via AJAX
66                    if (!loadedResources.php.includes(file)) {
67                        loadedResources.php.push(file);
68                        
69                        // Make AJAX request to load PHP content
70                        $.ajax({
71                            url: typeof cmModalsConfig !== 'undefined' ? cmModalsConfig.ajaxurl : ajaxurl,
72                            type: 'POST',
73                            data: {
74                                action: 'cm_load_modal_include',
75                                nonce: typeof cmModalsConfig !== 'undefined' ? cmModalsConfig.nonce : '',
76                                file: file,
77                                modal_id: modalId,
78                                settings: JSON.stringify(settings)
79                            },
80                            success: function(response) {
81                                // Replace loading indicator in the included content section
82                                $contentContainer.find('.cm-modal-included-content').html(response);
83                                
84                                // Trigger an event so other scripts can initialize
85                                $(document).trigger('cm_modal_content_loaded', [modalId, file, settings]);
86                                
87                                // Handle specific case for edit-media-dialog.php
88                                if (file.includes('edit-media-dialog.php')) {
89                                    // Re-initialize UI controls if CMColorPicker exists
90                                    if (typeof window.CMColorPicker !== 'undefined' && typeof window.CMColorPicker.init === 'function') {
91                                        window.CMColorPicker.init('.cm-color-picker');
92                                    }
93                                }
94                            },
95                            error: function(xhr, status, error) {
96                                console.error(`Failed to load include file: ${file}`, error);
97                                $contentContainer.find('.cm-modal-included-content').html(`<div class="cm-modal-error">Error loading content: ${error}</div>`);
98                                
99                                // If this was the default include file, fall back to direct content
100                                // No need for special default handling anymore since content is already displayed
101                            }
102                        });
103                    }
104                    break;
105                    
106                default:
107                    // Assume PHP file if no extension or unknown
108                    if (!loadedResources.php.includes(file)) {
109                        loadedResources.php.push(file);
110                        
111                        // Make AJAX request to load PHP content (assuming it's a PHP file)
112                        $.ajax({
113                            url: typeof cmModalsConfig !== 'undefined' ? cmModalsConfig.ajaxurl : ajaxurl,
114                            type: 'POST',
115                            data: {
116                                action: 'cm_load_modal_include',
117                                nonce: typeof cmModalsConfig !== 'undefined' ? cmModalsConfig.nonce : '',
118                                file: file,
119                                modal_id: modalId,
120                                settings: JSON.stringify(settings)
121                            },
122                            success: function(response) {
123                                // Replace loading indicator in the included content section
124                                $contentContainer.find('.cm-modal-included-content').html(response);
125                                
126                                // Trigger an event so other scripts can initialize
127                                $(document).trigger('cm_modal_content_loaded', [modalId, file, settings]);
128                                
129                                // Handle specific case for edit-media-dialog.php
130                                if (file.includes('edit-media-dialog.php')) {
131                                    // Re-initialize UI controls if CMColorPicker exists
132                                    if (typeof window.CMColorPicker !== 'undefined' && typeof window.CMColorPicker.init === 'function') {
133                                        window.CMColorPicker.init('.cm-color-picker');
134                                    }
135                                }
136                            },
137                            error: function(xhr, status, error) {
138                                console.error(`Failed to load include file: ${file}`, error);
139                                $contentContainer.find('.cm-modal-included-content').html(`<div class="cm-modal-error">Error loading content: ${error}</div>`);
140                                
141                                // Fall back to direct content
142                                $contentContainer.find('.cm-modal-included-content').html(`<div class="cm-modal-fallback">Error loading include file</div>`);
143                            }
144                        });
145                    }
146                    break;
147            }
148        });
149    }
150        
151    // Modal display function
152    // Helper function to create a close button for modals
153    function createCloseButton(modalId, showCloseButton) {
154        if (!showCloseButton) {
155            return '';
156        }
157        return `<div class="cm-modal-close" data-modal-id="${modalId}">×</div>`;
158    }    
159
160    // Create or get the modal container
161    function getModalContainer(modalId) {
162        // If a modalId is provided, create/get a dedicated container
163        if (modalId) {
164            // Create a container ID based on the modal ID
165            // If modalId already starts with 'cm-modal-', use just the numeric part
166            const baseId = modalId.startsWith('cm-modal-') ? modalId.substring(9) : modalId;
167            const containerId = `cm-modal-wrapper-${baseId}`;
168            
169            // Check if this container already exists
170            let $container = $(`#${containerId}`);
171            
172            if ($container.length === 0) {
173                $('body').append(`<div id="${containerId}" class="cm-modal-container-wrapper"></div>`);
174                $container = $(`#${containerId}`);
175                
176                // Store in tracking object with the original modalId
177                window.cmModalContainers[modalId] = {
178                    element: $container,
179                    containerId: containerId,
180                    baseId: baseId
181                };
182                
183                // Check if default container exists and is empty - clean it up if so
184                const $defaultContainer = $('#cm-modal-dynamic-container');
185                if ($defaultContainer.length > 0 && $defaultContainer.children().length === 0) {
186                    $defaultContainer.remove();
187                }
188            }
189            
190            return $container;
191        }
192        
193        // We should never reach here with our new approach, but keep as fallback
194        console.warn('No modalId provided for container creation - this should not happen');
195        let $container = $('#cm-modal-dynamic-container');
196        
197        if ($container.length === 0) {
198            $('body').append('<div id="cm-modal-dynamic-container" class="cm-modal-container-wrapper"></div>');
199            $container = $('#cm-modal-dynamic-container');
200        }
201        
202        return $container;
203    }
204
205    // Helper function to extract YouTube video ID from various URL formats
206    function getYouTubeVideoId(url) {
207        
208        if (!url) return null;
209        
210        // Clean URL if it contains any HTML tags
211        let cleanUrl = url;
212        if (cleanUrl.includes('<') && cleanUrl.includes('>')) {
213            // Try to extract just the URL part
214            const tempDiv = document.createElement('div');
215            tempDiv.innerHTML = cleanUrl;
216            cleanUrl = tempDiv.textContent.trim();
217        }
218        
219        // Try multiple extraction methods
220        
221        // Method 1: Using regex to match various YouTube URL formats
222        const regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/;
223        const match = cleanUrl.match(regExp);
224        
225        if (match && match[2] && match[2].length === 11) {
226            return match[2];
227        }
228        
229        // Method 2: Direct string manipulation for common formats
230        if (cleanUrl.includes('youtube.com/watch?v=')) {
231            const id = cleanUrl.split('youtube.com/watch?v=')[1].split(/[&#?]/)[0];
232            if (id && id.length === 11) {
233                return id;
234            }
235        } 
236        
237        if (cleanUrl.includes('youtu.be/')) {
238            const id = cleanUrl.split('youtu.be/')[1].split(/[&#?]/)[0];
239            if (id && id.length === 11) {
240                return id;
241            }
242        }
243        
244        if (cleanUrl.includes('youtube.com/embed/')) {
245            const id = cleanUrl.split('youtube.com/embed/')[1].split(/[&#?]/)[0];
246            if (id && id.length === 11) {
247                return id;
248            }
249        }
250        
251        // Method 3: Check if the string itself is just an 11-character YouTube ID
252        if (cleanUrl.length === 11 && /^[a-zA-Z0-9_-]{11}$/.test(cleanUrl)) {
253            return cleanUrl;
254        }
255        
256        return null;
257    }
258
259    // Function to extract custom dimensions from YouTube content
260    function extractYouTubeDimensions(content) {
261        let customDimensions = null;
262        let videoUrl = content;
263        
264        // Handle HTML content first - extract the actual URL from HTML if needed
265        if (typeof content === 'string' && (content.includes('<div') || content.includes('<span'))) {
266            // Create a temporary element to parse the HTML
267            const tempDiv = document.createElement('div');
268            tempDiv.innerHTML = content;
269            
270            // Try to find the URL in the inner text of the div
271            const innerText = tempDiv.textContent.trim();
272            if (innerText.includes('youtube.com') || innerText.includes('youtu.be')) {
273                videoUrl = innerText;
274            }
275            
276            // Also try to find any divs with style attributes for dimensions
277            const styledDiv = tempDiv.querySelector('div[style]');
278            if (styledDiv) {
279                const styleAttr = styledDiv.getAttribute('style');
280                const innerContent = styledDiv.textContent.trim();
281                
282                // If the inner content is a YouTube URL, use that
283                if (innerContent.includes('youtube.com') || innerContent.includes('youtu.be')) {
284                    videoUrl = innerContent;
285                }
286                
287                // Extract width and height from style attribute
288                const widthMatch = styleAttr ? styleAttr.match(/width\s*:\s*([^;]+)/i) : null;
289                const heightMatch = styleAttr ? styleAttr.match(/height\s*:\s*([^;]+)/i) : null;
290                
291                if (widthMatch) {
292                    customDimensions = customDimensions || {};
293                    customDimensions.width = widthMatch[1].trim();
294                    
295                    // Store numeric width if possible (for calculations)
296                    const numericWidth = parseFloat(customDimensions.width);
297                    if (!isNaN(numericWidth)) {
298                        customDimensions.numericWidth = numericWidth;
299                        customDimensions.unit = customDimensions.width.replace(/[\d.]+/, '');
300                    }
301                }
302                
303                if (heightMatch) {
304                    customDimensions = customDimensions || {};
305                    customDimensions.height = heightMatch[1].trim();
306                    
307                    // If height is 'auto', calculate based on 16:9 aspect ratio
308                    if (heightMatch[1].trim() === 'auto' && customDimensions.numericWidth) {
309                        const heightValue = customDimensions.numericWidth * 0.5625; // 16:9 aspect ratio
310                        customDimensions.height = heightValue + customDimensions.unit;
311                    }
312                }
313            }
314        } else if (typeof content === 'string') {
315            // If content is already a string but not HTML, just use it directly
316            videoUrl = content.trim();
317        }
318        
319        // As a fallback, also try a regex match for a div without using DOM parsing
320        if (typeof content === 'string' && content.includes('<div') && videoUrl === content) {
321            const divRegex = /<div[^>]*>(.*?)<\/div>/is;
322            const match = content.match(divRegex);
323            if (match && match[1] && (match[1].includes('youtube.com') || match[1].includes('youtu.be'))) {
324                videoUrl = match[1].trim();
325            }
326        }
327        
328        return { 
329            videoUrl: videoUrl,
330            customDimensions: customDimensions
331        };
332    }
333        
334    window.cmDisplayModal = function(options) {
335        
336        // Default options
337        // include_files: allows a comma separated list of files to be included in the modal
338        const defaults = {
339            type_modal: 'html',
340            type_alert: '',
341            heading: '',
342            heading_align: 'center',
343            heading_background_color: '#0000007A',
344            heading_color: '#d8d8d8',
345            heading_border: '',
346            heading_padding: '.2em .5em .2em .5em',
347            heading_text_align: 'center',
348            heading_styles: null,
349            content: '',
350            content_styles: null,
351            include_files: '',
352            php_include_file: '',  // For backward compatibility
353            content_below_included_files: true, // Controls content position relative to included files
354            fadeInDuration: 0.5,
355            faderColor: 'rgba(0, 0, 0, 0.3)',
356            closeButton: true,
357            custom_id: null,
358            container_classes: null,
359            container_styles: null,
360            auto_play: null,
361            data_attributes: {}
362        };
363        
364        // Merges the options supplied over any existing defaults
365        const settings = $.extend({}, defaults, options);
366
367        // Validate type_modal against content and show error messages if mismatched
368        if (settings.type_modal && settings.content) {
369            const contentStr = String(settings.content).trim();
370            const isYouTubeUrl = getYouTubeVideoId(contentStr) !== null;
371            
372            if (settings.type_modal === 'html' && isYouTubeUrl) {
373                settings.content = `<span style="color:red;">You have provided modal_type=html but provided a youtube url ${contentStr}</span>`;
374            } else if (settings.type_modal === 'youtube' && !isYouTubeUrl) {
375                settings.content = '<span style="color:red;">You provided modal_type=youtube but then you did not provide a youtube url</span>';
376            }
377        }
378
379        // Generate or use custom modal ID
380        let modalId;
381        if (settings.custom_id) {
382            modalId = settings.custom_id;
383            // Check if modal with this ID already exists
384            if ($(`#${modalId}`).length > 0 || window.cmActiveModals.has(modalId)) {
385                console.warn(`Modal with ID ${modalId} already exists. Will replace it.`);
386                closeModal(modalId); // Close existing modal with this ID
387            }
388        } else {
389            // Generate a random ID with the same format as toast alerts for consistency
390            modalId = 'cm-modal-' + Math.floor(Math.random() * 1000000);
391        }
392        
393        // Create the data attributes HTML
394        let dataAttributesHtml = '';
395        if (settings.data_attributes && typeof settings.data_attributes === 'object') {
396            Object.keys(settings.data_attributes).forEach(key => {
397                dataAttributesHtml += ` data-${key}="${settings.data_attributes[key]}"`;
398            });
399        }
400        
401        // Handle backward compatibility with php_include_file
402        if (settings.php_include_file && !settings.include_files) {
403            settings.include_files = settings.php_include_file;
404        }
405        
406        // No default include file - if include_files is empty, we'll just use the content directly
407        
408        // Find the highest z-index of existing modal faders
409        let highestZIndex = 9999; // Start with the CSS default
410        $('.cm-modal-fader').each(function() {
411            const zIndex = parseInt($(this).css('z-index'), 10);
412            if (!isNaN(zIndex) && zIndex > highestZIndex) {
413                highestZIndex = zIndex;
414            }
415        });
416
417        // Increment for the new modal
418        const newZIndex = highestZIndex + 1;
419
420        // Create the modal HTML structure with the new z-index applied
421        const modalHTML = `
422            <div class="cm-modal-fader" id="${modalId}-fader" style="background-color: ${settings.faderColor}; z-index: ${newZIndex};">
423                <div class="cm-modal-container ${settings.container_
423classes || ''}" 
424                     id="${modalId}-container" 
425                     style="${settings.container_styles || ''}; z-index: ${newZIndex + 1}"
426                     ${dataAttributesHtml}>
427                    ${createCloseButton(modalId, settings.closeButton)}
428                    <div class="cm-modal-header" style="background-color: ${settings.heading_background_color}; color: ${settings.heading_color}; border: ${settings.heading_border}; padding: ${settings.heading_padding}; text-align: ${settings.heading_align}; ${settings.heading_styles || ''}">
429                        ${settings.heading}
430                    </div>
431                    <div class="cm-modal-content" data-include-files="${settings.include_files}" data-modal-id="${modalId}">
432                        ${!settings.include_files || settings.content_below_included_files === false ? 
433                          `<div class="cm-modal-direct-content" style="${settings.content_styles || ''}">${settings.content}</div>` : ''}
434                        <div class="cm-modal-included-content">
435                            ${settings.include_files ? '<div class="cm-modal-loading">Loading...</div>' : ''}
436                        </div>
437                        ${settings.include_files && settings.content_below_included_files === true && settings.content ? 
438                          `<div class="cm-modal-direct-content" style="${settings.content_styles || ''}">${settings.content}</div>` : ''}
439                    </div>
440                </div>
441            </div>
442        `;
443
444        // Use the wrapper ID for the container
445        const wrapperAlertId = settings.custom_id || modalId;
446        const container = getModalContainer(wrapperAlertId);
447        
448        // Append modal to container
449        container.append(modalHTML);
450        
451        // Process include files if specified
452        if (settings.include_files) {
453            processIncludeFiles(modalId, settings);
454        }
455        
456        // Get the modal elements
457        const $fader = $(`#${modalId}-fader`);
458        const $container = $(`#${modalId}-container`);
459        
460        // Show the modal with optional fade
461        if (settings.fadeInDuration > 0) {
462            $fader.css('animation-duration', settings.fadeInDuration + 's');
463            $container.css('animation-duration', settings.fadeInDuration + 's');
464            
465            $fader.addClass('cm-modal-fade-in').css('display', 'block');
466            $container.addClass('cm-modal-fade-in').css('display', 'block');
467        } else {
468            $fader.css('display', 'block');
469            $container.css('display', 'block');
470        }
471        
472        // Add to active modals set for tracking
473        window.cmActiveModals.add(modalId);
474        
475        // Set up event handlers for closing
476        $fader.on('click', function(e) {
477            // Only close if the fader itself was clicked (not bubbled from container)
478            if (e.target === this) {
479                closeModal(modalId);
480            }
481        });
482        
483        $(document).on('click', `.cm-modal-close[data-modal-id="${modalId}"]`, function() {
484            closeModal(modalId);
485        });
486        
487        // Return the modal ID for potential future reference
488        return modalId;
489    };
490    
491    // Function to close a specific modal
492    function closeModal(modalId) {
493        // Get the fader element for this modal
494        const $fader = $(`#${modalId}-fader`);
495        if ($fader.length === 0) {
496            console.warn(`Fader for modal ${modalId} not found`);
497            window.cmActiveModals.delete(modalId);
498            return;
499        }
500        
501        // Keep track of the parent container before removing the modal
502        const $parentContainer = $fader.parent();
503        const containerId = $parentContainer.attr('id');
504        
505        // Remove from active modals set
506        window.cmActiveModals.delete(modalId);
507        
508        // Execute modal close callbacks BEFORE removing the modal
509        // This allows callbacks to access modal elements if needed
510        
511        // Execute global callbacks
512        window.cmModalCloseCallbacks.forEach(callback => {
513            try {
514                callback(modalId);
515            } catch (e) {
516                console.error('Global modal close callback failed:', e);
517            }
518        });
519        
520        // Execute modal-specific callbacks
521        if (window.cmModalSpecificCallbacks[modalId]) {
522            window.cmModalSpecificCallbacks[modalId].forEach(callback => {
523                try {
524                    callback(modalId);
525                } catch (e) {
526                    console.error('Modal-specific close callback failed:', e);
527                }
528            });
529            
530            // Clean up specific callbacks for this modal
531            delete window.cmModalSpecificCallbacks[modalId];
532        }
533        
534        // Fire custom jQuery event for other systems to listen to
535        $(document).trigger('cm_modal_closed', [modalId]);
536        
537        // Remove the fader and its contents
538        $fader.remove();
539        
540        // Find the entry in our tracking object for this modal
541        if (window.cmModalContainers[modalId]) {
542            // Container info is now stored in the tracking object
543            const containerInfo = window.cmModalContainers[modalId];
544            
545            // Check if the container is now empty
546            if ($parentContainer.children().length === 0) {
547                $parentContainer.remove();
548                
549                // Clean up tracking object
550                delete window.cmModalContainers[modalId];
551            }
552        } else {
553            // If container is empty, remove it anyway
554            if (containerId && containerId.startsWith('cm-modal-wrapper-') && $parentContainer.children().length === 0) {
555                $parentContainer.remove();
556            }
557        }
558    }
559    
560    // Make closeModal available globally for other components
561    window.closeModal = closeModal;
562
563    /**
564     * Convenience function to close all active modals or a specific modal
565     * @param {string} modalId - Optional specific modal ID to close. If omitted, closes all active modals.
566     */
567    window.cmCloseModal = function(modalId) {
568        if (modalId) {
569            // Close specific modal
570            closeModal(modalId);
571        } else {
572            // Close all active modals (most recent first)
573            const activeModals = Array.from(window.cmActiveModals);
574            activeModals.reverse().forEach(id => closeModal(id));
575        }
576    };
577
578    /**
579     * Register a global callback that fires when ANY modal closes
580     * @param {function} callback - Function to call when modal closes. Receives modalId as parameter.
581     */
582    window.registerGlobalModalCloseCallback = function(callback) {
583        if (typeof callback === 'function') {
584            window.cmModalCloseCallbacks.push(callback);
585        } else {
586            console.error('registerGlobalModalCloseCallback: callback must be a function');
587        }
588    };
589    
590    /**
591     * Register a callback for a specific modal ID
592     * @param {string} modalId - The modal ID to listen for
593     * @param {function} callback - Function to call when this specific modal closes
594     */
595    window.registerModalCloseCallback = function(modalId, callback) {
596        if (typeof callback === 'function') {
597            if (!window.cmModalSpecificCallbacks[modalId]) {
598                window.cmModalSpecificCallbacks[modalId] = [];
599            }
600            window.cmModalSpecificCallbacks[modalId].push(callback);
601        } else {
602            console.error('registerModalCloseCallback: callback must be a function');
603        }
604    };
605    
606    /**
607     * Remove all callbacks for a specific modal ID
608     * @param {string} modalId - The modal ID to remove callbacks for
609     */
610    window.unregisterModalCloseCallbacks = function(modalId) {
611        if (window.cmModalSpecificCallbacks[modalId]) {
612            delete window.cmModalSpecificCallbacks[modalId];
613        }
614    };
615    
616    /**
617     * JavaScript wrapper for cmDisplayModal
618     * Can be called directly as a regular function from other JS code
619     * 
620     * @param {string} type_modal - Type of modal ('html', 'youtube')
621     * @param {string} heading - Modal heading
622     * @param {string|HTMLElement} content - Modal content (HTML string or YouTube URL)
623     * @param {Object} additionalOptions - Optional additional parameters
624     * @returns {string} - The ID of the created modal
625     */
626    window.cmOpenModal = function(type_modal, heading, content, additionalOptions = {}) {
627        
628        // Create options object with required parameters
629        const options = {
630            type_modal: type_modal || 'html',
631            heading: heading || '',
632            content: content || ''
633        };
634        
635        // Merge with additional options
636        if (additionalOptions && typeof additionalOptions === 'object') {
637            Object.assign(options, additionalOptions);
638        }
639        
640        // Call the main function
641        return window.cmDisplayModal(options);
642    };
643
644}); 

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.