PageSourceSearch

https://www.bikewinnipeg.ca/vendor/civicrm/summernote/distros/summ…/elfinder/summernote-ext-elfinder.js

js bikewinnipeg.ca collected 2026-10-01 14:40:06 UTC 6,484 bytes, 229 lines download raw bytes

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.