PageSourceSearch

https://easyfie.com/superadmin/js/app-toast.js?v=1780468594

js easyfie.com collected 2026-10-01 06:53:17 UTC 6,595 bytes, 252 lines download raw bytes

1/**
2 * Global Toast Notification Utility
3 * Wrapper around Toastr.js with consistent styling across the app
4 */
5
6const AppToast = {
7  /**
8   * Canonical Toastr options. Returned fresh on every call so the auto-close
9   * timer is guaranteed to be applied regardless of script/init load order.
10   */
11  _options() {
12    return {
13      closeButton: true,
14      debug: false,
15      newestOnTop: true,
16      progressBar: true,
17      positionClass: 'toast-top-right',
18      preventDuplicates: false,
19      onclick: null,
20      showDuration: 300,
21      hideDuration: 1000,
22      timeOut: 3000,
23      extendedTimeOut: 1000,
24      showEasing: 'swing',
25      hideEasing: 'linear',
26      showMethod: 'fadeIn',
27      hideMethod: 'fadeOut'
28    };
29  },
30
31  init() {
32    if (typeof toastr === 'undefined') {
33      console.warn('Toastr.js is not loaded. Toast notifications will not work.');
34      return;
35    }
36    toastr.options = this._options();
37  },
38
39  /**
40   * Normalizes flexible arguments into { message, title }.
41   * Accepts either AppToast.x('message', 'title') or AppToast.x({ title, message, text }).
42   * When an object provides only a title, it is shown as the message so text always renders.
43   */
44  _normalize(message, title) {
45    if (message && typeof message === 'object') {
46      const opts = message;
47      let msg = opts.message || opts.text || '';
48      let ttl = opts.title || '';
49      if (!msg) {
50        msg = ttl;
51        ttl = '';
52      }
53      return { message: msg, title: ttl };
54    }
55    return { message: message || '', title: title || '' };
56  },
57
58  success(message, title = '') {
59    const n = this._normalize(message, title);
60    if (typeof toastr === 'undefined') {
61      console.log('[Success]', n.title || 'Success', n.message);
62      return;
63    }
64    toastr.success(n.message, n.title, this._options());
65  },
66
67  error(message, title = '') {
68    const n = this._normalize(message, title);
69    if (typeof toastr === 'undefined') {
70      console.error('[Error]', n.title || 'Error', n.message);
71      return;
72    }
73    toastr.error(n.message, n.title, this._options());
74  },
75
76  warning(message, title = '') {
77    const n = this._normalize(message, title);
78    if (typeof toastr === 'undefined') {
79      console.warn('[Warning]', n.title || 'Warning', n.message);
80      return;
81    }
82    toastr.warning(n.message, n.title, this._options());
83  },
84
85  info(message, title = '') {
86    const n = this._normalize(message, title);
87    if (typeof toastr === 'undefined') {
88      console.info('[Info]', n.title || 'Info', n.message);
89      return;
90    }
91    toastr.info(n.message, n.title, this._options());
92  },
93
94  /**
95   * Confirmation dialog. Accepts either positional args
96   * confirm('message', 'title', onConfirm, onCancel)
97   * or a single options object
98   * confirm({ message, title, onConfirm, onCancel, confirmText, cancelText }).
99   */
100  confirm(message, title = 'Confirm Action', onConfirm = null, onCancel = null) {
101    let confirmText = 'Yes, Confirm';
102    let cancelText = 'Cancel';
103
104    if (message && typeof message === 'object') {
105      const opts = message;
106      title = opts.title || 'Confirm Action';
107      message = opts.message || opts.text || '';
108      onConfirm = opts.onConfirm || null;
109      onCancel = opts.onCancel || null;
110      confirmText = opts.confirmText || confirmText;
111      cancelText = opts.cancelText || cancelText;
112    }
113
114    const confirmModal = document.createElement('div');
115    confirmModal.className = 'app-toast-confirm-modal';
116    confirmModal.innerHTML = `
117      <div class="app-toast-confirm-backdrop"></div>
118      <div class="app-toast-confirm-box">
119        <h5 class="app-toast-confirm-title">${title}</h5>
120        <p class="app-toast-confirm-message">${message}</p>
121        <div class="app-toast-confirm-buttons">
122          <button class="app-toast-confirm-btn app-toast-confirm-cancel">${cancelText}</button>
123          <button class="app-toast-confirm-btn app-toast-confirm-yes">${confirmText}</button>
124        </div>
125      </div>
126    `;
127
128    document.body.appendChild(confirmModal);
129
130    const confirmBtn = confirmModal.querySelector('.app-toast-confirm-yes');
131    const cancelBtn = confirmModal.querySelector('.app-toast-confirm-cancel');
132    const backdrop = confirmModal.querySelector('.app-toast-confirm-backdrop');
133
134    const cleanup = () => confirmModal.remove();
135
136    confirmBtn.addEventListener('click', () => {
137      cleanup();
138      if (onConfirm) onConfirm();
139    });
140
141    const handleCancel = () => {
142      cleanup();
143      if (onCancel) onCancel();
144    };
145    cancelBtn.addEventListener('click', handleCancel);
146    backdrop.addEventListener('click', handleCancel);
147  },
148
149  clear() {
150    if (typeof toastr === 'undefined') {
151      return;
152    }
153    toastr.clear();
154  }
155};
156
157document.addEventListener('DOMContentLoaded', function() {
158  AppToast.init();
159});
160
161// Add CSS for confirmation modal
162const style = document.createElement('style');
163style.textContent = `
164  .app-toast-confirm-modal {
165    position: fixed;
166    top: 0;
167    left: 0;
168    width: 100%;
169    height: 100%;
170    z-index: 9999;
171    display: flex;
172    align-items: center;
173    justify-content: center;
174  }
175
176  .app-toast-confirm-backdrop {
177    position: absolute;
178    inset: 0;
179    background: rgba(0, 0, 0, 0.5);
180    animation: fadeIn 0.3s ease;
181  }
182
183  .app-toast-confirm-box {
184    position: relative;
185    background: white;
186    border-radius: 12px;
187    padding: 24px;
188    max-width: 400px;
189    width: 90%;
190    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
191    animation: slideUp 0.3s ease;
192  }
193
194  .app-toast-confirm-title {
195    margin: 0 0 12px 0;
196    font-size: 18px;
197    font-weight: 600;
198    color: #1c1a18;
199  }
200
201  .app-toast-confirm-message {
202    margin: 0 0 20px 0;
203    font-size: 14px;
204    color: #666;
205    line-height: 1.5;
206  }
207
208  .app-toast-confirm-buttons {
209    display: flex;
210    gap: 12px;
211    justify-content: flex-end;
212  }
213
214  .app-toast-confirm-btn {
215    padding: 8px 16px;
216    border: none;
217    border-radius: 6px;
218    cursor: pointer;
219    font-size: 14px;
220    font-weight: 500;
221    transition: all 0.2s ease;
222  }
223
224  .app-toast-confirm-cancel {
225    background: #e5e7eb;
226    color: #374151;
227  }
228
229  .app-toast-confirm-cancel:hover {
230    background: #d1d5db;
231  }
232
233  .app-toast-confirm-yes {
234    background: #ef4444;
235    color: white;
236  }
237
238  .app-toast-confirm-yes:hover {
239    background: #dc2626;
240  }
241
242  @keyframes fadeIn {
243    from { opacity: 0; }
244    to { opacity: 1; }
245  }
246
247  @keyframes slideUp {
248    from { transform: translateY(20px); opacity: 0; }
249    to { transform: translateY(0); opacity: 1; }
250  }
251`;
252document.head.appendChild(style);

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.