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.