web-mojo 2.2.102 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/CHANGELOG.md +131 -0
  2. package/dist/admin-models.cjs.js +1 -1
  3. package/dist/admin-models.es.js +1 -1
  4. package/dist/admin.cjs.js +1 -1
  5. package/dist/admin.es.js +1 -1
  6. package/dist/auth.cjs.js +1 -1
  7. package/dist/auth.cjs.js.map +1 -1
  8. package/dist/auth.es.js +1 -1
  9. package/dist/auth.es.js.map +1 -1
  10. package/dist/charts.cjs.js +1 -1
  11. package/dist/charts.es.js +1 -1
  12. package/dist/chunks/{AssistantPanelView-CMRTtoqS.js → AssistantPanelView-CE6me7sJ.js} +2 -2
  13. package/dist/chunks/{AssistantPanelView-CMRTtoqS.js.map → AssistantPanelView-CE6me7sJ.js.map} +1 -1
  14. package/dist/chunks/{AssistantPanelView-CaVkWhVD.js → AssistantPanelView-DvsLAAIF.js} +2 -2
  15. package/dist/chunks/{AssistantPanelView-CaVkWhVD.js.map → AssistantPanelView-DvsLAAIF.js.map} +1 -1
  16. package/dist/chunks/{ChatView-B73uox2v.js → ChatView-CXykdAV1.js} +2 -2
  17. package/dist/chunks/{ChatView-B73uox2v.js.map → ChatView-CXykdAV1.js.map} +1 -1
  18. package/dist/chunks/{ChatView-W8daOwIo.js → ChatView-ys5MIiBf.js} +2 -2
  19. package/dist/chunks/{ChatView-W8daOwIo.js.map → ChatView-ys5MIiBf.js.map} +1 -1
  20. package/dist/chunks/{Collection-BZlmtcuL.js → Collection-BpUmNuDZ.js} +2 -2
  21. package/dist/chunks/{Collection-BZlmtcuL.js.map → Collection-BpUmNuDZ.js.map} +1 -1
  22. package/dist/chunks/{Collection-Bwoq6muu.js → Collection-CY6BblFn.js} +2 -2
  23. package/dist/chunks/{Collection-Bwoq6muu.js.map → Collection-CY6BblFn.js.map} +1 -1
  24. package/dist/chunks/{ContextMenu-q76hjQb6.js → ContextMenu-PtN50qH2.js} +2 -2
  25. package/dist/chunks/{ContextMenu-q76hjQb6.js.map → ContextMenu-PtN50qH2.js.map} +1 -1
  26. package/dist/chunks/{ContextMenu-BPPtuqKk.js → ContextMenu-XQVFU0mL.js} +2 -2
  27. package/dist/chunks/{ContextMenu-BPPtuqKk.js.map → ContextMenu-XQVFU0mL.js.map} +1 -1
  28. package/dist/chunks/{DataView-k-7wmk5_.js → DataView-BTi_BZHx.js} +2 -2
  29. package/dist/chunks/{DataView-k-7wmk5_.js.map → DataView-BTi_BZHx.js.map} +1 -1
  30. package/dist/chunks/{DataView-BbrwHMV4.js → DataView-WHRh1o6E.js} +2 -2
  31. package/dist/chunks/{DataView-BbrwHMV4.js.map → DataView-WHRh1o6E.js.map} +1 -1
  32. package/dist/chunks/{FormView-Dcy7XOtC.js → FormView-BzaGMf5_.js} +3 -3
  33. package/dist/chunks/{FormView-Dcy7XOtC.js.map → FormView-BzaGMf5_.js.map} +1 -1
  34. package/dist/chunks/{FormView-DPSuwWMq.js → FormView-DTD-Zy22.js} +3 -3
  35. package/dist/chunks/{FormView-DPSuwWMq.js.map → FormView-DTD-Zy22.js.map} +1 -1
  36. package/dist/chunks/{ListView-iGBsD4a7.js → ListView-BsnnTcmC.js} +2 -2
  37. package/dist/chunks/{ListView-iGBsD4a7.js.map → ListView-BsnnTcmC.js.map} +1 -1
  38. package/dist/chunks/{ListView-DHC-yBIw.js → ListView-Xf7kO6Me.js} +2 -2
  39. package/dist/chunks/{ListView-DHC-yBIw.js.map → ListView-Xf7kO6Me.js.map} +1 -1
  40. package/dist/chunks/{MetricsCountryMapView-CAD9wR_T.js → MetricsCountryMapView-BzGOi1d2.js} +2 -2
  41. package/dist/chunks/{MetricsCountryMapView-CAD9wR_T.js.map → MetricsCountryMapView-BzGOi1d2.js.map} +1 -1
  42. package/dist/chunks/{MetricsCountryMapView-Dzk3Yrzx.js → MetricsCountryMapView-C-S00Wiw.js} +2 -2
  43. package/dist/chunks/{MetricsCountryMapView-Dzk3Yrzx.js.map → MetricsCountryMapView-C-S00Wiw.js.map} +1 -1
  44. package/dist/chunks/{Modal-DuULCMFZ.js → Modal-GWjyfcz5.js} +3 -3
  45. package/dist/chunks/Modal-GWjyfcz5.js.map +1 -0
  46. package/dist/chunks/{Modal-DBJU16cc.js → Modal-wrfWfQhv.js} +3 -3
  47. package/dist/chunks/Modal-wrfWfQhv.js.map +1 -0
  48. package/dist/chunks/{Passkeys-CGRZ8ZMv.js → Passkeys-BviQX3_5.js} +2 -2
  49. package/dist/chunks/{Passkeys-CGRZ8ZMv.js.map → Passkeys-BviQX3_5.js.map} +1 -1
  50. package/dist/chunks/{Passkeys-Dr8-oSm9.js → Passkeys-gXR1Rc6C.js} +2 -2
  51. package/dist/chunks/{Passkeys-Dr8-oSm9.js.map → Passkeys-gXR1Rc6C.js.map} +1 -1
  52. package/dist/chunks/TokenManager-C6aXkRaI.js +2 -0
  53. package/dist/chunks/TokenManager-C6aXkRaI.js.map +1 -0
  54. package/dist/chunks/TokenManager-DgvhhTqN.js +2 -0
  55. package/dist/chunks/TokenManager-DgvhhTqN.js.map +1 -0
  56. package/dist/chunks/{User-Dg7xpYEI.js → User-B_Urf7U7.js} +2 -2
  57. package/dist/chunks/{User-Dg7xpYEI.js.map → User-B_Urf7U7.js.map} +1 -1
  58. package/dist/chunks/{User-DNQhdBtI.js → User-KTBU_5cr.js} +2 -2
  59. package/dist/chunks/{User-DNQhdBtI.js.map → User-KTBU_5cr.js.map} +1 -1
  60. package/dist/chunks/{UserProfileView-Bpz3VZmP.js → UserProfileView-BmduMJ86.js} +2 -2
  61. package/dist/chunks/{UserProfileView-Bpz3VZmP.js.map → UserProfileView-BmduMJ86.js.map} +1 -1
  62. package/dist/chunks/{UserProfileView-B5nczdfw.js → UserProfileView-COxSyPB0.js} +2 -2
  63. package/dist/chunks/{UserProfileView-B5nczdfw.js.map → UserProfileView-COxSyPB0.js.map} +1 -1
  64. package/dist/chunks/{View-Yazho7OL.js → View-C8UWvaSM.js} +2 -2
  65. package/dist/chunks/{View-Yazho7OL.js.map → View-C8UWvaSM.js.map} +1 -1
  66. package/dist/chunks/{View-C5n3sIFi.js → View-Cvs2TY7b.js} +2 -2
  67. package/dist/chunks/{View-C5n3sIFi.js.map → View-Cvs2TY7b.js.map} +1 -1
  68. package/dist/chunks/{WebApp-CeiDNV6L.js → WebApp-DuwanN2O.js} +2 -2
  69. package/dist/chunks/{WebApp-CeiDNV6L.js.map → WebApp-DuwanN2O.js.map} +1 -1
  70. package/dist/chunks/{WebApp-irKlhuFX.js → WebApp-kbRq7dM_.js} +2 -2
  71. package/dist/chunks/{WebApp-irKlhuFX.js.map → WebApp-kbRq7dM_.js.map} +1 -1
  72. package/dist/chunks/admin-CszsTA0T.js +2 -0
  73. package/dist/chunks/admin-CszsTA0T.js.map +1 -0
  74. package/dist/chunks/admin-vsKDrnpV.js +2 -0
  75. package/dist/chunks/admin-vsKDrnpV.js.map +1 -0
  76. package/dist/chunks/{exportChart-Dk8D_du5.js → exportChart-Bkxr7mCe.js} +2 -2
  77. package/dist/chunks/exportChart-Bkxr7mCe.js.map +1 -0
  78. package/dist/chunks/{exportChart-DbsHDCxw.js → exportChart-Dn2pioNl.js} +2 -2
  79. package/dist/chunks/exportChart-Dn2pioNl.js.map +1 -0
  80. package/dist/chunks/{index-BNjCQA7q.js → index-BCWkcyOy.js} +2 -2
  81. package/dist/chunks/{index-BNjCQA7q.js.map → index-BCWkcyOy.js.map} +1 -1
  82. package/dist/chunks/{index-DBsIDOAa.js → index-CJeTVskY.js} +2 -2
  83. package/dist/chunks/{index-DBsIDOAa.js.map → index-CJeTVskY.js.map} +1 -1
  84. package/dist/chunks/version-CRFysUYb.js +2 -0
  85. package/dist/chunks/version-CRFysUYb.js.map +1 -0
  86. package/dist/chunks/version-uUvnEzFR.js +2 -0
  87. package/dist/chunks/version-uUvnEzFR.js.map +1 -0
  88. package/dist/core.css +134 -109
  89. package/dist/css/web-mojo.css +1 -1
  90. package/dist/docit.cjs.js +1 -1
  91. package/dist/docit.es.js +1 -1
  92. package/dist/index.cjs.js +1 -1
  93. package/dist/index.cjs.js.map +1 -1
  94. package/dist/index.es.js +1 -1
  95. package/dist/index.es.js.map +1 -1
  96. package/dist/lightbox.cjs.js +1 -1
  97. package/dist/lightbox.es.js +1 -1
  98. package/dist/map.cjs.js +1 -1
  99. package/dist/map.es.js +1 -1
  100. package/dist/mojo-auth.es.js +94 -65
  101. package/dist/mojo-auth.umd.js +1 -1
  102. package/dist/portal.css +32 -0
  103. package/dist/timeline.cjs.js +1 -1
  104. package/dist/timeline.es.js +1 -1
  105. package/dist/user-profile.cjs.js +1 -1
  106. package/dist/user-profile.es.js +1 -1
  107. package/dist/web-mojo.lite.iife.js +33 -9
  108. package/dist/web-mojo.lite.iife.js.map +1 -1
  109. package/dist/web-mojo.lite.iife.min.js +5 -5
  110. package/dist/web-mojo.lite.iife.min.js.map +1 -1
  111. package/package.json +1 -1
  112. package/dist/chunks/Modal-DBJU16cc.js.map +0 -1
  113. package/dist/chunks/Modal-DuULCMFZ.js.map +0 -1
  114. package/dist/chunks/TokenManager-CFsr1qUV.js +0 -2
  115. package/dist/chunks/TokenManager-CFsr1qUV.js.map +0 -1
  116. package/dist/chunks/TokenManager-CHQxK_e5.js +0 -2
  117. package/dist/chunks/TokenManager-CHQxK_e5.js.map +0 -1
  118. package/dist/chunks/admin-BkxeK68u.js +0 -2
  119. package/dist/chunks/admin-BkxeK68u.js.map +0 -1
  120. package/dist/chunks/admin-vjoNbv_1.js +0 -2
  121. package/dist/chunks/admin-vjoNbv_1.js.map +0 -1
  122. package/dist/chunks/exportChart-DbsHDCxw.js.map +0 -1
  123. package/dist/chunks/exportChart-Dk8D_du5.js.map +0 -1
  124. package/dist/chunks/version-B0cBv8MN.js +0 -2
  125. package/dist/chunks/version-B0cBv8MN.js.map +0 -1
  126. package/dist/chunks/version-DtqCY0ZY.js +0 -2
  127. package/dist/chunks/version-DtqCY0ZY.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Modal-GWjyfcz5.js","sources":["../../src/core/services/ToastService.js","../../src/core/views/feedback/ProgressView.js","../../src/core/services/FileUpload.js","../../src/core/models/Files.js","../../src/core/views/feedback/ModalView.js","../../src/core/views/feedback/BusyIndicator.js","../../src/core/views/feedback/CodeViewer.js","../../src/core/views/feedback/HtmlPreview.js","../../src/core/views/feedback/Modal.js"],"sourcesContent":["/**\n * ToastService - Bootstrap 5 Toast Notification Service\n * Provides methods to display toast notifications with different types and options\n *\n * Features:\n * - Bootstrap 5 toast integration\n * - Multiple toast types (success, error, info, warning)\n * - Auto-dismiss with customizable delays\n * - Toast container management\n * - Event integration\n * - Proper cleanup and memory management\n *\n * @example\n * const toastService = new ToastService();\n * toastService.success('Operation completed successfully!');\n * toastService.error('Something went wrong');\n * toastService.info('FYI: This is informational');\n * toastService.warning('Please be careful');\n */\n\nclass ToastService {\n constructor(options = {}) {\n this.options = {\n containerId: 'toast-container',\n position: 'top-end', // top-start, top-center, top-end, middle-start, etc.\n autohide: true,\n defaultDelay: 3000, // 3 seconds\n maxToasts: 5, // Maximum number of toasts to show at once\n ...options\n };\n\n this.toasts = new Map(); // Track active toasts\n this.toastCounter = 0; // For unique IDs\n\n this.init();\n }\n\n /**\n * Initialize the toast service\n */\n init() {\n this.createContainer();\n }\n\n /**\n * Create the toast container if it doesn't exist\n */\n createContainer() {\n let container = document.getElementById(this.options.containerId);\n\n if (!container) {\n container = document.createElement('div');\n container.id = this.options.containerId;\n container.className = `toast-container position-fixed ${this.getPositionClasses()}`;\n container.style.zIndex = '1070'; // Bootstrap toast z-index\n container.setAttribute('aria-live', 'polite');\n container.setAttribute('aria-atomic', 'true');\n\n document.body.appendChild(container);\n }\n\n this.container = container;\n }\n\n /**\n * Get CSS classes for toast positioning\n */\n getPositionClasses() {\n const positionMap = {\n 'top-start': 'top-0 start-0 p-3',\n 'top-center': 'top-0 start-50 translate-middle-x p-3',\n 'top-end': 'top-0 end-0 p-3',\n 'middle-start': 'top-50 start-0 translate-middle-y p-3',\n 'middle-center': 'top-50 start-50 translate-middle p-3',\n 'middle-end': 'top-50 end-0 translate-middle-y p-3',\n 'bottom-start': 'bottom-0 start-0 p-3',\n 'bottom-center': 'bottom-0 start-50 translate-middle-x p-3',\n 'bottom-end': 'bottom-0 end-0 p-3'\n };\n\n return positionMap[this.options.position] || positionMap['top-end'];\n }\n\n\n\n /**\n * Show a success toast\n * @param {string} message - The message to display\n * @param {object} options - Additional options\n */\n success(message, options = {}) {\n return this.show(message, 'success', {\n icon: 'bi-check-circle-fill',\n ...options\n });\n }\n\n /**\n * Show an error toast\n * @param {string} message - The message to display\n * @param {object} options - Additional options\n */\n error(message, options = {}) {\n return this.show(message, 'error', {\n icon: 'bi-exclamation-triangle-fill',\n autohide: true, // Keep error toasts visible until manually dismissed\n ...options\n });\n }\n\n /**\n * Show an info toast\n * @param {string} message - The message to display\n * @param {object} options - Additional options\n */\n info(message, options = {}) {\n return this.show(message, 'info', {\n icon: 'bi-info-circle-fill',\n ...options\n });\n }\n\n /**\n * Show a warning toast\n * @param {string} message - The message to display\n * @param {object} options - Additional options\n */\n warning(message, options = {}) {\n return this.show(message, 'warning', {\n icon: 'bi-exclamation-triangle-fill',\n ...options\n });\n }\n\n /**\n * Show a plain toast without specific styling\n * @param {string} message - The message to display\n * @param {object} options - Additional options\n */\n plain(message, options = {}) {\n return this.show(message, 'plain', {\n ...options\n });\n }\n\n /**\n * Show a toast with specified type and options\n * @param {string} message - The message to display\n * @param {string} type - Toast type (success, error, info, warning)\n * @param {object} options - Additional options\n */\n show(message, type = 'info', options = {}) {\n // Enforce max toasts limit\n this.enforceMaxToasts();\n\n const toastId = `toast-${++this.toastCounter}`;\n const config = {\n title: this.getDefaultTitle(type),\n icon: this.getDefaultIcon(type),\n autohide: this.options.autohide,\n delay: this.options.defaultDelay,\n dismissible: true,\n ...options\n };\n\n const toastElement = this.createToastElement(toastId, message, type, config);\n this.container.appendChild(toastElement);\n\n // Initialize Bootstrap toast\n if (typeof bootstrap === 'undefined') {\n throw new Error('Bootstrap is required for ToastService. Make sure Bootstrap 5 is loaded.');\n }\n const bsToast = new bootstrap.Toast(toastElement, {\n autohide: config.autohide,\n delay: config.delay\n });\n\n // Store toast reference\n this.toasts.set(toastId, {\n element: toastElement,\n bootstrap: bsToast,\n type: type,\n message: message\n });\n\n // Setup cleanup on hide\n toastElement.addEventListener('hidden.bs.toast', () => {\n this.cleanup(toastId);\n });\n\n // Show the toast\n bsToast.show();\n\n return {\n id: toastId,\n hide: () => {\n try {\n bsToast.hide();\n } catch (error) {\n console.warn('Error hiding toast:', error);\n }\n },\n dispose: () => this.cleanup(toastId),\n updateProgress: options.updateProgress || null\n };\n }\n\n /**\n * Show a toast with a View component in the body\n * @param {View} view - The View component to display\n * @param {string} type - Toast type (success, error, info, warning, plain)\n * @param {object} options - Additional options\n */\n showView(view, type = 'info', options = {}) {\n // Enforce max toasts limit\n this.enforceMaxToasts();\n\n const toastId = `toast-${++this.toastCounter}`;\n const config = {\n title: options.title || this.getDefaultTitle(type),\n icon: options.icon || this.getDefaultIcon(type),\n autohide: this.options.autohide,\n delay: this.options.defaultDelay,\n dismissible: true,\n size: 'md', // View toasts default to medium\n ...options\n };\n\n const toastElement = this.createViewToastElement(toastId, view, type, config);\n this.container.appendChild(toastElement);\n\n // Initialize Bootstrap toast\n if (typeof bootstrap === 'undefined') {\n throw new Error('Bootstrap is required for ToastService. Make sure Bootstrap 5 is loaded.');\n }\n const bsToast = new bootstrap.Toast(toastElement, {\n autohide: config.autohide,\n delay: config.delay\n });\n\n // Store toast reference with view\n this.toasts.set(toastId, {\n element: toastElement,\n bootstrap: bsToast,\n type: type,\n view: view,\n message: 'View toast'\n });\n\n // Setup cleanup on hide - dispose view properly\n toastElement.addEventListener('hidden.bs.toast', () => {\n this.cleanupView(toastId);\n });\n\n // Mount and render the view\n const bodyContainer = toastElement.querySelector('.toast-view-body');\n if (bodyContainer && view) {\n view.render(true, bodyContainer);\n }\n\n // Show the toast\n bsToast.show();\n\n return {\n id: toastId,\n view: view,\n hide: () => {\n try {\n bsToast.hide();\n } catch (error) {\n console.warn('Error hiding view toast:', error);\n }\n },\n dispose: () => this.cleanupView(toastId),\n updateProgress: (progressInfo) => {\n if (view && typeof view.updateProgress === 'function') {\n view.updateProgress(progressInfo);\n }\n }\n };\n }\n\n /**\n * Create toast DOM element\n */\n createToastElement(id, message, type, config) {\n const toast = document.createElement('div');\n toast.id = id;\n toast.className = `toast toast-service-${type}${config.size ? ` toast-${config.size}` : ''}`;\n toast.setAttribute('role', 'alert');\n toast.setAttribute('aria-live', 'assertive');\n toast.setAttribute('aria-atomic', 'true');\n\n const header = config.title || config.icon ? this.createToastHeader(config, type) : '';\n const body = this.createToastBody(message, config.icon && !config.title);\n\n toast.innerHTML = `\n ${header}\n ${body}\n `;\n\n return toast;\n }\n\n /**\n * Create toast DOM element for View component\n */\n createViewToastElement(id, view, type, config) {\n const toast = document.createElement('div');\n toast.id = id;\n toast.className = `toast toast-service-${type}${config.size ? ` toast-${config.size}` : ''}`;\n toast.setAttribute('role', 'alert');\n toast.setAttribute('aria-live', 'assertive');\n toast.setAttribute('aria-atomic', 'true');\n\n const header = config.title || config.icon ? this.createToastHeader(config, type) : '';\n const body = this.createViewToastBody();\n\n toast.innerHTML = `\n ${header}\n ${body}\n `;\n\n return toast;\n }\n\n /**\n * Create toast body for View component\n */\n createViewToastBody() {\n return `\n <div class=\"toast-body p-0\">\n <div class=\"toast-view-body p-3\"></div>\n </div>\n `;\n }\n\n /**\n * Create toast header with title and icon\n */\n createToastHeader(config, _type) {\n const iconHtml = config.icon ?\n `<i class=\"${config.icon} toast-service-icon me-2\"></i>` : '';\n\n const titleHtml = config.title ?\n `<strong class=\"me-auto\">${iconHtml}${this.escapeHtml(config.title)}</strong>` : '';\n\n const timeHtml = config.showTime ?\n `<small class=\"text-muted\">${this.getTimeString()}</small>` : '';\n\n const closeButton = config.dismissible ?\n `<button type=\"button\" class=\"btn-close toast-service-close\" data-bs-dismiss=\"toast\" aria-label=\"Close\"></button>` : '';\n\n if (!titleHtml && !timeHtml && !closeButton) {\n return '';\n }\n\n return `\n <div class=\"toast-header\">\n ${titleHtml}\n ${timeHtml}\n ${closeButton}\n </div>\n `;\n }\n\n /**\n * Create toast body with message\n */\n createToastBody(message, showIcon = false) {\n const iconHtml = showIcon ?\n `<i class=\"${this.getDefaultIcon('info')} toast-service-icon me-2\"></i>` : '';\n\n return `\n <div class=\"toast-body d-flex align-items-center\">\n ${iconHtml}\n <span>${this.escapeHtml(message)}</span>\n </div>\n `;\n }\n\n /**\n * Get default title for toast type\n */\n getDefaultTitle(type) {\n const titles = {\n success: 'Success',\n error: 'Error',\n warning: 'Warning',\n info: 'Information',\n plain: ''\n };\n return titles[type] || 'Notification';\n }\n\n /**\n * Get default icon for toast type\n */\n getDefaultIcon(type) {\n const icons = {\n success: 'bi-check-circle-fill',\n error: 'bi-exclamation-triangle-fill',\n warning: 'bi-exclamation-triangle-fill',\n info: 'bi-info-circle-fill',\n plain: ''\n };\n return icons[type] || 'bi-info-circle-fill';\n }\n\n /**\n * Enforce maximum number of toasts\n */\n enforceMaxToasts() {\n const activeToasts = Array.from(this.toasts.values());\n\n if (activeToasts.length >= this.options.maxToasts) {\n // Remove oldest toast\n const oldestId = this.toasts.keys().next().value;\n const oldest = this.toasts.get(oldestId);\n\n if (oldest) {\n oldest.bootstrap.hide();\n }\n }\n }\n\n /**\n * Clean up toast resources\n */\n cleanup(toastId) {\n const toast = this.toasts.get(toastId);\n\n if (toast) {\n // Dispose Bootstrap toast\n try {\n toast.bootstrap.dispose();\n } catch (e) {\n console.warn('Error disposing toast:', e);\n }\n\n // Remove from DOM\n if (toast.element && toast.element.parentNode) {\n toast.element.parentNode.removeChild(toast.element);\n }\n\n // Remove from tracking\n this.toasts.delete(toastId);\n }\n }\n\n /**\n * Clean up view toast resources with proper view disposal\n */\n cleanupView(toastId) {\n const toast = this.toasts.get(toastId);\n\n if (toast) {\n // Dispose view first if it exists\n if (toast.view && typeof toast.view.dispose === 'function') {\n try {\n toast.view.dispose();\n } catch (e) {\n console.warn('Error disposing view in toast:', e);\n }\n }\n\n // Dispose Bootstrap toast\n try {\n toast.bootstrap.dispose();\n } catch (e) {\n console.warn('Error disposing toast:', e);\n }\n\n // Remove from DOM\n if (toast.element && toast.element.parentNode) {\n toast.element.parentNode.removeChild(toast.element);\n }\n\n // Remove from tracking\n this.toasts.delete(toastId);\n }\n }\n\n /**\n * Hide all active toasts\n */\n hideAll() {\n this.toasts.forEach((toast, _id) => {\n toast.bootstrap.hide();\n });\n }\n\n /**\n * Clear all toasts immediately\n */\n clearAll() {\n this.toasts.forEach((toast, id) => {\n this.cleanup(id);\n });\n }\n\n /**\n * Get current time string\n */\n getTimeString() {\n return new Date().toLocaleTimeString([], {\n hour: '2-digit',\n minute: '2-digit'\n });\n }\n\n /**\n * Escape HTML to prevent XSS\n */\n escapeHtml(str) {\n const div = document.createElement('div');\n div.textContent = str;\n return div.innerHTML;\n }\n\n /**\n * Dispose of the entire toast service\n */\n dispose() {\n this.clearAll();\n\n if (this.container && this.container.parentNode) {\n this.container.parentNode.removeChild(this.container);\n }\n }\n\n /**\n * Get statistics about active toasts\n */\n getStats() {\n const stats = {\n total: this.toasts.size,\n byType: {}\n };\n\n this.toasts.forEach(toast => {\n stats.byType[toast.type] = (stats.byType[toast.type] || 0) + 1;\n });\n\n return stats;\n }\n\n /**\n * Set global options\n */\n setOptions(newOptions) {\n this.options = { ...this.options, ...newOptions };\n\n // Recreate container if position changed\n if (newOptions.position) {\n if (this.container) {\n this.container.className = `toast-container position-fixed ${this.getPositionClasses()}`;\n }\n }\n }\n}\n\nexport default ToastService;\n","/**\n * ProgressView - File upload progress component\n * \n * Shows upload progress with progress bar, filename, and cancellation option\n * Integrates with FileUpload service for real-time progress updates\n * \n * Features:\n * - Bootstrap progress bar with percentage\n * - File information (name, size)\n * - Bytes uploaded/total display\n * - Cancel button with confirmation\n * - Responsive design\n * \n * Events:\n * - 'cancel' - Emitted when user cancels upload\n * \n * @example\n * const progressView = new ProgressView({\n * filename: 'document.pdf',\n * filesize: 1024000,\n * onCancel: () => fileUpload.cancel()\n * });\n * \n * // Update progress\n * progressView.updateProgress({ progress: 0.5, loaded: 512000, total: 1024000 });\n */\n\nimport View from '@core/View.js';\nimport dataFormatter from '@core/utils/DataFormatter.js';\n\nclass ProgressView extends View {\n constructor(options = {}) {\n super({\n template: 'progress-view-template',\n ...options\n });\n\n // Initialize progress data\n this.filename = options.filename || 'Unknown file';\n this.filesize = options.filesize || 0;\n this.filesizeFormatted = dataFormatter.pipe(this.filesize, 'filesize');\n \n // Progress state\n this.progress = 0;\n this.percentage = 0;\n this.loaded = 0;\n this.total = this.filesize;\n this.loadedFormatted = '0 B';\n this.totalFormatted = this.filesizeFormatted;\n this.status = 'Starting upload...';\n \n // Options\n this.showCancel = options.showCancel !== false;\n this.onCancel = options.onCancel || null;\n \n // State\n this.cancelled = false;\n this.completed = false;\n }\n\n /**\n * Get template for the progress view\n */\n getTemplate() {\n return `\n <div class=\"progress-view\">\n <div class=\"d-flex justify-content-between align-items-start mb-2\">\n <div class=\"flex-grow-1 min-width-0\">\n <div class=\"fw-medium text-truncate\" title=\"{{filename}}\">\n <i class=\"bi bi-file-earmark me-1\"></i>\n {{filename}}\n </div>\n <small class=\"text-muted\">{{status}}</small>\n </div>\n {{#showCancel}}\n <button type=\"button\" \n class=\"btn btn-sm btn-outline-secondary ms-2\" \n data-action=\"cancel\"\n {{#cancelled}}disabled{{/cancelled}}>\n <i class=\"bi bi-x\"></i>\n </button>\n {{/showCancel}}\n </div>\n \n <div class=\"progress mb-2\" style=\"height: 8px;\">\n <div class=\"progress-bar\" \n role=\"progressbar\" \n style=\"width: {{percentage}}%\"\n aria-valuenow=\"{{percentage}}\" \n aria-valuemin=\"0\" \n aria-valuemax=\"100\">\n </div>\n </div>\n \n <div class=\"d-flex justify-content-between\">\n <small class=\"text-muted\">\n {{loadedFormatted}} / {{totalFormatted}}\n </small>\n <small class=\"text-muted\">\n {{percentage}}%\n </small>\n </div>\n </div>\n `;\n }\n\n /**\n * Update progress information\n * @param {Object} progressInfo - Progress data\n * @param {number} progressInfo.progress - Progress as decimal (0-1)\n * @param {number} progressInfo.loaded - Bytes loaded\n * @param {number} progressInfo.total - Total bytes\n * @param {number} progressInfo.percentage - Progress as percentage (0-100)\n */\n updateProgress(progressInfo) {\n if (this.cancelled || this.completed) {\n return;\n }\n\n this.progress = progressInfo.progress;\n this.percentage = progressInfo.percentage;\n this.loaded = progressInfo.loaded;\n this.total = progressInfo.total || this.filesize;\n \n // Format bytes for display\n this.loadedFormatted = dataFormatter.pipe(this.loaded, 'filesize');\n this.totalFormatted = dataFormatter.pipe(this.total, 'filesize');\n \n // Update status message\n if (this.percentage < 100) {\n this.status = `Uploading... ${this.percentage}%`;\n } else {\n this.status = 'Finalizing upload...';\n }\n\n // Re-render to show updated progress\n this.render();\n }\n\n /**\n * Mark upload as completed\n * @param {string} message - Success message\n */\n markCompleted(message = 'Upload completed!') {\n this.completed = true;\n this.progress = 1;\n this.percentage = 100;\n this.status = message;\n this.render();\n }\n\n /**\n * Mark upload as failed\n * @param {string} message - Error message\n */\n markFailed(message = 'Upload failed') {\n this.status = message;\n this.render();\n }\n\n /**\n * Mark upload as cancelled\n */\n markCancelled() {\n this.cancelled = true;\n this.status = 'Upload cancelled';\n this.render();\n }\n\n /**\n * Handle cancel button click\n * @param {string} action - Action name\n * @param {Event} event - Click event\n * @param {Element} element - Button element\n */\n async onActionCancel(action, event, element) {\n if (this.cancelled || this.completed) {\n return;\n }\n\n // Disable button immediately\n element.disabled = true;\n \n // Mark as cancelled\n this.markCancelled();\n \n // Emit cancel event\n this.emit('cancel');\n \n // Call cancel callback if provided\n if (typeof this.onCancel === 'function') {\n try {\n await this.onCancel();\n } catch (error) {\n console.error('Error in cancel callback:', error);\n }\n }\n }\n\n /**\n * Set filename\n * @param {string} filename - New filename\n */\n setFilename(filename) {\n this.filename = filename;\n this.render();\n }\n\n /**\n * Set file size\n * @param {number} size - File size in bytes\n */\n setFilesize(size) {\n this.filesize = size;\n this.filesizeFormatted = dataFormatter.pipe(size, 'filesize');\n this.total = size;\n this.totalFormatted = this.filesizeFormatted;\n this.render();\n }\n\n /**\n * Get current progress as percentage\n * @returns {number} Progress percentage (0-100)\n */\n getPercentage() {\n return this.percentage;\n }\n\n /**\n * Check if upload is completed\n * @returns {boolean} True if completed\n */\n isCompleted() {\n return this.completed;\n }\n\n /**\n * Check if upload is cancelled\n * @returns {boolean} True if cancelled\n */\n isCancelled() {\n return this.cancelled;\n }\n\n /**\n * Get upload statistics\n * @returns {Object} Upload stats\n */\n getStats() {\n return {\n filename: this.filename,\n filesize: this.filesize,\n progress: this.progress,\n percentage: this.percentage,\n loaded: this.loaded,\n total: this.total,\n cancelled: this.cancelled,\n completed: this.completed,\n status: this.status\n };\n }\n}\n\nexport default ProgressView;","/**\n * FileUpload - File upload service with progress tracking and UI integration\n *\n * Features:\n * - Auto-start upload process\n * - Progress tracking with detailed information\n * - Promise interface with cancellation support\n * - Toast integration for user feedback\n * - Three-stage upload process (initiate → upload → complete)\n *\n * @example\n * const file = new File();\n * const upload = file.upload({\n * file: fileObject,\n * name: 'avatar.jpg',\n * group: 'profile-pics',\n * description: 'User avatar',\n * onProgress: ({ progress, loaded, total, percentage }) => {\n * console.log(`${percentage}% complete`);\n * }\n * });\n *\n * upload.then(result => console.log('Success!'))\n * .catch(error => console.error('Failed:', error));\n */\n\nimport ToastService from './ToastService.js';\nimport ProgressView from '../views/feedback/ProgressView.js';\n\nclass FileUpload {\n constructor(fileModel, options = {}) {\n this.fileModel = fileModel;\n this.options = {\n file: null,\n name: null,\n group: null,\n description: null,\n onProgress: null,\n onComplete: null,\n onError: null,\n showToast: true,\n ...options\n };\n\n // Validation\n if (!this.options.file || !(this.options.file instanceof File)) {\n throw new Error('FileUpload requires a valid File object');\n }\n\n // State management\n this.cancelled = false;\n this.uploadRequest = null;\n this.progressToast = null;\n this.progressView = null;\n this.toastService = null;\n\n // Initialize toast service if needed\n if (this.options.showToast) {\n this.toastService = new ToastService();\n }\n\n // Auto-start upload (Option 3 behavior)\n this.promise = this._startUpload();\n }\n\n /**\n * Main upload orchestration\n * @returns {Promise} Upload promise\n * @private\n */\n async _startUpload() {\n try {\n if (this.options.showToast) {\n this._showProgressToast();\n }\n\n // Stage 1: Initiate upload and get signed URL\n let uploadData;\n try {\n uploadData = await this._initiateUpload();\n } catch (error) {\n throw new Error(`Failed to initiate upload: ${error.message}`);\n }\n\n if (this.cancelled) {\n throw new Error('Upload cancelled');\n }\n\n // Validate upload data\n if (!uploadData || !uploadData.upload_url) {\n throw new Error('Invalid upload response: missing upload URL');\n }\n\n // Normalise upload_url — the backend can return either:\n // • a plain string → direct PUT with raw bytes (e.g. S3 signed URL)\n // • a config object → POST multipart/form-data (e.g. filesystem backend)\n // { upload_url: string, method: string, fields: object, headers: object }\n let uploadConfig;\n if (typeof uploadData.upload_url === 'string') {\n uploadConfig = {\n url: uploadData.upload_url,\n method: 'PUT',\n fields: null,\n headers: {}\n };\n } else if (uploadData.upload_url && typeof uploadData.upload_url === 'object'\n && uploadData.upload_url.upload_url) {\n uploadConfig = {\n url: uploadData.upload_url.upload_url,\n method: uploadData.upload_url.method || 'POST',\n fields: uploadData.upload_url.fields || null,\n headers: uploadData.upload_url.headers || {}\n };\n } else {\n throw new Error(\n `Invalid upload response: unrecognised upload_url format. ` +\n `Server returned: ${JSON.stringify(uploadData.upload_url)}`\n );\n }\n\n // Stage 2: Upload file to signed URL / endpoint\n let result;\n try {\n result = await this._performUpload(uploadConfig);\n } catch (error) {\n throw new Error(`File upload failed: ${error.message}`);\n }\n\n if (this.cancelled) {\n throw new Error('Upload cancelled');\n }\n\n // Stage 3: Mark upload as completed\n try {\n await this._completeUpload();\n } catch (error) {\n console.warn('Failed to mark upload as completed:', error);\n // Don't fail the entire upload for completion marking errors\n // The file was successfully uploaded\n }\n\n // Handle success\n this._onComplete(this.fileModel);\n return this.fileModel;\n\n } catch (error) {\n if (error.message !== 'Upload cancelled') {\n this._onError(error);\n }\n throw error;\n }\n }\n\n /**\n * Initiate upload by calling the API to get signed URL\n * @returns {Promise<Object>} Upload initiation data\n * @private\n */\n async _initiateUpload() {\n try {\n const payload = {\n filename: this.options.name || this.options.file.name,\n file_size: this.options.file.size,\n content_type: this.options.file.type,\n };\n\n if (this.options.group) payload.group = this.options.group;\n if (this.options.description) payload.description = this.options.description;\n\n const response = await this.fileModel.rest.POST('/api/fileman/upload/initiate', payload);\n\n if (!response) {\n throw new Error('No response from upload initiation API');\n }\n\n if (!response.data) {\n throw new Error('Upload initiation response missing data');\n }\n\n // Check server response first (prefer server error messages)\n if (!response.data.status) {\n const errorMessage = response.data.error || 'Upload initiation failed';\n throw new Error(errorMessage);\n }\n\n if (!response.data.data) {\n throw new Error('Upload initiation response missing data payload');\n }\n\n // Set model ID for completion step\n if (response.data.data.id) {\n this.fileModel.set('id', response.data.data.id);\n }\n\n return response.data.data; // { id, upload_url }\n\n } catch (error) {\n // Re-throw with more context if it's a generic error\n if (error.message === 'Network Error' || error.name === 'TypeError') {\n throw new Error('Network error during upload initiation. Please check your connection.');\n }\n throw error;\n }\n }\n\n /**\n * Upload file using the normalised upload config from _startUpload.\n *\n * Two dispatch paths:\n * - PUT + raw bytes → legacy plain-string upload_url, or any config with method PUT.\n * Used by backends that issue a direct signed PUT URL (e.g. S3 signed URL).\n * - POST + FormData → config object with method POST and optional fields dict.\n * Used by the local filesystem backend and S3 presigned POST.\n * fields are appended first, then the file as the \"file\" key,\n * matching Django's request.FILES['file'] convention.\n * Content-Type is intentionally NOT set manually so the browser\n * can write the correct multipart boundary.\n *\n * @param {{ url: string, method: string, fields: object|null, headers: object }} uploadConfig\n * @returns {Promise} Upload result\n * @private\n */\n async _performUpload(uploadConfig) {\n return new Promise((resolve, reject) => {\n if (!(this.options.file instanceof File)) {\n reject(new Error('Only single File objects are supported'));\n return;\n }\n\n const { url, method, fields, headers } = uploadConfig;\n const useFormData = method === 'POST' && fields !== null;\n\n const xhr = new XMLHttpRequest();\n this.uploadRequest = xhr;\n\n // Progress tracking\n xhr.upload.onprogress = (event) => {\n if (this.cancelled) return;\n this._onProgress({\n progress: event.loaded / event.total,\n loaded: event.loaded,\n total: event.total,\n percentage: Math.round((event.loaded / event.total) * 100)\n });\n };\n\n xhr.onload = () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve({ data: xhr.response, status: xhr.status, statusText: xhr.statusText, xhr });\n } else {\n reject(new Error(`Upload failed: ${xhr.status} ${xhr.statusText}`));\n }\n };\n\n xhr.onerror = () => reject(new Error('Upload failed: Network error'));\n xhr.ontimeout = () => reject(new Error('Upload timed out — file may be too large or connection too slow'));\n xhr.onabort = () => reject(new Error('Upload cancelled'));\n\n // Relative URLs must be prefixed with /api/ so Django's URL router picks them\n // up correctly, then resolved through the REST service so they target the\n // configured API host rather than the browser's current web host.\n // Absolute URLs (http/https) are returned unchanged — needed for S3 etc.\n let apiUrl = url;\n if (url.startsWith('/') && !url.startsWith('/api/')) {\n apiUrl = '/api' + url;\n }\n const resolvedUrl = this.fileModel.rest.buildUrl(apiUrl);\n xhr.open(method, resolvedUrl);\n xhr.timeout = 30000;\n\n if (useFormData) {\n // POST multipart/form-data — filesystem backend and S3 presigned POST.\n // Any extra headers from the config (e.g. x-amz-* for S3) are set here,\n // but Content-Type is deliberately skipped so the browser sets the boundary.\n for (const [key, value] of Object.entries(headers || {})) {\n if (key.toLowerCase() !== 'content-type') {\n xhr.setRequestHeader(key, value);\n }\n }\n\n const formData = new FormData();\n for (const [key, value] of Object.entries(fields)) {\n formData.append(key, value);\n }\n // File must be last — required by S3 presigned POST policy; harmless for Django.\n formData.append('file', this.options.file);\n xhr.send(formData);\n\n } else {\n // PUT raw bytes — legacy / direct signed URL path.\n xhr.setRequestHeader('Content-Type', this.options.file.type);\n for (const [key, value] of Object.entries(headers || {})) {\n if (key.toLowerCase() !== 'content-type') {\n xhr.setRequestHeader(key, value);\n }\n }\n xhr.send(this.options.file);\n }\n });\n }\n\n /**\n * Mark upload as completed in the API\n * @returns {Promise} Completion result\n * @private\n */\n async _completeUpload() {\n try {\n const response = await this.fileModel.save({ action: 'mark_as_completed' });\n\n if (!response) {\n throw new Error('No response from upload completion API');\n }\n\n // Check server response format (prefer server errors over HTTP errors)\n if (response.data && !response.data.status) {\n const errorMessage = response.data.error || 'Failed to mark upload as completed';\n throw new Error(errorMessage);\n }\n\n return response;\n } catch (error) {\n // Re-throw with more context\n if (error.message === 'Network Error' || error.name === 'TypeError') {\n throw new Error('Network error during upload completion. The file may have uploaded successfully.');\n }\n throw error;\n }\n }\n\n /**\n * Handle progress updates\n * @param {Object} progressInfo - Progress information\n * @private\n */\n _onProgress(progressInfo) {\n // Update progress toast if shown\n if (this.progressToast && this.progressToast.updateProgress) {\n this.progressToast.updateProgress(progressInfo);\n }\n\n // Call user-provided progress callback\n if (typeof this.options.onProgress === 'function') {\n this.options.onProgress(progressInfo);\n }\n }\n\n /**\n * Handle successful upload completion\n * @param {Object} result - Upload result\n * @private\n */\n _onComplete(result) {\n // Mark progress view as completed\n if (this.progressView) {\n this.progressView.markCompleted('Upload completed successfully!');\n }\n\n // Auto-hide progress toast after a delay\n if (this.progressToast) {\n setTimeout(() => {\n try {\n if (this.progressToast && typeof this.progressToast.hide === 'function') {\n this.progressToast.hide();\n }\n } catch (error) {\n console.warn('Error hiding progress toast:', error);\n }\n }, 2000);\n }\n\n // Call user-provided completion callback\n if (typeof this.options.onComplete === 'function') {\n this.options.onComplete(result);\n }\n }\n\n /**\n * Handle upload errors\n * @param {Error} error - Error object\n * @private\n */\n _onError(error) {\n // Hide progress toast immediately and show error toast\n if (this.progressToast) {\n try {\n this.progressToast.hide();\n } catch (error) {\n console.warn('Error hiding progress toast on error:', error);\n }\n }\n\n // Show error toast with immediate feedback\n if (this.toastService) {\n this.toastService.error(`Upload failed: ${error.message}`);\n }\n\n // Call user-provided error callback\n if (typeof this.options.onError === 'function') {\n this.options.onError(error);\n }\n }\n\n /**\n * Show progress toast with ProgressView component\n * @private\n */\n _showProgressToast() {\n // Create progress view with file information\n this.progressView = new ProgressView({\n filename: this.options.name || this.options.file.name,\n filesize: this.options.file.size,\n showCancel: true,\n onCancel: () => this.cancel()\n });\n\n // Show progress view in toast\n this.progressToast = this.toastService.showView(this.progressView, 'info', {\n title: 'File Upload',\n autohide: false,\n dismissible: false\n });\n }\n\n /**\n * Cancel the upload\n * @returns {boolean} True if cancelled, false if already completed\n */\n cancel() {\n if (this.cancelled) {\n return false;\n }\n\n this.cancelled = true;\n\n // Cancel the upload request if in progress\n if (this.uploadRequest && typeof this.uploadRequest.abort === 'function') {\n this.uploadRequest.abort();\n }\n\n // Mark progress view as cancelled\n if (this.progressView) {\n this.progressView.markCancelled();\n }\n\n // Hide progress toast after a delay\n if (this.progressToast) {\n setTimeout(() => {\n try {\n if (this.progressToast && typeof this.progressToast.hide === 'function') {\n this.progressToast.hide();\n }\n } catch (error) {\n console.warn('Error hiding progress toast on cancel:', error);\n }\n }, 1500);\n }\n\n return true;\n }\n\n /**\n * Check if upload is cancelled\n * @returns {boolean} True if cancelled\n */\n isCancelled() {\n return this.cancelled;\n }\n\n /**\n * Promise interface - then\n * @param {function} onSuccess - Success handler\n * @param {function} onError - Error handler\n * @returns {Promise} Promise chain\n */\n then(onSuccess, onError) {\n return this.promise.then(onSuccess, onError);\n }\n\n /**\n * Promise interface - catch\n * @param {function} onError - Error handler\n * @returns {Promise} Promise chain\n */\n catch(onError) {\n return this.promise.catch(onError);\n }\n\n /**\n * Promise interface - finally\n * @param {function} onFinally - Finally handler\n * @returns {Promise} Promise chain\n */\n finally(onFinally) {\n return this.promise.finally(onFinally);\n }\n\n /**\n * Get upload statistics\n * @returns {Object} Upload stats\n */\n getStats() {\n return {\n filename: this.options.file.name,\n size: this.options.file.size,\n type: this.options.file.type,\n cancelled: this.cancelled,\n group: this.options.group,\n description: this.options.description\n };\n }\n}\n\nexport default FileUpload;\n","\nimport Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\nimport FileUpload from '@core/services/FileUpload.js';\nimport {UserList} from '@core/models/User.js';\nimport {GroupList} from '@core/models/Group.js';\n/* =========================\n * FileManager\n * ========================= */\nclass FileManager extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/fileman/manager',\n });\n }\n}\n\nclass FileManagerList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: FileManager,\n endpoint: '/api/fileman/manager',\n size: 10,\n ...options,\n });\n }\n}\n\nconst FileManagerForms = {\n create: {\n title: 'Add Storage Backend',\n fields: [\n {\n name: 'name',\n type: 'text',\n label: 'Display Name',\n placeholder: 'Enter Display Name',\n cols: 12,\n },\n {\n name: 'use',\n type: 'text',\n label: 'Use',\n placeholder: 'Enter User or Leave Blank',\n cols: 12,\n },\n {\n name: 'backend_url',\n type: 'text',\n label: 'Backend URL',\n required: true,\n value: \"s3://BUCKET_NAME/OPTION_FOLDER\",\n placeholder: 's3://bucket_name/optional folder',\n help: 'Format: service://path. Valid services: s3',\n cols: 12,\n },\n {\n name: 'aws_region',\n type: 'select',\n label: 'AWS Region (optional)',\n value: 'us-east-1',\n options: [\n { value: '', text: 'System Default' },\n { value: 'us-east-1', text: 'US East (N. Virginia)' },\n { value: 'us-east-2', text: 'US East (Ohio)' },\n { value: 'us-west-1', text: 'US West (N. California)' },\n { value: 'us-west-2', text: 'US West (Oregon)' },\n { value: 'ca-central-1', text: 'Canada (Central)' },\n { value: 'eu-west-1', text: 'Europe (Ireland)' },\n { value: 'eu-west-2', text: 'Europe (London)' },\n { value: 'eu-west-3', text: 'Europe (Paris)' },\n { value: 'eu-central-1', text: 'Europe (Frankfurt)' },\n { value: 'eu-north-1', text: 'Europe (Stockholm)' },\n { value: 'eu-south-1', text: 'Europe (Milan)' },\n { value: 'ap-southeast-2', text: 'Asia Pacific (Sydney)' }\n ],\n columns: 12,\n help: 'Optional. Defaults to project AWS_REGION if omitted.'\n },\n {\n name: 'aws_key',\n type: 'text',\n label: 'AWS Key (optional)',\n placeholder: 'enter your AWS Key with S3 permissions',\n columns: 12,\n help: 'Optional, AWS Key with S3 permissions'\n },\n {\n name: 'aws_secret',\n type: 'text',\n label: 'AWS Secret (optional)',\n placeholder: 'enter your AWS Secret with S3 permissions',\n columns: 12,\n help: 'Optional, AWS Secret with S3 permissions'\n },\n {\n name: 'is_default',\n type: 'switch',\n label: 'Is Default',\n cols: 6,\n },\n {\n name: 'is_active',\n type: 'switch',\n label: 'Is Active',\n default: true,\n cols: 6,\n },\n ],\n },\n\n edit: {\n title: 'Edit Storage Backend',\n fields: [\n {\n name: 'name',\n type: 'text',\n label: 'Display Name',\n placeholder: 'Enter Display Name',\n cols: 12,\n },\n {\n name: 'use',\n type: 'text',\n label: 'Use',\n placeholder: 'Enter User or Leave Blank',\n cols: 12,\n },\n {\n name: 'backend_url',\n type: 'text',\n label: 'Backend URL',\n required: true,\n placeholder: 's3://bucket_name/optional folder',\n help: 'Format: service://path. Valid services: s3',\n cols: 12,\n },\n {\n name: 'allowed_origins',\n type: 'text',\n label: 'Domains Who Can Upload',\n cols: 12,\n },\n {\n name: 'is_default',\n type: 'switch',\n label: 'Is Default',\n cols: 6,\n },\n {\n name: 'is_active',\n type: 'switch',\n label: 'Is Active',\n default: true,\n cols: 6,\n },\n {\n name: 'is_public',\n type: 'switch',\n label: 'Is Public',\n default: true,\n cols: 6,\n }\n ],\n },\n\n owners: {\n fields: [\n {\n type: 'collection',\n name: 'group',\n label: 'Group (Owner)',\n Collection: GroupList, // Collection class\n labelField: 'name', // Field to display in dropdown\n valueField: 'id', // Field to use as value\n maxItems: 10, // Max items to show in dropdown\n placeholder: 'Search groups...',\n emptyFetch: false,\n debounceMs: 300, // Search debounce delay\n },\n {\n type: 'collection',\n name: 'user',\n label: 'User (Owner)',\n Collection: UserList, // Collection class\n labelField: 'display_name', // Field to display in dropdown\n valueField: 'id', // Field to use as value\n maxItems: 10, // Max items to show in dropdown\n placeholder: 'Search users...',\n emptyFetch: false,\n debounceMs: 300, // Search debounce delay\n },\n ],\n },\n\n credentials: {\n fields: [\n {\n name: 'aws_region',\n type: 'select',\n label: 'AWS Region (optional)',\n value: 'us-east-1',\n options: [\n { value: '', text: 'System Default' },\n { value: 'us-east-1', text: 'US East (N. Virginia)' },\n { value: 'us-east-2', text: 'US East (Ohio)' },\n { value: 'us-west-1', text: 'US West (N. California)' },\n { value: 'us-west-2', text: 'US West (Oregon)' },\n { value: 'ca-central-1', text: 'Canada (Central)' },\n { value: 'eu-west-1', text: 'Europe (Ireland)' },\n { value: 'eu-west-2', text: 'Europe (London)' },\n { value: 'eu-west-3', text: 'Europe (Paris)' },\n { value: 'eu-central-1', text: 'Europe (Frankfurt)' },\n { value: 'eu-north-1', text: 'Europe (Stockholm)' },\n { value: 'eu-south-1', text: 'Europe (Milan)' },\n { value: 'ap-southeast-2', text: 'Asia Pacific (Sydney)' }\n ],\n columns: 12,\n help: 'Optional. Defaults to project AWS_REGION if omitted.'\n },\n {\n name: 'aws_key',\n type: 'text',\n label: 'AWS Key (optional)',\n placeholder: 'enter your AWS Key with S3 permissions',\n columns: 12,\n help: 'Optional, AWS Key with S3 permissions'\n },\n {\n name: 'aws_secret',\n type: 'text',\n label: 'AWS Secret (optional)',\n placeholder: 'enter your AWS Secret with S3 permissions',\n columns: 12,\n help: 'Optional, AWS Secret with S3 permissions'\n },\n ]\n }\n};\n\n/* =========================\n * File\n * ========================= */\nclass File extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/fileman/file',\n });\n }\n\n isImage() {\n return this.get(\"category\") === 'image';\n }\n\n /**\n * Get the file's category. Prefers the backend-provided `category` field.\n * Falls back to inferring from `content_type` for locally constructed\n * models that haven't been saved yet. Returns 'other' if both are missing.\n * @returns {string} One of: image, video, audio, pdf, document, spreadsheet,\n * presentation, archive, other.\n */\n getCategory() {\n return this.get('category') || this._inferCategoryFromContentType();\n }\n\n /**\n * Infer category from content_type when the backend `category` field is\n * missing. Mirrors the backend mapping in\n * django-mojo/mojo/apps/fileman/renderer/utils.py CATEGORY_MAP.\n * @returns {string}\n * @private\n */\n _inferCategoryFromContentType() {\n const ct = (this.get('content_type') || '').toLowerCase();\n if (!ct) return 'other';\n if (ct.startsWith('image/')) return 'image';\n if (ct.startsWith('video/')) return 'video';\n if (ct.startsWith('audio/')) return 'audio';\n if (ct === 'application/pdf') return 'pdf';\n if (ct.startsWith('text/') ||\n ct === 'application/msword' ||\n ct.startsWith('application/vnd.openxmlformats-officedocument.wordprocessingml') ||\n ct === 'application/vnd.oasis.opendocument.text') return 'document';\n if (ct === 'application/vnd.ms-excel' ||\n ct.startsWith('application/vnd.openxmlformats-officedocument.spreadsheetml') ||\n ct === 'application/vnd.oasis.opendocument.spreadsheet') return 'spreadsheet';\n if (ct === 'application/vnd.ms-powerpoint' ||\n ct.startsWith('application/vnd.openxmlformats-officedocument.presentationml') ||\n ct === 'application/vnd.oasis.opendocument.presentation') return 'presentation';\n if (ct === 'application/zip' ||\n ct === 'application/x-rar-compressed' ||\n ct === 'application/x-7z-compressed' ||\n ct === 'application/x-tar' ||\n ct === 'application/gzip') return 'archive';\n return 'other';\n }\n\n /**\n * True if the file has at least one rendition.\n * @returns {boolean}\n */\n hasRenditions() {\n const r = this.get('renditions');\n return !!(r && Object.keys(r).length);\n }\n\n /**\n * True when the upload itself is still in flight (no renditions can exist\n * yet). When `upload_status === 'completed'` the rendition pipeline has\n * already run — an empty `renditions` map then just means this file has\n * no renditions, NOT that work is in progress.\n * @returns {boolean}\n */\n isUploadPending() {\n const status = this.get('upload_status');\n return !!(status && status !== 'completed' && status !== 'failed');\n }\n\n /**\n * Trigger a background rebuild of renditions.\n *\n * Backend contract (django-mojo fileman, 2026 update):\n * POST /api/fileman/file/<id>\n * { \"regenerate_renditions\": true } # all default roles\n * { \"regenerate_renditions\": [\"thumbnail\", ...] } # specific roles (<=20)\n *\n * Returns immediately; the renditions map repopulates as the worker\n * finishes. FileView starts a short-lived poll after this call so the\n * gallery picks up the new renditions automatically.\n *\n * @param {string[]} [roles] - Optional rendition roles to rebuild\n * @returns {Promise}\n */\n regenerateRenditions(roles) {\n const id = this.id || this.get('id');\n if (!id) return Promise.reject(new Error('Cannot regenerate renditions on an unsaved file'));\n const body = (Array.isArray(roles) && roles.length)\n ? { regenerate_renditions: roles }\n : { regenerate_renditions: true };\n return this.rest.POST(`${this.endpoint}/${id}`, body);\n }\n\n /**\n * Mint a new shortlink that redirects to this file. Each call creates a\n * distinct ShortLink attributed to the calling user (audit trail).\n *\n * Backend contract:\n * POST /api/fileman/file/<id>\n * { \"share\": true }\n * { \"share\": { \"expire_days\": 30, \"track_clicks\": true, \"note\": \"...\" } }\n *\n * Server clamps: expire_days <= 3650, note <= 512 chars.\n *\n * Response shape is the action dict directly (no {status, data} wrap):\n * { url, shortlink_code, expires_at, track_clicks, code, server }\n *\n * The full Rest response is returned; read fields from `response.data`.\n *\n * @param {true|object} [options=true] - true for defaults, or object with\n * `expire_days` (number, optional), `track_clicks` (bool, optional),\n * `note` (string, optional)\n * @returns {Promise}\n */\n share(options = true) {\n const id = this.id || this.get('id');\n if (!id) return Promise.reject(new Error('Cannot share an unsaved file'));\n return this.rest.POST(`${this.endpoint}/${id}`, { share: options });\n }\n\n /**\n * Get all renditions as an array. Backend returns renditions as a\n * role-keyed object; this normalizes to an array for easy iteration.\n * @returns {Array<object>}\n */\n getRenditions() {\n const r = this.get('renditions');\n return r ? Object.values(r) : [];\n }\n\n /**\n * Pick the best image rendition by pixel area (width * height).\n * Only considers renditions with an `image/*` content_type. Returns null\n * when no image rendition is available.\n * @returns {object|null}\n */\n getBestImageRendition() {\n const images = this.getRenditions().filter(\n r => r && typeof r.content_type === 'string' && r.content_type.startsWith('image/')\n );\n if (!images.length) return null;\n return images.reduce((best, current) => {\n const bestArea = (parseInt(best.width) || 0) * (parseInt(best.height) || 0);\n const currentArea = (parseInt(current.width) || 0) * (parseInt(current.height) || 0);\n return currentArea > bestArea ? current : best;\n });\n }\n\n /**\n * Get a URL suitable for a small preview/thumbnail. Prefers the explicit\n * `thumbnail` rendition, then falls back to the best available image\n * rendition. Returns null when no thumbnail-ish URL is available.\n * @returns {string|null}\n */\n getThumbnailUrl() {\n const renditions = this.get('renditions') || {};\n if (renditions.thumbnail && renditions.thumbnail.url) {\n return renditions.thumbnail.url;\n }\n const best = this.getBestImageRendition();\n return best ? best.url : null;\n }\n\n /**\n * Upload file with progress tracking and UI integration\n * Returns a FileUpload instance with promise interface and cancellation support\n *\n * @param {object} options - Upload configuration\n * @param {File} options.file - File object to upload\n * @param {string} options.name - Custom filename (optional)\n * @param {string} options.group - File group/category (optional)\n * @param {string} options.description - File description (optional)\n * @param {function} options.onProgress - Progress callback ({ progress, loaded, total, percentage })\n * @param {function} options.onComplete - Success callback\n * @param {function} options.onError - Error callback\n * @param {boolean} options.showToast - Show progress toast (default: true)\n * @returns {FileUpload} Upload instance with promise interface\n */\n upload(options = {}) {\n return new FileUpload(this, options);\n }\n}\n\nclass FileList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: File,\n endpoint: '/api/fileman/file',\n size: 10,\n ...options,\n });\n }\n}\n\nconst FileForms = {\n create: {\n title: 'Add File',\n fields: [\n\n ],\n },\n\n edit: {\n title: 'Edit File Backend',\n fields: [\n\n ],\n },\n};\n\nexport {\n FileManager,\n FileManagerList,\n FileManagerForms,\n File,\n FileList,\n FileForms,\n};\n","/**\n * ModalView — Bootstrap 5 modal as a focused View class.\n *\n * Owns only the modal-class mechanics: lifecycle, sizing, stacking,\n * header/body/footer composition, button rendering, and the optional\n * header context menu. All static convenience helpers (alert, confirm,\n * prompt, dialog, form, modelForm, code, htmlPreview, ...) live on\n * `Modal.js` — the canonical static API.\n *\n * Direct instantiation is supported for callers that need the instance\n * handle (event listeners, dynamic setContent, etc.). Most code should\n * use `Modal.dialog()` / `Modal.show()` / `app.modal.*` instead.\n *\n * @example\n * import ModalView from '@core/views/feedback/ModalView.js';\n *\n * const modal = new ModalView({\n * title: 'Hello',\n * body: '<p>World</p>',\n * buttons: [{ text: 'OK', class: 'btn-primary', dismiss: true }]\n * });\n * await modal.render(true, document.body);\n * modal.show();\n */\n\nimport View from '@core/View.js';\n\nclass ModalView extends View {\n // Stack of currently-open modals, used for z-index management.\n static _openDialogs = [];\n\n static _baseZIndex = {\n backdrop: 1050,\n modal: 1055\n };\n\n /**\n * Return a z-index pair appropriate for the current page state.\n * When a `.table-fullscreen` element is active (TableView fullscreen\n * mode), modals must stack above it; otherwise the Bootstrap defaults\n * are fine.\n */\n static getFullscreenAwareZIndex() {\n const fullscreenTable = document.querySelector('.table-fullscreen');\n if (fullscreenTable) {\n return { backdrop: 10040, modal: 10050 };\n }\n return ModalView._baseZIndex;\n }\n\n /**\n * Re-stack all open modal backdrops so each backdrop sits below its\n * own modal but above all earlier modals in the stack.\n */\n static fixAllBackdropStacking() {\n const backdrops = document.querySelectorAll('.modal-backdrop');\n const openDialogs = ModalView._openDialogs;\n if (backdrops.length === 0 || openDialogs.length === 0) return;\n\n const sorted = [...openDialogs].sort(\n (a, b) => (a._dialogZIndex || 0) - (b._dialogZIndex || 0)\n );\n\n const fullscreen = document.querySelector('.table-fullscreen');\n const targetContainer = fullscreen || document.body;\n\n backdrops.forEach((backdrop, index) => {\n if (index >= sorted.length) return;\n const dialog = sorted[index];\n backdrop.style.zIndex = dialog._dialogZIndex - 5;\n if (backdrop.parentNode !== targetContainer) {\n targetContainer.appendChild(backdrop);\n }\n });\n }\n\n static updateAllBackdropStacking() {\n ModalView.fixAllBackdropStacking();\n }\n\n /**\n * Resolve where a modal should be appended — inside the active\n * fullscreen element (so its z-index is contained) or otherwise on\n * `document.body`. Static so callers can match the mount target\n * before rendering (e.g. `await modal.render(true, getMountTarget())`).\n */\n static getMountTarget() {\n return document.querySelector('.table-fullscreen') || document.body;\n }\n\n constructor(options = {}) {\n const modalId = options.id || `modal-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;\n\n // Suppress the colored hero band when the caller owns the top of the\n // card — i.e. they passed their own header content/view or disabled\n // the header entirely. The band's `::before` overlays the top 28px of\n // .modal-content and would otherwise paint over a custom header.\n const headerDisabled = options.header === false || options.header === null;\n const headerCustomized = options.headerContent !== undefined && options.headerContent !== null;\n\n // `eyebrow: false` (or null) suppresses the colored hero band entirely\n // via the `modal-bandless` class. A truthy string sets the band's text\n // through the --mojo-eyebrow CSS variable. `undefined` leaves the band\n // alone (default behavior).\n let resolvedStyle = options.style;\n let bandlessClass = '';\n if (options.eyebrow === false || options.eyebrow === null) {\n bandlessClass = 'modal-bandless';\n } else if (options.eyebrow) {\n const safe = String(options.eyebrow).replace(/['\"\\\\]/g, '');\n const styleVar = `--mojo-eyebrow: '${safe}'`;\n resolvedStyle = [resolvedStyle, styleVar].filter(Boolean).join('; ');\n }\n\n super({\n ...options,\n id: modalId,\n tagName: 'div',\n className: `modal ${options.fade !== false ? 'fade' : ''} ${bandlessClass} ${options.className || ''}`.trim().replace(/\\s+/g, ' '),\n style: resolvedStyle,\n attributes: {\n tabindex: '-1',\n 'aria-hidden': 'true',\n 'aria-labelledby': options.labelledBy || `${modalId}-label`,\n 'aria-describedby': options.describedBy || null,\n ...options.attributes\n }\n });\n\n this.modalId = modalId;\n this.title = options.title || '';\n this.titleId = `${this.modalId}-label`;\n\n // Sizing\n this.size = options.size || '';\n this.centered = options.centered !== undefined ? options.centered : false;\n this.scrollable = options.scrollable !== undefined ? options.scrollable : false;\n this.autoSize = options.autoSize || options.size === 'auto';\n\n // Bootstrap modal options\n this.backdrop = options.backdrop !== undefined ? options.backdrop : true;\n this.keyboard = options.keyboard !== undefined ? options.keyboard : true;\n this.focus = options.focus !== undefined ? options.focus : true;\n\n // Header\n this.header = options.header !== undefined ? options.header : true;\n this.headerContent = options.headerContent || null;\n this.headerView = null;\n this.closeButton = options.closeButton !== undefined ? options.closeButton : true;\n this.contextMenu = options.contextMenu || null;\n this._processHeaderContent(this.headerContent);\n\n // Body — accepts string, View, function, or Promise<View>.\n // Aliases: `view`, `message`, `content`. Priority: body > view > message > content.\n this.body = options.body ?? options.view ?? options.message ?? options.content ?? '';\n this.bodyView = null;\n this.bodyClass = options.bodyClass || '';\n this.noBodyPadding = options.noBodyPadding || false;\n\n // Auto-size constraints\n this.minWidth = options.minWidth || 300;\n this.minHeight = options.minHeight || 200;\n if (options.maxHeight) this.maxHeight = options.maxHeight;\n this.maxWidthPercent = options.maxWidthPercent || 0.9;\n this.maxHeightPercent = options.maxHeightPercent || 0.8;\n\n this._processBodyContent(this.body);\n\n // Footer\n this.footer = options.footer || null;\n this.footerView = null;\n this.footerClass = options.footerClass || '';\n this._processFooterContent(this.footer);\n\n // Buttons (rendered into the footer when no custom footer is given)\n this.buttons = options.buttons || null;\n\n // Bootstrap-event callbacks (pass-through to native modal events)\n this.onShow = options.onShow || null;\n this.onShown = options.onShown || null;\n this.onHide = options.onHide || null;\n this.onHidden = options.onHidden || null;\n this.onHidePrevented = options.onHidePrevented || null;\n\n this.autoShow = options.autoShow !== undefined ? options.autoShow : false;\n this.modal = null;\n this.relatedTarget = options.relatedTarget || null;\n }\n\n // ── Body / header / footer normalization ─────────────────\n\n _processBodyContent(body) {\n if (body instanceof View || (body && typeof body === 'object' && typeof body.render === 'function')) {\n this.bodyView = body;\n this.body = '';\n this.addChild(this.bodyView);\n } else if (typeof body === 'function') {\n try {\n const result = body();\n if (result instanceof View) {\n this.bodyView = result;\n this.body = '';\n this.addChild(this.bodyView);\n } else if (result instanceof Promise) {\n this.bodyPromise = result;\n this.body = '<div class=\"text-center\"><div class=\"spinner-border spinner-border-sm\"></div></div>';\n } else {\n this.body = result;\n }\n } catch (error) {\n console.error('ModalView: error processing body function:', error);\n this.body = body;\n }\n } else {\n this.body = body;\n }\n }\n\n _processHeaderContent(headerContent) {\n if (headerContent instanceof View) {\n this.headerView = headerContent;\n this.headerContent = null;\n this.addChild(this.headerView);\n } else if (typeof headerContent === 'function') {\n try {\n const result = headerContent();\n if (result instanceof View) {\n this.headerView = result;\n this.headerContent = null;\n this.addChild(this.headerView);\n } else if (result instanceof Promise) {\n this.headerPromise = result;\n this.headerContent = '<div class=\"text-center\"><div class=\"spinner-border spinner-border-sm\"></div></div>';\n } else {\n this.headerContent = result;\n }\n } catch (error) {\n console.error('ModalView: error processing headerContent function:', error);\n this.headerContent = headerContent;\n }\n } else {\n this.headerContent = headerContent;\n }\n }\n\n _processFooterContent(footer) {\n if (footer instanceof View) {\n this.footerView = footer;\n this.footer = null;\n this.addChild(this.footerView);\n } else if (typeof footer === 'function') {\n try {\n const result = footer();\n if (result instanceof View) {\n this.footerView = result;\n this.footer = null;\n this.addChild(this.footerView);\n } else if (result instanceof Promise) {\n this.footerPromise = result;\n this.footer = '<div class=\"text-center\"><div class=\"spinner-border spinner-border-sm\"></div></div>';\n } else {\n this.footer = result;\n }\n } catch (error) {\n console.error('ModalView: error processing footer function:', error);\n this.footer = footer;\n }\n } else {\n this.footer = footer;\n }\n }\n\n // ── Template ──────────────────────────────────────────────\n\n async getTemplate() {\n const dialogClasses = ['modal-dialog'];\n\n if (this.size && this.size !== 'auto') {\n if (this.size.startsWith('fullscreen')) {\n dialogClasses.push(`modal-${this.size}`);\n } else if (['sm', 'lg', 'xl', 'xxl'].includes(this.size)) {\n dialogClasses.push(`modal-${this.size}`);\n if (['lg', 'xl', 'xxl'].includes(this.size)) {\n dialogClasses.push('modal-fullscreen-sm-down');\n }\n }\n }\n\n if (this.centered) dialogClasses.push('modal-dialog-centered');\n\n if (this.scrollable) {\n if (!this.maxHeight) {\n dialogClasses.push('modal-dialog-scrollable');\n } else {\n dialogClasses.push('overflow-hidden');\n }\n }\n\n return `\n <div class=\"${dialogClasses.join(' ')}\">\n <div class=\"modal-content\">\n ${await this.buildHeader()}\n ${await this.buildBody()}\n ${await this.buildFooter()}\n </div>\n </div>\n `;\n }\n\n async buildHeader() {\n if (!this.header) return '';\n\n if (this.headerView) {\n this.headerView.replaceById = true;\n return `<div class=\"modal-header\" data-view-container=\"header\">\n <div id=\"${this.headerView.id}\"></div>\n </div>`;\n }\n\n if (this.headerContent) {\n return `<div class=\"modal-header\">${this.headerContent}</div>`;\n }\n\n let headerActions = '';\n if (this.contextMenu && this.contextMenu.items && this.contextMenu.items.length > 0) {\n headerActions = await this.buildContextMenu();\n } else if (this.closeButton) {\n headerActions = '<button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>';\n }\n\n return `\n <div class=\"modal-header\">\n ${this.title ? `<h5 class=\"modal-title\" id=\"${this.titleId}\">${this.title}</h5>` : ''}\n ${headerActions}\n </div>\n `;\n }\n\n async buildContextMenu() {\n const menuItems = await this.filterContextMenuItems();\n if (menuItems.length === 0) {\n return this.closeButton\n ? '<button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>'\n : '';\n }\n\n const triggerIcon = this.contextMenu.icon || 'bi-three-dots-vertical';\n const buttonClass = this.contextMenu.buttonClass || 'btn btn-link p-1 mojo-modal-context-menu-btn';\n\n const menuItemsHtml = menuItems.map(item => {\n if (item.type === 'divider') {\n return '<li><hr class=\"dropdown-divider\"></li>';\n }\n const icon = item.icon ? `<i class=\"${item.icon} me-2\"></i>` : '';\n const label = item.label || '';\n\n if (item.href) {\n return `<li><a class=\"dropdown-item\" href=\"${item.href}\"${item.target ? ` target=\"${item.target}\"` : ''}>${icon}${label}</a></li>`;\n } else if (item.action) {\n const dataAttrs = Object.keys(item)\n .filter(key => key.startsWith('data-'))\n .map(key => `${key}=\"${item[key]}\"`)\n .join(' ');\n return `<li><a class=\"dropdown-item\" data-action=\"${item.action}\" ${dataAttrs}>${icon}${label}</a></li>`;\n }\n return '';\n }).join('');\n\n return `\n <div class=\"dropdown\">\n <button class=\"${buttonClass}\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n <i class=\"${triggerIcon}\"></i>\n </button>\n <ul class=\"dropdown-menu dropdown-menu-end\">\n ${menuItemsHtml}\n </ul>\n </div>\n `;\n }\n\n async filterContextMenuItems() {\n if (!this.contextMenu || !this.contextMenu.items) return [];\n\n const filtered = [];\n for (const item of this.contextMenu.items) {\n if (item.type === 'divider') {\n filtered.push(item);\n continue;\n }\n\n if (item.permissions) {\n try {\n const app = this.getApp?.();\n let user = app?.activeUser || app?.getState?.('activeUser') || null;\n\n if (!user && typeof window !== 'undefined' && window.getApp) {\n try {\n user = window.getApp()?.activeUser;\n } catch {\n // Ignore — fallback is permission denied for this item\n }\n }\n\n if (user?.hasPermission) {\n if (!user.hasPermission(item.permissions)) continue;\n } else {\n // No permission system — items with permission requirements stay hidden\n continue;\n }\n } catch (error) {\n console.warn('ModalView: error checking permissions for context menu item:', error);\n continue;\n }\n }\n\n filtered.push(item);\n }\n\n return filtered;\n }\n\n async buildBody() {\n // `noBodyPadding` paints body content edge-to-edge. CSS handles the band\n // clearance: 28px top reserve when banded, 0 when bandless. See\n // .modal-body-flush rules in core.css.\n const bodyClass = `modal-body ${this.noBodyPadding ? 'modal-body-flush' : ''} ${this.bodyClass}`.replace(/\\s+/g, ' ').trim();\n\n if (this.bodyView) {\n this.bodyView.replaceById = true;\n return `<div class=\"${bodyClass}\" data-view-container=\"body\">\n <div id=\"${this.bodyView.id}\"></div>\n </div>`;\n }\n\n if (!this.body && this.body !== '') return '';\n\n return `<div class=\"${bodyClass}\">${this.body}</div>`;\n }\n\n async buildFooter() {\n if (this.footerView) {\n return `<div class=\"modal-footer ${this.footerClass}\" data-view-container=\"footer\"></div>`;\n }\n\n if (this.footer !== null && typeof this.footer === 'string') {\n return `<div class=\"modal-footer ${this.footerClass}\">${this.footer}</div>`;\n }\n\n if (this.buttons && this.buttons.length > 0) {\n const buttonsHtml = this.buttons.map(btn => {\n const dismissAttr = btn.dismiss ? 'data-bs-dismiss=\"modal\"' : '';\n const actionAttr = btn.action ? `data-action=\"${btn.action}\"` : '';\n const idAttr = btn.id ? `id=\"${btn.id}\"` : '';\n const disabledAttr = btn.disabled ? 'disabled' : '';\n\n return `\n <button type=\"${btn.type || 'button'}\"\n class=\"btn ${btn.class || 'btn-secondary'}\"\n ${idAttr} ${dismissAttr} ${actionAttr} ${disabledAttr}>\n ${btn.icon ? `<i class=\"bi ${btn.icon} me-1\"></i>` : ''}\n ${btn.text || 'Button'}\n </button>\n `;\n }).join('');\n\n return `<div class=\"modal-footer ${this.footerClass}\">${buttonsHtml}</div>`;\n }\n\n return '';\n }\n\n // ── Lifecycle ──────────────────────────────────────────────\n\n /**\n * Override mount: modals don't need a container — they attach\n * themselves to the active fullscreen element or document.body.\n */\n async mount(_container = null) {\n if (this.mounted || this.destroyed) return;\n if (!this.element) {\n throw new Error('Cannot mount modal without element');\n }\n\n await this.onBeforeMount();\n\n const targetContainer = ModalView.getMountTarget();\n targetContainer.appendChild(this.element);\n\n this.bindEvents();\n this.mounted = true;\n\n await this.onAfterMount();\n this.emit('mounted', { view: this });\n\n return this;\n }\n\n async onAfterRender() {\n await super.onAfterRender();\n\n // Apply Prism syntax highlighting if any code blocks are present.\n if (window.Prism && this.element) {\n const codeBlocks = this.element.querySelectorAll('pre code');\n if (codeBlocks.length > 0) {\n window.Prism.highlightAllUnder(this.element);\n }\n }\n\n if (this.autoSize) {\n this.setupAutoSizing();\n } else if (this.maxHeight) {\n const modalBody = this.element.querySelector('.modal-body');\n if (modalBody) modalBody.style.maxHeight = `${this.maxHeight}px`;\n }\n }\n\n async onAfterMount() {\n await super.onAfterMount();\n\n if (typeof window === 'undefined' || !window.bootstrap?.Modal) return;\n\n if (this.backdrop === 'static') {\n this.element.setAttribute('data-bs-backdrop', 'static');\n }\n if (!this.keyboard) {\n this.element.setAttribute('data-bs-keyboard', 'false');\n }\n\n this.modal = new window.bootstrap.Modal(this.element, {\n backdrop: this.backdrop,\n keyboard: this.keyboard,\n focus: this.focus\n });\n\n this.bindBootstrapEvents();\n\n if (this.autoShow) this.show(this.relatedTarget);\n }\n\n // ── Auto-sizing ────────────────────────────────────────────\n\n setupAutoSizing() {\n if (!this.element) return;\n\n this.element.addEventListener('shown.bs.modal', () => {\n this.applyAutoSizing();\n }, { once: true });\n\n // Fallback for cases where the modal renders without animation.\n setTimeout(() => {\n if (this.isShown()) this.applyAutoSizing();\n }, 100);\n }\n\n applyAutoSizing() {\n if (!this.element) return;\n\n try {\n const modalDialog = this.element.querySelector('.modal-dialog');\n const modalContent = this.element.querySelector('.modal-content');\n const modalBody = this.element.querySelector('.modal-body');\n\n if (!modalDialog || !modalContent || !modalBody) {\n console.warn('ModalView auto-sizing: required elements not found');\n return;\n }\n\n // Body view may not be rendered yet — retry shortly.\n if (this.bodyView && !this.bodyView.element) {\n setTimeout(() => this.applyAutoSizing(), 50);\n return;\n }\n\n const originalStyles = {\n dialogMaxWidth: modalDialog.style.maxWidth,\n dialogWidth: modalDialog.style.width,\n contentWidth: modalContent.style.width,\n contentMaxHeight: modalContent.style.maxHeight,\n hadScrollableClass: modalDialog.classList.contains('modal-dialog-scrollable')\n };\n\n // Strip constraints, force layout, measure natural content size.\n modalDialog.style.maxWidth = 'none';\n modalDialog.style.width = 'auto';\n modalContent.style.width = 'auto';\n modalContent.style.maxHeight = 'none';\n // eslint-disable-next-line no-unused-expressions\n modalContent.offsetHeight;\n\n const contentRect = modalContent.getBoundingClientRect();\n\n const viewportMargin = 40;\n const maxWidth = Math.min(\n window.innerWidth * this.maxWidthPercent,\n window.innerWidth - viewportMargin\n );\n let maxHeight = Math.min(\n window.innerHeight * this.maxHeightPercent,\n window.innerHeight - viewportMargin\n );\n\n let optimalWidth = Math.max(this.minWidth, Math.ceil(contentRect.width + 20));\n let optimalHeight = Math.max(this.minHeight, Math.ceil(contentRect.height));\n if (this.maxHeight) {\n maxHeight = Math.min(this.maxHeight, maxHeight);\n modalDialog.style.maxHeight = `${maxHeight}px`;\n }\n\n optimalWidth = Math.min(optimalWidth, maxWidth);\n const heightExceedsMax = contentRect.height > maxHeight;\n\n modalDialog.style.maxWidth = `${optimalWidth}px`;\n modalDialog.style.width = `${optimalWidth}px`;\n\n if (heightExceedsMax) {\n if (!modalDialog.classList.contains('modal-dialog-scrollable')) {\n modalDialog.classList.add('modal-dialog-scrollable');\n }\n modalContent.style.maxHeight = `${maxHeight}px`;\n optimalHeight = maxHeight;\n }\n\n this.autoSizedWidth = optimalWidth;\n this.autoSizedHeight = optimalHeight;\n this._originalStyles = originalStyles;\n } catch (error) {\n console.error('ModalView: error in auto-sizing:', error);\n const dialog = this.element?.querySelector('.modal-dialog');\n if (dialog) dialog.style.maxWidth = '';\n }\n }\n\n resetAutoSizing() {\n if (!this.autoSize || !this._originalStyles || !this.element) return;\n\n try {\n const modalDialog = this.element.querySelector('.modal-dialog');\n const modalContent = this.element.querySelector('.modal-content');\n const modalBody = this.element.querySelector('.modal-body');\n\n if (modalDialog && modalContent && modalBody) {\n modalDialog.style.maxWidth = this._originalStyles.dialogMaxWidth || '';\n modalDialog.style.width = this._originalStyles.dialogWidth || '';\n modalContent.style.width = this._originalStyles.contentWidth || '';\n modalContent.style.maxHeight = this._originalStyles.contentMaxHeight || '';\n\n if (!this._originalStyles.hadScrollableClass &&\n modalDialog.classList.contains('modal-dialog-scrollable')) {\n modalDialog.classList.remove('modal-dialog-scrollable');\n }\n\n delete this.autoSizedWidth;\n delete this.autoSizedHeight;\n delete this._originalStyles;\n }\n } catch (error) {\n console.error('ModalView: error resetting auto-sizing:', error);\n }\n }\n\n // ── Bootstrap event wiring ─────────────────────────────────\n\n bindBootstrapEvents() {\n // show.bs.modal — assign z-index and push onto the open stack.\n this.element.addEventListener('show.bs.modal', (e) => {\n const stackIndex = ModalView._openDialogs.length;\n const zIndexBase = ModalView.getFullscreenAwareZIndex();\n const newZIndex = zIndexBase.modal + (stackIndex * 20);\n this.element.style.zIndex = newZIndex;\n\n this._dialogZIndex = newZIndex;\n this._backdropZIndex = newZIndex - 10;\n\n ModalView._openDialogs.push(this);\n\n if (this.onShow) this.onShow(e);\n this.emit('show', { dialog: this, relatedTarget: e.relatedTarget });\n });\n\n // shown.bs.modal — fix backdrop stacking and focus first input.\n this.element.addEventListener('shown.bs.modal', (e) => {\n setTimeout(() => ModalView.fixAllBackdropStacking(), 50);\n\n if (this.onShown) this.onShown(e);\n this.emit('shown', { dialog: this, relatedTarget: e.relatedTarget });\n\n if (this.focus) {\n const firstInput = this.element.querySelector('input:not([type=\"hidden\"]), textarea, select');\n if (firstInput) firstInput.focus();\n }\n });\n\n // hide.bs.modal — blur focused element to silence accessibility warning.\n this.element.addEventListener('hide.bs.modal', (e) => {\n const focused = this.element.querySelector(':focus');\n if (focused) focused.blur();\n\n if (this.onHide) {\n const result = this.onHide(e);\n if (result === false) {\n e.preventDefault();\n return;\n }\n }\n this.emit('hide', { dialog: this });\n });\n\n // hidden.bs.modal — pop from stack, restore focus, restack remaining.\n this.element.addEventListener('hidden.bs.modal', (e) => {\n const index = ModalView._openDialogs.indexOf(this);\n if (index > -1) ModalView._openDialogs.splice(index, 1);\n\n // Bootstrap removes `modal-open` when its own count reaches zero,\n // but stacked modals share that flag — restore it if any remain.\n if (ModalView._openDialogs.length > 0) {\n document.body.classList.add('modal-open');\n setTimeout(() => ModalView.fixAllBackdropStacking(), 50);\n }\n\n if (this.previousFocus && document.body.contains(this.previousFocus)) {\n this.previousFocus.focus();\n }\n\n if (this.onHidden) this.onHidden(e);\n this.emit('hidden', { dialog: this });\n });\n\n this.element.addEventListener('hidePrevented.bs.modal', (e) => {\n if (this.onHidePrevented) this.onHidePrevented(e);\n this.emit('hidePrevented', { dialog: this });\n });\n }\n\n // ── Instance methods ───────────────────────────────────────\n\n show(relatedTarget = null) {\n this.previousFocus = document.activeElement;\n window.lastDialog = this;\n if (this.modal) this.modal.show(relatedTarget);\n }\n\n hide() {\n const focused = this.element?.querySelector(':focus');\n if (focused) focused.blur();\n if (this.modal) this.modal.hide();\n }\n\n toggle(relatedTarget = null) {\n if (this.modal) this.modal.toggle(relatedTarget);\n }\n\n isShown() {\n return this.element?.classList.contains('show') || false;\n }\n\n getModal() {\n return this.modal;\n }\n\n handleUpdate() {\n if (this.modal) this.modal.handleUpdate();\n }\n\n /**\n * Replace the modal body. Accepts a string/HTML or a View instance.\n * Runs after the modal is mounted, so child views must be rendered\n * manually into the body element.\n */\n async setContent(content) {\n if (content instanceof View) {\n if (this.bodyView) {\n await this.bodyView.destroy();\n this.removeChild(this.bodyView);\n }\n\n this.bodyView = content;\n this.body = '';\n this.addChild(this.bodyView);\n\n const bodyEl = this.element?.querySelector('.modal-body');\n if (bodyEl) {\n bodyEl.innerHTML = '';\n await this.bodyView.render(bodyEl);\n }\n } else {\n this.body = content;\n const bodyEl = this.element?.querySelector('.modal-body');\n if (bodyEl) bodyEl.innerHTML = content;\n }\n\n this.handleUpdate();\n }\n\n setTitle(title) {\n this.title = title;\n const titleEl = this.element?.querySelector('.modal-title');\n if (titleEl) titleEl.textContent = title;\n }\n\n setLoading(loading = true, message = 'Loading...') {\n const bodyEl = this.element?.querySelector('.modal-body');\n if (!bodyEl) return;\n\n if (loading) {\n bodyEl.innerHTML = `\n <div class=\"text-center py-4\">\n <div class=\"spinner-border text-primary mb-3\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n <p>${message}</p>\n </div>\n `;\n } else if (this.bodyView) {\n bodyEl.replaceChildren(this.bodyView.element);\n }\n }\n\n async destroy() {\n if (this.modal) {\n const focused = this.element?.querySelector(':focus');\n if (focused) focused.blur();\n\n this.modal.dispose();\n this.modal = null;\n }\n\n if (this.previousFocus && document.body.contains(this.previousFocus)) {\n this.previousFocus.focus();\n this.previousFocus = null;\n }\n\n if (this.autoSize) this.resetAutoSizing();\n\n await super.destroy();\n }\n\n async onBeforeDestroy() {\n if (this.headerView) await this.headerView.destroy();\n if (this.bodyView) await this.bodyView.destroy();\n if (this.footerView) await this.footerView.destroy();\n\n await super.onBeforeDestroy();\n\n // Defensive — destroy() also disposes, but onBeforeDestroy can be\n // reached via a parent's lifecycle path.\n if (this.modal) {\n this.modal.dispose();\n this.modal = null;\n }\n }\n}\n\nexport default ModalView;\n","/**\n * BusyIndicator — global full-screen loading overlay.\n *\n * Singleton frosted-glass card with a spinner and a configurable message.\n * Reference-counted so nested call sites compose cleanly:\n * show('Saving...')\n * show('Uploading...') // updates the message\n * hide() // counter: 2 → 1 (still visible)\n * hide() // counter: 1 → 0 (fades out)\n *\n * Z-index is read from `ModalView.getFullscreenAwareZIndex()` so the\n * spinner always stacks above any open modal (and inside an active\n * `.table-fullscreen` element when one exists).\n */\n\nimport ModalView from './ModalView.js';\n\nlet _el = null;\nlet _counter = 0;\nlet _timeout = null;\n\nconst STYLE = `\n .mojo-loading-overlay {\n position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;\n background: rgba(255, 255, 255, 0.4);\n backdrop-filter: blur(2px);\n -webkit-backdrop-filter: blur(2px);\n display: flex; align-items: center; justify-content: center;\n opacity: 0; transition: opacity 0.2s ease;\n }\n .mojo-loading-overlay.show { opacity: 1; }\n .mojo-loading-card {\n display: flex; align-items: center; gap: 0.85rem;\n background: #fff;\n border: 1px solid #e9ecef;\n border-radius: 12px;\n padding: 1rem 1.5rem;\n box-shadow: 0 4px 24px rgba(0,0,0,0.08), 0 1px 4px rgba(0,0,0,0.04);\n }\n .mojo-loading-spinner {\n width: 22px; height: 22px;\n border: 2.5px solid #e9ecef;\n border-top-color: #0d6efd;\n border-radius: 50%;\n animation: mojo-spin 0.7s linear infinite;\n flex-shrink: 0;\n }\n .mojo-loading-message {\n font-size: 0.88rem;\n font-weight: 500;\n color: #495057;\n white-space: nowrap;\n }\n @keyframes mojo-spin { to { transform: rotate(360deg); } }\n`;\n\nconst BusyIndicator = {\n /**\n * Show the overlay. Increments the reference counter.\n * @param {string|object} [options] - message string or { message, timeout }\n */\n show(options) {\n if (typeof options === 'string') options = { message: options };\n const { message = 'Loading...', timeout = 30000 } = options || {};\n\n _counter++;\n\n if (_counter === 1) {\n if (_timeout) clearTimeout(_timeout);\n\n // Z-index needs to clear any open modal; recompute on every\n // first-show so a modal opened after the spinner still loses.\n const zBase = ModalView.getFullscreenAwareZIndex();\n const overlayZ = zBase.modal + 1000;\n\n if (!_el) {\n _el = document.createElement('div');\n _el.className = 'mojo-loading-overlay';\n _el.innerHTML = `\n <div class=\"mojo-loading-card\">\n <div class=\"mojo-loading-spinner\"></div>\n <div class=\"mojo-loading-message\">${message}</div>\n </div>\n <style>${STYLE}</style>\n `;\n document.body.appendChild(_el);\n }\n\n _el.style.zIndex = String(overlayZ);\n\n const msgEl = _el.querySelector('.mojo-loading-message');\n if (msgEl) msgEl.textContent = message;\n\n requestAnimationFrame(() => {\n if (_el) _el.classList.add('show');\n });\n\n if (timeout > 0) {\n _timeout = setTimeout(() => {\n console.error('BusyIndicator timed out.');\n BusyIndicator.hide(true);\n }, timeout);\n }\n } else {\n // Already shown — just update the message text.\n if (_el) {\n const msgEl = _el.querySelector('.mojo-loading-message');\n if (msgEl) msgEl.textContent = message;\n }\n }\n },\n\n /**\n * Hide the overlay. Decrements the counter; only removes when it\n * reaches zero. Pass `true` to force-hide regardless of counter.\n */\n hide(force = false) {\n if (force) {\n _counter = 0;\n } else {\n _counter--;\n }\n\n if (_counter > 0) return;\n _counter = 0;\n\n if (_timeout) {\n clearTimeout(_timeout);\n _timeout = null;\n }\n\n if (_el) {\n _el.classList.remove('show');\n setTimeout(() => {\n if (_el && _counter === 0) {\n _el.remove();\n _el = null;\n }\n }, 200);\n }\n },\n\n isShown() {\n return _el !== null && _counter > 0;\n }\n};\n\nexport default BusyIndicator;\n","/**\n * CodeViewer — syntax-highlighted code block as a View.\n *\n * Used by `Modal.code()` to render arbitrary source code with optional\n * Prism.js highlighting and a VS Code-flavored dark theme. Falls back\n * to escaped plain text when Prism is not loaded.\n */\n\nimport View from '@core/View.js';\n\nconst CODE_STYLES = `\n max-height: 60vh;\n overflow-y: auto;\n background: #1e1e1e;\n color: #d4d4d4;\n padding: 1.25rem;\n border-radius: 0.5rem;\n margin: 0;\n font-family: 'Cascadia Code', 'Fira Code', 'JetBrains Mono', 'Consolas', 'Monaco', monospace;\n font-size: 0.9rem;\n line-height: 1.6;\n border: 1px solid #2d2d30;\n box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);\n`.replace(/\\s+/g, ' ').trim();\n\nconst PRISM_OVERRIDES = `\n .dialog-code-block .token.comment { color: #6a9955; }\n .dialog-code-block .token.string { color: #ce9178; }\n .dialog-code-block .token.keyword { color: #569cd6; }\n .dialog-code-block .token.function { color: #dcdcaa; }\n .dialog-code-block .token.number { color: #b5cea8; }\n .dialog-code-block .token.operator { color: #d4d4d4; }\n .dialog-code-block .token.class-name { color: #4ec9b0; }\n .dialog-code-block .token.punctuation { color: #d4d4d4; }\n .dialog-code-block .token.boolean { color: #569cd6; }\n .dialog-code-block .token.property { color: #9cdcfe; }\n .dialog-code-block .token.tag { color: #569cd6; }\n .dialog-code-block .token.attr-name { color: #9cdcfe; }\n .dialog-code-block .token.attr-value { color: #ce9178; }\n .dialog-code-block ::selection { background: #264f78; }\n`;\n\nclass CodeViewer extends View {\n constructor(options = {}) {\n super({ tagName: 'div', className: 'mojo-code-viewer', ...options });\n this.code = options.code || '';\n this.language = options.language || 'javascript';\n }\n\n async getTemplate() {\n return CodeViewer.formatCode(this.code, this.language);\n }\n\n /**\n * Format `code` as a highlighted HTML block. Returns a string suitable\n * for embedding directly into a modal body.\n */\n static formatCode(code, language = 'javascript') {\n let highlighted;\n\n if (window.Prism && window.Prism.languages[language]) {\n highlighted = window.Prism.highlight(code, window.Prism.languages[language], language);\n } else {\n highlighted = String(code)\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#039;');\n }\n\n const prismClass = window.Prism ? `language-${language}` : '';\n\n return `\n <style>${PRISM_OVERRIDES}</style>\n <pre class=\"${prismClass} dialog-code-block\" style=\"${CODE_STYLES}\">\n <code class=\"${prismClass}\" style=\"color: inherit; background: transparent; text-shadow: none;\">${highlighted}</code>\n </pre>\n `;\n }\n\n /**\n * Trigger Prism highlighting on already-rendered `<code>` blocks under\n * `container`. No-op when Prism isn't loaded.\n */\n static highlightCodeBlocks(container = document) {\n if (window.Prism && window.Prism.highlightAllUnder) {\n window.Prism.highlightAllUnder(container);\n }\n }\n}\n\nexport default CodeViewer;\n","/**\n * HtmlPreview — sandboxed HTML preview as a View.\n *\n * Used by `Modal.htmlPreview()` to render arbitrary HTML inside a\n * sandboxed `<iframe>` with a Refresh control. The iframe write happens\n * in `onAfterMount` because `iframe.contentDocument` is only reachable\n * after the element is attached to the DOM.\n */\n\nimport View from '@core/View.js';\n\nclass HtmlPreview extends View {\n constructor(options = {}) {\n super({ tagName: 'div', className: 'mojo-html-preview', ...options });\n this.html = options.html || options.content || '';\n this.height = options.height || 500;\n }\n\n async getTemplate() {\n return `\n <div class=\"html-preview-container\">\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <small class=\"text-muted\">Preview (sandboxed)</small>\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\" data-action=\"refresh\">\n <i class=\"bi bi-arrow-clockwise\"></i> Refresh\n </button>\n </div>\n <iframe\n class=\"border rounded w-100 mojo-html-preview-frame\"\n style=\"height: ${this.height}px; background: white;\"\n sandbox=\"allow-same-origin\"\n frameborder=\"0\"\n ></iframe>\n </div>\n `;\n }\n\n async onAfterMount() {\n await super.onAfterMount();\n this._writeIframe();\n }\n\n onActionRefresh() {\n this._writeIframe();\n }\n\n _writeIframe() {\n const iframe = this.element?.querySelector('.mojo-html-preview-frame');\n if (!iframe) return;\n const doc = iframe.contentDocument || iframe.contentWindow?.document;\n if (!doc) return;\n doc.open();\n doc.write(this.html);\n doc.close();\n }\n\n setHtml(html) {\n this.html = html;\n this._writeIframe();\n }\n}\n\nexport default HtmlPreview;\n","/**\n * Modal — canonical static API for showing modal dialogs.\n *\n * All modal interactions in new code should go through `Modal.*` (or\n * `app.modal.*`). The underlying View class is `ModalView` — use\n * `new ModalView({...})` only when you need a long-lived instance handle\n * for streaming updates or external event wiring.\n *\n * @example\n * import Modal from '@core/views/feedback/Modal.js';\n *\n * await Modal.alert('All set!', 'Saved', { type: 'success' });\n * const ok = await Modal.confirm('Delete this?');\n * const name = await Modal.prompt('Name?');\n *\n * await Modal.show(new MyView({ model }));\n * await Modal.showModel(userModel);\n * await Modal.showModelById(User, 42);\n *\n * await Modal.dialog({ title: 'Choose', body: '...', buttons: [...] });\n * await Modal.form({ title: 'Add', fields: [...] });\n * await Modal.modelForm({ model, fields: [...] });\n * await Modal.code({ code: src, language: 'javascript' });\n * await Modal.htmlPreview({ html: '<h1>Hi</h1>' });\n *\n * Modal.loading('Saving...');\n * await someApiCall();\n * Modal.hideLoading();\n */\n\nimport ModalView from './ModalView.js';\nimport BusyIndicator from './BusyIndicator.js';\nimport CodeViewer from './CodeViewer.js';\nimport HtmlPreview from './HtmlPreview.js';\nimport View from '@core/View.js';\nimport { File as FileModel } from '@core/models/Files.js';\n\nclass Modal {\n\n // ── Global eyebrow toggle ──────────────────────────────────\n //\n // The eyebrow band can be disabled app-wide by setting the\n // `mojo-no-eyebrow` class on <html>. These helpers just wrap that so\n // callers don't have to touch the DOM directly.\n //\n // Modal.setEyebrowEnabled(false); // turn the band off everywhere\n // Modal.setEyebrowEnabled(true); // turn it back on\n // if (Modal.isEyebrowEnabled()) ...\n\n /** Enable or disable the eyebrow band on every modal in the app. */\n static setEyebrowEnabled(enabled) {\n if (typeof document === 'undefined') return;\n document.documentElement.classList.toggle('mojo-no-eyebrow', !enabled);\n }\n\n /** True when the eyebrow band is enabled (the default). */\n static isEyebrowEnabled() {\n if (typeof document === 'undefined') return true;\n return !document.documentElement.classList.contains('mojo-no-eyebrow');\n }\n\n // ── Internal: shared render → show → resolve helper ────────\n //\n // Most static helpers want the same lifecycle: render the modal into\n // the active fullscreen element (or body), show it, attach button\n // listeners that resolve a Promise on click, and clean up on hide.\n // Wrapping that here kills hundreds of lines of duplication.\n\n /**\n * Render `modal`, show it, and return a Promise that resolves based\n * on button clicks or dismissal.\n *\n * @param {ModalView} modal - the modal instance\n * @param {object} options\n * @param {Array} [options.buttons] - the button configs (from constructor)\n * @param {boolean} [options.rejectOnDismiss=false] - reject vs resolve(null) on dismiss\n * @param {Function} [options.onAction] - async (action, ctx) => result; called for buttons that have `action`\n * @param {Function} [options.cleanup] - extra teardown to run after `hidden`\n * @returns {Promise<*>}\n */\n static _renderAndAwait(modal, {\n buttons = null,\n rejectOnDismiss = false,\n onAction = null,\n cleanup = null\n } = {}) {\n const target = ModalView.getMountTarget();\n\n return new Promise((resolve, reject) => {\n let resolved = false;\n const finish = (value) => {\n if (resolved) return;\n resolved = true;\n resolve(value);\n };\n\n const fail = (err) => {\n if (resolved) return;\n resolved = true;\n reject(err);\n };\n\n // Render asynchronously, then wire up.\n (async () => {\n try {\n await modal.render(true, target);\n } catch (err) {\n fail(err);\n return;\n }\n\n // Wire up footer button click handlers if provided.\n if (buttons && buttons.length > 0 && modal.element) {\n const buttonElements = modal.element.querySelectorAll('.modal-footer button');\n buttonElements.forEach((btnEl, index) => {\n const cfg = buttons[index];\n if (!cfg) return;\n\n btnEl.addEventListener('click', async (event) => {\n if (resolved) return;\n\n const defaultValue = cfg.value !== undefined\n ? cfg.value\n : (cfg.action ?? index);\n\n // 1) Per-button handler (legacy showDialog signature)\n if (typeof cfg.handler === 'function') {\n try {\n const result = await cfg.handler({\n dialog: modal,\n button: cfg,\n index,\n event\n });\n\n // null/false → keep open\n if (result === null || result === false) return;\n\n const valueToResolve = (result === true || result === undefined)\n ? defaultValue\n : result;\n\n if (!cfg.dismiss) modal.hide();\n finish(valueToResolve);\n } catch (err) {\n console.error('Modal button handler error:', err);\n // Keep open on handler error\n }\n return;\n }\n\n // 2) Caller-provided onAction (form/modelForm/etc.)\n if (typeof onAction === 'function' && cfg.action) {\n try {\n const result = await onAction(cfg.action, {\n dialog: modal,\n button: cfg,\n index,\n event\n });\n\n if (result === null || result === false) return;\n\n const valueToResolve = (result === true || result === undefined)\n ? defaultValue\n : result;\n\n if (!cfg.dismiss) modal.hide();\n finish(valueToResolve);\n } catch (err) {\n console.error('Modal onAction error:', err);\n }\n return;\n }\n\n // 3) No handler — resolve with default and close\n if (!cfg.dismiss) modal.hide();\n finish(defaultValue);\n });\n });\n }\n\n // Cleanup on hidden — covers backdrop click, ESC, or our own hide().\n modal.on('hidden', () => {\n if (!resolved) {\n if (rejectOnDismiss) {\n fail(new Error('Dialog dismissed'));\n } else {\n finish(null);\n }\n }\n setTimeout(async () => {\n try {\n if (typeof cleanup === 'function') await cleanup(modal);\n } catch (err) {\n console.error('Modal cleanup error:', err);\n }\n try {\n await modal.destroy();\n } catch (err) {\n console.error('Modal destroy error:', err);\n }\n if (modal.element?.parentNode) {\n modal.element.parentNode.removeChild(modal.element);\n }\n }, 100);\n });\n\n modal.show();\n })();\n });\n }\n\n // ── Generic dialog ────────────────────────────────────────\n\n /**\n * Generic promise-based dialog. Returns the clicked button's\n * `value` (or its `action` / index when `value` is omitted), or\n * `null` on dismiss.\n */\n /**\n * Build a CSS `style` string with the `--mojo-eyebrow` custom property\n * set so the hero band's `content` picks it up. View applies the\n * returned string to `element.style.cssText` on the modal root.\n *\n * Caller's `eyebrow` option (string | null | '' | false | undefined)\n * always wins over the helper's default. `null`, `''`, and `false`\n * all clear the band content. `undefined` falls back to the default.\n *\n * @param {string} defaultEyebrow - the helper's default eyebrow text\n * @param {string|null|false|undefined} callerEyebrow - user override\n * @param {string} [callerStyle] - any other style string from the caller\n * @returns {string} merged style string suitable for opts.style\n */\n /**\n * Resolve the eyebrow text a helper will end up showing.\n * Pure function — no formatting, just the value.\n */\n static _resolveEyebrow(defaultEyebrow, callerEyebrow) {\n if (callerEyebrow === null || callerEyebrow === false || callerEyebrow === '') return '';\n if (typeof callerEyebrow === 'string') return callerEyebrow;\n return defaultEyebrow || '';\n }\n\n /**\n * If the modal-header title would just duplicate the band's eyebrow\n * (case-insensitive), suppress the header title — the band already\n * carries it and is the always-on system anchor.\n */\n static _suppressDuplicateTitle(title, eyebrowText) {\n if (!eyebrowText || !title) return title;\n const t = String(title).trim().toUpperCase();\n const e = String(eyebrowText).trim().toUpperCase();\n return t === e ? '' : title;\n }\n\n static _eyebrowStyle(defaultEyebrow, callerEyebrow, callerStyle) {\n // If a higher-level helper already resolved the eyebrow into\n // callerStyle (e.g. Modal.alert → Modal.dialog), don't clobber it\n // by appending the current helper's default. Caller's explicit\n // eyebrow option (when defined) still wins below.\n if (callerEyebrow === undefined &&\n typeof callerStyle === 'string' &&\n callerStyle.includes('--mojo-eyebrow')) {\n return callerStyle;\n }\n\n const finalText = Modal._resolveEyebrow(defaultEyebrow, callerEyebrow);\n\n // Strip quotes/backslashes to keep the CSS string syntactically valid\n const safe = String(finalText).replace(/['\"\\\\]/g, '');\n const styleVar = `--mojo-eyebrow: '${safe}'`;\n return [callerStyle, styleVar].filter(Boolean).join('; ');\n }\n\n static async dialog(options = {}) {\n // Legacy signature: (message, title, options)\n if (typeof options === 'string') {\n const message = arguments[0];\n const title = arguments[1] || 'Alert';\n const opts = arguments[2] || {};\n options = { ...opts, body: message, title };\n }\n\n const {\n title = 'Dialog',\n content,\n body,\n view,\n message,\n size = 'md',\n centered = true,\n buttons = [{ text: 'OK', class: 'btn-primary', value: true }],\n rejectOnDismiss = false,\n eyebrow,\n style: callerStyle,\n ...rest\n } = options;\n\n const resolvedBody = body ?? view ?? message ?? content ?? '';\n\n // Modal.dialog is the generic surface — band is empty unless the\n // caller passes an explicit eyebrow.\n const eyebrowText = Modal._resolveEyebrow('', eyebrow);\n const finalTitle = Modal._suppressDuplicateTitle(title, eyebrowText);\n const style = Modal._eyebrowStyle('', eyebrow, callerStyle);\n\n const modal = new ModalView({\n title: finalTitle,\n body: resolvedBody,\n size,\n centered,\n buttons,\n style,\n ...rest\n });\n\n return Modal._renderAndAwait(modal, { buttons, rejectOnDismiss });\n }\n\n // ── show / showModel / showModelById ──────────────────────\n\n /**\n * Drawer-style modal with a standardized \"context · title · meta\"\n * header. Used by dashboard drill-downs (day detail, country detail,\n * filtered table) so every drawer has the same shape.\n *\n * Header layout:\n * [eyebrow]\n * Big Title\n * meta · meta · meta · …\n *\n * @param {object} options\n * @param {string} [options.eyebrow] - Small uppercase tag above title\n * @param {string} options.title - Drawer title (string only)\n * @param {Array<string|{icon,text}>} [options.meta] - Subtitle row items\n * @param {View|string} options.view - Body content (View or HTML string)\n * @param {string} [options.size='lg'] - Modal size\n * @returns {Promise<*>}\n */\n static async drawer(options = {}) {\n const {\n eyebrow,\n title,\n meta = [],\n view,\n body,\n size = 'lg',\n ...rest\n } = options;\n\n const metaHtml = meta.length ? `\n <div class=\"modal-drawer-meta\">\n ${meta.map(m => {\n if (typeof m === 'string') return `<span>${Modal._esc(m)}</span>`;\n const icon = m.icon ? `<i class=\"${Modal._esc(m.icon)} me-1\"></i>` : '';\n return `<span>${icon}${Modal._esc(m.text || '')}</span>`;\n }).join('')}\n </div>` : '';\n\n const headerHtml = `\n <div class=\"modal-drawer-head\">\n ${eyebrow ? `<span class=\"modal-drawer-eyebrow\">${Modal._esc(eyebrow)}</span>` : ''}\n <h2 class=\"modal-drawer-title\">${Modal._esc(title || '')}</h2>\n ${metaHtml}\n </div>\n `;\n\n // Compose the body: drawer header + caller's view/body. The body\n // accepts a View or a raw HTML string.\n let composedBody;\n if (view && typeof view === 'object' && typeof view.render === 'function') {\n // Wrap the View in a small holder so the modal renders the\n // header markup AND the caller's View as a child.\n const Wrapper = class extends View {\n async getTemplate() {\n return `${headerHtml}<div class=\"modal-drawer-body\" data-container=\"drawer-body\"></div>`;\n }\n async onInit() {\n view.containerId = 'drawer-body';\n this.addChild(view);\n }\n };\n composedBody = new Wrapper();\n } else {\n composedBody = `${headerHtml}<div class=\"modal-drawer-body\">${body || ''}</div>`;\n }\n\n return Modal.dialog({\n header: false,\n body: composedBody,\n size,\n centered: false,\n // The drawer header replaces the modal's hero band entirely —\n // suppress the band so the caller's eyebrow/title/meta block\n // owns the top of the card cleanly.\n className: `modal-bandless ${rest.className || ''}`.trim(),\n buttons: [{ text: 'Close', class: 'btn-secondary', dismiss: true }],\n ...rest\n });\n }\n\n static _esc(s) {\n const d = (typeof document !== 'undefined') ? document.createElement('div') : null;\n if (!d) return String(s ?? '');\n d.textContent = String(s ?? '');\n return d.innerHTML;\n }\n\n /**\n * Show a View instance in a modal. Header is hidden by default\n * (views typically have their own headers). Size defaults to `lg`.\n */\n static async show(view, options = {}) {\n const { eyebrow, style: callerStyle, ...rest } = options;\n return Modal.dialog({\n header: options.title !== undefined ? !!options.title : false,\n title: options.title || undefined,\n body: view,\n size: 'lg',\n centered: false,\n buttons: [{ text: 'Close', class: 'btn-secondary', dismiss: true }],\n style: Modal._eyebrowStyle('DETAILS', eyebrow, callerStyle),\n ...rest\n });\n }\n\n /**\n * Look up `model.constructor.VIEW_CLASS` and show it in a modal.\n */\n static async showModel(model, options = {}) {\n const ModelClass = model.constructor;\n const ViewClass = ModelClass?.VIEW_CLASS;\n\n if (!ViewClass) {\n throw new Error(\n `Modal.showModel: No VIEW_CLASS defined on ${ModelClass?.name || 'model'}. ` +\n `Set ${ModelClass?.name || 'Model'}.VIEW_CLASS = YourView to use this method.`\n );\n }\n\n const view = new ViewClass({ model });\n return Modal.show(view, options);\n }\n\n /**\n * Fetch a model by ID, then show its VIEW_CLASS. Resolves to `null`\n * if the model is not found (with a warning alert).\n */\n static async showModelById(ModelClass, id, options = {}) {\n const model = new ModelClass({ id });\n await model.fetch();\n\n if (!model.id) {\n Modal.alert({\n message: `Could not find ${ModelClass.name || 'record'} with ID: ${id}`,\n type: 'warning'\n });\n return null;\n }\n\n return Modal.showModel(model, options);\n }\n\n /**\n * Read-only display of a model in a modal — uses the model's\n * registered VIEW_CLASS without buttons.\n */\n static async showModelView(model, options = {}) {\n const ModelClass = model.constructor;\n const ViewClass = ModelClass?.VIEW_CLASS;\n if (!ViewClass) {\n throw new Error(\n `Modal.showModelView: No VIEW_CLASS defined on ${ModelClass?.name || 'model'}.`\n );\n }\n const viewInstance = new ViewClass({ model });\n const { eyebrow, style: callerStyle, ...rest } = options;\n return Modal.dialog({\n header: false,\n body: viewInstance,\n size: 'lg',\n centered: false,\n style: Modal._eyebrowStyle('DETAILS', eyebrow, callerStyle),\n ...rest\n });\n }\n\n // ── alert / confirm / prompt / showError ──────────────────\n\n /**\n * Typed alert with a single OK button. Dialog style follows `type`\n * ('info' | 'success' | 'warning' | 'error' | 'danger'); the modal\n * root receives a `modal-alert modal-alert-{type}` className.\n */\n static async alert(messageOrOptions = {}, title, options) {\n let opts;\n if (typeof messageOrOptions === 'string') {\n opts = {\n message: messageOrOptions,\n ...(title !== undefined ? { title } : {}),\n ...(options || {})\n };\n } else {\n opts = { ...messageOrOptions };\n }\n\n const {\n message = '',\n title: resolvedTitle = 'Alert',\n type = 'info',\n eyebrow: callerEyebrow,\n className: callerClassName,\n style: callerStyle,\n ...rest\n } = opts;\n\n const typeKey = type === 'danger' ? 'error' : type;\n const typeClass = `modal-alert modal-alert-${typeKey}`;\n const className = [typeClass, callerClassName].filter(Boolean).join(' ');\n\n // Hero band carries the type label. Default per type, override via\n // `eyebrow: 'CUSTOM'`, suppress with `eyebrow: null|false|''`.\n const defaultEyebrowMap = {\n info: 'INFORMATION',\n success: 'SUCCESS',\n warning: 'WARNING',\n error: 'ERROR'\n };\n const defaultEyebrow = defaultEyebrowMap[typeKey] ?? defaultEyebrowMap.info;\n const eyebrowText = Modal._resolveEyebrow(defaultEyebrow, callerEyebrow);\n const finalTitle = Modal._suppressDuplicateTitle(resolvedTitle, eyebrowText);\n const style = Modal._eyebrowStyle(defaultEyebrow, callerEyebrow, callerStyle);\n\n // Title is just the headline. The band (CSS) carries the type label;\n // headline is the user-supplied title — UNLESS it would just repeat\n // the eyebrow, in which case suppress it (the band already shows it).\n const titleHtml = finalTitle\n ? `<span class=\"modal-alert-headline\">${finalTitle}</span>`\n : '';\n\n return Modal.dialog({\n title: titleHtml,\n body: `<p class=\"modal-alert-message\">${message}</p>`,\n size: 'sm',\n centered: true,\n className,\n style,\n buttons: [{ text: 'OK', class: 'btn-primary', value: true }],\n ...rest\n });\n }\n\n /**\n * Confirmation dialog. Resolves `true` on Confirm, `false` on\n * Cancel/dismiss.\n */\n static async confirm(messageOrOptions, title = 'Confirm', options = {}) {\n let message;\n if (typeof messageOrOptions === 'object' && messageOrOptions !== null) {\n options = messageOrOptions;\n message = options.message;\n title = options.title || title;\n } else {\n message = messageOrOptions;\n }\n\n const buttons = [\n { text: options.cancelText || 'Cancel', class: 'btn-secondary', dismiss: true, action: 'cancel' },\n { text: options.confirmText || 'Confirm', class: options.confirmClass || 'btn-primary', action: 'confirm' }\n ];\n\n const { eyebrow, style: callerStyle, ...rest } = options;\n const eyebrowText = Modal._resolveEyebrow('CONFIRM', eyebrow);\n const finalTitle = Modal._suppressDuplicateTitle(title, eyebrowText);\n const style = Modal._eyebrowStyle('CONFIRM', eyebrow, callerStyle);\n\n const modal = new ModalView({\n title: finalTitle,\n body: `<p>${message}</p>`,\n size: options.size || 'sm',\n centered: true,\n backdrop: 'static',\n buttons,\n style,\n ...rest\n });\n\n const result = await Modal._renderAndAwait(modal, { buttons });\n return result === 'confirm';\n }\n\n /**\n * Prompt dialog with a text input. Resolves to the entered string\n * on OK, `null` on Cancel/dismiss.\n */\n static async prompt(message, title = 'Input', options = {}) {\n const inputId = `prompt-input-${Date.now()}`;\n const defaultValue = options.defaultValue || '';\n const inputType = options.inputType || 'text';\n const placeholder = options.placeholder || '';\n\n const buttons = [\n { text: 'Cancel', class: 'btn-secondary', dismiss: true },\n { text: 'OK', class: 'btn-primary', action: 'ok' }\n ];\n\n const { eyebrow, style: callerStyle, ...rest } = options;\n const eyebrowText = Modal._resolveEyebrow('INPUT', eyebrow);\n const finalTitle = Modal._suppressDuplicateTitle(title, eyebrowText);\n const style = Modal._eyebrowStyle('INPUT', eyebrow, callerStyle);\n\n const modal = new ModalView({\n title: finalTitle,\n body: `\n <p>${message}</p>\n <input type=\"${inputType}\"\n class=\"form-control\"\n id=\"${inputId}\"\n value=\"${defaultValue}\"\n placeholder=\"${placeholder}\">\n `,\n size: options.size || 'sm',\n centered: true,\n backdrop: 'static',\n buttons,\n style,\n ...rest\n });\n\n modal.on('shown', () => {\n const input = modal.element.querySelector(`#${inputId}`);\n if (input) {\n input.focus();\n input.select();\n }\n });\n\n return Modal._renderAndAwait(modal, {\n buttons,\n onAction: async (action) => {\n if (action !== 'ok') return null;\n const input = modal.element.querySelector(`#${inputId}`);\n return input ? input.value : null;\n }\n });\n }\n\n /** Convenience: equivalent to `Modal.alert(msg, 'Error', { type: 'error' })`. */\n static showError(message) {\n return Modal.alert(message, 'Error', { type: 'error' });\n }\n\n // ── form / modelForm / data ───────────────────────────────\n\n /**\n * Show a `FormView` in a modal for ad-hoc data collection\n * (no automatic model save). Resolves with the collected data,\n * or `null` on cancel/dismiss.\n */\n static async form(options = {}) {\n const {\n title = 'Form',\n formConfig = {},\n size = 'md',\n centered = true,\n submitText = 'Submit',\n cancelText = 'Cancel',\n eyebrow,\n style: callerStyle,\n ...rest\n } = options;\n\n const FormView = (await import('@core/forms/FormView.js')).default;\n const formView = new FormView({\n fileHandling: options.fileHandling || 'base64',\n data: options.data,\n defaults: options.defaults,\n model: options.model,\n formConfig: {\n fields: formConfig.fields || options.fields,\n ...formConfig,\n submitButton: false,\n resetButton: false\n }\n });\n\n const buttons = [\n { text: cancelText, class: 'btn-secondary', action: 'cancel' },\n { text: submitText, class: 'btn-primary', action: 'submit' }\n ];\n\n // Form's title becomes the eyebrow (uppercased) by default;\n // header title is suppressed when it would just duplicate the band.\n const defaultEyebrow = String(title || 'FORM').toUpperCase();\n const eyebrowText = Modal._resolveEyebrow(defaultEyebrow, eyebrow);\n const finalTitle = Modal._suppressDuplicateTitle(title, eyebrowText);\n const style = Modal._eyebrowStyle(defaultEyebrow, eyebrow, callerStyle);\n\n const modal = new ModalView({\n title: finalTitle, body: formView, size, centered, buttons, style, ...rest\n });\n\n return Modal._renderAndAwait(modal, {\n buttons,\n onAction: async (action) => {\n if (action === 'cancel') {\n // _renderAndAwait treats `null` as \"keep dialog open\", so we\n // can't just return null to mean \"resolve null\". Explicitly\n // hide() — the `hidden` event handler in _renderAndAwait then\n // resolves the promise with null via the dismissal path.\n modal.hide();\n return null;\n }\n if (action !== 'submit') return null;\n\n if (!formView.validate()) {\n formView.focusFirstError();\n return false; // keep open\n }\n\n if (options.autoSave && options.model) {\n modal.setLoading(true);\n const result = await formView.saveModel();\n if (!result.success) {\n modal.setLoading(false);\n await modal.render();\n modal.getApp()?.toast?.error(result.message);\n return false;\n }\n return result;\n }\n\n try {\n return await formView.getFormData();\n } catch (error) {\n console.error('Modal.form: error collecting form data:', error);\n formView.showError('Error collecting form data');\n return false;\n }\n },\n cleanup: async () => {\n try { await formView.destroy(); } catch { /* best-effort */ }\n }\n });\n }\n\n /**\n * Show a `FormView` bound to a model. On submit, validates and\n * saves the model automatically. Resolves with the save result,\n * or `null` on cancel/dismiss.\n */\n static async modelForm(options = {}) {\n const {\n title = 'Edit',\n formConfig = {},\n size = 'md',\n centered = true,\n submitText = 'Save',\n cancelText = 'Cancel',\n model,\n fields,\n eyebrow,\n style: callerStyle,\n ...rest\n } = options;\n\n if (!model) {\n throw new Error('Modal.modelForm requires a model');\n }\n\n const FormView = (await import('@core/forms/FormView.js')).default;\n const formView = new FormView({\n fileHandling: options.fileHandling || 'base64',\n model,\n data: options.data,\n defaults: options.defaults,\n formConfig: {\n fields: fields || formConfig.fields || [],\n ...formConfig,\n submitButton: false,\n resetButton: false\n }\n });\n\n const buttons = [\n { text: cancelText, class: 'btn-secondary', action: 'cancel' },\n { text: submitText, class: 'btn-primary', action: 'submit' }\n ];\n\n // Form's title becomes the eyebrow (uppercased) by default;\n // header title is suppressed when it would just duplicate the band.\n const defaultEyebrow = String(title || 'EDIT').toUpperCase();\n const eyebrowText = Modal._resolveEyebrow(defaultEyebrow, eyebrow);\n const finalTitle = Modal._suppressDuplicateTitle(title, eyebrowText);\n const style = Modal._eyebrowStyle(defaultEyebrow, eyebrow, callerStyle);\n\n const modal = new ModalView({\n title: finalTitle, body: formView, size, centered, buttons, style, ...rest\n });\n\n return Modal._renderAndAwait(modal, {\n buttons,\n onAction: async (action) => {\n if (action === 'cancel') {\n // _renderAndAwait treats `null` as \"keep dialog open\", so we\n // can't just return null to mean \"resolve null\". Explicitly\n // hide() — the `hidden` event handler in _renderAndAwait then\n // resolves the promise with null via the dismissal path.\n modal.hide();\n return null;\n }\n if (action !== 'submit') return null;\n\n modal.setLoading(true, 'Saving...');\n\n try {\n const result = await formView.handleSubmit();\n if (result.success) return result;\n\n modal.setLoading(false);\n let errmsg = result.error;\n if (result.data?.error) errmsg = result.data.error;\n modal.getApp()?.toast?.error(errmsg);\n return false;\n } catch (error) {\n console.error('Modal.modelForm: error saving:', error);\n await modal.setContent(formView);\n formView.showError(error.message || 'An error occurred while saving');\n return false;\n }\n },\n cleanup: async () => {\n try { await formView.destroy(); } catch { /* best-effort */ }\n }\n });\n }\n\n /**\n * Show structured data in a modal using the `DataView` component.\n * Resolves when the user closes the dialog.\n */\n static async data(options = {}) {\n const {\n title = 'Data View',\n data: payload = {},\n model = null,\n fields = [],\n columns = 2,\n responsive = true,\n showEmptyValues = false,\n emptyValueText = '—',\n size = 'lg',\n centered = true,\n closeText = 'Close',\n ...rest\n } = options;\n\n const DataView = (await import('@core/views/data/DataView.js')).default;\n const dataView = new DataView({\n data: payload, model, fields, columns, responsive, showEmptyValues, emptyValueText\n });\n\n const buttons = [{ text: closeText, class: 'btn-secondary', value: 'close' }];\n\n const modal = new ModalView({\n title, body: dataView, size, centered, buttons, ...rest\n });\n\n // Forward DataView events for callers that wired listeners on the dialog.\n dataView.on('field:click', (d) => modal.emit('dataview:field:click', d));\n dataView.on('error', (d) => modal.emit('dataview:error', d));\n\n return Modal._renderAndAwait(modal, {\n buttons,\n cleanup: async () => {\n try { await dataView.destroy(); } catch { /* best-effort */ }\n }\n });\n }\n\n // ── code / htmlPreview ────────────────────────────────────\n\n /**\n * Show source code with syntax highlighting and copy-to-clipboard.\n * Accepts an options object: `{ code, language, title, size }`.\n */\n static async code(options = {}) {\n const {\n code = '',\n language = 'javascript',\n title = 'Source Code',\n size = 'lg',\n ...rest\n } = options;\n\n const codeView = new CodeViewer({ code, language });\n\n const buttons = [\n { text: 'Copy to Clipboard', class: 'btn-primary', icon: 'bi-clipboard', action: 'copy' },\n { text: 'Close', class: 'btn-secondary', dismiss: true }\n ];\n\n const modal = new ModalView({\n title, body: codeView, size, scrollable: true, buttons, ...rest\n });\n\n return Modal._renderAndAwait(modal, {\n buttons,\n onAction: async (action) => {\n if (action !== 'copy') return null;\n\n if (!navigator.clipboard) return false;\n try {\n await navigator.clipboard.writeText(code);\n Modal._showCopySuccess(modal);\n } catch (err) {\n console.error('Modal.code: clipboard write failed:', err);\n }\n return false; // keep open after copy\n }\n });\n }\n\n static _showCopySuccess(modal) {\n const btn = modal.element?.querySelector('[data-action=\"copy\"]');\n if (!btn) return;\n const originalHtml = btn.innerHTML;\n btn.innerHTML = '<i class=\"bi bi-check me-1\"></i>Copied!';\n btn.classList.remove('btn-primary');\n btn.classList.add('btn-success');\n btn.disabled = true;\n setTimeout(() => {\n btn.innerHTML = originalHtml;\n btn.classList.remove('btn-success');\n btn.classList.add('btn-primary');\n btn.disabled = false;\n }, 2000);\n }\n\n /**\n * Render an HTML string in a sandboxed iframe.\n */\n static async htmlPreview(options = {}) {\n const {\n html = options.content || '',\n title = 'HTML Preview',\n size = 'lg',\n height = 500,\n ...rest\n } = options;\n\n const previewView = new HtmlPreview({ html, height });\n\n const buttons = [{ text: 'Close', class: 'btn-secondary', dismiss: true }];\n\n const modal = new ModalView({\n title, body: previewView, size, scrollable: false, buttons, ...rest\n });\n\n return Modal._renderAndAwait(modal, { buttons });\n }\n\n // ── updateModelImage (avatar uploader) ────────────────────\n\n /**\n * Convenience for \"upload an image and save it to a model field\".\n * Shows an image-upload form, optionally uploads via the FileUpload\n * service, and saves the resulting file ID to the model.\n */\n static async updateModelImage(options = {}, fieldOptions = {}) {\n const upload = options.upload || false;\n const fieldName = fieldOptions.name || options.field || 'image';\n\n const formOptions = {\n title: 'Upload Your Avatar',\n model: null,\n autoSave: !upload,\n size: 'sm',\n fields: [{\n type: 'image',\n name: fieldName,\n size: 'lg',\n imageSize: { width: 200, height: 200 },\n placeholder: 'Upload your image',\n ...fieldOptions\n }],\n ...options\n };\n\n const result = await Modal.form(formOptions);\n\n if (!upload || !result || !options.model) return result;\n\n const base64Data = result[fieldName];\n if (!base64Data || !base64Data.startsWith('data:')) return result;\n\n // Decode base64 → File. Defensive about missing File constructor\n // (server-side rendering, locked-down WebView contexts).\n const arr = base64Data.split(',');\n const mimeMatch = arr[0]?.match(/:(.*?);/);\n const mime = mimeMatch?.[1] || 'image/png';\n const bstr = atob(arr[1]);\n let n = bstr.length;\n const u8arr = new Uint8Array(n);\n while (n--) { u8arr[n] = bstr.charCodeAt(n); }\n const ext = mime.split('/')[1] || 'png';\n const FileCtor = (typeof window !== 'undefined' && window.File) || globalThis.File;\n if (!FileCtor) {\n throw new Error('File API is not available in this environment');\n }\n const file = new FileCtor([u8arr], `${fieldName}.${ext}`, { type: mime });\n\n const fileModel = new FileModel();\n await fileModel.upload({\n file,\n name: `${fieldName}.${ext}`,\n description: options.uploadDescription || `${fieldName} upload`,\n showToast: true\n });\n\n return options.model.save({ [fieldName]: fileModel.id });\n }\n\n // ── Loading indicator (delegates to BusyIndicator) ────────\n\n /**\n * Show full-screen loading overlay. Reference-counted — pair every\n * `loading()` with a `hideLoading()`.\n */\n static loading(options) {\n BusyIndicator.show(options);\n }\n\n /**\n * Hide the loading overlay. Pass `true` to force-hide regardless\n * of the reference counter.\n */\n static hideLoading(force) {\n BusyIndicator.hide(force);\n }\n\n /** @alias loading — backward compat with `Dialog.showBusy`. */\n static showBusy(options) { return Modal.loading(options); }\n /** @alias hideLoading — backward compat with `Dialog.hideBusy`. */\n static hideBusy(force) { return Modal.hideLoading(force); }\n}\n\nexport default Modal;\n"],"names":["ToastService","constructor","options","this","containerId","position","autohide","defaultDelay","maxToasts","toasts","Map","toastCounter","init","createContainer","container","document","getElementById","createElement","id","className","getPositionClasses","style","zIndex","setAttribute","body","appendChild","positionMap","success","message","show","icon","error","info","warning","plain","type","enforceMaxToasts","toastId","config","title","getDefaultTitle","getDefaultIcon","delay","dismissible","toastElement","createToastElement","bootstrap","Error","bsToast","Toast","set","element","addEventListener","cleanup","hide","console","warn","dispose","updateProgress","showView","view","size","createViewToastElement","cleanupView","bodyContainer","querySelector","render","progressInfo","toast","header","createToastHeader","createToastBody","innerHTML","createViewToastBody","_type","iconHtml","titleHtml","escapeHtml","timeHtml","showTime","getTimeString","closeButton","showIcon","Array","from","values","length","oldestId","keys","next","value","oldest","get","e","parentNode","removeChild","delete","hideAll","forEach","_id","clearAll","Date","toLocaleTimeString","hour","minute","str","div","textContent","getStats","stats","total","byType","setOptions","newOptions","ProgressView","View","super","template","filename","filesize","filesizeFormatted","dataFormatter","pipe","progress","percentage","loaded","loadedFormatted","totalFormatted","status","showCancel","onCancel","cancelled","completed","getTemplate","markCompleted","markFailed","markCancelled","onActionCancel","action","event","disabled","emit","setFilename","setFilesize","getPercentage","isCompleted","isCancelled","FileUpload","fileModel","file","name","group","description","onProgress","onComplete","onError","showToast","File","uploadRequest","progressToast","progressView","toastService","promise","_startUpload","uploadData","uploadConfig","result","_showProgressToast","_initiateUpload","upload_url","url","method","fields","headers","JSON","stringify","_performUpload","_completeUpload","_onComplete","_onError","payload","file_size","content_type","response","rest","POST","data","errorMessage","Promise","resolve","reject","useFormData","xhr","XMLHttpRequest","upload","onprogress","_onProgress","Math","round","onload","statusText","onerror","ontimeout","onabort","apiUrl","startsWith","resolvedUrl","buildUrl","open","timeout","key","Object","entries","toLowerCase","setRequestHeader","formData","FormData","append","send","save","setTimeout","cancel","abort","then","onSuccess","catch","onFinally","finally","FileManager","Model","endpoint","FileManagerList","Collection","ModelClass","FileManagerForms","create","label","placeholder","cols","required","help","text","columns","default","edit","owners","GroupList","labelField","valueField","maxItems","emptyFetch","debounceMs","UserList","credentials","isImage","getCategory","_inferCategoryFromContentType","ct","hasRenditions","r","isUploadPending","regenerateRenditions","roles","isArray","regenerate_renditions","share","getRenditions","getBestImageRendition","images","filter","reduce","best","current","bestArea","parseInt","width","height","getThumbnailUrl","renditions","thumbnail","FileList","FileForms","ModalView","static","backdrop","modal","getFullscreenAwareZIndex","_baseZIndex","fixAllBackdropStacking","backdrops","querySelectorAll","openDialogs","_openDialogs","sorted","sort","a","b","_dialogZIndex","targetContainer","index","dialog","updateAllBackdropStacking","getMountTarget","modalId","now","random","toString","slice","headerContent","resolvedStyle","bandlessClass","eyebrow","String","replace","Boolean","join","tagName","fade","trim","attributes","tabindex","labelledBy","describedBy","titleId","centered","scrollable","autoSize","keyboard","focus","headerView","contextMenu","_processHeaderContent","content","bodyView","bodyClass","noBodyPadding","minWidth","minHeight","maxHeight","maxWidthPercent","maxHeightPercent","_processBodyContent","footer","footerView","footerClass","_processFooterContent","buttons","onShow","onShown","onHide","onHidden","onHidePrevented","autoShow","relatedTarget","addChild","bodyPromise","headerPromise","footerPromise","dialogClasses","push","includes","buildHeader","buildBody","buildFooter","replaceById","headerActions","items","buildContextMenu","menuItems","filterContextMenuItems","triggerIcon","buttonClass","map","item","href","target","dataAttrs","filtered","permissions","app","getApp","user","activeUser","getState","window","hasPermission","buttonsHtml","btn","dismissAttr","dismiss","actionAttr","idAttr","disabledAttr","class","mount","_container","mounted","destroyed","onBeforeMount","bindEvents","onAfterMount","onAfterRender","Prism","highlightAllUnder","setupAutoSizing","modalBody","Modal","bindBootstrapEvents","applyAutoSizing","once","isShown","modalDialog","modalContent","originalStyles","dialogMaxWidth","maxWidth","dialogWidth","contentWidth","contentMaxHeight","hadScrollableClass","classList","contains","offsetHeight","contentRect","getBoundingClientRect","viewportMargin","min","innerWidth","innerHeight","optimalWidth","max","ceil","optimalHeight","heightExceedsMax","add","autoSizedWidth","autoSizedHeight","_originalStyles","resetAutoSizing","remove","stackIndex","newZIndex","_backdropZIndex","firstInput","focused","blur","preventDefault","indexOf","splice","previousFocus","activeElement","lastDialog","toggle","getModal","handleUpdate","setContent","destroy","bodyEl","setTitle","titleEl","setLoading","loading","replaceChildren","onBeforeDestroy","_el","_counter","_timeout","BusyIndicator","overlayZ","msgEl","requestAnimationFrame","force","clearTimeout","CODE_STYLES","CodeViewer","code","language","formatCode","highlighted","languages","highlight","prismClass","highlightCodeBlocks","HtmlPreview","html","_writeIframe","onActionRefresh","iframe","doc","contentDocument","contentWindow","write","close","setHtml","setEyebrowEnabled","enabled","documentElement","isEyebrowEnabled","_renderAndAwait","rejectOnDismiss","onAction","resolved","finish","fail","err","btnEl","cfg","async","defaultValue","handler","button","valueToResolve","on","_resolveEyebrow","defaultEyebrow","callerEyebrow","_suppressDuplicateTitle","eyebrowText","toUpperCase","_eyebrowStyle","callerStyle","finalText","arguments","resolvedBody","finalTitle","drawer","meta","metaHtml","m","_esc","headerHtml","composedBody","onInit","s","d","showModel","model","ViewClass","VIEW_CLASS","showModelById","fetch","alert","showModelView","viewInstance","messageOrOptions","opts","resolvedTitle","callerClassName","typeKey","defaultEyebrowMap","confirm","cancelText","confirmText","confirmClass","prompt","inputId","inputType","input","select","showError","form","formConfig","submitText","formView","FormView","import","n","c","fileHandling","defaults","submitButton","resetButton","validate","focusFirstError","autoSave","saveModel","getFormData","modelForm","handleSubmit","errmsg","responsive","showEmptyValues","emptyValueText","closeText","dataView","DataView","codeView","navigator","clipboard","writeText","_showCopySuccess","originalHtml","htmlPreview","previewView","updateModelImage","fieldOptions","fieldName","field","formOptions","imageSize","base64Data","arr","split","mimeMatch","match","mime","bstr","atob","u8arr","Uint8Array","charCodeAt","ext","FileCtor","globalThis","FileModel","uploadDescription","hideLoading","showBusy","hideBusy"],"mappings":"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"}