README.md
5 months ago
wpdm-modal.css
3 months ago
wpdm-modal.js
3 months ago
wpdm-modal.min.css
3 months ago
wpdm-modal.min.js
3 months ago
wpdm-modal.js
603 lines
| 1 | /** |
| 2 | * WPDM Modal Dialog System |
| 3 | * Enterprise-Grade Modal Dialogs for WordPress Download Manager |
| 4 | * Version: 1.1.0 |
| 5 | * |
| 6 | * Usage: |
| 7 | * WPDMDialog.alert('Title', 'Message'); |
| 8 | * WPDMDialog.confirm('Title', 'Message').then(confirmed => { ... }); |
| 9 | * WPDMDialog.prompt('Title', 'Message', { placeholder: 'Enter value' }).then(value => { ... }); |
| 10 | * WPDMDialog.ajax('Title', '/api/endpoint').then(result => { ... }); |
| 11 | * WPDMDialog.ajax('Title', { url: '/api', method: 'POST', data: {...} }, { size: 'lg' }); |
| 12 | * |
| 13 | * Or via WPDM global (if available): |
| 14 | * WPDM.dialog.alert('Title', 'Message'); |
| 15 | * WPDM.dialog.ajax('Title', '/api/endpoint'); |
| 16 | */ |
| 17 | |
| 18 | var WPDMDialog = (function($) { |
| 19 | 'use strict'; |
| 20 | |
| 21 | // SVG Icons |
| 22 | var icons = { |
| 23 | info: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>', |
| 24 | success: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg>', |
| 25 | warning: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>', |
| 26 | danger: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line></svg>', |
| 27 | question: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>', |
| 28 | close: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>' |
| 29 | }; |
| 30 | |
| 31 | // Loading spinner HTML (uses CSS pseudo-elements + span for 3-dot bounce animation) |
| 32 | var loadingSpinner = '<div class="wpdm-dialog__loading"><div class="wpdm-dialog__spinner"><span></span></div><p class="wpdm-dialog__loading-text">Loading...</p></div>'; |
| 33 | |
| 34 | /** |
| 35 | * Detect dark mode from WPDM settings or page classes/attributes |
| 36 | * Priority: WPDM settings > page classes > system preference |
| 37 | * @returns {string} 'light-mode', 'dark-mode', or '' (system preference) |
| 38 | */ |
| 39 | function detectDarkMode() { |
| 40 | |
| 41 | if($('body').hasClass('wp-admin')) return 'light-mode'; |
| 42 | |
| 43 | // Check WPDM color scheme setting first (from wpdm_js localized script) |
| 44 | if (typeof wpdm_js !== 'undefined' && wpdm_js.color_scheme) { |
| 45 | if (wpdm_js.color_scheme === 'light') return 'light-mode'; |
| 46 | if (wpdm_js.color_scheme === 'dark') return 'dark-mode'; |
| 47 | // 'system' falls through to check page classes or return empty |
| 48 | } |
| 49 | |
| 50 | // Check page classes and attributes |
| 51 | var isLightMode = $('body').hasClass('light-mode') || |
| 52 | $('.w3eden').hasClass('light-mode') || |
| 53 | $('html').attr('data-theme') === 'light' || |
| 54 | $('body').attr('data-theme') === 'light'; |
| 55 | var isDarkMode = $('body').hasClass('dark-mode') || |
| 56 | $('.w3eden').hasClass('dark-mode') || |
| 57 | $('html').hasClass('dark-mode') || |
| 58 | $('body').hasClass('dark') || |
| 59 | $('html').attr('data-theme') === 'dark' || |
| 60 | $('body').attr('data-theme') === 'dark'; |
| 61 | |
| 62 | if (isLightMode) return 'light-mode'; |
| 63 | if (isDarkMode) return 'dark-mode'; |
| 64 | return ''; // System preference (CSS media query handles this) |
| 65 | } |
| 66 | |
| 67 | /** |
| 68 | * Escape HTML to prevent XSS |
| 69 | * @param {string} text Text to escape |
| 70 | * @returns {string} Escaped text |
| 71 | */ |
| 72 | function escapeHtml(text) { |
| 73 | if (typeof text !== 'string') return text; |
| 74 | var map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }; |
| 75 | return text.replace(/[&<>"']/g, function(m) { return map[m]; }); |
| 76 | } |
| 77 | |
| 78 | /** |
| 79 | * Generate unique ID for dialog |
| 80 | * @returns {string} Unique ID |
| 81 | */ |
| 82 | function uniqueId() { |
| 83 | return 'wpdm-dialog-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9); |
| 84 | } |
| 85 | |
| 86 | /** |
| 87 | * Create dialog HTML |
| 88 | * @param {Object} options Dialog options |
| 89 | * @returns {string} HTML string |
| 90 | */ |
| 91 | function createDialogHtml(options) { |
| 92 | |
| 93 | var id = options.id || uniqueId(); |
| 94 | var darkModeClass = detectDarkMode(); |
| 95 | var sizeClass = options.size ? 'wpdm-dialog--' + options.size : ''; |
| 96 | var showIcon = options.icon !== false; |
| 97 | var iconType = options.type || 'info'; |
| 98 | |
| 99 | var html = '<div class="wpdm-dialog-wrapper w3eden ' + darkModeClass + '" id="' + id + '">'; |
| 100 | html += '<div class="wpdm-dialog-backdrop"></div>'; |
| 101 | html += '<div class="wpdm-dialog ' + sizeClass + '" role="dialog" aria-modal="true" aria-labelledby="' + id + '-title">'; |
| 102 | |
| 103 | // Close button (if closable) |
| 104 | if (options.closable !== false) { |
| 105 | html += '<button type="button" class="wpdm-dialog__close" aria-label="Close">' + icons.close + '</button>'; |
| 106 | } |
| 107 | |
| 108 | // Header |
| 109 | html += '<div class="wpdm-dialog__header">'; |
| 110 | if (showIcon) { |
| 111 | html += '<div class="wpdm-dialog__icon wpdm-dialog__icon--' + iconType + '">' + icons[iconType] + '</div>'; |
| 112 | } |
| 113 | html += '<div class="wpdm-dialog__header-content">'; |
| 114 | html += '<h3 class="wpdm-dialog__title" id="' + id + '-title">' + escapeHtml(options.title) + '</h3>'; |
| 115 | if (options.subtitle) { |
| 116 | html += '<p class="wpdm-dialog__subtitle">' + escapeHtml(options.subtitle) + '</p>'; |
| 117 | } |
| 118 | html += '</div>'; |
| 119 | html += '</div>'; |
| 120 | |
| 121 | // Body |
| 122 | html += '<div class="wpdm-dialog__body">'; |
| 123 | if (options.message) { |
| 124 | html += '<p class="wpdm-dialog__message">' + (options.html ? options.message : escapeHtml(options.message)) + '</p>'; |
| 125 | } |
| 126 | if (options.input) { |
| 127 | html += '<div class="wpdm-dialog__input-wrapper">'; |
| 128 | html += '<input type="' + (options.inputType || 'text') + '" class="wpdm-dialog__input" placeholder="' + escapeHtml(options.placeholder || '') + '" value="' + escapeHtml(options.inputValue || '') + '">'; |
| 129 | html += '</div>'; |
| 130 | } |
| 131 | if (options.content) { |
| 132 | html += options.content; |
| 133 | } |
| 134 | html += '</div>'; |
| 135 | |
| 136 | // Footer |
| 137 | if (options.buttons && options.buttons.length > 0) { |
| 138 | var footerClass = options.compactFooter ? 'wpdm-dialog__footer wpdm-dialog__footer--compact' : 'wpdm-dialog__footer'; |
| 139 | html += '<div class="' + footerClass + '">'; |
| 140 | options.buttons.forEach(function(btn, index) { |
| 141 | var btnClass = 'wpdm-dialog__btn wpdm-dialog__btn--' + (btn.type || 'secondary'); |
| 142 | html += '<button type="button" class="' + btnClass + '" data-action="' + (btn.action || index) + '">' + escapeHtml(btn.text) + '</button>'; |
| 143 | }); |
| 144 | html += '</div>'; |
| 145 | } |
| 146 | |
| 147 | html += '</div>'; |
| 148 | html += '</div>'; |
| 149 | |
| 150 | return html; |
| 151 | } |
| 152 | |
| 153 | /** |
| 154 | * Show dialog |
| 155 | * @param {Object} options Dialog options |
| 156 | * @returns {Promise} Resolves with { action: string, value: string|null } |
| 157 | */ |
| 158 | function show(options) { |
| 159 | return new Promise(function(resolve) { |
| 160 | var html = createDialogHtml(options); |
| 161 | var $dialog = $(html); |
| 162 | $('body').append($dialog); |
| 163 | |
| 164 | // Focus trap elements |
| 165 | var $wrapper = $dialog; |
| 166 | var $input = $wrapper.find('.wpdm-dialog__input'); |
| 167 | var $buttons = $wrapper.find('.wpdm-dialog__btn'); |
| 168 | var $closeBtn = $wrapper.find('.wpdm-dialog__close'); |
| 169 | |
| 170 | // Show dialog with animation |
| 171 | requestAnimationFrame(function() { |
| 172 | $wrapper.addClass('wpdm-dialog-visible'); |
| 173 | if ($input.length) { |
| 174 | $input.focus().select(); |
| 175 | } else if ($buttons.length) { |
| 176 | $buttons.last().focus(); |
| 177 | } |
| 178 | }); |
| 179 | |
| 180 | // Close function |
| 181 | function close(result) { |
| 182 | $wrapper.removeClass('wpdm-dialog-visible'); |
| 183 | $(document).off('keydown.wpdmDialog'); |
| 184 | setTimeout(function() { |
| 185 | $wrapper.remove(); |
| 186 | resolve(result); |
| 187 | }, 250); |
| 188 | } |
| 189 | |
| 190 | // Button clicks |
| 191 | $buttons.on('click', function() { |
| 192 | var action = $(this).data('action'); |
| 193 | var inputValue = $input.length ? $input.val() : null; |
| 194 | close({ action: action, value: inputValue }); |
| 195 | }); |
| 196 | |
| 197 | // Close button click |
| 198 | $closeBtn.on('click', function() { |
| 199 | close({ action: 'close', value: null }); |
| 200 | }); |
| 201 | |
| 202 | // Backdrop click (if not static) |
| 203 | if (options.backdrop !== 'static') { |
| 204 | $wrapper.find('.wpdm-dialog-backdrop').on('click', function() { |
| 205 | close({ action: 'backdrop', value: null }); |
| 206 | }); |
| 207 | } |
| 208 | |
| 209 | // Escape key |
| 210 | if (options.keyboard !== false) { |
| 211 | $(document).on('keydown.wpdmDialog', function(e) { |
| 212 | if (e.key === 'Escape') { |
| 213 | close({ action: 'escape', value: null }); |
| 214 | } |
| 215 | }); |
| 216 | } |
| 217 | |
| 218 | // Enter key for prompt |
| 219 | if (options.input) { |
| 220 | $input.on('keydown', function(e) { |
| 221 | if (e.key === 'Enter') { |
| 222 | e.preventDefault(); |
| 223 | close({ action: 'confirm', value: $input.val() }); |
| 224 | } |
| 225 | }); |
| 226 | } |
| 227 | }); |
| 228 | } |
| 229 | |
| 230 | // Public API |
| 231 | return { |
| 232 | /** |
| 233 | * Show a custom dialog |
| 234 | * @param {Object} options Dialog options |
| 235 | * @returns {Promise} |
| 236 | */ |
| 237 | show: show, |
| 238 | |
| 239 | /** |
| 240 | * Show an alert dialog |
| 241 | * @param {string} title Dialog title |
| 242 | * @param {string} message Dialog message |
| 243 | * @param {Object} options Additional options |
| 244 | * @returns {Promise<string>} |
| 245 | */ |
| 246 | alert: function(title, message, options) { |
| 247 | options = options || {}; |
| 248 | options.buttons = options.buttons || [ |
| 249 | { text: options.buttonText || 'OK', type: 'primary', action: 'ok' } |
| 250 | ]; |
| 251 | return show({ |
| 252 | title: title, |
| 253 | message: message, |
| 254 | type: options.type || 'info', |
| 255 | icon: options.icon, |
| 256 | size: options.size || 'sm', |
| 257 | html: options.html, |
| 258 | compactFooter: true, |
| 259 | buttons: options.buttons, |
| 260 | backdrop: 'static', |
| 261 | keyboard: true |
| 262 | }).then(function(result) { |
| 263 | return result.action; |
| 264 | }); |
| 265 | }, |
| 266 | |
| 267 | /** |
| 268 | * Show a success alert |
| 269 | * @param {string} title Dialog title |
| 270 | * @param {string} message Dialog message |
| 271 | * @param {Object} options Additional options |
| 272 | * @returns {Promise<string>} |
| 273 | */ |
| 274 | success: function(title, message, options) { |
| 275 | options = options || {}; |
| 276 | options.type = 'success'; |
| 277 | return this.alert(title, message, options); |
| 278 | }, |
| 279 | |
| 280 | /** |
| 281 | * Show a warning alert |
| 282 | * @param {string} title Dialog title |
| 283 | * @param {string} message Dialog message |
| 284 | * @param {Object} options Additional options |
| 285 | * @returns {Promise<boolean>} |
| 286 | */ |
| 287 | warning: function(title, message, options) { |
| 288 | options = options || {}; |
| 289 | options.type = 'warning'; |
| 290 | return this.alert(title, message, options); |
| 291 | }, |
| 292 | |
| 293 | /** |
| 294 | * Show an error alert |
| 295 | * @param {string} title Dialog title |
| 296 | * @param {string} message Dialog message |
| 297 | * @param {Object} options Additional options |
| 298 | * @returns {Promise<boolean>} |
| 299 | */ |
| 300 | error: function(title, message, options) { |
| 301 | options = options || {}; |
| 302 | options.type = 'danger'; |
| 303 | return this.alert(title, message, options); |
| 304 | }, |
| 305 | |
| 306 | /** |
| 307 | * Show a confirm dialog |
| 308 | * @param {string} title Dialog title |
| 309 | * @param {string} message Dialog message |
| 310 | * @param {Object} options Additional options |
| 311 | * @returns {Promise<boolean>} |
| 312 | */ |
| 313 | confirm: function(title, message, options) { |
| 314 | options = options || {}; |
| 315 | options.buttons = options.buttons || [ |
| 316 | { text: options.cancelText || 'Cancel', type: 'secondary', action: 'cancel' }, |
| 317 | { text: options.confirmText || 'Confirm', type: options.confirmType || 'primary', action: 'confirm' } |
| 318 | ]; |
| 319 | return show({ |
| 320 | title: title, |
| 321 | message: message, |
| 322 | type: options.type || 'question', |
| 323 | icon: options.icon, |
| 324 | size: options.size || 'sm', |
| 325 | html: options.html, |
| 326 | buttons: options.buttons, |
| 327 | backdrop: options.backdrop || 'static', |
| 328 | keyboard: options.keyboard !== false |
| 329 | }).then(function(result) { |
| 330 | return result.action === 'confirm'; |
| 331 | }); |
| 332 | }, |
| 333 | |
| 334 | /** |
| 335 | * Show a delete confirm dialog |
| 336 | * @param {string} title Dialog title |
| 337 | * @param {string} message Dialog message |
| 338 | * @param {Object} options Additional options |
| 339 | * @returns {Promise<boolean>} |
| 340 | */ |
| 341 | confirmDelete: function(title, message, options) { |
| 342 | options = options || {}; |
| 343 | options.type = 'danger'; |
| 344 | options.confirmText = options.confirmText || 'Delete'; |
| 345 | options.confirmType = 'danger'; |
| 346 | return this.confirm(title, message, options); |
| 347 | }, |
| 348 | |
| 349 | /** |
| 350 | * Show a prompt dialog |
| 351 | * @param {string} title Dialog title |
| 352 | * @param {string} message Dialog message (optional) |
| 353 | * @param {Object} options Additional options |
| 354 | * @returns {Promise<string|null>} |
| 355 | */ |
| 356 | prompt: function(title, message, options) { |
| 357 | options = options || {}; |
| 358 | return show({ |
| 359 | title: title, |
| 360 | message: message, |
| 361 | type: options.type || 'question', |
| 362 | icon: options.icon, |
| 363 | size: options.size || 'md', |
| 364 | input: true, |
| 365 | inputType: options.inputType || 'text', |
| 366 | inputValue: options.inputValue || '', |
| 367 | placeholder: options.placeholder || '', |
| 368 | html: options.html, |
| 369 | buttons: [ |
| 370 | { text: options.cancelText || 'Cancel', type: 'secondary', action: 'cancel' }, |
| 371 | { text: options.confirmText || 'Submit', type: 'primary', action: 'confirm' } |
| 372 | ], |
| 373 | backdrop: options.backdrop || 'static', |
| 374 | keyboard: options.keyboard !== false |
| 375 | }).then(function(result) { |
| 376 | return result.action === 'confirm' ? result.value : null; |
| 377 | }); |
| 378 | }, |
| 379 | |
| 380 | /** |
| 381 | * Show a dialog with async content loaded via AJAX |
| 382 | * @param {string} title Dialog title |
| 383 | * @param {string|Object} urlOrOptions URL string or AJAX options object |
| 384 | * @param {Object} options Additional dialog options |
| 385 | * @returns {Promise} Resolves with { action: string, data: any } |
| 386 | * |
| 387 | * Usage: |
| 388 | * WPDMDialog.ajax('Title', '/api/endpoint'); |
| 389 | * WPDMDialog.ajax('Title', '/api/endpoint', { size: 'lg' }); |
| 390 | * WPDMDialog.ajax('Title', { url: '/api/endpoint', method: 'POST', data: { id: 123 } }); |
| 391 | */ |
| 392 | ajax: function(title, urlOrOptions, options) { |
| 393 | options = options || {}; |
| 394 | var ajaxOptions = typeof urlOrOptions === 'string' ? { url: urlOrOptions } : urlOrOptions; |
| 395 | |
| 396 | return new Promise(function(resolve, reject) { |
| 397 | var dialogId = uniqueId(); |
| 398 | var darkModeClass = detectDarkMode(); |
| 399 | var sizeClass = options.size ? 'wpdm-dialog--' + options.size : 'wpdm-dialog--md'; |
| 400 | |
| 401 | // Build dialog HTML with loading state |
| 402 | var html = '<div class="wpdm-dialog-wrapper w3eden ' + darkModeClass + '" id="' + dialogId + '">'; |
| 403 | html += '<div class="wpdm-dialog-backdrop"></div>'; |
| 404 | html += '<div class="wpdm-dialog ' + sizeClass + '" role="dialog" aria-modal="true" aria-labelledby="' + dialogId + '-title">'; |
| 405 | |
| 406 | // Close button |
| 407 | if (options.closable !== false) { |
| 408 | html += '<button type="button" class="wpdm-dialog__close" aria-label="Close">' + icons.close + '</button>'; |
| 409 | } |
| 410 | |
| 411 | // Header |
| 412 | html += '<div class="wpdm-dialog__header">'; |
| 413 | if (options.icon !== false) { |
| 414 | var iconType = options.type || 'info'; |
| 415 | html += '<div class="wpdm-dialog__icon wpdm-dialog__icon--' + iconType + '">' + icons[iconType] + '</div>'; |
| 416 | } |
| 417 | html += '<div class="wpdm-dialog__header-content">'; |
| 418 | html += '<h3 class="wpdm-dialog__title" id="' + dialogId + '-title">' + escapeHtml(title) + '</h3>'; |
| 419 | if (options.subtitle) { |
| 420 | html += '<p class="wpdm-dialog__subtitle">' + escapeHtml(options.subtitle) + '</p>'; |
| 421 | } |
| 422 | html += '</div></div>'; |
| 423 | |
| 424 | // Body with loading spinner |
| 425 | html += '<div class="wpdm-dialog__body wpdm-dialog__body--ajax">' + loadingSpinner + '</div>'; |
| 426 | |
| 427 | // Footer placeholder (will be updated after AJAX) |
| 428 | html += '<div class="wpdm-dialog__footer wpdm-dialog__footer--hidden"></div>'; |
| 429 | |
| 430 | html += '</div></div>'; |
| 431 | |
| 432 | var $dialog = $(html); |
| 433 | $('body').append($dialog); |
| 434 | |
| 435 | var $wrapper = $dialog; |
| 436 | var $body = $wrapper.find('.wpdm-dialog__body'); |
| 437 | var $footer = $wrapper.find('.wpdm-dialog__footer'); |
| 438 | var $closeBtn = $wrapper.find('.wpdm-dialog__close'); |
| 439 | |
| 440 | // Show dialog |
| 441 | requestAnimationFrame(function() { |
| 442 | $wrapper.addClass('wpdm-dialog-visible'); |
| 443 | }); |
| 444 | |
| 445 | // Close function |
| 446 | function close(result) { |
| 447 | $wrapper.removeClass('wpdm-dialog-visible'); |
| 448 | $(document).off('keydown.wpdmDialog'); |
| 449 | setTimeout(function() { |
| 450 | $wrapper.remove(); |
| 451 | resolve(result); |
| 452 | }, 250); |
| 453 | } |
| 454 | |
| 455 | // Close button click |
| 456 | $closeBtn.on('click', function() { |
| 457 | close({ action: 'close', data: null }); |
| 458 | }); |
| 459 | |
| 460 | // Backdrop click |
| 461 | if (options.backdrop !== 'static') { |
| 462 | $wrapper.find('.wpdm-dialog-backdrop').on('click', function() { |
| 463 | close({ action: 'backdrop', data: null }); |
| 464 | }); |
| 465 | } |
| 466 | |
| 467 | // Escape key |
| 468 | if (options.keyboard !== false) { |
| 469 | $(document).on('keydown.wpdmDialog', function(e) { |
| 470 | if (e.key === 'Escape') { |
| 471 | close({ action: 'escape', data: null }); |
| 472 | } |
| 473 | }); |
| 474 | } |
| 475 | |
| 476 | // Make AJAX request |
| 477 | $.ajax({ |
| 478 | url: ajaxOptions.url, |
| 479 | method: ajaxOptions.method || 'GET', |
| 480 | data: ajaxOptions.data || {}, |
| 481 | dataType: ajaxOptions.dataType || 'html', |
| 482 | headers: ajaxOptions.headers || {}, |
| 483 | timeout: ajaxOptions.timeout || 30000 |
| 484 | }).done(function(response) { |
| 485 | // Handle JSON response with content field |
| 486 | var content = response; |
| 487 | var buttons = options.buttons; |
| 488 | var responseData = null; |
| 489 | |
| 490 | if (typeof response === 'object') { |
| 491 | responseData = response; |
| 492 | content = response.content || response.html || response.body || ''; |
| 493 | // Allow response to override buttons |
| 494 | if (response.buttons) { |
| 495 | buttons = response.buttons; |
| 496 | } |
| 497 | } |
| 498 | |
| 499 | // Update body content with animation |
| 500 | $body.removeClass('wpdm-dialog__body--ajax wpdm-dialog__body--loaded').html(content); |
| 501 | |
| 502 | // Trigger reflow and add loaded class for animation |
| 503 | requestAnimationFrame(function() { |
| 504 | $body.addClass('wpdm-dialog__body--loaded'); |
| 505 | }); |
| 506 | |
| 507 | // Update footer with buttons if provided |
| 508 | if (buttons && buttons.length > 0) { |
| 509 | var footerHtml = ''; |
| 510 | buttons.forEach(function(btn, index) { |
| 511 | var btnClass = 'wpdm-dialog__btn wpdm-dialog__btn--' + (btn.type || 'secondary'); |
| 512 | footerHtml += '<button type="button" class="' + btnClass + '" data-action="' + (btn.action || index) + '">' + escapeHtml(btn.text) + '</button>'; |
| 513 | }); |
| 514 | $footer.html(footerHtml).removeClass('wpdm-dialog__footer--hidden'); |
| 515 | |
| 516 | // Bind button clicks |
| 517 | $footer.find('.wpdm-dialog__btn').on('click', function() { |
| 518 | var action = $(this).data('action'); |
| 519 | close({ action: action, data: responseData }); |
| 520 | }); |
| 521 | } |
| 522 | |
| 523 | // Run callback if provided |
| 524 | if (typeof options.onLoad === 'function') { |
| 525 | options.onLoad($body, responseData); |
| 526 | } |
| 527 | |
| 528 | }).fail(function(xhr, status, error) { |
| 529 | // Show error state |
| 530 | var errorHtml = '<div class="wpdm-dialog__error">'; |
| 531 | errorHtml += '<div class="wpdm-dialog__error-icon">' + icons.danger + '</div>'; |
| 532 | errorHtml += '<p class="wpdm-dialog__error-message">' + escapeHtml(options.errorMessage || 'Failed to load content. Please try again.') + '</p>'; |
| 533 | if (options.showRetry !== false) { |
| 534 | errorHtml += '<button type="button" class="wpdm-dialog__btn wpdm-dialog__btn--secondary wpdm-dialog__retry">Retry</button>'; |
| 535 | } |
| 536 | errorHtml += '</div>'; |
| 537 | |
| 538 | // Update body with error and animate |
| 539 | $body.removeClass('wpdm-dialog__body--ajax wpdm-dialog__body--loaded').html(errorHtml); |
| 540 | requestAnimationFrame(function() { |
| 541 | $body.addClass('wpdm-dialog__body--loaded'); |
| 542 | }); |
| 543 | |
| 544 | // Retry button |
| 545 | $body.find('.wpdm-dialog__retry').on('click', function() { |
| 546 | $body.addClass('wpdm-dialog__body--ajax').html(loadingSpinner); |
| 547 | // Re-trigger AJAX |
| 548 | $.ajax({ |
| 549 | url: ajaxOptions.url, |
| 550 | method: ajaxOptions.method || 'GET', |
| 551 | data: ajaxOptions.data || {}, |
| 552 | dataType: ajaxOptions.dataType || 'html', |
| 553 | headers: ajaxOptions.headers || {}, |
| 554 | timeout: ajaxOptions.timeout || 30000 |
| 555 | }).done(function(response) { |
| 556 | var content = response; |
| 557 | if (typeof response === 'object') { |
| 558 | content = response.content || response.html || response.body || ''; |
| 559 | } |
| 560 | $body.removeClass('wpdm-dialog__body--ajax wpdm-dialog__body--loaded').html(content); |
| 561 | requestAnimationFrame(function() { |
| 562 | $body.addClass('wpdm-dialog__body--loaded'); |
| 563 | }); |
| 564 | if (typeof options.onLoad === 'function') { |
| 565 | options.onLoad($body, response); |
| 566 | } |
| 567 | }).fail(function() { |
| 568 | $body.removeClass('wpdm-dialog__body--ajax wpdm-dialog__body--loaded').html(errorHtml); |
| 569 | requestAnimationFrame(function() { |
| 570 | $body.addClass('wpdm-dialog__body--loaded'); |
| 571 | }); |
| 572 | }); |
| 573 | }); |
| 574 | |
| 575 | // Call error callback if provided |
| 576 | if (typeof options.onError === 'function') { |
| 577 | options.onError(xhr, status, error); |
| 578 | } |
| 579 | }); |
| 580 | }); |
| 581 | }, |
| 582 | |
| 583 | /** |
| 584 | * Show a dialog and load content into body from URL |
| 585 | * Alias for ajax() with simplified options |
| 586 | * @param {string} title Dialog title |
| 587 | * @param {string} url URL to load |
| 588 | * @param {Object} options Additional options |
| 589 | * @returns {Promise} |
| 590 | */ |
| 591 | load: function(title, url, options) { |
| 592 | return this.ajax(title, url, options); |
| 593 | } |
| 594 | }; |
| 595 | })(jQuery); |
| 596 | |
| 597 | // Attach to WPDM global object after all JS is fully loaded |
| 598 | jQuery(function($) { |
| 599 | if (typeof WPDM !== 'undefined') { |
| 600 | WPDM.dialog = WPDMDialog; |
| 601 | } |
| 602 | }); |
| 603 |