1(function (factory) { 2 /* global define */ 3 if (typeof define === 'function' && define.amd) { 4 define(['jquery'], factory); 5 } else if (typeof module === 'object' && module.exports) { 6 module.exports = factory(require('jquery')); 7 } else { 8 factory(window.jQuery); 9 } 10}(function ($) { 11 12 function elfinderDialog(context) { 13 const connectorUrl = CRM.url('civicrm/summernote/image/access', null, 'back'); 14 15 // ---- Loading indicator helpers ----------------------------------------- 16 function ensureLoadingStyles() { 17 if (document.getElementById('snelf-loading-style')) return; 18 19 const css = ` 20 .snelf-loading { 21 position: absolute; 22 inset: 0; 23 z-index: 99999; 24 display: none; 25 align-items: center; 26 justify-content: center; 27 background: rgba(255,255,255,0.6); 28 } 29 .snelf-loading.is-active { display: flex; } 30 .snelf-loading .snelf-loading-box { 31 background: #fff; 32 border: 1px solid rgba(0,0,0,0.15); 33 border-radius: 6px; 34 padding: 10px 14px; 35 display: flex; 36 gap: 10px; 37 align-items: center; 38 box-shadow: 0 2px 10px rgba(0,0,0,0.12); 39 font-size: 13px; 40 color: #333; 41 } 42 .snelf-spinner { 43 width: 18px; 44 height: 18px; 45 border: 2px solid rgba(0,0,0,0.2); 46 border-top-color: rgba(0,0,0,0.6); 47 border-radius: 50%; 48 animation: snelf-spin 0.8s linear infinite; 49 } 50 @keyframes snelf-spin { 51 to { transform: rotate(360deg); } 52 } 53 `; 54 55 const style = document.createElement('style'); 56 style.id = 'snelf-loading-style'; 57 style.type = 'text/css'; 58 style.appendChild(document.createTextNode(css)); 59 document.head.appendChild(style); 60 } 61 62 function getDialogRootEl(fmInstance) { 63 try { 64 const $ui = fmInstance && typeof fmInstance.getUI === 'function' 65 ? fmInstance.getUI() 66 : null; 67 if ($ui && $ui.length) return $ui.get(0); 68 } catch (e) {} 69 return null; 70 } 71 72 function ensureLoadingOverlay(fmInstance) { 73 ensureLoadingStyles(); 74 const root = getDialogRootEl(fmInstance); 75 if (!root) return null; 76 77 if (window.getComputedStyle(root).position === 'static') { 78 root.style.position = 'relative'; 79 } 80 81 let overlay = root.querySelector('.snelf-loading'); 82 if (!overlay) { 83 overlay = document.createElement('div'); 84 overlay.className = 'snelf-loading'; 85 overlay.innerHTML = ` 86 <div class="snelf-loading-box" role="status" aria-live="polite"> 87 <span class="snelf-spinner" aria-hidden="true"></span> 88 <span class="snelf-loading-text">Attaching fileâ¦</span> 89 </div> 90 `; 91 root.appendChild(overlay); 92 } 93 return overlay; 94 } 95 96 function setLoading(fmInstance, isOn, text) { 97 const overlay = ensureLoadingOverlay(fmInstance); 98 if (!overlay) return; 99 100 const textEl = overlay.querySelector('.snelf-loading-text'); 101 if (textEl && text) textEl.textContent = text; 102 103 overlay.classList.toggle('is-active', !!isOn); 104 } 105 // ------------------------------------------------------------------------ 106 107 function resolveUrlWithRetry(file, fmInstance, done, attempt) { 108 attempt = attempt || 0; 109 if (!file || !file.hash || !fmInstance) return done(null); 110 111 let url = null; 112 try { 113 if (typeof fmInstance.url === 'function') { 114 url = fmInstance.url(file.hash); 115 } 116 } catch (e) {} 117 118 if (url && url.indexOf('/persist/contribute/e/') === -1) { 119 return done(url); 120 } 121 122 if (attempt >= 4) { 123 return done(url); 124 } 125 126 try { 127 if (typeof fmInstance.request === 'function') { 128 fmInstance.request({ cmd: 'open', target: file.phash || file.hash }); 129 } 130 } catch (e) {} 131 132 setTimeout(function () { 133 resolveUrlWithRetry(file, fmInstance, done, attempt + 1); 134 }, 250); 135 } 136 137 function insertIntoSummernote(file, fmInstance) { 138 if (!file || !file.mime) return; 139
140 setLoading(fmInstance, true, 'Attaching fileâ¦'); 141 142 resolveUrlWithRetry(file, fmInstance, function (resolvedUrl) { 143 if (!resolvedUrl) { 144 setLoading(fmInstance, false); 145 return; 146 } 147 148 try { 149 if (file.mime.match('application')) { 150 context.invoke('editor.createLink', { 151 text: file.name, 152 url: resolvedUrl, 153 newWindow: true 154 }); 155 } else if (file.mime.match('image')) { 156 context.invoke('editor.insertImage', resolvedUrl); 157 } 158 } finally { 159 setLoading(fmInstance, false); 160 } 161 162 try { 163 fmInstance.getUI().dialogelfinder('close'); 164 } catch (e) {} 165 }); 166 } 167 168 $('<div/>').dialogelfinder({ 169 title: 'File Manager', 170 url: connectorUrl, 171 lang: 'en', 172 width: '80%', 173 height: '70%', 174 destroyOnClose: true, 175 useBrowserHistory: false, 176 debug: false, 177 178 getFileCallback: function (files, fmInstance) { 179 const file = Array.isArray(files) ? files[0] : files; 180 if (!file || !file.mime) return false; 181 insertIntoSummernote(file, fmInstance); 182 return false; 183 }, 184 185 commandsOptions: { 186 getfile: { 187 oncomplete: 'close', 188 folders: false 189 } 190 }, 191 192 handlers: { 193 dblclick: function (event, fmInstance) { 194 const file = fmInstance.file(event.data.file); 195 if (!file || !file.mime) return false; 196 insertIntoSummernote(file, fmInstance); 197 return false; 198 } 199 } 200 201 }).dialogelfinder('instance'); 202 } 203 204 $.extend($.summernote.plugins, { 205 'elfinder': function (context) { 206 const ui = $.summernote.ui; 207 208 context.memo('button.elfinder', function () { 209 const button = ui.button({ 210 contents: '<i class="fa fa-list-alt"/> File Manager', 211 tooltip: 'elfinder', 212 container: '.note-editor.note-frame', 213 click: function () { 214 elfinderDialog(context); 215 } 216 }); 217 return button.render(); 218 }); 219 220 this.destroy = function () { 221 if (this.$panel) { 222 this.$panel.remove(); 223 this.$panel = null; 224 } 225 }; 226 } 227 }); 228 229}));
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.