mailcatcher-ng 1.3.0 → 1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: afadfb9772aa8cd6e53a99e420060b1a3e6c93781b6d81ceaad04ac32bf4ec91
4
- data.tar.gz: 9eea84760e814326716fb59aac72dc6606b156f3662d24cfe642b7b66b33add0
3
+ metadata.gz: 71c31256fbd4bdc76749770fe321db1351839f23fd01d1ef2ab6f21382da774b
4
+ data.tar.gz: 7f77c22a8dd2e3b7cea54c6ec415af7f408ad39588e48772cffaec625dd8fd15
5
5
  SHA512:
6
- metadata.gz: 48d1f1af70237339b6ffa7bbcc2ffcf0fed8b04b04973cec17fdee831d66c9893be184dac7bebe334d273393659e87ab4e44b252b3c2fde27741a6f9c197cb54
7
- data.tar.gz: 4f28976f41605c8fd3d1f87ba037aa1a75722fdb34d02c7ed42394290be4c2c81c04bd5d201ca38d34eaaf671d1c7c7667a1a51768518636f5ebd1140109b5b6
6
+ metadata.gz: 3e47f2938af045fd8da6b1e7eacb1a0552a5076bd5415c1079db271335271fc1299c3c47e3dfc78b9eaf1383699f339c1b7f674ff3169bdf65f270666c819255
7
+ data.tar.gz: c49f21a836aae6a35f84568997ddc29f4c619b3cebead68421a882b4ab3ff5ae4685f39e7a69a0473840d83831f88b55b01b4cbe0af5399981f04663319e65f5
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MailCatcher
4
- VERSION = '1.3.0'
4
+ VERSION = '1.3.1'
5
5
  end
@@ -0,0 +1,536 @@
1
+ // Utility functions for MailCatcher UI
2
+ window.MailCatcherUI = window.MailCatcherUI || {};
3
+
4
+ // HTML escape utility
5
+ window.MailCatcherUI.escapeHtml = function(text) {
6
+ const map = {
7
+ '&': '&',
8
+ '<': '&lt;',
9
+ '>': '&gt;',
10
+ '"': '&quot;',
11
+ "'": '&#039;'
12
+ };
13
+ return text.replace(/[&<>"']/g, m => map[m]);
14
+ };
15
+ // Message list resizer functionality
16
+ window.MailCatcherUI = window.MailCatcherUI || {};
17
+
18
+ window.MailCatcherUI.initializeResizer = function() {
19
+ const resizer = document.getElementById('resizer');
20
+ const messagesSection = document.getElementById('messages');
21
+ let isResizing = false;
22
+
23
+ if (resizer && messagesSection) {
24
+ resizer.addEventListener('mousedown', function(e) {
25
+ e.preventDefault();
26
+ isResizing = true;
27
+ const startY = e.clientY;
28
+ const startHeight = messagesSection.offsetHeight;
29
+
30
+ const handleMouseMove = (e) => {
31
+ if (!isResizing) return;
32
+ const delta = e.clientY - startY;
33
+ const newHeight = Math.max(150, startHeight + delta);
34
+ messagesSection.style.flex = `0 0 ${newHeight}px`;
35
+ localStorage.setItem('mailcatcherSeparatorHeight', newHeight);
36
+ };
37
+
38
+ const handleMouseUp = () => {
39
+ isResizing = false;
40
+ document.removeEventListener('mousemove', handleMouseMove);
41
+ document.removeEventListener('mouseup', handleMouseUp);
42
+ };
43
+
44
+ document.addEventListener('mousemove', handleMouseMove);
45
+ document.addEventListener('mouseup', handleMouseUp);
46
+ });
47
+ }
48
+
49
+ // Restore saved resizer position
50
+ const savedHeight = localStorage.getItem('mailcatcherSeparatorHeight');
51
+ if (savedHeight && messagesSection) {
52
+ messagesSection.style.flex = `0 0 ${savedHeight}px`;
53
+ }
54
+ };
55
+ // UI event handlers (buttons, count, etc.)
56
+ window.MailCatcherUI = window.MailCatcherUI || {};
57
+
58
+ window.MailCatcherUI.initializeUIHandlers = function() {
59
+ // Update email count display
60
+ function updateEmailCount() {
61
+ const count = document.querySelectorAll('#messages tbody tr').length;
62
+ document.getElementById('emailCount').textContent = count === 1 ? '1 email' : count + ' emails';
63
+ }
64
+
65
+ // Setup button handlers
66
+ const serverInfoBtn = document.getElementById('serverInfoBtn');
67
+ if (serverInfoBtn) {
68
+ serverInfoBtn.addEventListener('click', function(e) {
69
+ e.preventDefault();
70
+ window.location.href = new URL('server-info', document.baseURI).toString();
71
+ });
72
+ }
73
+
74
+ const clearBtn = document.getElementById('clearBtn');
75
+ if (clearBtn) {
76
+ clearBtn.addEventListener('click', function(e) {
77
+ e.preventDefault();
78
+ if (window.MailCatcher && document.querySelectorAll('#messages tbody tr').length > 0) {
79
+ const confirmText = 'You will lose all your received messages.\n\nAre you sure you want to clear all messages?';
80
+ if (confirm(confirmText)) {
81
+ window.MailCatcher.clearMessages();
82
+ // Also send DELETE request
83
+ fetch(new URL('messages', document.baseURI).toString(), { method: 'DELETE' });
84
+ }
85
+ }
86
+ });
87
+ }
88
+
89
+ const quitBtn = document.getElementById('quitBtn');
90
+ if (quitBtn) {
91
+ quitBtn.addEventListener('click', function(e) {
92
+ e.preventDefault();
93
+ const confirmText = 'You will lose all your received messages.\n\nAre you sure you want to quit?';
94
+ if (confirm(confirmText)) {
95
+ fetch(new URL('', document.baseURI).toString(), { method: 'DELETE' });
96
+ }
97
+ });
98
+ }
99
+
100
+ // Monitor updates for email count
101
+ const observer = new MutationObserver(() => updateEmailCount());
102
+ const tbody = document.querySelector('#messages tbody');
103
+ if (tbody) {
104
+ observer.observe(tbody, { childList: true });
105
+ }
106
+
107
+ // Handle download button
108
+ const downloadBtn = document.querySelector('.download-btn');
109
+ if (downloadBtn) {
110
+ downloadBtn.addEventListener('click', function(e) {
111
+ e.preventDefault();
112
+ if (window.MailCatcher) {
113
+ const id = window.MailCatcher.selectedMessage();
114
+ if (id) {
115
+ window.location.href = `messages/${id}.eml`;
116
+ }
117
+ }
118
+ });
119
+ }
120
+
121
+ // Handle copy source button
122
+ const copySourceBtn = document.getElementById('copySourceBtn');
123
+ if (copySourceBtn) {
124
+ copySourceBtn.addEventListener('click', function(e) {
125
+ e.preventDefault();
126
+ e.stopPropagation();
127
+ if (window.MailCatcher) {
128
+ const id = window.MailCatcher.selectedMessage();
129
+ if (id) {
130
+ // Fetch the raw email source
131
+ fetch(`messages/${id}.source`)
132
+ .then(response => response.text())
133
+ .then(sourceText => {
134
+ // Copy to clipboard
135
+ navigator.clipboard.writeText(sourceText)
136
+ .then(() => {
137
+ // Show checkmark icon
138
+ const copyIcon = copySourceBtn.querySelector('.copy-icon');
139
+ const checkmarkIcon = copySourceBtn.querySelector('.checkmark-icon');
140
+
141
+ if (copyIcon && checkmarkIcon) {
142
+ copyIcon.style.display = 'none';
143
+ checkmarkIcon.style.display = 'block';
144
+
145
+ // Reset after 3 seconds
146
+ setTimeout(() => {
147
+ copyIcon.style.display = 'block';
148
+ checkmarkIcon.style.display = 'none';
149
+ }, 3000);
150
+ }
151
+
152
+ console.log('Email source copied to clipboard');
153
+ })
154
+ .catch(err => {
155
+ console.error('Failed to copy to clipboard:', err);
156
+ alert('Failed to copy email source to clipboard');
157
+ });
158
+ })
159
+ .catch(err => {
160
+ console.error('Failed to fetch email source:', err);
161
+ alert('Failed to load email source');
162
+ });
163
+ }
164
+ }
165
+ });
166
+ }
167
+
168
+ // Initial count update
169
+ updateEmailCount();
170
+ };
171
+ // Email signature and encryption tooltips
172
+ window.MailCatcherUI = window.MailCatcherUI || {};
173
+
174
+ window.MailCatcherUI.initializeSignatureTooltip = function() {
175
+ const signatureInfoBtn = document.getElementById('signatureInfoBtn');
176
+ let signatureTooltip = null;
177
+
178
+ function getStatusBadgeClass(status) {
179
+ if (!status) return 'neutral';
180
+ return status === 'pass' ? 'pass' : status === 'fail' ? 'fail' : 'neutral';
181
+ }
182
+
183
+ function getStatusLabel(status) {
184
+ if (!status) return 'Not checked';
185
+ return status.charAt(0).toUpperCase() + status.slice(1);
186
+ }
187
+
188
+ function generateSignatureContent(authResults) {
189
+ const dmarc = authResults?.dmarc;
190
+ const dkim = authResults?.dkim;
191
+ const spf = authResults?.spf;
192
+
193
+ // Check if any auth data exists
194
+ const hasAuthData = dmarc || dkim || spf;
195
+
196
+ if (!hasAuthData) {
197
+ return `
198
+ <div class="signature-tooltip-content">
199
+ <p style="color: #999; font-size: 12px;">No authentication headers found for this email.</p>
200
+ </div>
201
+ `;
202
+ }
203
+
204
+ return `
205
+ <div class="signature-tooltip-content">
206
+ ${dmarc ? `
207
+ <h3>DMARC</h3>
208
+ <div class="signature-tooltip-item">
209
+ <span class="signature-status-badge ${getStatusBadgeClass(dmarc)}">
210
+ ${getStatusLabel(dmarc)}
211
+ </span>
212
+ </div>
213
+ ` : ''}
214
+ ${dkim ? `
215
+ <h3>DKIM</h3>
216
+ <div class="signature-tooltip-item">
217
+ <span class="signature-status-badge ${getStatusBadgeClass(dkim)}">
218
+ ${getStatusLabel(dkim)}
219
+ </span>
220
+ </div>
221
+ ` : ''}
222
+ ${spf ? `
223
+ <h3>SPF</h3>
224
+ <div class="signature-tooltip-item">
225
+ <span class="signature-status-badge ${getStatusBadgeClass(spf)}">
226
+ ${getStatusLabel(spf)}
227
+ </span>
228
+ </div>
229
+ ` : ''}
230
+ </div>
231
+ `;
232
+ }
233
+
234
+ if (signatureInfoBtn) {
235
+ signatureInfoBtn.addEventListener('click', function(e) {
236
+ e.preventDefault();
237
+ if (window.MailCatcher) {
238
+ const messageId = window.MailCatcher.selectedMessage();
239
+ if (!messageId) return;
240
+
241
+ // Destroy existing tooltip if any
242
+ if (signatureTooltip) {
243
+ signatureTooltip.destroy();
244
+ }
245
+
246
+ // Fetch message data
247
+ fetch(new URL(`messages/${messageId}.json`, document.baseURI).toString())
248
+ .then(response => response.json())
249
+ .then(data => {
250
+ const authResults = data.authentication_results || {};
251
+ const content = generateSignatureContent(authResults);
252
+
253
+ // Create tooltip with content
254
+ signatureTooltip = tippy(signatureInfoBtn, {
255
+ content: content,
256
+ allowHTML: true,
257
+ theme: 'light',
258
+ placement: 'bottom-start',
259
+ interactive: true,
260
+ duration: [200, 150],
261
+ arrow: true,
262
+ trigger: 'manual',
263
+ maxWidth: 360,
264
+ onClickOutside: (instance) => {
265
+ instance.hide();
266
+ },
267
+ });
268
+
269
+ // Show tooltip
270
+ signatureTooltip.show();
271
+ })
272
+ .catch(error => {
273
+ console.error('Error fetching signature data:', error);
274
+ const errorContent = `
275
+ <div class="signature-tooltip-content">
276
+ <p style="color: #999; font-size: 12px;">Error loading signature data.</p>
277
+ </div>
278
+ `;
279
+
280
+ signatureTooltip = tippy(signatureInfoBtn, {
281
+ content: errorContent,
282
+ allowHTML: true,
283
+ theme: 'light',
284
+ placement: 'bottom-start',
285
+ interactive: true,
286
+ duration: [200, 150],
287
+ arrow: true,
288
+ trigger: 'manual',
289
+ onClickOutside: (instance) => {
290
+ instance.hide();
291
+ },
292
+ });
293
+
294
+ signatureTooltip.show();
295
+ });
296
+ }
297
+ });
298
+
299
+ // Close tooltip when clicking elsewhere
300
+ document.addEventListener('click', function(e) {
301
+ // Check if this click is on the button
302
+ if (signatureInfoBtn && signatureInfoBtn.contains(e.target)) return;
303
+
304
+ // Check if this click is on a tippy-box
305
+ if (e.target.closest('.tippy-box')) return;
306
+
307
+ // Click is outside everything, hide tooltip
308
+ if (signatureTooltip) {
309
+ signatureTooltip.hide();
310
+ }
311
+
312
+ // Also ensure any visible tippy-box is hidden
313
+ const visibleBoxes = document.querySelectorAll('.tippy-box[data-state="visible"]');
314
+ visibleBoxes.forEach(box => {
315
+ box.style.visibility = 'hidden';
316
+ box.style.pointerEvents = 'none';
317
+ });
318
+ });
319
+ }
320
+ };
321
+
322
+ window.MailCatcherUI.initializeEncryptionTooltip = function() {
323
+ const encryptionInfoBtn = document.getElementById('encryptionInfoBtn');
324
+ let encryptionTooltip = null;
325
+
326
+ function generateEncryptionContent(encryptionData) {
327
+ const smime = encryptionData?.smime;
328
+ const pgp = encryptionData?.pgp;
329
+
330
+ // Check if any encryption data exists
331
+ const hasEncryptionData = smime || pgp;
332
+
333
+ if (!hasEncryptionData) {
334
+ return `
335
+ <div class="encryption-tooltip-content">
336
+ <p class="encryption-no-data">No encryption or signature information found for this email.</p>
337
+ </div>
338
+ `;
339
+ }
340
+
341
+ let content = '<div class="encryption-tooltip-content">';
342
+
343
+ if (smime) {
344
+ content += `
345
+ <h3>S/MIME</h3>
346
+ ${smime.certificate ? `
347
+ <div class="encryption-info-item">
348
+ <span class="encryption-info-label">Certificate:</span>
349
+ <span class="encryption-info-value">${window.MailCatcherUI.escapeHtml(smime.certificate.substring(0, 40))}...</span>
350
+ </div>
351
+ <button class="encryption-copy-button" data-copy-type="smime-cert" data-value="${window.MailCatcherUI.escapeHtml(smime.certificate)}">
352
+ <svg fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
353
+ <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 7.5V6.108c0-1.135.845-2.098 1.976-2.192.373-.03.748-.057 1.123-.08M15.75 18H18a2.25 2.25 0 0 0 2.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 0 0-1.123-.08M15.75 18.75v-1.875a3.375 3.375 0 0 0-3.375-3.375h-1.5a1.125 1.125 0 0 1-1.125-1.125v-1.5A3.375 3.375 0 0 0 6.375 7.5H5.25m11.9-3.664A2.251 2.251 0 0 0 15 2.25h-1.5a2.251 2.251 0 0 0-2.15 1.586m5.8 0c.065.21.1.433.1.664v.75h-6V4.5c0-.231.035-.454.1-.664M6.75 7.5H4.875c-.621 0-1.125.504-1.125 1.125v12c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V16.5a9 9 0 0 0-9-9Z"></path>
354
+ </svg>
355
+ Copy Certificate
356
+ </button>
357
+ ` : ''}
358
+ ${smime.signature ? `
359
+ <div class="encryption-info-item">
360
+ <span class="encryption-info-label">Signature:</span>
361
+ <span class="encryption-info-value">${window.MailCatcherUI.escapeHtml(smime.signature.substring(0, 40))}...</span>
362
+ </div>
363
+ <button class="encryption-copy-button" data-copy-type="smime-sig" data-value="${window.MailCatcherUI.escapeHtml(smime.signature)}">
364
+ <svg fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
365
+ <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 7.5V6.108c0-1.135.845-2.098 1.976-2.192.373-.03.748-.057 1.123-.08M15.75 18H18a2.25 2.25 0 0 0 2.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 0 0-1.123-.08M15.75 18.75v-1.875a3.375 3.375 0 0 0-3.375-3.375h-1.5a1.125 1.125 0 0 1-1.125-1.125v-1.5A3.375 3.375 0 0 0 6.375 7.5H5.25m11.9-3.664A2.251 2.251 0 0 0 15 2.25h-1.5a2.251 2.251 0 0 0-2.15 1.586m5.8 0c.065.21.1.433.1.664v.75h-6V4.5c0-.231.035-.454.1-.664M6.75 7.5H4.875c-.621 0-1.125.504-1.125 1.125v12c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V16.5a9 9 0 0 0-9-9Z"></path>
366
+ </svg>
367
+ Copy Signature
368
+ </button>
369
+ ` : ''}
370
+ `;
371
+ }
372
+
373
+ if (pgp) {
374
+ content += `
375
+ <h3>OpenPGP</h3>
376
+ ${pgp.key ? `
377
+ <div class="encryption-info-item">
378
+ <span class="encryption-info-label">Key:</span>
379
+ <span class="encryption-info-value">${window.MailCatcherUI.escapeHtml(pgp.key.substring(0, 40))}...</span>
380
+ </div>
381
+ <button class="encryption-copy-button" data-copy-type="pgp-key" data-value="${window.MailCatcherUI.escapeHtml(pgp.key)}">
382
+ <svg fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
383
+ <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 7.5V6.108c0-1.135.845-2.098 1.976-2.192.373-.03.748-.057 1.123-.08M15.75 18H18a2.25 2.25 0 0 0 2.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 0 0-1.123-.08M15.75 18.75v-1.875a3.375 3.375 0 0 0-3.375-3.375h-1.5a1.125 1.125 0 0 1-1.125-1.125v-1.5A3.375 3.375 0 0 0 6.375 7.5H5.25m11.9-3.664A2.251 2.251 0 0 0 15 2.25h-1.5a2.251 2.251 0 0 0-2.15 1.586m5.8 0c.065.21.1.433.1.664v.75h-6V4.5c0-.231.035-.454.1-.664M6.75 7.5H4.875c-.621 0-1.125.504-1.125 1.125v12c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V16.5a9 9 0 0 0-9-9Z"></path>
384
+ </svg>
385
+ Copy Key
386
+ </button>
387
+ ` : ''}
388
+ ${pgp.signature ? `
389
+ <div class="encryption-info-item">
390
+ <span class="encryption-info-label">Signature:</span>
391
+ <span class="encryption-info-value">${window.MailCatcherUI.escapeHtml(pgp.signature.substring(0, 40))}...</span>
392
+ </div>
393
+ <button class="encryption-copy-button" data-copy-type="pgp-sig" data-value="${window.MailCatcherUI.escapeHtml(pgp.signature)}">
394
+ <svg fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
395
+ <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 7.5V6.108c0-1.135.845-2.098 1.976-2.192.373-.03.748-.057 1.123-.08M15.75 18H18a2.25 2.25 0 0 0 2.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 0 0-1.123-.08M15.75 18.75v-1.875a3.375 3.375 0 0 0-3.375-3.375h-1.5a1.125 1.125 0 0 1-1.125-1.125v-1.5A3.375 3.375 0 0 0 6.375 7.5H5.25m11.9-3.664A2.251 2.251 0 0 0 15 2.25h-1.5a2.251 2.251 0 0 0-2.15 1.586m5.8 0c.065.21.1.433.1.664v.75h-6V4.5c0-.231.035-.454.1-.664M6.75 7.5H4.875c-.621 0-1.125.504-1.125 1.125v12c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V16.5a9 9 0 0 0-9-9Z"></path>
396
+ </svg>
397
+ Copy Signature
398
+ </button>
399
+ ` : ''}
400
+ `;
401
+ }
402
+
403
+ content += '</div>';
404
+ return content;
405
+ }
406
+
407
+ if (encryptionInfoBtn) {
408
+ encryptionInfoBtn.addEventListener('click', function(e) {
409
+ e.preventDefault();
410
+ if (window.MailCatcher) {
411
+ const messageId = window.MailCatcher.selectedMessage();
412
+ if (!messageId) return;
413
+
414
+ // Destroy existing tooltip if any
415
+ if (encryptionTooltip) {
416
+ encryptionTooltip.destroy();
417
+ }
418
+
419
+ // Fetch message data
420
+ fetch(new URL(`messages/${messageId}.json`, document.baseURI).toString())
421
+ .then(response => response.json())
422
+ .then(data => {
423
+ const encryptionData = data.encryption_data || {};
424
+ const content = generateEncryptionContent(encryptionData);
425
+
426
+ // Create tooltip with content
427
+ encryptionTooltip = tippy(encryptionInfoBtn, {
428
+ content: content,
429
+ allowHTML: true,
430
+ theme: 'light',
431
+ placement: 'bottom-start',
432
+ interactive: true,
433
+ duration: [200, 150],
434
+ arrow: true,
435
+ trigger: 'manual',
436
+ maxWidth: 400,
437
+ onClickOutside: (instance) => {
438
+ instance.hide();
439
+ },
440
+ });
441
+
442
+ // Show tooltip
443
+ encryptionTooltip.show();
444
+
445
+ // Setup copy button handlers
446
+ const copyButtons = document.querySelectorAll('.encryption-copy-button');
447
+ copyButtons.forEach(btn => {
448
+ btn.addEventListener('click', function(e) {
449
+ e.preventDefault();
450
+ e.stopPropagation();
451
+
452
+ const value = this.getAttribute('data-value');
453
+ const copyType = this.getAttribute('data-copy-type');
454
+
455
+ // Copy to clipboard
456
+ navigator.clipboard.writeText(value).then(() => {
457
+ // Show success state
458
+ const originalHTML = this.innerHTML;
459
+ const originalClass = this.className;
460
+
461
+ this.classList.add('copied');
462
+ this.innerHTML = `
463
+ <svg fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
464
+ <path stroke-linecap="round" stroke-linejoin="round" d="M11.35 3.836c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75 2.25 2.25 0 0 0-.1-.664m-5.8 0A2.251 2.251 0 0 1 13.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m8.9-4.414c.376.023.75.05 1.124.08 1.131.094 1.976 1.057 1.976 2.192V16.5A2.25 2.25 0 0 1 18 18.75h-2.25m-7.5-10.5H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V18.75m-7.5-10.5h6.375c.621 0 1.125.504 1.125 1.125v9.375m-8.25-3 1.5 1.5 3-3.75"></path>
465
+ </svg>
466
+ Copied!
467
+ `;
468
+
469
+ // Reset after 3 seconds
470
+ setTimeout(() => {
471
+ this.className = originalClass;
472
+ this.innerHTML = originalHTML;
473
+ }, 3000);
474
+ }).catch(err => {
475
+ console.error('Failed to copy to clipboard:', err);
476
+ });
477
+ });
478
+ });
479
+ })
480
+ .catch(error => {
481
+ console.error('Error fetching encryption data:', error);
482
+ const errorContent = `
483
+ <div class="encryption-tooltip-content">
484
+ <p class="encryption-no-data">Error loading encryption data.</p>
485
+ </div>
486
+ `;
487
+
488
+ encryptionTooltip = tippy(encryptionInfoBtn, {
489
+ content: errorContent,
490
+ allowHTML: true,
491
+ theme: 'light',
492
+ placement: 'bottom-start',
493
+ interactive: true,
494
+ duration: [200, 150],
495
+ arrow: true,
496
+ trigger: 'manual',
497
+ onClickOutside: (instance) => {
498
+ instance.hide();
499
+ },
500
+ });
501
+
502
+ encryptionTooltip.show();
503
+ });
504
+ }
505
+ });
506
+
507
+ // Close tooltip when clicking elsewhere
508
+ document.addEventListener('click', function(e) {
509
+ // Check if this click is on the button
510
+ if (encryptionInfoBtn && encryptionInfoBtn.contains(e.target)) return;
511
+
512
+ // Check if this click is on a tippy-box
513
+ if (e.target.closest('.tippy-box')) return;
514
+
515
+ // Click is outside everything, hide tooltip
516
+ if (encryptionTooltip) {
517
+ encryptionTooltip.hide();
518
+ }
519
+ });
520
+ }
521
+ };
522
+
523
+
524
+
525
+
526
+
527
+ // Initialize all UI components on page load
528
+ document.addEventListener('DOMContentLoaded', function() {
529
+ // Initialize UI components in order
530
+ window.MailCatcherUI.initializeResizer();
531
+ window.MailCatcherUI.initializeUIHandlers();
532
+ window.MailCatcherUI.initializeSignatureTooltip();
533
+ window.MailCatcherUI.initializeEncryptionTooltip();
534
+
535
+ console.log('[MailCatcher] UI components initialized');
536
+ });