1/** 2 * Modal Component (Vencord-inspired) 3 * Reusable modal/dialog component 4 */ 5 6window.ChatroomModal = (function() { 7 'use strict'; 8 9 /** 10 * Create a modal 11 */ 12 function create(options = {}) { 13 const { 14 title = '', 15 content = '', 16 footer = '', 17 onClose = null, 18 className = '', 19 closeOnOutsideClick = true, 20 closeOnEscape = true 21 } = options; 22 23 const overlay = document.createElement('div'); 24 overlay.className = `modal-overlay ${className}`; 25 26 const modal = document.createElement('div'); 27 modal.className = 'modal-container'; 28 29 const header = document.createElement('div'); 30 header.className = 'modal-header'; 31 header.innerHTML = ` 32 <h3>${title}</h3> 33 <button class="modal-close-btn">×</button> 34 `; 35 36 const body = document.createElement('div'); 37 body.className = 'modal-body'; 38 if (typeof content === 'string') { 39 body.innerHTML = content; 40 } else { 41 body.appendChild(content); 42 } 43 44 modal.appendChild(header); 45 modal.appendChild(body); 46 47 if (footer) { 48 const footerEl = document.createElement('div'); 49 footerEl.className = 'modal-footer'; 50 if (typeof footer === 'string') { 51 footerEl.innerHTML = footer; 52 } else { 53 footerEl.appendChild(footer); 54 } 55 modal.appendChild(footerEl); 56 } 57 58 overlay.appendChild(modal); 59 60 // Close handlers 61 const close = () => { 62 overlay.classList.remove('show'); 63 setTimeout(() => { 64 overlay.remove(); 65 if (onClose) onClose(); 66 }, 300); 67 }; 68 69 header.querySelector('.modal-close-btn').addEventListener('click', close); 70 71 if (closeOnOutsideClick) { 72 overlay.addEventListener('click', (e) => { 73 if (e.target === overlay) { 74 close(); 75 } 76 }); 77 } 78 79 if (closeOnEscape) { 80 const escapeHandler = (e) => { 81 if (e.key === 'Escape') { 82 close(); 83 document.removeEventListener('keydown', escapeHandler); 84 } 85 }; 86 document.addEventListener('keydown', escapeHandler); 87 } 88 89 // Show modal 90 document.body.appendChild(overlay); 91 setTimeout(() => { 92 overlay.classList.add('show'); 93 }, 10); 94 95 return { 96 element: overlay, 97 body: body, 98 close: close, 99 updateContent: (newContent) => { 100 body.innerHTML = ''; 101 if (typeof newContent === 'string') { 102 body.innerHTML = newContent; 103 } else { 104 body.appendChild(newContent); 105 } 106 } 107 }; 108 } 109 110 /** 111 * Create confirm dialog 112 */ 113 function confirm(options = {}) { 114 const { 115 title = 'Confirm', 116 message = 'Are you sure?', 117 confirmText = 'Confirm', 118 cancelText = 'Cancel', 119 onConfirm = null, 120 onCancel = null, 121 danger = false 122 } = options; 123 124 return new Promise((resolve) => { 125 const footer = document.createElement('div'); 126 footer.style.display = 'flex'; 127 footer.style.gap = '10px'; 128 footer.style.justifyContent = 'flex-end'; 129 130 const cancelBtn = window.ChatroomButton.create({ 131 text: cancelText, 132 variant: 'ghost', 133 onClick: () => { 134 modal.close(); 135 if (onCancel) onCancel(); 136 resolve(false); 137 } 138 }); 139 140 const confirmBtn = window.ChatroomButton.create({ 141 text: confirmText, 142 variant: danger ? 'danger' : 'primary', 143 onClick: () => { 144 modal.close(); 145 if (onConfirm) onConfirm(); 146 resolve(true); 147 } 148 }); 149 150 footer.appendChild(cancelBtn); 151 footer.appendChild(confirmBtn); 152 153 const modal = create({ 154 title, 155 content: `<p>${message}</p>`, 156 footer: footer, 157 className: danger ? 'modal-danger' : '' 158 }); 159 }); 160 } 161 162 /** 163 * Create alert dialog 164 */ 165 function alert(options = {}) { 166 const { 167 title = 'Alert', 168 message = '', 169 buttonText = 'OK' 170 } = options; 171 172 return new Promise((resolve) => { 173 const footer = document.createElement('div'); 174 footer.style.display = 'flex'; 175 footer.style.justifyContent = 'flex-end'; 176 177 const okBtn = window.ChatroomButton.create({ 178 text: buttonText, 179 variant: 'primary', 180 onClick: () => { 181 modal.close(); 182 resolve(); 183 } 184 }); 185 186 footer.appendChild(okBtn); 187 188 const modal = create({ 189 title, 190 content: `<p>${message}</p>`, 191 footer: footer 192 }); 193 }); 194 } 195 196 return { 197 create, 198 confirm, 199 alert 200 }; 201})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.