PageSourceSearch

https://premkatha.com/wp-content/plugins/MyLove/assets/js/complements.js

js premkatha.com collected 2026-10-02 01:39:56 UTC 37,652 bytes, 795 lines download raw bytes

1(function($){
2    var isFetching = false;
3    
4    function fetchList(box, page){
5        if(isFetching) return; // Prevent duplicate requests
6        isFetching = true;
7        
8        // Ensure box is set correctly - normalize it
9        if(!box || box === 'undefined' || box === '') {
10            box = $('#dm-complements-list').attr('data-active') || 'inbox';
11        }
12        box = String(box).toLowerCase().trim();
13        
14        $('#dm-complements-list').attr('data-active', box).html('<p class="dm-loading">Loading...</p>');
15        $.get(dm_letters.ajax_url, {
16            action: 'dm_get_complements',
17            box: box,
18            page: page || 1
19        }).done(function(resp){
20            if(resp.success){
21                var html = '';
22                if(resp.data.items.length === 0){
23                    html = '<p class="dm-empty">No complements yet.</p>';
24                } else {
25                    // Use the normalized box parameter - ensure it's consistent
26                    var currentBox = String(box).toLowerCase().trim();
27                    resp.data.items.forEach(function(it){
28                        // Check if unread
29                        var isUnread = it.status === 'sent';
30                        var unreadClass = isUnread ? ' unread' : '';
31                        
32                        // Determine display name based on context
33                        var displayName = 'Unknown';
34                        // Check if anonymous - handle both string and number formats
35                        var isAnonymous = false;
36                        if(it.is_anonymous !== undefined && it.is_anonymous !== null) {
37                            var anonValue = parseInt(it.is_anonymous);
38                            isAnonymous = (anonValue === 1 || it.is_anonymous === '1' || it.is_anonymous === true || it.is_anonymous === 1);
39                        }
40                        
41                        // Normalize currentBox for comparison
42                        var normalizedBox = String(currentBox).toLowerCase().trim();
43                        
44                        if(normalizedBox === 'inbox' || normalizedBox === 'public') {
45                            // For inbox/public: show sender name, or "Unknown" if anonymous
46                            if(isAnonymous) {
47                                displayName = 'Unknown';
48                            } else {
49                                displayName = it.sender || 'Unknown';
50                            }
51                        } else {
52                            // For sent/outbox: show recipient name (sender always knows who they sent to)
53                            displayName = it.recipient || 'Unknown';
54                        }
55                        
56                        // List item (Gmail-style) - no preview text
57                        html += '<div class="dm-letter-list-item' + unreadClass + '" data-id="' + it.id + '">';
58                        html += '<div class="dm-letter-envelope-icon">';
59                        html += '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h640q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160Zm320-280L160-640v400h640v-400L480-440Zm0-80 320-200H160l320 200ZM160-640v-80 480-400Z"/></svg>';
60                        html += '</div>';
61                        html += '<div class="dm-letter-list-sender-info">';
62                        html += '<div class="dm-letter-list-sender' + (isAnonymous && (currentBox === 'inbox' || currentBox === 'public') ? ' anonymous' : '') + '">' + displayName + '</div>';
63                        html += '<div class="dm-letter-list-date">' + it.created_at + '</div>';
64                        html += '</div>';
65                        html += '<span class="dm-badge dm-letter-list-badge dm-visibility">' + it.visibility + '</span>';
66                        html += '</div>';
67                        
68                        // Full letter view (hidden by default, shown when expanded)
69                        html += '<div class="dm-letter expanded-view" data-letter-id="' + it.id + '" style="display:none;">';
70                        html += '<div class="dm-letter-content">' + it.content + '</div>';
71                        // Read receipt indicator (double tick) - show for both sender and receiver if letter is read
72                        // Don't show if letter is accepted or rejected
73                        if(dm_letters.is_logged && it.status === 'read' && !(it.rejected_by_recipient && it.rejected_by_recipient === 1)){
74                            html += '<div class="dm-read-receipt">';
75                            html += '<svg xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 -960 960 960" width="20px" fill="#4CAF50"><path d="M382-240 154-468l57-57 171 171 367-367 57 57-424 424Zm0-240L154-708l57-57 171 171 367-367 57 57-424 424Z"/></svg>';
76                            html += '</div>';
77                        }
78                        // Accepted indicator - show if letter is accepted
79                        if(dm_letters.is_logged && it.status === 'accepted'){
80                            html += '<div class="dm-accepted-indicator">';
81                            html += '<svg xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 -960 960 960" width="20px" fill="#4CAF50"><path d="M382-240 154-468l57-57 171 171 367-367 57 57-424 424Zm0-240L154-708l57-57 171 171 367-367 57 57-424 424Z"/></svg>';
82                            html += '<span>' + (currentBox === 'inbox' ? 'Accepted' : 'Accepted') + '</span>';
83                            html += '</div>';
84                        }
85                        // Rejected indicator - show if letter is rejected (only in outbox for sender)
86                        if(dm_letters.is_logged && currentBox === 'outbox' && it.rejected_by_recipient && it.rejected_by_recipient === 1){
87                            html += '<div class="dm-rejected-indicator">';
88                            html += '<svg xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 -960 960 960" width="20px" fill="#4CAF50"><path d="M382-240 154-468l57-57 171 171 367-367 57 57-424 424Zm0-240L154-708l57-57 171 171 367-367 57 57-424 424Z"/></svg>';
89                            html += '<span>Rejected</span>';
90                            html += '</div>';
91                        }
92                        // Show actions based on box type
93                        if(dm_letters.is_logged){
94                            html += '<div class="dm-letter-actions" data-id="'+ it.id +'">';
95                            // In inbox: show accept and reject buttons if letter is not yet accepted and sender is registered
96                            if(currentBox === 'inbox' && it.status !== 'accepted' && it.status !== 'deleted' && it.sender_id && it.sender_id > 0){
97                                html += '<button class="dm-btn dm-accept-letter dm-btn-primary">Accept & Connect</button>';
98                                html += '<button class="dm-btn dm-reject-letter dm-btn-danger">Reject & Delete</button>';
99                            }
100                            // In outbox: show delete button
101                            if(currentBox === 'outbox'){
102                                html += '<button class="dm-btn dm-delete-letter">Delete</button>';
103                            }
104                            html += '</div>';
105                        }
106                        html += '</div>';
107                    });
108                }
109                $('#dm-complements-list').html(html);
110            } else {
111                $('#dm-complements-list').html('<p class="dm-error">'+ (resp.data && resp.data.message ? resp.data.message : 'Error') +'</p>');
112            }
113        }).fail(function(){
114            $('#dm-complements-list').html('<p class="dm-error">Network error.</p>');
115        }).always(function(){
116            isFetching = false;
117        });
118    }
119
120    // Complement preview functionality
121    function updatePreview() {
122        // Try new ID first, fallback to old for backward compatibility
123        var $textarea = $('#dm-complement-content');
124        if (!$textarea.length) {
125            $textarea = $('#dm-letter-content');
126        }
127        var content = $textarea.val();
128        var $preview = $('#dm-preview-content');
129        
130        if (!content || content.trim() === '') {
131            $preview.html('<p class="dm-preview-placeholder">Start typing your complement to see the preview...</p>');
132            return;
133        }
134        
135        // Convert line breaks to paragraphs
136        var formattedContent = content.split('\n\n').map(function(paragraph) {
137            if (paragraph.trim()) {
138                return '<p>' + paragraph.trim().replace(/\n/g, '<br>') + '</p>';
139            }
140            return '';
141        }).filter(function(p) { return p; }).join('');
142        
143        if (!formattedContent) {
144            formattedContent = '<p>' + content.replace(/\n/g, '<br>') + '</p>';
145        }
146        
147        $preview.html(formattedContent);
148    }
149    
150    // Initialize preview on content change
151    $(document).on('input', '#dm-complement-content, #dm-letter-content', function() {
152        updatePreview();
153    });
154    
155    // Initialize preview on page load
156    $(document).ready(function() {
157        updatePreview();
158    });
159
160    function sendLetter(e){
161        e.preventDefault();
162        var $form = $('#dm-compose-form');
163        
164        // Check if form is disabled (suspended account)
165        if ($form.closest('.dm-compose').css('pointer-events') === 'none') {
166            alert('Your account has been suspended. You cannot send complements.');
167            return;
168        }
169        
170        var data = {
171            action: 'dm_send_complement',
172            nonce: dm_letters.nonce,
173            recipient: $form.find('[name="recipient"]').val(),
174            content: $form.find('[name="content"]').val(),
175            visibility: $form.find('[name="visibility"]').val(),
176            is_anonymous: $form.find('[name="is_anonymous"]').is(':checked') ? 1 : 0
177        };
178        $form.find('button[type="submit"]').prop('disabled', true);
179        $.post(dm_letters.ajax_url, data).done(function(resp){
180            var $feedback = $('.dm-feedback');
181            if(resp.success){
182                $feedback.removeClass('error').text(dm_letters.strings.sent).show();
183                $form[0].reset();
184                updatePreview(); // Reset preview after form submission
185                fetchList('outbox', 1);
186                // Hide feedback after 3 seconds
187                setTimeout(function(){ $feedback.fadeOut(); }, 3000);
188            } else {
189                $feedback.addClass('error').text(resp.data && resp.data.message ? resp.data.message : dm_letters.strings.error).show();
190            }
191        }).fail(function(){
192            $('.dm-feedback').addClass('error').text(dm_letters.strings.error).show();
193        }).always(function(){
194            $form.find('button[type="submit"]').prop('disabled', false);
195        });
196    }
197
198    // User autocomplete functionality
199    var autocompleteTimeout = null;
200    var selectedUserIndex = -1;
201    var connectionCheckTimeout = null;
202    
203    // Function to check connection status and update UI (global scope for event handlers)
204    function checkRecipientConnectionStatus(recipient) {
205        var $anonymousField = $('#dm-anonymous-field');
206        
207        if (connectionCheckTimeout) {
208            clearTimeout(connectionCheckTimeout);
209        }
210        
211        // Clear recipient - show anonymous field
212        if (!recipient || recipient.trim() === '') {
213            $anonymousField.show();
214            $('#dm-is-anonymous').prop('checked', false);
215            return;
216        }
217        
218        // Debounce connection check
219        connectionCheckTimeout = setTimeout(function() {
220            $.get(dm_letters.ajax_url, {
221                action: 'dm_check_recipient_connection',
222                nonce: dm_letters.nonce,
223                recipient: recipient
224            }).done(function(resp) {
225                if (resp.success && resp.data) {
226                    var isConnected = resp.data.is_connected === true;
227                    
228                    if (isConnected) {
229                        // Hide anonymous field and uncheck if connected
230                        $anonymousField.slideUp(200);
231                        $('#dm-is-anonymous').prop('checked', false);
232                    } else {
233                        // Show anonymous field if not connected
234                        $anonymousField.slideDown(200);
235                    }
236                } else {
237                    // Default: show anonymous field
238                    $anonymousField.slideDown(200);
239                }
240            }).fail(function() {
241                // On error, show anonymous field (safe default)
242                $anonymousField.slideDown(200);
243            });
244        }, 500);
245    }
246    
247    function initUserAutocomplete() {
248        var $input = $('#dm-recipient-input');
249        var $dropdown = $('#dm-user-autocomplete');
250        
251        if (!$input.length || !$dropdown.length) return;
252        
253        // Handle input
254        $input.on('input', function() {
255            var query = $(this).val().trim();
256            
257            // Clear previous timeout
258            if (autocompleteTimeout) {
259                clearTimeout(autocompleteTimeout);
260            }
261            
262            // Check connection status when recipient changes
263            checkRecipientConnectionStatus(query);
264            
265            // Hide dropdown if empty or looks like email (contains @ and more than just @)
266            if (query.length < 2 || (query.includes('@') && query.indexOf('@') > 0)) {
267                $dropdown.hide().empty();
268                selectedUserIndex = -1;
269                return;
270            }
271            
272            // Remove @ prefix for search if present
273            if (query.startsWith('@')) {
274                query = query.substring(1);
275            }
276            
277            // Debounce search
278            autocompleteTimeout = setTimeout(function() {
279                searchUsers(query);
280            }, 300);
281        });
282        
283        // Handle keyboard navigation
284        $input.on('keydown', function(e) {
285            var $items = $dropdown.find('.dm-autocomplete-item');
286            
287            if ($items.length === 0) return;
288            
289            // Arrow down
290            if (e.key === 'ArrowDown') {
291                e.preventDefault();
292                selectedUserIndex = Math.min(selectedUserIndex + 1, $items.length - 1);
293                updateSelection($items);
294            }
295            // Arrow up
296            else if (e.key === 'ArrowUp') {
297                e.preventDefault();
298                selectedUserIndex = Math.max(selectedUserIndex - 1, -1);
299                updateSelection($items);
300            }
301            // Enter
302            else if (e.key === 'Enter' && selectedUserIndex >= 0) {
303                e.preventDefault();
304                $items.eq(selectedUserIndex).click();
305            }
306            // Escape
307            else if (e.key === 'Escape') {
308                $dropdown.hide().empty();
309                selectedUserIndex = -1;
310            }
311        });
312        
313        // Hide dropdown when clicking outside
314        $(document).on('click', function(e) {
315            if (!$(e.target).closest('.dm-recipient-input-wrapper').length) {
316                $dropdown.hide();
317                selectedUserIndex = -1;
318            }
319        });
320        
321        // Handle item selection
322        $(document).on('click', '.dm-autocomplete-item', function(e) {
323            e.preventDefault();
324            var username = $(this).data('username');
325            // Add @ prefix if not already present
326            var value = username.startsWith('@') ? username : '@' + username;
327            $input.val(value);
328            $dropdown.hide().empty();
329            selectedUserIndex = -1;
330            
331            // Check connection status for selected user
332            checkRecipientConnectionStatus(value);
333            
334            $input.focus();
335        });
336    }
337    
338    function searchUsers(query) {
339        var $dropdown = $('#dm-user-autocomplete');
340        
341        // Remove @ prefix if present for search
342        var searchQuery = query.startsWith('@') ? query.substring(1) : query;
343        
344        $.get(dm_letters.ajax_url, {
345            action: 'dm_search_users',
346            nonce: dm_letters.nonce,
347            q: searchQuery
348        }).done(function(resp) {
349            if (resp.success && resp.data && resp.data.length > 0) {
350                var html = '';
351                resp.data.forEach(function(user) {
352                    html += '<div class="dm-autocomplete-item" data-username="' + 
353                            user.username + '" data-email="' + user.email + '">';
354                    html += '<div class="dm-autocomplete-name">' + user.name + '</div>';
355                    html += '<div class="dm-autocomplete-username">@' + user.username + '</div>';
356                    html += '</div>';
357                });
358                $dropdown.html(html).show();
359                selectedUserIndex = -1;
360            } else {
361                $dropdown.hide().empty();
362            }
363        }).fail(function() {
364            $dropdown.hide().empty();
365        });
366    }
367    
368    function updateSelection($items) {
369        $items.removeClass('selected');
370        if (selectedUserIndex >= 0) {
371            $items.eq(selectedUserIndex).addClass('selected');
372            // Scroll into view
373            var $selected = $items.eq(selectedUserIndex);
374            var dropdown = $selected.closest('.dm-user-autocomplete')[0];
375            if (dropdown) {
376                $selected[0].scrollIntoView({ block: 'nearest', behavior: 'smooth' });
377            }
378        }
379    }
380
381    function bindEvents(){
382        $(document).on('submit', '#dm-compose-form', sendLetter);
383        $(document).on('click', '.dm-tabs .dm-tab', function(){
384            var $tab = $(this);
385            var box = $tab.data('box');
386            $('.dm-tabs .dm-tab').removeClass('active');
387            $tab.addClass('active');
388            $('#dm-complements-list').attr('data-active', box);
389            
390            // Update URL with box parameter
391            var url = new URL(window.location.href);
392            url.searchParams.set('box', box);
393            // Ensure tab=complements is set
394            url.searchParams.set('tab', 'complements');
395            window.history.pushState({tab: 'complements', box: box}, '', url.toString());
396            
397            fetchList(box, 1);
398        });
399        // Toggle letter expansion (Gmail-style)
400        $(document).on('click', '.dm-letter-list-item', function(e){
401            // Don't trigger if clicking on delete or reject button
402            if($(e.target).closest('.dm-delete-letter').length) return;
403            if($(e.target).closest('.dm-reject-letter').length) return;
404            
405            var $item = $(this);
406            var letterId = $item.data('id');
407            var $fullLetter = $('.dm-letter.expanded-view[data-letter-id="' + letterId + '"]');
408            var currentBox = $('#dm-complements-list').attr('data-active') || 'inbox';
409            currentBox = String(currentBox).toLowerCase().trim();
410            
411            if($fullLetter.length) {
412                // Toggle expansion
413                if($fullLetter.is(':visible')) {
414                    $fullLetter.slideUp(300);
415                    $item.removeClass('expanded');
416                } else {
417                    // Close other expanded letters
418                    $('.dm-letter.expanded-view:visible').slideUp(300);
419                    $('.dm-letter-list-item.expanded').removeClass('expanded');
420                    
421                    // Show this letter
422                    $fullLetter.slideDown(300);
423                    $item.addClass('expanded');
424                    
425                    // Mark as read if in inbox and letter is unread
426                    if((currentBox === 'inbox' || currentBox === 'public') && $item.hasClass('unread')) {
427                        $.post(dm_letters.ajax_url, {
428                            action: 'dm_mark_letter_read',
429                            nonce: dm_letters.nonce,
430                            id: letterId
431                        }).done(function(resp){
432                            if(resp.success){
433                                // Remove unread class and update status
434                                $item.removeClass('unread');
435                                // Update the status in the data so read receipt can appear if viewed in outbox
436                                $item.data('status', 'read');
437                            }
438                        });
439                    }
440                    
441                    // Scroll to letter if needed
442                    $('html, body').animate({
443                        scrollTop: $item.offset().top - 20
444                    }, 300);
445                }
446            }
447        });
448        
449        $(document).on('click', '.dm-delete-letter', function(e){
450            e.stopPropagation(); // Prevent triggering list item click
451            var id = $(this).closest('.dm-letter-actions').data('id');
452            var $btn = $(this);
453            
454            // Use custom modal instead of confirm
455            if (typeof dmConfirm === 'function') {
456                dmConfirm('Are you sure you want to proceed?', 'Delete Letter').then(function(confirmed) {
457                    if (!confirmed) return;
458                    
459                    $.post(dm_letters.ajax_url, {
460                        action: 'dm_delete_complement',
461                        nonce: dm_letters.nonce,
462                        id: id
463                    }).done(function(resp){
464                        if(resp.success){
465                            // No success alert - just refresh the list
466                            var box = $('#dm-complements-list').attr('data-active') || 'outbox';
467                            fetchList(box, 1);
468                        } else {
469                            if (typeof dmAlert === 'function') {
470                                dmAlert(resp.data && resp.data.message ? resp.data.message : dm_letters.strings.error, 'Error', 'error');
471                            } else {
472                                alert(resp.data && resp.data.message ? resp.data.message : dm_letters.strings.error);
473                            }
474                        }
475                    });
476                });
477            } else {
478                // Fallback to native confirm if modal not loaded
479                if(!confirm('Are you sure you want to proceed?')) return;
480                $.post(dm_letters.ajax_url, {
481                    action: 'dm_delete_complement',
482                    nonce: dm_letters.nonce,
483                    id: id
484                }).done(function(resp){
485                    if(resp.success){
486                        // No success alert - just refresh the list
487                        var box = $('#dm-letters-list').attr('data-active') || 'outbox';
488                        fetchList(box, 1);
489                    } else {
490                        alert(resp.data && resp.data.message ? resp.data.message : dm_letters.strings.error);
491                    }
492                });
493            }
494        });
495        
496        // Handle accept letter button
497        $(document).on('click', '.dm-accept-letter', function(e){
498            e.stopPropagation(); // Prevent triggering list item click
499            var id = $(this).closest('.dm-letter-actions').data('id');
500            var $btn = $(this);
501            
502            // Disable button to prevent double-click
503            $btn.prop('disabled', true).text('Accepting...');
504            
505            $.post(dm_letters.ajax_url, {
506                action: 'dm_accept_complement',
507                nonce: dm_letters.nonce,
508                id: id
509            }).done(function(resp){
510                if(resp.success){
511                    // Show success message
512                    if (typeof dmAlert === 'function') {
513                        dmAlert(resp.data && resp.data.message ? resp.data.message : 'Letter accepted! Connection request sent.', 'Success', 'success');
514                    } else {
515                        alert(resp.data && resp.data.message ? resp.data.message : 'Letter accepted! Connection request sent.');
516                    }
517                    // Refresh the list to show updated status
518                    var box = $('#dm-complements-list').attr('data-active') || 'inbox';
519                    fetchList(box, 1);
520                } else {
521                    // Re-enable button on error
522                    $btn.prop('disabled', false).text('Accept & Connect');
523                    if (typeof dmAlert === 'function') {
524                        dmAlert(resp.data && resp.data.message ? resp.data.message : dm_letters.strings.error, 'Error', 'error');
525                    } else {
526                        alert(resp.data && resp.data.message ? resp.data.message : dm_letters.strings.error);
527                    }
528                }
529            }).fail(function(){
530                // Re-enable button on failure
531                $btn.prop('disabled', false).text('Accept & Connect');
532                if (typeof dmAlert === 'function') {
533                    dmAlert('Network error. Please try again.', 'Error', 'error');
534                } else {
535                    alert('Network error. Please try again.');
536                }
537            });
538        });
539
540        // Handle reject letter button
541        $(document).on('click', '.dm-reject-letter', function(e){
542            e.stopPropagation(); // Prevent triggering list item click
543            var id = $(this).closest('.dm-letter-actions').data('id');
544            var $btn = $(this);
545            
546            // Use custom modal instead of confirm
547            if (typeof dmConfirm === 'function') {
548                dmConfirm('Are you sure you want to reject and delete this letter? This action cannot be undone.', 'Reject & Delete Letter').then(function(confirmed) {
549                    if (!confirmed) return;
550                    
551                    $btn.prop('disabled', true).text('Processing...');
552                    
553                    $.post(dm_letters.ajax_url, {
554                        action: 'dm_reject_complement',
555                        nonce: dm_letters.nonce,
556                        id: id
557                    }).done(function(resp){
558                        if(resp.success){
559                            // Refresh the list
560                            var box = $('#dm-complements-list').attr('data-active') || 'inbox';
561                            fetchList(box, 1);
562                        } else {
563                            if (typeof dmAlert === 'function') {
564                                dmAlert(resp.data && resp.data.message ? resp.data.message : dm_letters.strings.error, 'Error', 'error');
565                            } else {
566                                alert(resp.data && resp.data.message ? resp.data.message : dm_letters.strings.error);
567                            }
568                            $btn.prop('disabled', false).text('Reject & Delete');
569                        }
570                    }).fail(function(){
571                        if (typeof dmAlert === 'function') {
572                            dmAlert('Network error. Please try again.', 'Error', 'error');
573                        } else {
574                            alert('Network error. Please try again.');
575                        }
576                        $btn.prop('disabled', false).text('Reject & Delete');
577                    });
578                });
579            } else {
580                // Fallback to native confirm if modal not loaded
581                if(!confirm('Are you sure you want to reject and delete this letter? This action cannot be undone.')) return;
582                
583                $btn.prop('disabled', true).text('Processing...');
584                
585                $.post(dm_letters.ajax_url, {
586                    action: 'dm_reject_complement',
587                    nonce: dm_letters.nonce,
588                    id: id
589                }).done(function(resp){
590                    if(resp.success){
591                        // Refresh the list
592                        var box = $('#dm-complements-list').attr('data-active') || 'inbox';
593                        fetchList(box, 1);
594                    } else {
595                        if (typeof dmAlert === 'function') {
596                            dmAlert(resp.data && resp.data.message ? resp.data.m
596essage : dm_letters.strings.error, 'Error', 'error');
597                        } else {
598                            alert(resp.data && resp.data.message ? resp.data.message : dm_letters.strings.error);
599                        }
600                        $btn.prop('disabled', false).text('Reject & Delete');
601                    }
602                }).fail(function(){
603                    if (typeof dmAlert === 'function') {
604                        dmAlert('Network error. Please try again.', 'Error', 'error');
605                    } else {
606                        alert('Network error. Please try again.');
607                    }
608                    $btn.prop('disabled', false).text('Reject & Delete');
609                });
610            }
611        });
612    }
613
614    // Function to update URL with tab parameter
615    function updateURL(tab, box) {
616        var url = new URL(window.location.href);
617        if(tab === 'compose') {
618            // Remove tab and box parameters for compose (default view)
619            url.searchParams.delete('tab');
620            url.searchParams.delete('box');
621        } else {
622            url.searchParams.set('tab', tab);
623            // Update box parameter if provided and tab is complements
624            if (box && tab === 'complements') {
625                url.searchParams.set('box', box);
626            } else if (tab === 'complements' && !box) {
627                // If switching to complements but no box specified, keep existing box or default to inbox
628                var currentBox = url.searchParams.get('box') || 'inbox';
629                url.searchParams.set('box', currentBox);
630            }
631        }
632        window.history.pushState({tab: tab, box: box || null}, '', url.toString());
633    }
634    
635    // Function to switch main tab
636    function switchMainTab(section, updateUrl) {
637        // Update main tabs
638        $('.dm-main-tab').removeClass('active');
639        $('.dm-main-tab[data-section="' + section + '"]').addClass('active');
640        
641        // Update sections
642        $('.dm-section').removeClass('active');
643        $('.dm-section-' + section).addClass('active');
644        
645        // Get current box from URL or data attribute
646        var currentBox = null;
647        if (section === 'complements') {
648            var urlParams = new URLSearchParams(window.location.search);
649            currentBox = urlParams.get('box') || $('#dm-complements-list').attr('data-active') || 'inbox';
650            currentBox = String(currentBox).toLowerCase().trim();
651            // Validate box
652            if (!['inbox', 'outbox', 'public'].includes(currentBox)) {
653                currentBox = 'inbox';
654            }
655        }
656        
657        // Update URL if requested
658        if(updateUrl !== false) {
659            updateURL(section, currentBox);
660        }
661        
662        // If switching to letters section, load the list if not already loaded
663        if(section === 'complements' && $('#dm-complements-list').length){
664            var active = currentBox || $('#dm-complements-list').attr('data-active') || 'inbox';
665            active = String(active).toLowerCase().trim();
666            
667            // Validate active box
668            if (!['inbox', 'outbox', 'public'].includes(active)) {
669                active = 'inbox';
670            }
671            
672            // Check if any tab already has active class (from PHP)
673            var $existingActive = $('.dm-tabs .dm-tab.active');
674            if($existingActive.length === 0) {
675                // No active tab found, set the correct one as active
676                $('.dm-tabs .dm-tab').removeClass('active');
677                var $activeTab = $('.dm-tabs .dm-tab[data-box="' + active + '"]');
678                if($activeTab.length === 0) {
679                    active = 'inbox';
680                    $activeTab = $('.dm-tabs .dm-tab').first();
681                }
682                $activeTab.addClass('active');
683            } else {
684                // Use the box from URL if it differs from the active tab
685                var activeTabBox = $existingActive.attr('data-box');
686                if (activeTabBox !== active) {
687                    $('.dm-tabs .dm-tab').removeClass('active');
688                    $('.dm-tabs .dm-tab[data-box="' + active + '"]').addClass('active');
689                }
690                active = activeTabBox || active;
691            }
692            
693            $('#dm-complements-list').attr('data-active', active);
694            
695            // Only fetch if list is empty or if box changed
696            if($('#dm-complements-list').html().trim() === '' || $('#dm-complements-list').attr('data-active') !== active) {
697                fetchList(active, 1);
698            }
699        }
700    }
701    
702    // Main tab switching (Write a Complement / Complements)
703    $(document).on('click', '.dm-main-tab', function(e){
704        e.preventDefault();
705        var section = $(this).data('section');
706        switchMainTab(section, true);
707    });
708    
709    // Handle browser back/forward navigation
710    window.addEventListener('popstate', function(event) {
711        var urlParams = new URLSearchParams(window.location.search);
712        var tab = urlParams.get('tab') || 'compose';
713        var box = urlParams.get('box') || 'inbox';
714        
715        // Update the data-active attribute if we're on complements tab
716        if (tab === 'complements') {
717            $('#dm-complements-list').attr('data-active', box);
718            // Update active sub-tab
719            $('.dm-tabs .dm-tab').removeClass('active');
720            $('.dm-tabs .dm-tab[data-box="' + box + '"]').addClass('active');
721        }
722        
723        switchMainTab(tab, false); // Don't update URL again since browser already did
724    });
725
726    $(function(){
727        bindEvents();
728        initUserAutocomplete();
729        
730        // Pre-fill recipient when opened via compose_to (e.g. from Love Sprint Send Compliment)
731        var $composeCard = $('.dm-compose');
732        if ($composeCard.length) {
733            var composeToUsername = $composeCard.attr('data-compose-to-username');
734            if (composeToUsername && $('#dm-recipient-input').length) {
735                $('#dm-recipient-input').val(composeToUsername);
736            }
737        }
738        
739        // Read tab and box from URL on page load
740        var urlParams = new URLSearchParams(window.location.search);
741        var tabFromUrl = urlParams.get('tab') || 'compose';
742        var boxFromUrl = urlParams.get('box') || 'inbox';
743        
744        // Validate box
745        if (!['inbox', 'outbox', 'public'].includes(boxFromUrl)) {
746            boxFromUrl = 'inbox';
747        }
748        
749        // Set the active box if we're on complements tab
750        if (tabFromUrl === 'complements' && $('#dm-complements-list').length) {
751            $('#dm-complements-list').attr('data-active', boxFromUrl);
752            // Update active sub-tab
753            $('.dm-tabs .dm-tab').removeClass('active');
754            $('.dm-tabs .dm-tab[data-box="' + boxFromUrl + '"]').addClass('active');
755        }
756        
757        // Switch to the tab from URL
758        switchMainTab(tabFromUrl, false); // Don't update URL on initial load
759        
760        // Auto-load default tab if container exists
761        if($('#dm-letters-list').length){
762            // Get active tab from data attribute, default to 'inbox'
763            var active = $('#dm-letters-list').attr('data-active');
764            if(!active || active === '' || active === 'undefined') {
765                active = 'inbox';
766                // Set the data attribute if it wasn't set
767                $('#dm-complements-list').attr('data-active', active);
768            }
769            active = String(active).toLowerCase().trim();
770            
771            // Check if any tab already has active class (from PHP)
772            var $existingActive = $('.dm-tabs .dm-tab.active');
773            if($existingActive.length === 0) {
774                // No active tab found, set the first one (inbox) as active
775                $('.dm-tabs .dm-tab').removeClass('active');
776                var $activeTab = $('.dm-tabs .dm-tab[data-box="' + active + '"]');
777                if($activeTab.length === 0) {
778                    // If tab not found, default to first tab (inbox)
779                    active = 'inbox';
780                    $activeTab = $('.dm-tabs .dm-tab').first();
781                }
782                $activeTab.addClass('active');
783            } else {
784                // Use the already active tab's data-box value
785                active = $existingActive.attr('data-box') || active;
786                $('#dm-complements-list').attr('data-active', active);
787            }
788            
789            // Only load if letters section is active (not hidden)
790            if($('.dm-section-complements').hasClass('active') || $('.dm-section-letters').hasClass('active')){
791                fetchList(active, 1);
792            }
793        }
794    });
795})(jQuery);

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.