Changeset 3106 in subversion
- Timestamp:
- Nov 13, 2009 9:27:04 AM (4 years ago)
- Location:
- branches/devel-threads
- Files:
-
- 3 added
- 19 edited
-
THREADS (added)
-
config/main.inc.php.dist (modified) (2 diffs)
-
index.php (modified) (1 diff)
-
program/include/rcube_imap.php (modified) (26 diffs)
-
program/include/rcube_shared.inc (modified) (1 diff)
-
program/js/app.js (modified) (33 diffs)
-
program/js/list.js (modified) (11 diffs)
-
program/lib/imap.inc (modified) (8 diffs)
-
program/localization/en_US/labels.inc (modified) (3 diffs)
-
program/steps/mail/check_recent.inc (modified) (2 diffs)
-
program/steps/mail/func.inc (modified) (16 diffs)
-
program/steps/mail/list.inc (modified) (3 diffs)
-
program/steps/mail/move_del.inc (modified) (3 diffs)
-
program/steps/mail/search.inc (modified) (2 diffs)
-
program/steps/settings/func.inc (modified) (1 diff)
-
program/steps/settings/manage_folders.inc (modified) (7 diffs)
-
program/steps/settings/save_prefs.inc (modified) (1 diff)
-
skins/default/functions.js (modified) (4 diffs)
-
skins/default/images/icons/columnpicker.gif (added)
-
skins/default/images/icons/unread_children.png (added)
-
skins/default/mail.css (modified) (3 diffs)
-
skins/default/templates/mail.html (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
branches/devel-threads/config/main.inc.php.dist
r3049 r3106 77 77 $rcmail_config['imap_root'] = null; 78 78 $rcmail_config['imap_delimiter'] = null; 79 80 // The default IMAP message THREAD retrieval algorithm. 81 // A common one for threading would be REFERENCES. 82 // Make sure that your IMAP server supports this! 83 $rcmail_config['imap_thread_algorithm'] = 'REFERENCES'; 84 85 // 0 - Do not expand threads 86 // 1 - Expand all threads automatically 87 // 2 - Expand only threads with unread messages 88 $rcmail_config['autoexpand_threads'] = 0; 79 89 80 90 // Automatically add this domain to user names for login … … 342 352 343 353 // default sort col 344 $rcmail_config['message_sort_col'] = 'd ate';354 $rcmail_config['message_sort_col'] = 'default'; 345 355 346 356 // default sort order -
branches/devel-threads/index.php
r3063 r3106 227 227 'subscribe' => 'manage_folders.inc', 228 228 'unsubscribe' => 'manage_folders.inc', 229 'enable-threading' => 'manage_folders.inc', 230 'disable-threading' => 'manage_folders.inc', 229 231 'add-identity' => 'edit_identity.inc', 230 232 ) -
branches/devel-threads/program/include/rcube_imap.php
r3048 r3106 49 49 var $list_page = 1; 50 50 var $page_size = 10; 51 var $sort_field = ' date';51 var $sort_field = ''; 52 52 var $sort_order = 'DESC'; 53 var $index_sort = true;54 53 var $delimiter = NULL; 55 var $caching_enabled = FALSE; 54 var $threading = false; 55 var $caching_enabled = false; 56 56 var $default_charset = 'ISO-8859-1'; 57 57 var $struct_charset = NULL; … … 64 64 var $uid_id_map = array(); 65 65 var $msg_headers = array(); 66 var $skip_deleted = FALSE;66 var $skip_deleted = false; 67 67 var $search_set = NULL; 68 68 var $search_string = ''; 69 69 var $search_charset = ''; 70 var $search_sort_field = ''; 70 var $search_sort_field = ''; 71 var $search_threads = false; 71 72 var $debug_level = 1; 72 73 var $error_code = 0; 74 var $db_header_fields = array('idx', 'uid', 'subject', 'from', 'to', 'cc', 'date', 'size'); 73 75 var $options = array('auth_method' => 'check'); 74 76 … … 309 311 * @param string Sorting field 310 312 */ 311 function set_search_set($str=null, $msgs=null, $charset=null, $sort_field=null )313 function set_search_set($str=null, $msgs=null, $charset=null, $sort_field=null, $threads=false) 312 314 { 313 315 if (is_array($str) && $msgs == null) … … 320 322 $this->search_charset = $charset; 321 323 $this->search_sort_field = $sort_field; 324 $this->search_threads = $threads; 322 325 } 323 326 … … 329 332 function get_search_set() 330 333 { 331 return array($this->search_string, $this->search_set, $this->search_charset, $this->search_sort_field); 334 return array($this->search_string, 335 $this->search_set, 336 $this->search_charset, 337 $this->search_sort_field, 338 $this->search_threads, 339 ); 332 340 } 333 341 … … 355 363 { 356 364 return iil_C_GetCapability($this->conn, strtoupper($cap)); 365 } 366 367 368 /** 369 * Sets threading flag to the best supported THREAD algorithm 370 * 371 * @param boolean TRUE to enable and FALSE 372 * @return string Algorithm or false if THREAD is not supported 373 * @access public 374 */ 375 function set_threading($enable=false) 376 { 377 $this->threading = false; 378 379 if ($enable) { 380 if ($this->get_capability('THREAD=REFS')) 381 $this->threading = 'REFS'; 382 else if ($this->get_capability('THREAD=REFERENCES')) 383 $this->threading = 'REFERENCES'; 384 else if ($this->get_capability('THREAD=ORDEREDSUBJECT')) 385 $this->threading = 'ORDEREDSUBJECT'; 386 } 387 388 return $this->threading; 357 389 } 358 390 … … 490 522 if (empty($mailbox)) 491 523 $mailbox = $this->mailbox; 492 524 493 525 // count search set 494 if ($this->search_string && $mailbox == $this->mailbox && $mode == 'ALL' && !$force) 495 return count((array)$this->search_set); 496 526 if ($this->search_string && $mailbox == $this->mailbox && ($mode == 'ALL' || $mode == 'THREADS') && !$force) { 527 if ($this->search_threads) 528 return $mode == 'ALL' ? count((array)$this->search_set['depth']) : count((array)$this->search_set['tree']); 529 else 530 return count((array)$this->search_set); 531 } 532 497 533 $a_mailbox_cache = $this->get_cache('messagecount'); 498 534 … … 501 537 return $a_mailbox_cache[$mailbox][$mode]; 502 538 539 if ($mode == 'THREADS') 540 $count = $this->_threadcount($mailbox); 541 503 542 // RECENT count is fetched a bit different 504 if ($mode == 'RECENT')543 else if ($mode == 'RECENT') 505 544 $count = iil_C_CheckForRecent($this->conn, $mailbox); 506 545 … … 536 575 537 576 return (int)$count; 577 } 578 579 580 /** 581 * Private method for getting nr of threads 582 * 583 * @access private 584 * @see rcube_imap::messagecount() 585 */ 586 private function _threadcount($mailbox) 587 { 588 if (!empty($this->cache['__threads'])) 589 return count($this->cache['__threads']['tree']); 590 591 list ($thread_tree, $msg_depth, $has_children) = $this->_fetch_threads($mailbox); 592 593 // $this->update_thread_cache($mailbox, $thread_tree, $msg_depth, $has_children); 594 return count($thread_tree); 538 595 } 539 596 … … 573 630 return $this->_list_header_set($mailbox, $page, $sort_field, $sort_order, $slice); 574 631 632 if ($this->threading) 633 return $this->_list_thread_headers($mailbox, $page, $sort_field, $sort_order, $recursive, $slice); 634 575 635 $this->_set_sort_order($sort_field, $sort_order); 576 636 … … 599 659 $a_msg_headers = array(); 600 660 601 // use message index sort for sorting by Date(for better performance)602 if ( $this->index_sort && $this->sort_field == 'date')661 // use message index sort as default sorting (for better performance) 662 if (!$this->sort_field) 603 663 { 604 664 if ($this->skip_deleted) { … … 675 735 676 736 /** 737 * Private method for listing message headers using threads 738 * 739 * @access private 740 * @see rcube_imap::list_headers 741 */ 742 private function _list_thread_headers($mailbox, $page=NULL, $sort_field=NULL, $sort_order=NULL, $recursive=FALSE, $slice=0) 743 { 744 $this->_set_sort_order($sort_field, $sort_order); 745 746 $page = $page ? $page : $this->list_page; 747 // $cache_key = $mailbox.'.msg'; 748 // $cache_status = $this->check_cache_status($mailbox, $cache_key); 749 750 // get all threads (default sort order) 751 list ($thread_tree, $msg_depth, $has_children) = $this->_fetch_threads($mailbox); 752 753 if (empty($thread_tree)) 754 return array(); 755 756 $msg_index = $this->_sort_threads($mailbox, $thread_tree); 757 758 return $this->_fetch_thread_headers($mailbox, $thread_tree, $msg_depth, $has_children, 759 $msg_index, $page, $lice); 760 } 761 762 763 /** 764 * Private method for fetching threads data 765 * 766 * @param string Mailbox/folder name 767 * @return array Array with thread data 768 * @access private 769 */ 770 private function _fetch_threads($mailbox) 771 { 772 if (empty($this->cache['__threads'])) { 773 // get all threads 774 list ($thread_tree, $msg_depth, $has_children) = iil_C_Thread($this->conn, 775 $mailbox, $this->threading, $this->skip_deleted ? 'UNDELETED' : ''); 776 777 // add to internal (fast) cache 778 $this->cache['__threads'] = array(); 779 $this->cache['__threads']['tree'] = $thread_tree; 780 $this->cache['__threads']['depth'] = $msg_depth; 781 $this->cache['__threads']['has_children'] = $has_children; 782 } 783 784 return array( 785 $this->cache['__threads']['tree'], 786 $this->cache['__threads']['depth'], 787 $this->cache['__threads']['has_children'], 788 ); 789 } 790 791 792 /** 793 * Private method for fetching threaded messages headers 794 * 795 * @access private 796 */ 797 private function _fetch_thread_headers($mailbox, $thread_tree, $msg_depth, $has_children, $msg_index, $page, $slice=0) 798 { 799 $cache_key = $mailbox.'.msg'; 800 // now get IDs for current page 801 $max = max($msg_index); 802 list($begin, $end) = $this->_get_message_range(count($msg_index), $page); 803 $msg_index = array_slice($msg_index, $begin, $end-$begin); 804 805 if ($slice) 806 $msg_index = array_slice($msg_index, ($this->sort_order == 'DESC' ? 0 : -$slice), $slice); 807 808 if ($this->sort_order == 'DESC') 809 $msg_index = array_reverse($msg_index); 810 811 // flatten threads array 812 // @TODO: fetch children only in expanded mode 813 $all_ids = array(); 814 foreach($msg_index as $root) { 815 $all_ids[] = $root; 816 if (!empty($thread_tree[$root])) 817 $all_ids = array_merge($all_ids, array_keys_recursive($thread_tree[$root])); 818 } 819 820 // fetch reqested headers from server 821 $this->_fetch_headers($mailbox, $all_ids, $a_msg_headers, $cache_key); 822 823 // return empty array if no messages found 824 if (!is_array($a_msg_headers) || empty($a_msg_headers)) 825 return array(); 826 827 // use this class for message sorting 828 $sorter = new rcube_header_sorter(); 829 $sorter->set_sequence_numbers($all_ids); 830 $sorter->sort_headers($a_msg_headers); 831 832 // Set depth, has_children and unread_children fields in headers 833 $this->_set_thread_flags($a_msg_headers, $msg_depth, $has_children); 834 835 return array_values($a_msg_headers); 836 } 837 838 839 /** 840 * Private method for setting threaded messages flags: 841 * depth, has_children and unread_children 842 * 843 * @param array Reference to headers array indexed by message ID 844 * @param array Array of messages depth indexed by message ID 845 * @param array Array of messages children flags indexed by message ID 846 * @return array Message headers array indexed by message ID 847 * @access private 848 */ 849 private function _set_thread_flags(&$headers, $msg_depth, $msg_children) 850 { 851 $parents = array(); 852 853 foreach ($headers as $idx => $header) { 854 $id = $header->id; 855 $depth = $msg_depth[$id]; 856 $parents = array_slice($parents, 0, $depth); 857 858 if (!empty($parents)) { 859 $headers[$idx]->parent_uid = end($parents); 860 if (!$header->seen) 861 $headers[$parents[0]]->unread_children = true; 862 } 863 array_push($parents, $header->uid); 864 865 $headers[$idx]->depth = $depth; 866 $headers[$idx]->has_children = $msg_children[$id]; 867 } 868 } 869 870 871 /** 677 872 * Private method for listing a set of message headers (search results) 678 873 * … … 691 886 return array(); 692 887 888 // use saved messages from searching 889 if ($this->threading) 890 return $this->_list_thread_header_set($mailbox, $page, $sort_field, $sort_order, $slice); 891 693 892 $msgs = $this->search_set; 694 893 $a_msg_headers = array(); … … 698 897 $this->_set_sort_order($sort_field, $sort_order); 699 898 700 // quickest method 701 if ( $this->index_sort && $this->search_sort_field == 'date' && $this->sort_field == 'date')899 // quickest method (default sorting) 900 if (!$this->search_sort_field && !$this->sort_field) 702 901 { 703 902 if ($sort_order == 'DESC') … … 720 919 return array_values($a_msg_headers); 721 920 } 921 722 922 // sorted messages, so we can first slice array and then fetch only wanted headers 723 if ($this->get_capability('sort') && (!$this->index_sort || $this->sort_field != 'date')) // SORT searching result923 if ($this->get_capability('sort')) // SORT searching result 724 924 { 725 925 // reset search set if sorting field has been changed … … 749 949 return array_values($a_msg_headers); 750 950 } 751 else { // SEARCH searchingresult, need sorting951 else { // SEARCH result, need sorting 752 952 $cnt = count($msgs); 753 953 // 300: experimantal value for best result 754 if (($cnt > 300 && $cnt > $this->page_size) || ($this->index_sort && $this->sort_field == 'date')) {954 if (($cnt > 300 && $cnt > $this->page_size) || !$this->sort_field) { 755 955 // use memory less expensive (and quick) method for big result set 756 956 $a_index = $this->message_index('', $this->sort_field, $this->sort_order); … … 791 991 } 792 992 } 993 } 994 995 996 /** 997 * Private method for listing a set of threaded message headers (search results) 998 * 999 * @param string Mailbox/folder name 1000 * @param int Current page to list 1001 * @param string Header field to sort by 1002 * @param string Sort order [ASC|DESC] 1003 * @param boolean Number of slice items to extract from result array 1004 * @return array Indexed array with message header objects 1005 * @access private 1006 * @see rcube_imap::list_header_set() 1007 */ 1008 private function _list_thread_header_set($mailbox, $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0) 1009 { 1010 $thread_tree = $this->search_set['tree']; 1011 $msg_depth = $this->search_set['depth']; 1012 $has_children = $this->search_set['children']; 1013 $a_msg_headers = array(); 1014 1015 $page = $page ? $page : $this->list_page; 1016 $start_msg = ($page-1) * $this->page_size; 1017 1018 $this->_set_sort_order($sort_field, $sort_order); 1019 1020 $msg_index = $this->_sort_threads($mailbox, $thread_tree, array_keys($msg_depth)); 1021 1022 return $this->_fetch_thread_headers($mailbox, $thread_tree, $msg_depth, $has_children, $msg_index, $page, $slice=0); 793 1023 } 794 1024 … … 891 1121 $this->cache[$key] = array(); 892 1122 893 // use message index sort for sorting by Date894 if ( $this->index_sort && $this->sort_field == 'date')1123 // use message index sort as default sorting 1124 if (!$this->sort_field) 895 1125 { 896 1126 $msgs = $this->search_set; … … 943 1173 } 944 1174 945 // use message index sort for sorting by Date946 if ( $this->index_sort && $this->sort_field == 'date')1175 // use message index sort as default sorting 1176 if (!$this->sort_field) 947 1177 { 948 1178 if ($this->skip_deleted) { … … 1082 1312 } 1083 1313 1084 $this->set_search_set($str, $results, $charset, $sort_field );1314 $this->set_search_set($str, $results, $charset, $sort_field, (bool) $this->threading); 1085 1315 1086 1316 return $results; … … 1102 1332 $criteria = 'UNDELETED '.$criteria; 1103 1333 1104 if ($sort_field && $this->get_capability('sort') && (!$this->index_sort || $sort_field != 'date')) { 1334 if ($this->threading) { 1335 list ($thread_tree, $msg_depth, $has_children) = iil_C_Thread($this->conn, 1336 $mailbox, $this->threading, $criteria, $charset); 1337 1338 $a_messages = array( 1339 'tree' => $thread_tree, 1340 'depth' => $msg_depth, 1341 'children' => $has_children 1342 ); 1343 } 1344 else if ($sort_field && $this->get_capability('sort')) { 1105 1345 $charset = $charset ? $charset : $this->default_charset; 1106 1346 $a_messages = iil_C_Sort($this->conn, $mailbox, $sort_field, $criteria, FALSE, $charset); … … 1115 1355 1116 1356 // I didn't found that SEARCH always returns sorted IDs 1117 if ( $this->index_sort && $this->sort_field == 'date')1357 if (!$this->sort_field) 1118 1358 sort($a_messages); 1119 1359 } … … 1129 1369 1130 1370 /** 1371 * Sort thread 1372 * 1373 * @param array Unsorted thread tree (iil_C_Thread() result) 1374 * @param array Message IDs if we know what we need (e.g. search result) 1375 * @return array Sorted roots IDs 1376 * @access private 1377 */ 1378 private function _sort_threads($mailbox, $thread_tree, $ids=NULL) 1379 { 1380 // THREAD=ORDEREDSUBJECT: sorting by sent date of root message 1381 // THREAD=REFERENCES: sorting by sent date of root message 1382 // THREAD=REFS: sorting by the most recent date in each thread 1383 // default sorting 1384 if (!$this->sort_field || ($this->sort_field == 'date' && $this->threading == 'REFS')) { 1385 return array_keys($thread_tree); 1386 } 1387 // here we'll implement REFS sorting, for performance reason 1388 else { // ($sort_field == 'date' && $this->threading != 'REFS') 1389 // use SORT command 1390 if ($this->get_capability('sort')) { 1391 $a_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, 1392 !empty($ids) ? $ids : ($this->skip_deleted ? 'UNDELETED' : '')); 1393 } 1394 else { 1395 // fetch specified headers for all messages and sort them 1396 $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, !empty($ids) ? $ids : "1:*", 1397 $this->sort_field, $this->skip_deleted); 1398 asort($a_index); // ASC 1399 $a_index = array_values($a_index); 1400 } 1401 1402 return $this->_sort_thread_refs($thread_tree, $a_index); 1403 } 1404 /* 1405 // other sorting, we'll sort roots only 1406 else { 1407 // use SORT command for root messages sorting 1408 if ($this->get_capability('sort')) { 1409 $msg_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, array_keys($thread_tree)); 1410 } 1411 else { 1412 // fetch specified headers for all root messages and sort 1413 $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, 1414 array_keys($thread_tree), $this->sort_field, $this->skip_deleted); 1415 asort($a_index); // ASC 1416 $msg_index = array_keys($a_index); 1417 } 1418 } 1419 */ 1420 return array(); 1421 } 1422 1423 1424 /** 1425 * THREAD=REFS sorting implementation 1426 * 1427 * @param array Thread tree array (message identifiers as keys) 1428 * @param array Array of sorted message identifiers 1429 * @return array Array of sorted roots messages 1430 * @access private 1431 */ 1432 private function _sort_thread_refs($tree, $index) 1433 { 1434 if (empty($tree)) 1435 return array(); 1436 1437 $index = array_combine(array_values($index), $index); 1438 1439 // assign roots 1440 foreach ($tree as $idx => $val) { 1441 $index[$idx] = $idx; 1442 if (!empty($val)) { 1443 $idx_arr = array_keys_recursive($tree[$idx]); 1444 foreach ($idx_arr as $subidx) 1445 $index[$subidx] = $idx; 1446 } 1447 } 1448 1449 $index = array_values($index); 1450 1451 // create sorted array of roots 1452 $msg_index = array(); 1453 if ($this->sort_order != 'DESC') { 1454 foreach ($index as $idx) 1455 if (!isset($msg_index[$idx])) 1456 $msg_index[$idx] = $idx; 1457 $msg_index = array_values($msg_index); 1458 } 1459 else { 1460 for ($x=count($index)-1; $x>=0; $x--) 1461 if (!isset($msg_index[$index[$x]])) 1462 $msg_index[$index[$x]] = $index[$x]; 1463 $msg_index = array_reverse($msg_index); 1464 } 1465 1466 return $msg_index; 1467 } 1468 1469 1470 /** 1131 1471 * Refresh saved search set 1132 1472 * … … 1136 1476 { 1137 1477 if (!empty($this->search_string)) 1138 $this->search_set = $this->search('', $this->search_string, $this->search_charset, $this->search_sort_field); 1478 $this->search_set = $this->search('', $this->search_string, $this->search_charset, 1479 $this->search_sort_field, $this->search_threads); 1139 1480 1140 1481 return $this->get_search_set(); … … 2365 2706 { 2366 2707 $cache_key = "$key:$from:$to:$sort_field:$sort_order"; 2367 $db_header_fields = array('idx', 'uid', 'subject', 'from', 'to', 'cc', 'date', 'size');2368 2708 2369 2709 $config = rcmail::get_instance()->config; 2370 2710 2371 // use idx sort for sorting by Date with index_sort=true or for unknown field 2372 if (($sort_field == 'date' && $this->index_sort) 2373 || !in_array($sort_field, $db_header_fields)) { 2711 // use idx sort as default sorting 2712 if (!$sort_field || !in_array($sort_field, $this->db_header_fields)) { 2374 2713 $sort_field = 'idx'; 2375 2714 } … … 2448 2787 return $sa_message_index[$key]; 2449 2788 2450 // use idx sort for sorting by Date with index_sort=true2451 if ( $sort_field == 'date' && $this->index_sort)2789 // use idx sort as default 2790 if (!$sort_field || !in_array($sort_field, $this->db_header_fields)) 2452 2791 $sort_field = 'idx'; 2453 2792 -
branches/devel-threads/program/include/rcube_shared.inc
r3063 r3106 682 682 683 683 /** 684 * Get all keys from array (recursive) 685 * 686 * @param array Input array 687 * @return array 688 */ 689 function array_keys_recursive($array) 690 { 691 $keys = array(); 692 693 if (!empty($array)) 694 foreach ($array as $key => $child) { 695 $keys[] = $key; 696 if ($children = array_keys_recursive($child)) 697 $keys = array_merge($keys, $children); 698 } 699 return $keys; 700 } 701 702 703 /** 684 704 * mbstring replacement functions 685 705 */ -
branches/devel-threads/program/js/app.js
r3059 r3106 163 163 if (this.gui_objects.messagelist) 164 164 { 165 this.message_list = new rcube_list_widget(this.gui_objects.messagelist, {multiselect:true, draggable:true, keyboard:true, dblclick_time:this.dblclick_time}); 165 this.message_list = new rcube_list_widget(this.gui_objects.messagelist, 166 {multiselect:true, multiexpand:true, draggable:true, keyboard:true, dblclick_time:this.dblclick_time}); 166 167 this.message_list.row_init = function(o){ p.init_message_row(o); }; 167 168 this.message_list.addEventListener('dblclick', function(o){ p.msglist_dbl_click(o); }); … … 180 181 else 181 182 this.message_list.focus(); 183 184 switch (this.env.autoexpand_threads) { 185 case 2: this.message_list.expand_unread(); break; 186 case 1: this.message_list.expand_all(); break; 187 } 188 this.message_list.expand(null); 182 189 } 183 190 184 191 if (this.env.coltypes) 185 192 this.set_message_coltypes(this.env.coltypes); … … 244 251 } 245 252 246 if (this.env.messagecount) 253 if (this.env.messagecount) { 247 254 this.enable_command('select-all', 'select-none', 'expunge', true); 255 this.enable_command('expand-all', 'collapse-all', this.env.threading); 256 } 248 257 249 258 if (this.purge_mailbox_test()) … … 334 343 } 335 344 else if (this.env.action=='folders') 336 this.enable_command('subscribe', 'unsubscribe', 'create-folder', 'rename-folder', 'delete-folder', true);345 this.enable_command('subscribe', 'unsubscribe', 'create-folder', 'rename-folder', 'delete-folder', 'enable-threading', 'disable-threading', true); 337 346 338 347 if (this.gui_objects.identitieslist) … … 425 434 row.flagged = this.env.messages[uid].flagged ? true : false; 426 435 row.forwarded = this.env.messages[uid].forwarded ? true : false; 436 row.has_children = this.env.messages[uid].has_children ? true : false; 437 row.depth = this.env.messages[uid].depth ? this.env.messages[uid].depth : 0; 438 row.unread_children = this.env.messages[uid].unread_children; 439 row.parent_uid = this.env.messages[uid].parent_uid; 440 // row.expanded = this.env.autoexpand && row.has_children ? true : false; 427 441 } 428 442 … … 441 455 var found; 442 456 if((found = find_in_array('flag', this.env.coltypes)) >= 0) 443 this.set_env('flagged_col', found +1);457 this.set_env('flagged_col', found); 444 458 } 445 459 … … 454 468 455 469 this.triggerEvent('insertrow', { uid:uid, row:row }); 470 471 // expando is handled here rather than in rcube_list_widget so that the 472 // expanded state may be persisted in this.env.messages 473 var expando = document.getElementById('rcmexpando' + uid); 474 if (expando != null) 475 { 476 var p = this; 477 expando.onmousedown = function(e) { return p.message_list.expand_row(e, uid); }; 478 } 456 479 }; 457 480 … … 617 640 var sort_order, sort_col = props; 618 641 619 if (this.env.sort_col==sort_col)642 if (this.env.sort_col==sort_col) 620 643 sort_order = this.env.sort_order=='ASC' ? 'DESC' : 'ASC'; 621 else644 else 622 645 sort_order = 'ASC'; 623 646 624 647 // set table header class 625 648 $('#rcm'+this.env.sort_col).removeClass('sorted'+(this.env.sort_order.toUpperCase())); 626 $('#rcm'+sort_col).addClass('sorted'+sort_order); 649 if (sort_col) 650 $('#rcm'+sort_col).addClass('sorted'+sort_order); 627 651 628 652 // save new sort properties … … 744 768 // mail task 745 769 if (this.task=='mail') 746 this.delete_messages(); 770 { 771 if (this.delete_messages() && this.env.threading) 772 { 773 // It is very hard to re-thread message list if some messages were removed from 774 // the middle of a thread (we need to fully implement RFC5256 algorythm, because 775 // we are a "disconnected client" if we re-threading without a servers help) 776 // Reload message list 777 this.list_mailbox(this.env.mailbox, this.env.current_page); 778 } 779 } 747 780 // addressbook task 748 781 else if (this.task=='addressbook') … … 758 791 case 'moveto': 759 792 if (this.task == 'mail') 793 { 760 794 this.move_messages(props); 795 if (this.env.threadeding) 796 { 797 // The same as for 'delete' 798 this.list_mailbox(this.env.mailbox, this.env.current_page); 799 } 800 } 761 801 else if (this.task == 'addressbook' && this.drag_active) 762 802 this.copy_contact(null, props); … … 846 886 case 'select-none': 847 887 this.message_list.clear_selection(); 888 break; 889 890 case 'expand-all': 891 this.message_list.expand_all(); 892 break; 893 894 case 'collapse-all': 895 this.message_list.collapse_all(); 848 896 break; 849 897 … … 1109 1157 this.unsubscribe_folder(props); 1110 1158 break; 1111 1159 1160 case 'enable-threading': 1161 this.enable_threading(props); 1162 break; 1163 1164 case 'disable-threading': 1165 this.disable_threading(props); 1166 break; 1167 1112 1168 case 'create-folder': 1113 1169 this.create_folder(props); … … 1443 1499 clearTimeout(this.preview_timer); 1444 1500 1445 var selected = list. selection.length==1;1501 var selected = list.get_single_selection() != null; 1446 1502 1447 1503 // Hide certain command buttons when Drafts folder is selected … … 1547 1603 { 1548 1604 this.set_message(id, 'unread', false); 1605 this.update_parents(id, 'read'); 1549 1606 if (this.env.unread_counts[this.env.mailbox]) 1550 1607 { … … 1732 1789 }; 1733 1790 1791 // update parents in a thread 1792 this.update_parents = function(uid, flag) 1793 { 1794 var r = this.message_list.rows[uid]; 1795 if (r.parent_uid) { 1796 var p = this.message_list.rows[r.parent_uid]; 1797 if (flag == 'read' && p.unread_children > 0) { 1798 p.unread_children--; 1799 } else if (flag == 'unread') { 1800 p.unread_children++; 1801 } else { 1802 return; 1803 } 1804 this.set_message_icon(r.parent_uid); 1805 this.update_parents(r.parent_uid, flag); 1806 } 1807 }; 1808 1734 1809 // set message icon 1735 1810 this.set_message_icon = function(uid) … … 1740 1815 if (!rows[uid]) 1741 1816 return false; 1742 1743 if (rows[uid].deleted && this.env.deletedicon) 1817 if (!rows[uid].unread && rows[uid].unread_children > 0 && this.env.unreadchildrenicon) { 1818 icn_src = this.env.unreadchildrenicon; 1819 } 1820 else if (rows[uid].deleted && this.env.deletedicon) 1744 1821 icn_src = this.env.deletedicon; 1745 1822 else if (rows[uid].replied && this.env.repliedicon) … … 1789 1866 rows[uid].flagged = status; 1790 1867 1791 this.env.messages[uid] = rows[uid];1868 // this.env.messages[uid] = rows[uid]; 1792 1869 } 1793 1870 … … 1874 1951 1875 1952 // if config is set to flag for deletion 1876 if (this.env.flag_for_deletion) 1953 if (this.env.flag_for_deletion) { 1877 1954 this.mark_message('delete'); 1955 return false; 1956 } 1878 1957 // if there isn't a defined trash mailbox or we are in it 1879 1958 else if (!this.env.trash_mailbox || String(this.env.mailbox).toLowerCase() == String(this.env.trash_mailbox).toLowerCase()) … … 1890 1969 this.move_messages(this.env.trash_mailbox); 1891 1970 } 1971 1972 return true; 1892 1973 }; 1893 1974 … … 1942 2023 var a_uids = new Array(); 1943 2024 var r_uids = new Array(); 1944 var selection = this.message_list ? this.message_list.get_selection( ) : new Array();2025 var selection = this.message_list ? this.message_list.get_selection('mark') : new Array(); 1945 2026 1946 2027 if (uid) … … 2002 2083 2003 2084 this.http_post('mark', '_uid='+a_uids.join(',')+'&_flag='+flag); 2085 2086 for (var i=0; i<a_uids.length; i++) 2087 this.update_parents(a_uids[i], flag); 2004 2088 }; 2005 2089 … … 2119 2203 } 2120 2204 }; 2121 2205 2122 2206 2123 2207 /*********************************************************/ … … 3469 3553 this.http_post('unsubscribe', '_mbox='+urlencode(folder)); 3470 3554 }; 3471 3555 3556 this.enable_threading = function(folder) 3557 { 3558 if (folder) 3559 this.http_post('enable-threading', '_mbox='+urlencode(folder)); 3560 }; 3561 3562 this.disable_threading = function(folder) 3563 { 3564 if (folder) 3565 this.http_post('disable-threading', '_mbox='+urlencode(folder)); 3566 }; 3567 3568 3472 3569 // helper method to find a specific mailbox row ID 3473 3570 this.get_folder_row_id = function(folder) … … 3785 3882 { 3786 3883 col = this.coltypes[n]; 3787 if ((cell = thead.rows[0].cells[n +1]) && (col=='from' || col=='to'))3884 if ((cell = thead.rows[0].cells[n]) && (col=='from' || col=='to')) 3788 3885 { 3789 3886 // if we have links for sorting, it's a bit more complicated... … … 3800 3897 } 3801 3898 else if (col == 'subject' && this.message_list) 3802 this.message_list.subject_col = n +1;3899 this.message_list.subject_col = n; 3803 3900 } 3804 3901 }; … … 3818 3915 var even = rowcount%2; 3819 3916 3820 this.env.messages[uid] = {3917 var message = this.env.messages[uid] = { 3821 3918 deleted: flags.deleted?1:0, 3822 3919 replied: flags.replied?1:0, 3823 3920 unread: flags.unread?1:0, 3824 3921 forwarded: flags.forwarded?1:0, 3825 flagged:flags.flagged?1:0 3826 }; 3922 flagged: flags.flagged?1:0, 3923 has_children: flags.has_children?1:0, 3924 depth: flags.depth?flags.depth:0, 3925 unread_children: flags.unread_children, 3926 parent_uid: flags.parent_uid, 3927 } 3827 3928 3828 3929 var css_class = 'message' … … 3839 3940 3840 3941 var icon = this.env.messageicon; 3841 if (flags.deleted && this.env.deletedicon) 3942 if (!flags.unread && flags.unread_children > 0 && this.env.unreadchildrenicon) 3943 icon = this.env.unreadchildrenicon; 3944 else if (flags.deleted && this.env.deletedicon) 3842 3945 icon = this.env.deletedicon; 3843 3946 else if (flags.replied && this.env.repliedicon) … … 3852 3955 else if(flags.unread && this.env.unreadicon) 3853 3956 icon = this.env.unreadicon; 3854 3855 // add icon col 3856 var col = document.createElement('td'); 3857 col.className = 'icon'; 3858 col.innerHTML = icon ? '<img src="'+icon+'" alt="" />' : ''; 3859 row.appendChild(col); 3860 3957 var tree = ''; 3958 3959 if (this.env.threading) 3960 { 3961 // XXX: This assumes that div width is hardcoded to 15px, 3962 // Chris did it a bit differently in an original patch, he was adding so much divs as depth is 3963 // I replaced logic in list.js:drag_mouse_move() so subject text is picked defferently, so 3964 // either method of could be used (that was only the one problem I noted with these added divs). 3965 // The same is true for an offline list (program/steps/mail/func.inc:rcmail_message_list()). 3966 // Bubble 3967 var width = message.depth * 15; 3968 if (width) 3969 tree += '<div id="rcmtab' + uid + '" class="branch" style="width:' + width + 'px;"> </div>'; 3970 if (message.has_children && !message.depth) 3971 tree += '<div id="rcmexpando' + uid + '" class="' + (message.expanded ? 'expanded' : 'collapsed') + '"> </div>'; 3972 else 3973 tree += '<div class="leaf"> </div>'; 3974 if (message.depth) 3975 row.style.display = 'none'; 3976 } 3977 3978 tree += icon ? '<img src="'+icon+'" alt="" />' : ''; 3979 3861 3980 // add each submitted col 3862 3981 for (var n = 0; n < this.coltypes.length; n++) { … … 3864 3983 col = document.createElement('td'); 3865 3984 col.className = String(c).toLowerCase(); 3866 3867 if (c=='flag') { 3985 3986 var html; 3987 if (c=='flag') 3988 { 3868 3989 if (flags.flagged && this.env.flaggedicon) 3869 col.innerHTML= '<img src="'+this.env.flaggedicon+'" alt="" />';3990 html = '<img src="'+this.env.flaggedicon+'" alt="" />'; 3870 3991 else if(!flags.flagged && this.env.unflaggedicon) 3871 col.innerHTML= '<img src="'+this.env.unflaggedicon+'" alt="" />';3872 }3992 html = '<img src="'+this.env.unflaggedicon+'" alt="" />'; 3993 } 3873 3994 else if (c=='attachment') 3874 col.innerHTML = (attachment && this.env.attachmenticon ? '<img src="'+this.env.attachmenticon+'" alt="" />' : ' ');3995 html = attachment && this.env.attachmenticon ? '<img src="'+this.env.attachmenticon+'" alt="" />' : ' '; 3875 3996 else 3876 col.innerHTML = cols[c]; 3997 html = cols[c]; 3998 if (n == 0) 3999 html = tree + html; 4000 col.innerHTML = html; 3877 4001 3878 4002 row.appendChild(col); … … 3888 4012 } 3889 4013 }; 4014 3890 4015 3891 4016 // messages list handling in background (for performance) … … 4238 4363 this.enable_command('show', 'reply', 'reply-all', 'forward', 'moveto', 'delete', 4239 4364 'mark', 'viewsource', 'open', 'edit', 'download', 'print', 'load-attachment', 4240 'purge', 'expunge', 'select-all', 'select-none', 'sort', false); 4365 'purge', 'expunge', 'select-all', 'select-none', 'sort', 'expand-all', 4366 'colapse-all', false); 4241 4367 } 4242 4368 break; … … 4250 4376 this.enable_command('show', 'expunge', 'select-all', 'select-none', 'sort', (this.env.messagecount > 0)); 4251 4377 this.enable_command('purge', this.purge_mailbox_test()); 4252 4378 4379 this.enable_command('expand-all', 'collapse-all', this.env.threading && this.env.messagecount); 4380 4253 4381 if (response.action == 'list') 4254 4382 this.triggerEvent('listupdate', { folder:this.env.mailbox, rowcount:this.message_list.rowcount }); … … 4381 4509 } // end object rcube_webmail 4382 4510 4383 4384 4511 // copy event engine prototype 4385 4512 rcube_webmail.prototype.addEventListener = rcube_event_engine.prototype.addEventListener; -
branches/devel-threads/program/js/list.js
r3055 r3106 38 38 this.shiftkey = false; 39 39 this.multiselect = false; 40 this.multiexpand = false; 40 41 this.multi_selecting = false; 41 42 this.draggable = false; … … 77 78 { 78 79 row = this.list.tBodies[0].childNodes[r]; 79 while (row && (row.nodeType != 1 || row.style.display == 'none'))80 while (row && row.nodeType != 1) 80 81 { 81 82 row = row.nextSibling; … … 320 321 321 322 323 expand_row: function(e, id) 324 { 325 var row = this.rows[id]; 326 var evtarget = rcube_event.get_target(e); 327 var mod_key = rcube_event.get_modifier(e); 328 // Don't select this message 329 this.dont_select = true; 330 // Don't treat double click on the expando as double click on the message. 331 row.clicked = 0; 332 if (row.expanded) 333 { 334 evtarget.className = "collapsed"; 335 if (mod_key == CONTROL_KEY || this.multiexpand) 336 this.collapse_all(row); 337 else 338 this.collapse(row); 339 } 340 else 341 { 342 evtarget.className = "expanded"; 343 if (mod_key == CONTROL_KEY || this.multiexpand) 344 this.expand_all(row); 345 else 346 this.expand(row); 347 } 348 }, 349 350 collapse: function(row) 351 { 352 row.expanded = false; 353 var depth = row.depth; 354 var new_row = row ? row.obj.nextSibling : null; 355 var r; 356 357 while (new_row) 358 { 359 if (new_row.nodeType == 1) 360 { 361 var r = this.rows[new_row.uid]; 362 if (r && r.depth <= depth) 363 break; 364 new_row.style.display = 'none'; 365 } 366 new_row = new_row.nextSibling; 367 } 368 369 return false; 370 }, 371 372 373 expand: function(row) 374 { 375 var depth, new_row; 376 var last_expanded_parent_depth; 377 378 if (row) 379 { 380 row.expanded = true; 381 depth = row.depth; 382 new_row = row.obj.nextSibling; 383 } 384 else 385 { 386 var tbody = this.list.tBodies[0]; 387 new_row = tbody.firstChild; 388 depth = 0; 389 last_expanded_parent_depth = 0; 390 } 391 392 while (new_row) 393 { 394 if (new_row.nodeType == 1) 395 { 396 var r = this.rows[new_row.uid]; 397 if (r) 398 { 399 if (row && r.depth <= depth) 400 break; 401 if (r.parent_uid) 402 { 403 var p = this.rows[r.parent_uid]; 404 if (p && p.expanded) 405 { 406 if ((row && p == row) || last_expanded_parent_depth >= p.depth - 1) 407 { 408 last_expanded_parent_depth = p.depth; 409 new_row.style.display = 'table-row'; 410 new_row.expanded = true; 411 } 412 } 413 else 414 if (row && (! p || p.depth <= depth)) 415 break; 416 } 417 } 418 } 419 new_row = new_row.nextSibling; 420 } 421 422 return false; 423 }, 424 425 426 collapse_all: function(row) 427 { 428 var depth, new_row; 429 var r; 430 431 if (row) 432 { 433 row.expanded = false; 434 depth = row.depth; 435 new_row = row.obj.nextSibling; 436 // don't collapse sub-root tree in multiexpand mode 437 if (depth && this.multiexpand) 438 return false; 439 } 440 else 441 { 442 var tbody = this.list.tBodies[0]; 443 new_row = tbody.firstChild; 444 depth = 0; 445 } 446 447 while (new_row) { 448 if (new_row.nodeType == 1) 449 { 450 var r = this.rows[new_row.uid]; 451 if (row && r.depth <= depth) 452 break; 453 if (row || r.depth) 454 new_row.style.display = 'none'; 455 if (r.has_children) { 456 r.expanded = false; 457 var expando = document.getElementById('rcmexpando' + r.uid); 458 if (expando) 459 expando.className = 'collapsed'; 460 } 461 } 462 new_row = new_row.nextSibling; 463 } 464 465 return false; 466 }, 467 468 expand_all: function(row) 469 { 470 var depth, new_row; 471 var r; 472 473 if (row) 474 { 475 row.expanded = true; 476 depth = row.depth; 477 new_row = row.obj.nextSibling; 478 } 479 else 480 { 481 var tbody = this.list.tBodies[0]; 482 new_row = tbody.firstChild; 483 depth = 0; 484 } 485 486 while (new_row) 487 { 488 if (new_row.nodeType == 1) 489 { 490 var r = this.rows[new_row.uid]; 491 if (row && r.depth <= depth) 492 break; 493 new_row.style.display = 'table-row'; 494 if (r.has_children) 495 { 496 r.expanded = true; 497 var expando = document.getElementById('rcmexpando' + r.uid); 498 if (expando) 499 expando.className = 'expanded'; 500 } 501 } 502 new_row = new_row.nextSibling; 503 } 504 return false; 505 }, 506 507 expand_unread: function() 508 { 509 var tbody = this.list.tBodies[0]; 510 new_row = tbody.firstChild; 511 var r; 512 var p; 513 514 while (new_row) { 515 if (new_row.nodeType == 1) 516 { 517 r = this.rows[new_row.uid]; 518 p = this.rows[r.parent_uid]; 519 if ((r.has_children && r.unread_children > 0) || (!r.has_children && r.unread)) 520 { 521 new_row.style.display = 'table-row'; 522 if (r.has_children) 523 { 524 r.expanded = true; 525 var expando = document.getElementById('rcmexpando' + r.uid); 526 if (expando) 527 expando.className = 'expanded'; 528 } 529 } 530 else if (p && p.unread_children > 0) 531 { 532 // Show all neighbours with the same parent at the same depth level (parent is expanded) 533 new_row.style.display = 'table-row'; 534 } 535 } 536 new_row = new_row.nextSibling; 537 } 538 return false; 539 }, 540 541 322 542 /** 323 543 * get first/next/previous/last rows that are not hidden … … 496 716 if ((this.rows[n].obj.rowIndex >= i) && (this.rows[n].obj.rowIndex <= j)) 497 717 { 498 if (!this.in_selection(n)) 718 if (!this.in_selection(n)) { 499 719 this.highlight_row(n, true); 720 } 500 721 } 501 722 else 502 723 { 503 if (this.in_selection(n) && !control) 724 if (this.in_selection(n) && !control) { 504 725 this.highlight_row(n, true); 726 } 505 727 } 506 728 } … … 517 739 return true; 518 740 519 return false; 741 return false; 520 742 }, 521 743 … … 568 790 569 791 for (var n in this.rows) 570 this.highlight_row(n, true); 792 this.highlight_row(n, true); 571 793 572 794 // trigger event if selection changed … … 686 908 rcube_event.cancel(e); 687 909 return this.use_arrow_key(keyCode, mod_key); 910 case 61: 911 case 107: // Plus sign on a numeric keypad (fc11 + firefox 3.5.2) 912 case 109: 913 case 32: 914 // Stop propagation 915 rcube_event.cancel(e); 916 var ret = this.use_plusminus_key(keyCode, mod_key); 917 this.key_pressed = keyCode; 918 this.triggerEvent('keypress'); 919 return ret; 688 920 default: 689 921 this.shiftkey = e.shiftKey; … … 713 945 case 63233: 714 946 case 63232: 947 case 61: 948 case 107: 949 case 109: 950 case 32: 715 951 if (!rcube_event.get_modifier(e) && this.focused) 716 952 return rcube_event.cancel(e); … … 747 983 748 984 /** 985 * Special handling method for +/- keys 986 */ 987 use_plusminus_key: function(keyCode, mod_key) 988 { 989 var selected_row = this.rows[this.last_selected]; 990 if (!selected_row) 991 return; 992 993 if (keyCode == 32) 994 keyCode = selected_row.expanded ? 109 : 61; 995 if (keyCode == 61 || keyCode == 107) 996 if (mod_key == CONTROL_KEY || this.multiexpand) 997 this.expand_all(selected_row); 998 else 999 this.expand(selected_row); 1000 else 1001 if (mod_key == CONTROL_KEY || this.multiexpand) 1002 this.collapse_all(selected_row); 1003 else 1004 this.collapse(selected_row); 1005 1006 var expando = document.getElementById('rcmexpando' + selected_row.uid); 1007 if (expando) 1008 expando.className = selected_row.expanded?'expanded':'collapsed'; 1009 1010 return false; 1011 }, 1012 1013 1014 /** 749 1015 * Try to scroll the list to make the specified row visible 750 1016 */ … … 780 1046 this.draglayer = $('<div>').attr('id', 'rcmdraglayer').css({ position:'absolute', display:'none', 'z-index':2000 }).appendTo(document.body); 781 1047 782 // get subjects of selected dmessages1048 // get subjects of selected messages 783 1049 var names = ''; 784 var c, i, node,subject, obj;1050 var c, i, subject, obj; 785 1051 for(var n=0; n<this.selection.length; n++) 786 1052 { … … 791 1057 } 792 1058 793 if ( this.rows[this.selection[n]].obj)1059 if (obj = this.rows[this.selection[n]].obj) 794 1060 { 795 obj = this.rows[this.selection[n]].obj;796 1061 subject = ''; 797 1062 798 for (c=0, i=0; i<obj.childNodes.length; i++)1063 for (c=0, i=0; i<obj.childNodes.length; i++) 799 1064 { 800 if (obj.childNodes[i].nodeName == 'TD')1065 if (obj.childNodes[i].nodeName == 'TD') 801 1066 { 802 if (((node = obj.childNodes[i].firstChild) && (node.nodeType==3 || node.nodeName=='A')) && 803 (this.subject_col < 0 || (this.subject_col >= 0 && this.subject_col == c))) 804 { 805 if (n == 0) { 806 if (node.nodeType == 3) 807 this.drag_start_pos = $(obj.childNodes[i]).offset(); 808 else 809 this.drag_start_pos = $(node).offset(); 1067 if (n == 0) 1068 this.drag_start_pos = $(obj.childNodes[i]).offset(); 1069 1070 if (this.subject_col < 0 || (this.subject_col >= 0 && this.subject_col == c)) 1071 { 1072 var node, tmp_node, nodes = obj.childNodes[i].childNodes; 1073 // find text node 1074 for (m=0; m<nodes.length; m++) { 1075 if ((tmp_node = obj.childNodes[i].childNodes[m]) && (tmp_node.nodeType==3 || tmp_node.nodeName=='A')) 1076 node = tmp_node; 810 1077 } 1078 1079 if (!node) 1080 break; 1081 811 1082 subject = node.nodeType==3 ? node.data : node.innerHTML; 812 1083 // remove leading spaces -
branches/devel-threads/program/lib/imap.inc
r3018 r3106 174 174 var $junk = false; 175 175 var $flagged = false; 176 var $has_children = false; 177 var $depth = 0; 178 var $unread_children = 0; 176 179 var $others = array(); 177 }178 179 /**180 * @todo Change class vars to public/private181 */182 class iilThreadHeader183 {184 var $id;185 var $sbj;186 var $irt;187 var $mid;188 180 } 189 181 … … 865 857 $fields = array('ARRIVAL' => 1,'CC' => 1,'DATE' => 1, 866 858 'FROM' => 1, 'SIZE' => 1, 'SUBJECT' => 1, 'TO' => 1); 867 859 868 860 if (!$fields[$field]) { 869 861 return false; … … 877 869 $is_uid = $is_uid ? 'UID ' : ''; 878 870 879 if (!empty($add)) { 871 // message IDs 872 if (is_array($add)) 873 $add = iil_CompressMessageSet(join(',', $add)); 874 875 if (!empty($add)) 880 876 $add = " $add"; 881 }882 877 883 878 $command = 's ' . $is_uid . 'SORT (' . $field . ') '; … … 909 904 function iil_C_FetchHeaderIndex(&$conn, $mailbox, $message_set, $index_field='', $skip_deleted=true) { 910 905 911 list($from_idx, $to_idx) = explode(':', $message_set); 912 if (empty($message_set) || 913 (isset($to_idx) && $to_idx != '*' && (int)$from_idx > (int)$to_idx)) { 914 return false; 915 } 916 906 if (is_array($message_set)) { 907 if (!($message_set = iil_CompressMessageSet(join(',', $message_set)))) 908 return false; 909 } else { 910 list($from_idx, $to_idx) = explode(':', $message_set); 911 if (empty($message_set) || 912 (isset($to_idx) && $to_idx != '*' && (int)$from_idx > (int)$to_idx)) { 913 return false; 914 } 915 } 916 917 917 $index_field = empty($index_field) ? 'DATE' : strtoupper($index_field); 918 918 … … 1012 1012 } while (!iil_StartsWith($line, $key, true)); 1013 1013 1014 /*1015 //check number of elements...1016 if (is_numeric($from_idx) && is_numeric($to_idx)) {1017 //count how many we should have1018 $should_have = $to_idx - $from_idx + 1;1019 1020 //if we have less, try and fill in the "gaps"1021 if (count($result) < $should_have) {1022 for ($i=$from_idx; $i<=$to_idx; $i++) {1023 if (!isset($result[$i])) {1024 $result[$i] = '';1025 }1026 }1027 }1028 }1029 */1030 1014 return $result; 1031 1015 } … … 1103 1087 } 1104 1088 1105 function iil_SortThreadHeaders($headers, $index_a, $uids) {1106 asort($index_a);1107 $result = array();1108 foreach ($index_a as $mid=>$foobar) {1109 $uid = $uids[$mid];1110 $result[$uid] = $headers[$uid];1111 }1112 return $result;1113 }1114 1115 function iil_C_FetchThreadHeaders(&$conn, $mailbox, $message_set) {1116 global $clock;1117 global $index_a;1118 1119 list($from_idx, $to_idx) = explode(':', $message_set);1120 if (empty($message_set) || (isset($to_idx)1121 && (int)$from_idx > (int)$to_idx)) {1122 return false;1123 }1124 1125 $result = array();1126 $uids = iil_C_FetchUIDs($conn, $mailbox);1127 $debug = false;1128 1129 $message_set = iil_CompressMessageSet($message_set);1130 1131 /* if we're missing any, get them */1132 if ($message_set) {1133 /* FETCH date,from,subject headers */1134 $key = 'fh';1135 $fp = $conn->fp;1136 $request = $key . " FETCH $message_set ";1137 $request .= "(BODY.PEEK[HEADER.FIELDS (SUBJECT MESSAGE-ID IN-REPLY-TO)])";1138 $mid_to_id = array();1139 if (!iil_PutLine($fp, $request)) {1140 return false;1141 }1142 do {1143 $line = chop(iil_ReadLine($fp, 1024));1144 if ($debug) {1145 echo $line . "\n";1146 }1147 if (preg_match('/\{[0-9]+\}$/', $line)) {1148 $a = explode(' ', $line);1149 $new = array();1150 1151 $new_thhd = new iilThreadHeader;1152 $new_thhd->id = $a[1];1153 do {1154 $line = chop(iil_ReadLine($fp, 1024), "\r\n");1155 if (iil_StartsWithI($line, 'Message-ID:')1156 || (iil_StartsWithI($line,'In-Reply-To:'))1157 || (iil_StartsWithI($line,'SUBJECT:'))) {1158 1159 $pos = strpos($line, ':');1160 $field_name = substr($line, 0, $pos);1161 $field_val = substr($line, $pos+1);1162 1163 $new[strtoupper($field_name)] = trim($field_val);1164 1165 } else if (preg_match('/^\s+/', $line)) {1166 $new[strtoupper($field_name)] .= trim($line);1167 }1168 } while ($line[0] != ')');1169 1170 $new_thhd->sbj = $new['SUBJECT'];1171 $new_thhd->mid = substr($new['MESSAGE-ID'], 1, -1);1172 $new_thhd->irt = substr($new['IN-REPLY-TO'], 1, -1);1173 1174 $result[$uids[$new_thhd->id]] = $new_thhd;1175 }1176 } while (!iil_StartsWith($line, 'fh'));1177 }1178 1179 /* sort headers */1180 if (is_array($index_a)) {1181 $result = iil_SortThreadHeaders($result, $index_a, $uids);1182 }1183 1184 //echo 'iil_FetchThreadHeaders:'."\n";1185 //print_r($result);1186 1187 return $result;1188 }1189 1190 function iil_C_BuildThreads2(&$conn, $mailbox, $message_set, &$clock) {1191 global $index_a;1192 1193 list($from_idx, $to_idx) = explode(':', $message_set);1194 if (empty($message_set) || (isset($to_idx)1195 && (int)$from_idx > (int)$to_idx)) {1196 return false;1197 }1198 1199 $result = array();1200 $roots = array();1201 $root_mids = array();1202 $sub_mids = array();1203 $strays = array();1204 $messages = array();1205 $fp = $conn->fp;1206 $debug = false;1207 1208 $sbj_filter_pat = '/[a-z]{2,3}(\[[0-9]*\])?:(\s*)/i';1209 1210 /* Do "SELECT" command */1211 if (!iil_C_Select($conn, $mailbox)) {1212 return false;1213 }1214 1215 /* FETCH date,from,subject headers */1216 $mid_to_id = array();1217 $messages = array();1218 $headers = iil_C_FetchThreadHeaders($conn, $mailbox, $message_set);1219 if ($clock) {1220 $clock->register('fetched headers');1221 }1222 1223 if ($debug) {1224 print_r($headers);1225 }1226 1227 /* go through header records */1228 foreach ($headers as $header) {1229 //$id = $header['i'];1230 //$new = array('id'=>$id, 'MESSAGE-ID'=>$header['m'],1231 // 'IN-REPLY-TO'=>$header['r'], 'SUBJECT'=>$header['s']);1232 $id = $header->id;1233 $new = array('id' => $id, 'MESSAGE-ID' => $header->mid,1234 'IN-REPLY-TO' => $header->irt, 'SUBJECT' => $header->sbj);1235 1236 /* add to message-id -> mid lookup table */1237 $mid_to_id[$new['MESSAGE-ID']] = $id;1238 1239 /* if no subject, use message-id */1240 if (empty($new['SUBJECT'])) {1241 $new['SUBJECT'] = $new['MESSAGE-ID'];1242 }1243 1244 /* if subject contains 'RE:' or has in-reply-to header, it's a reply */1245 $sbj_pre = '';1246 $has_re = false;1247 if (preg_match($sbj_filter_pat, $new['SUBJECT'])) {1248 $has_re = true;1249 }1250 if ($has_re || $new['IN-REPLY-TO']) {1251 $sbj_pre = 'RE:';1252 }1253 1254 /* strip out 're:', 'fw:' etc */1255 if ($has_re) {1256 $sbj = preg_replace($sbj_filter_pat, '', $new['SUBJECT']);1257 } else {1258 $sbj = $new['SUBJECT'];1259 }1260 $new['SUBJECT'] = $sbj_pre.$sbj;1261 1262 1263 /* if subject not a known thread-root, add to list */1264 if ($debug) {1265 echo $id . ' ' . $new['SUBJECT'] . "\t" . $new['MESSAGE-ID'] . "\n";1266 }1267 $root_id = $roots[$sbj];1268 1269 if ($root_id && ($has_re || !$root_in_root[$root_id])) {1270 if ($debug) {1271 echo "\tfound root: $root_id\n";1272 }1273 $sub_mids[$new['MESSAGE-ID']] = $root_id;1274 $result[$root_id][] = $id;1275 } else if (!isset($roots[$sbj]) || (!$has_re && $root_in_root[$root_id])) {1276 /* try to use In-Reply-To header to find root1277 unless subject contains 'Re:' */1278 if ($has_re&&$new['IN-REPLY-TO']) {1279 if ($debug) {1280 echo "\tlooking: ".$new['IN-REPLY-TO']."\n";1281 }1282 //reply to known message?1283 $temp = $sub_mids[$new['IN-REPLY-TO']];1284 1285 if ($temp) {1286 //found it, root:=parent's root1287 if ($debug) {1288 echo "\tfound parent: ".$new['SUBJECT']."\n";1289 }1290 $result[$temp][] = $id;1291 $sub_mids[$new['MESSAGE-ID']] = $temp;1292 $sbj = '';1293 } else {1294 //if we can't find referenced parent, it's a "stray"1295 $strays[$id] = $new['IN-REPLY-TO'];1296 }1297 }1298 1299 //add subject as root1300 if ($sbj) {1301 if ($debug) {1302 echo "\t added to root\n";1303 }1304 $roots[$sbj] = $id;1305 $root_in_root[$id] = !$has_re;1306 $sub_mids[$new['MESSAGE-ID']] = $id;1307 $result[$id] = array($id);1308 }1309 if ($debug) {1310 echo $new['MESSAGE-ID'] . "\t" . $sbj . "\n";1311 }1312 }1313 }1314 1315 //now that we've gone through all the messages,1316 //go back and try and link up the stray threads1317 if (count($strays) > 0) {1318 foreach ($strays as $id=>$irt) {1319 $root_id = $sub_mids[$irt];1320 if (!$root_id || $root_id==$id) {1321 continue;1322 }1323 $result[$root_id] = array_merge($result[$root_id],$result[$id]);1324 unset($result[$id]);1325 }1326 }1327 1328 if ($clock) {1329 $clock->register('data prepped');1330 }1331 1332 if ($debug) {1333 print_r($roots);1334 }1335 1336 return $result;1337 }1338 1339 function iil_SortThreads(&$tree, $index, $sort_order = 'ASC') {1340 if (!is_array($tree) || !is_array($index)) {1341 return false;1342 }1343 1344 //create an id to position lookup table1345 $i = 0;1346 foreach ($index as $id=>$val) {1347 $i++;1348 $index[$id] = $i;1349 }1350 $max = $i+1;1351 1352 //for each tree, set array key to position1353 $itree = array();1354 foreach ($tree as $id=>$node) {1355 if (count($tree[$id])<=1) {1356 //for "threads" with only one message, key is position of that message1357 $n = $index[$id];1358 $itree[$n] = array($n=>$id);1359 } else {1360 //for "threads" with multiple messages,1361 $min = $max;1362 $new_a = array();1363 foreach ($tree[$id] as $mid) {1364 $new_a[$index[$mid]] = $mid; //create new sub-array mapping position to id1365 $pos = $index[$mid];1366 if ($pos&&$pos<$min) {1367 $min = $index[$mid]; //find smallest position1368 }1369 }1370 $n = $min; //smallest position of child is thread position1371 1372 //assign smallest position to root level key1373 //set children array to one created above1374 ksort($new_a);1375 $itree[$n] = $new_a;1376 }1377 }1378 1379 //sort by key, this basically sorts all threads1380 ksort($itree);1381 $i = 0;1382 $out = array();1383 foreach ($itree as $k=>$node) {1384 $out[$i] = $itree[$k];1385 $i++;1386 }1387 1388 return $out;1389 }1390 1391 function iil_IndexThreads(&$tree) {1392 /* creates array mapping mid to thread id */1393 1394 if (!is_array($tree)) {1395 return false;1396 }1397 1398 $t_index = array();1399 foreach ($tree as $pos=>$kids) {1400 foreach ($kids as $kid) $t_index[$kid] = $pos;1401 }1402 1403 return $t_index;1404 }1405 1406 1089 function iil_C_FetchHeaders(&$conn, $mailbox, $message_set, $uidfetch=false, $bodystr=false, $add='') 1407 1090 { … … 1416 1099 return false; 1417 1100 } 1101 1102 if (is_array($message_set)) 1103 $message_set = join(',', $message_set); 1418 1104 1419 1105 $message_set = iil_CompressMessageSet($message_set); … … 1858 1544 } 1859 1545 1546 // Don't be tempted to change $str to pass by reference to speed this up - it will slow it down by about 1547 // 7 times instead :-) See comments on http://uk2.php.net/references and this article: 1548 // http://derickrethans.nl/files/phparch-php-variables-article.pdf 1549 function iil_ParseThread($str, $begin, $end, $root, $parent, $depth, &$depthmap, &$haschildren) { 1550 $node = array(); 1551 if ($str[$begin] != '(') { 1552 $stop = $begin + strspn($str, "1234567890", $begin, $end - $begin); 1553 $msg = substr($str, $begin, $stop - $begin); 1554 if ($msg == 0) 1555 return $node; 1556 if (is_null($root)) 1557 $root = $msg; 1558 $depthmap[$msg] = $depth; 1559 $haschildren[$msg] = false; 1560 if (!is_null($parent)) 1561 $haschildren[$parent] = true; 1562 if ($stop + 1 < $end) 1563 $node[$msg] = iil_ParseThread($str, $stop + 1, $end, $root, $msg, $depth + 1, $depthmap, $haschildren); 1564 else 1565 $node[$msg] = array(); 1566 } else { 1567 $off = $begin; 1568 while ($off < $end) { 1569 $start = $off; 1570 $off++; 1571 $n = 1; 1572 while ($n > 0) { 1573 $p = strpos($str, ')', $off); 1574 if ($p === false) { 1575 error_log('Mismatched brackets parsing IMAP THREAD response:'); 1576 error_log(substr($str, ($begin < 10) ? 0 : ($begin - 10), $end - $begin + 20)); 1577 error_log(str_repeat(' ', $off - (($begin < 10) ? 0 : ($begin - 10)))); 1578 return $node; 1579 } 1580 $p1 = strpos($str, '(', $off); 1581 if ($p1 !== false && $p1 < $p) { 1582 $off = $p1 + 1; 1583 $n++; 1584 } else { 1585 $off = $p + 1; 1586 $n--; 1587 } 1588 } 1589 $node += iil_ParseThread($str, $start + 1, $off - 1, $root, $parent, $depth, $depthmap, $haschildren); 1590 } 1591 } 1592 1593 return $node; 1594 } 1595 1596 function iil_C_Thread(&$conn, $folder, $algorithm='REFERENCES', $criteria='', 1597 $encoding='US-ASCII') { 1598 1599 if (iil_C_Select($conn, $folder)) { 1600 1601 $encoding = $encoding ? trim($encoding) : 'US-ASCII'; 1602 $algorithm = $algorithm ? trim($algorithm) : 'REFERENCES'; 1603 $criteria = $criteria ? 'ALL '.trim($criteria) : 'ALL'; 1604 1605 iil_PutLineC($conn->fp, "thrd1 THREAD $algorithm $encoding $criteria"); 1606 do { 1607 $line = trim(iil_ReadLine($conn->fp, 10000)); 1608 if (eregi("^\* THREAD", $line)) { 1609 $str = trim(substr($line, 8)); 1610 $depthmap = array(); 1611 $haschildren = array(); 1612 $tree = iil_ParseThread($str, 0, strlen($str), null, null, 0, $depthmap, $haschildren); 1613 } 1614 } while (!iil_StartsWith($line, 'thrd1', true)); 1615 1616 $result_code = iil_ParseResult($line); 1617 if ($result_code == 0) { 1618 return array($tree, $depthmap, $haschildren); 1619 } 1620 $conn->error = 'iil_C_Thread: ' . $line . "\n"; 1621 return false; 1622 } 1623 $conn->error = "iil_C_Thread: Couldn't select \"$folder\"\n"; 1624 return false; 1625 } 1626 1860 1627 function iil_C_Search(&$conn, $folder, $criteria) { 1861 1628 -
branches/devel-threads/program/localization/en_US/labels.inc
r2983 r3106 57 57 $labels['mailboxlist'] = 'Folders'; 58 58 $labels['messagesfromto'] = 'Messages $from to $to of $count'; 59 $labels['threadsfromto'] = 'Threads $from to $to of $count'; 59 60 $labels['messagenrof'] = 'Message $nr of $count'; 60 61 … … 154 155 $labels['invert'] = 'Invert'; 155 156 $labels['filter'] = 'Filter'; 157 158 $labels['threads'] = 'Threads'; 159 $labels['expand-all'] = 'Expand All'; 160 $labels['collapse-all'] = 'Collapse All'; 161 162 $labels['autoexpand_threads'] = 'Autoexpand threads'; 163 $labels['do_expand'] = 'All threads'; 164 $labels['dont_expand'] = 'None'; 165 $labels['expand_only_unread'] = 'Only with unread messages'; 156 166 157 167 $labels['compact'] = 'Compact'; … … 319 329 $labels['subscribed'] = 'Subscribed'; 320 330 $labels['messagecount'] = 'Messages'; 331 $labels['threaded'] = 'Threaded'; 321 332 $labels['create'] = 'Create'; 322 333 $labels['createfolder'] = 'Create new folder'; -
branches/devel-threads/program/steps/mail/check_recent.inc
r2962 r3106 29 29 if (($search_request = get_input_value('_search', RCUBE_INPUT_GPC)) && isset($_SESSION['search'][$search_request])) { 30 30 $_SESSION['search'][$search_request] = $IMAP->refresh_search(); 31 $all_count = $IMAP->messagecount( );31 $all_count = $IMAP->messagecount(NULL, $IMAP->threading ? 'THREADS' : 'ALL'); 32 32 } else { 33 $all_count = $IMAP->messagecount(NULL, 'ALL', TRUE);33 $all_count = $IMAP->messagecount(NULL, $IMAP->threading ? 'THREADS' : 'ALL', TRUE); 34 34 } 35 35 … … 56 56 continue; 57 57 58 // use SEARCH/SORT to find recent messages 59 $search_str = 'RECENT'; 60 if ($search_request) 61 $search_str .= ' '.$IMAP->search_string; 58 if ($IMAP->threading) { 59 $OUTPUT->command('message_list.clear'); 60 $sort_col = isset($_SESSION['sort_col']) ? $_SESSION['sort_col'] : $CONFIG['message_sort_col']; 61 $sort_order = isset($_SESSION['sort_order']) ? $_SESSION['sort_order'] : $CONFIG['message_sort_order']; 62 $result_h = $IMAP->list_headers($mbox_name, NULL, $sort_col, $sort_order); 63 // add to the list 64 rcmail_js_message_list($result_h); 65 } else { 66 // use SEARCH/SORT to find recent messages 67 $search_str = 'RECENT'; 68 if ($search_request) 69 $search_str .= ' '.$IMAP->search_string; 62 70 63 $result = $IMAP->search($mbox_name, $search_str, NULL, 'date');71 $result = $IMAP->search($mbox_name, $search_str, NULL, 'date'); 64 72 65 if ($result) { 66 // get the headers 67 $result_h = $IMAP->list_headers($mbox_name, 1, 'date', 'DESC'); 68 // add to the list 69 rcmail_js_message_list($result_h, true, false); 73 if ($result) { 74 // get the headers 75 $result_h = $IMAP->list_headers($mbox_name, 1, 'date', 'DESC'); 76 // add to the list 77 rcmail_js_message_list($result_h, TRUE); 78 } 70 79 } 71 80 } -
branches/devel-threads/program/steps/mail/func.inc
r3058 r3106 53 53 $_SESSION['sort_order'] = $CONFIG['message_sort_order']; 54 54 55 // enable threads mode 56 $a_message_threading = $RCMAIL->config->get('message_threading', array()); 57 $IMAP->set_threading($a_message_threading[$_SESSION['mbox']]); 58 55 59 // set message set for search result 56 60 if (!empty($_REQUEST['_search']) && isset($_SESSION['search'][$_REQUEST['_search']])) … … 80 84 $OUTPUT->set_env('search_mods', $_SESSION['search_mods'] ? $_SESSION['search_mods'] : array('subject'=>'subject')); 81 85 // make sure the message count is refreshed (for default view) 82 $IMAP->messagecount($mbox_name, 'ALL', true);86 $IMAP->messagecount($mbox_name, $IMAP->threading ? 'THREADS' : 'ALL', true); 83 87 } 84 88 … … 87 91 $OUTPUT->set_env('quota', $IMAP->get_capability('quota')); 88 92 $OUTPUT->set_env('delimiter', $IMAP->get_hierarchy_delimiter()); 93 $OUTPUT->set_env('threading', (bool) $IMAP->threading); 89 94 90 95 if ($CONFIG['flag_for_deletion']) … … 120 125 $skin_path = $CONFIG['skin_path']; 121 126 $image_tag = '<img src="%s%s" alt="%s" />'; 122 123 127 // check to see if we have some settings for sorting 124 128 $sort_col = $_SESSION['sort_col']; … … 162 166 // add col definition 163 167 $out .= '<colgroup>'; 164 $out .= '<col class="icon" />';165 168 166 169 foreach ($a_show_cols as $col) … … 170 173 171 174 // add table title 172 $out .= "<thead><tr>\n <td class=\"icon\"> </td>\n";175 $out .= "<thead><tr>\n"; 173 176 174 177 $javascript = ''; … … 234 237 235 238 // put it all together 236 if ($col!='attachment') 237 $out .= '<td class="'.$col.$sort_class.'" id="rcm'.$col.'">' . "$col_name$sort</td>\n"; 239 if ($col != 'attachment') { 240 // list options menu link 241 if ($col == 'subject' && !empty($attrib['optionsmenuicon'])) { 242 $list_menu = $OUTPUT->button(array( 243 'name' => 'listmenulink', 244 'id' => 'listmenulink', 245 // 'command' => '', 246 // 'prop' => '', 247 'image' => $attrib['optionsmenuicon'], 248 'align' => 'absmiddle', 249 'style' => 'float: left; padding-right: 5px', 250 'title' => 'listoptions')); 251 } else 252 $list_menu = ''; 253 254 $out .= '<td class="'.$col.$sort_class.'" id="rcm'.$col.'">' . "$list_menu$col_name$sort</td>\n"; 255 } 238 256 else 239 257 $out .= '<td class="icon" id="rcm'.$col.'">' . "$col_name$sort</td>\n"; … … 266 284 if ($header->flagged) 267 285 $js_row_arr['flagged'] = true; 268 269 // set message icon 270 if ($attrib['deletedicon'] && $header->deleted) 286 if ($header->has_children) 287 $js_row_arr['has_children'] = true; 288 if ($header->depth) 289 $js_row_arr['depth'] = $header->depth; 290 if ($header->parent_uid) 291 $js_row_arr['parent_uid'] = $header->parent_uid; 292 if ($header->unread_children) 293 $js_row_arr['unread_children'] = $header->unread_children; 294 295 // set message icon 296 if ($header->seen && $attrib['unreadchildrenicon'] && $header->unread_children > 0) 297 $message_icon = $attrib['unreadchildrenicon']; 298 else if ($attrib['deletedicon'] && $header->deleted) 271 299 $message_icon = $attrib['deletedicon']; 272 300 else if ($attrib['repliedicon'] && $header->answered) … … 293 321 $attach_icon = $attrib['attachmenticon']; 294 322 295 $out .= sprintf('<tr id="rcmrow%d" class="message%s%s%s%s" >'."\n",323 $out .= sprintf('<tr id="rcmrow%d" class="message%s%s%s%s"%s>'."\n", 296 324 $header->uid, 297 325 $header->seen ? '' : ' unread', 298 326 $header->deleted ? ' deleted' : '', 299 327 $header->flagged ? ' flagged' : '', 300 $zebra_class); 301 302 $out .= sprintf("<td class=\"icon\">%s</td>\n", $message_icon ? sprintf($image_tag, $skin_path, $message_icon, '') : ''); 328 $zebra_class, 329 ($header->depth) ? ' style="display: none"' : ''); 330 331 $tree = ''; 332 if ($IMAP->threading) 333 { 334 // XXX: This assumes that div width is hardcoded to 15px, 335 // Chris did it a bit differently in an original patch, he was adding so much divs as depth is 336 // I replaced logic in list.js:drag_mouse_move() so subject text is picked defferently, so 337 // either method of could be used (that was only the one problem I noted with these added divs). 338 // The same is true for an online list (program/js/app.js:add_message_row()). 339 // Bubble 340 $width = ($header->depth) * 15; 341 $tree .= '<div id="rcmtab' . $header->uid . '" class="branch" style="width:' . $width . 'px;"> </div>'; 342 if ($header->has_children && !$header->depth) 343 $tree .= '<div id="rcmexpando' . $header->uid . '" class="collapsed"> </div>'; 344 else 345 $tree .= '<div class="leaf"> </div>'; 346 } 347 348 $tree .= $message_icon ? sprintf($image_tag, $skin_path, $message_icon, '') : ''; 303 349 304 350 $IMAP->set_charset(!empty($header->charset) ? $header->charset : $CONFIG['default_charset']); 305 351 306 352 // format each col 353 $first = true; 307 354 foreach ($a_show_cols as $col) 308 355 { … … 326 373 $cont = Q($header->$col); 327 374 375 if ($first) { 376 $first = false; 377 $cont = $tree . $cont; 378 } 328 379 if ($col!='attachment') 329 380 $out .= '<td class="'.$col.'">' . $cont . "</td>\n"; … … 341 392 $out .= "</tbody></table>\n"; 342 393 343 $message_count = $IMAP->messagecount( );394 $message_count = $IMAP->messagecount(NULL, $IMAP->threading ? 'THREADS' : 'ALL'); 344 395 345 396 // set client env 346 397 $OUTPUT->add_gui_object('mailcontframe', 'mailcontframe'); 347 398 $OUTPUT->add_gui_object('messagelist', $attrib['id']); 399 $OUTPUT->set_env('autoexpand_threads', $CONFIG['autoexpand_threads']); 348 400 $OUTPUT->set_env('messagecount', $message_count); 349 401 $OUTPUT->set_env('current_page', $IMAP->list_page); … … 370 422 if ($attrib['unflaggedicon']) 371 423 $OUTPUT->set_env('unflaggedicon', $skin_path . $attrib['unflaggedicon']); 424 if ($attrib['unreadchildrenicon']) 425 $OUTPUT->set_env('unreadchildrenicon', $skin_path . $attrib['unreadchildrenicon']); 372 426 373 427 $OUTPUT->set_env('messages', $a_js_message_arr); … … 413 467 if ($browser->ie && $replace) 414 468 $OUTPUT->command('offline_message_list', true); 469 470 // remove 'attachment' and 'flag' columns, we don't need them here 471 if(($key = array_search('attachment', $a_show_cols)) !== FALSE) 472 unset($a_show_cols[$key]); 473 if(($key = array_search('flag', $a_show_cols)) !== FALSE) 474 unset($a_show_cols[$key]); 415 475 416 476 // loop through message headers … … 448 508 } 449 509 510 if ($header->depth) 511 $a_msg_flags['depth'] = $header->depth; 512 if ($header->parent_uid) 513 $a_msg_flags['parent_uid'] = $header->parent_uid; 514 if ($header->has_children) 515 $a_msg_flags['has_children'] = $header->has_children; 516 if ($header->unread_children) 517 $a_msg_flags['unread_children'] = $header->unread_children; 450 518 if ($header->deleted) 451 519 $a_msg_flags['deleted'] = 1; … … 575 643 { 576 644 return rcube_label(array('name' => 'messagenrof', 577 'vars' => array('nr' => $MESSAGE->index+1,578 'count' => $count!==NULL ? $count : $IMAP->messagecount())));645 'vars' => array('nr' => $MESSAGE->index+1, 646 'count' => $count!==NULL ? $count : $IMAP->messagecount(NULL, 'ALL')))); // Only messages, no threads here 579 647 } 580 648 … … 583 651 584 652 $start_msg = ($page-1) * $IMAP->page_size + 1; 585 $max = $count!==NULL ? $count : $IMAP->messagecount( );653 $max = $count!==NULL ? $count : $IMAP->messagecount(NULL, $IMAP->threading ? 'THREADS' : 'ALL'); 586 654 587 655 if ($max==0) 588 656 $out = rcube_label('mailboxempty'); 589 657 else 590 $out = rcube_label(array('name' => 'messagesfromto',591 'vars' => array('from' => $start_msg,592 'to' => min($max, $start_msg + $IMAP->page_size - 1),593 'count' => $max)));658 $out = rcube_label(array('name' => $IMAP->threading ? 'threadsfromto' : 'messagesfromto', 659 'vars' => array('from' => $start_msg, 660 'to' => min($max, $start_msg + $IMAP->page_size - 1), 661 'count' => $max))); 594 662 595 663 return Q($out); -
branches/devel-threads/program/steps/mail/list.inc
r2983 r3106 56 56 57 57 // fetch message headers 58 if ($count = $IMAP->messagecount($mbox_name, 'ALL', !empty($_REQUEST['_refresh'])))58 if ($count = $IMAP->messagecount($mbox_name, $IMAP->threading ? 'THREADS' : 'ALL', !empty($_REQUEST['_refresh']))) 59 59 $a_headers = $IMAP->list_headers($mbox_name, NULL, $sort_col, $sort_order); 60 60 … … 66 66 $OUTPUT->set_env('messagecount', $count); 67 67 $OUTPUT->set_env('pagecount', $pages); 68 $OUTPUT->set_env('threading', (bool) $IMAP->threading); 68 69 $OUTPUT->command('set_rowcount', rcmail_get_messagecount_text($count)); 69 70 $OUTPUT->command('set_mailboxname', rcmail_get_mailbox_name_text()); … … 81 82 $OUTPUT->show_message('nomessagesfound', 'notice'); 82 83 84 // deal with threaded view 85 if ($IMAP->threading) { 86 switch ($RCMAIL->config->get('autoexpand_threads')) { 87 case 2: 88 $OUTPUT->command('message_list.expand_unread'); 89 break; 90 case 1: 91 $OUTPUT->command('message_list.expand_all'); 92 break; 93 case 0: 94 default: 95 break; 96 } 97 } 98 83 99 // send response 84 100 $OUTPUT->send(); -
branches/devel-threads/program/steps/mail/move_del.inc
r2960 r3106 25 25 26 26 // count messages before changing anything 27 $old_count = $IMAP->messagecount( );27 $old_count = $IMAP->messagecount(NULL, $IMAP->threading ? 'THREADS' : 'ALL'); 28 28 $old_pages = ceil($old_count / $IMAP->page_size); 29 29 … … 83 83 else 84 84 { 85 $msg_count = $IMAP->messagecount( );85 $msg_count = $IMAP->messagecount(NULL, $IMAP->threading ? 'THREADS' : 'ALL'); 86 86 $pages = ceil($msg_count / $IMAP->page_size); 87 87 $nextpage_count = $old_count - $IMAP->page_size * $IMAP->list_page; … … 123 123 124 124 $a_headers = $IMAP->list_headers($mbox, NULL, $sort_col, $sort_order, $count); 125 if ($_SESSION['threads']) 126 // TODO: count number of roots deleted and slice that many roots from the end of $a_headers 127 $OUTPUT->command('message_list.clear'); 128 else 129 $a_headers = array_slice($a_headers, -$count, $count); 125 130 126 131 rcmail_js_message_list($a_headers, false, false); -
branches/devel-threads/program/steps/mail/search.inc
r2949 r3106 100 100 // Get the headers 101 101 $result_h = $IMAP->list_headers($mbox, 1, $_SESSION['sort_col'], $_SESSION['sort_order']); 102 $count = $IMAP->messagecount( );102 $count = $IMAP->messagecount(NULL, $IMAP->threading ? 'THREADS' : 'ALL'); 103 103 104 104 // save search results in session … … 116 116 rcmail_js_message_list($result_h); 117 117 if ($search_str) 118 $OUTPUT->show_message('searchsuccessful', 'confirmation', array('nr' => $ count));118 $OUTPUT->show_message('searchsuccessful', 'confirmation', array('nr' => $IMAP->messagecount(NULL, 'ALL'))); 119 119 } 120 120 else -
branches/devel-threads/program/steps/settings/func.inc
r3055 r3106 312 312 } 313 313 314 if (!isset($no_override['autoexpand_threads'])) { 315 $field_id = 'rcmfd_autoexpand_threads'; 316 $select_autoexpand_threads = new html_select(array('name' => '_autoexpand_threads', 'id' => $field_id)); 317 $select_autoexpand_threads->add(rcube_label('dont_expand'), 0); 318 $select_autoexpand_threads->add(rcube_label('do_expand'), 1); 319 $select_autoexpand_threads->add(rcube_label('expand_only_unread'), 2); 320 321 $blocks['main']['options']['autoexpand_threads'] = array( 322 'title' => html::label($field_id, Q(rcube_label('autoexpand_threads'))), 323 'content' => $select_autoexpand_threads->show($config['autoexpand_threads']), 324 ); 325 } 326 314 327 if (!isset($no_override['focus_on_new_message'])) { 315 328 $field_id = 'rcmfd_focus_on_new_message'; -
branches/devel-threads/program/steps/settings/manage_folders.inc
r2505 r3106 39 39 } 40 40 41 // enable threading for one or more mailboxes 42 else if ($RCMAIL->action=='enable-threading') 43 { 44 if ($mbox = get_input_value('_mbox', RCUBE_INPUT_POST, false, 'UTF7-IMAP')) 45 $a_user_prefs = $USER->get_prefs(); 46 if (!is_array($a_user_prefs['message_threading'])) 47 $a_user_prefs['message_threading'] = array(); 48 $a_user_prefs['message_threading'][$mbox] = true; 49 $USER->save_prefs($a_user_prefs); 50 } 51 52 // enable threading for one or more mailboxes 53 else if ($RCMAIL->action=='disable-threading') 54 { 55 if ($mbox = get_input_value('_mbox', RCUBE_INPUT_POST, false, 'UTF7-IMAP')) 56 $a_user_prefs = $USER->get_prefs(); 57 if (!is_array($a_user_prefs['message_threading'])) 58 $a_user_prefs['message_threading'] = array(); 59 unset($a_user_prefs['message_threading'][$mbox]); 60 $USER->save_prefs($a_user_prefs); 61 } 62 41 63 // create a new mailbox 42 64 else if ($RCMAIL->action=='create-folder') … … 160 182 function rcube_subscription_form($attrib) 161 183 { 162 global $IMAP, $CONFIG, $OUTPUT; 184 global $RCMAIL, $IMAP, $CONFIG, $OUTPUT; 185 186 $threading_supported = $IMAP->get_capability('thread=references') 187 || $IMAP->get_capability('thread=orderedsubject') 188 || $IMAP->get_capability('thread=refs'); 163 189 164 190 list($form_start, $form_end) = get_form_tags($attrib, 'folders'); … … 174 200 $table->add_header('msgcount', rcube_label('messagecount')); 175 201 $table->add_header('subscribed', rcube_label('subscribed')); 202 if ($threading_supported) 203 $table->add_header('threaded', rcube_label('threaded')); 176 204 $table->add_header('rename', ' '); 177 205 $table->add_header('delete', ' '); 178 206 179 180 207 // get folders from server 181 208 $IMAP->clear_cache('mailboxes'); … … 183 210 $a_unsubscribed = $IMAP->list_unsubscribed(); 184 211 $a_subscribed = $IMAP->list_mailboxes(); 212 $a_threaded = $RCMAIL->config->get('message_threading', array()); 185 213 $delimiter = $IMAP->get_hierarchy_delimiter(); 186 214 $a_js_folders = $seen_folders = $list_folders = array(); … … 212 240 'onclick' => JS_OBJECT_NAME.".command(this.checked?'subscribe':'unsubscribe',this.value)", 213 241 )); 242 $checkbox_threaded = new html_checkbox(array( 243 'name' => '_threaded[]', 244 'onclick' => JS_OBJECT_NAME.".command(this.checked?'enable-threading':'disable-threading',this.value)", 245 )); 214 246 215 247 if (!empty($attrib['deleteicon'])) … … 227 259 $idx = $i + 1; 228 260 $subscribed = in_array($folder['id'], $a_subscribed); 261 $threaded = $a_threaded[$folder['id']]; 229 262 $protected = ($CONFIG['protect_default_folders'] == true && in_array($folder['id'], $CONFIG['default_imap_folders'])); 230 263 $classes = array($i%2 ? 'even' : 'odd'); … … 239 272 240 273 $table->add('name', Q($display_folder)); 241 $table->add('msgcount', ($folder['virtual'] ? '' : $IMAP->messagecount($folder['id']))); 274 $table->add('msgcount', ($folder['virtual'] ? '' : $IMAP->messagecount($folder['id']))); // XXX: Use THREADS or ALL? 242 275 $table->add('subscribed', ($protected || $folder['virtual']) ? ($subscribed ? ' •' : ' ') : 243 276 $checkbox_subscribe->show(($subscribed ? $folder_utf8 : ''), array('value' => $folder_utf8))); 277 if ($IMAP->get_capability('thread=references')) { 278 $table->add('threaded', 279 $checkbox_threaded->show(($threaded ? $folder_utf8 : ''), array('value' => $folder_utf8))); 280 } 244 281 245 282 // add rename and delete buttons -
branches/devel-threads/program/steps/settings/save_prefs.inc
r2983 r3106 43 43 'focus_on_new_message' => isset($_POST['_focus_on_new_message']) ? TRUE : FALSE, 44 44 'preview_pane' => isset($_POST['_preview_pane']) ? TRUE : FALSE, 45 'autoexpand_threads' => isset($_POST['_autoexpand_threads']) ? intval($_POST['_autoexpand_threads']) : 0, 45 46 'mdn_requests' => isset($_POST['_mdn_requests']) ? intval($_POST['_mdn_requests']) : 0, 46 47 'keep_alive' => isset($_POST['_keep_alive']) ? intval($_POST['_keep_alive'])*60 : $CONFIG['keep_alive'], -
branches/devel-threads/skins/default/functions.js
r3060 r3106 125 125 this.searchmenu = $('#searchmenu'); 126 126 this.messagemenu = $('#messagemenu'); 127 this.listmenu = $('#listmenu'); 128 var ref = rcube_find_object('listmenulink'); 129 130 if (ref) 131 ref.onclick = function () { rcmail_ui.show_listmenu() }; 127 132 } 128 133 … … 184 189 }, 185 190 191 show_listmenu: function(show) 192 { 193 if (typeof show == 'undefined') 194 show = this.listmenu.is(':visible') ? false : true; 195 196 var ref = rcube_find_object('listmenulink'); 197 if (show && ref) { 198 var pos = $(ref).offset(); 199 this.listmenu.css({ left:pos.left, top:(pos.top + ref.offsetHeight)}); 200 } 201 this.listmenu[show?'show':'hide'](); 202 }, 203 186 204 body_mouseup: function(evt, p) 187 205 { … … 190 208 else if (this.messagemenu && this.messagemenu.is(':visible') && rcube_event.get_target(evt) != rcube_find_object('messagemenulink')) 191 209 this.show_messagemenu(false); 210 else if (this.listmenu && this.listmenu.is(':visible') && rcube_event.get_target(evt) != rcube_find_object('listmenulink')) { 211 var menu = rcube_find_object('listmenu'); 212 var target = rcube_event.get_target(evt); 213 while (target.parentNode) { 214 if (target.parentNode == menu) 215 return; 216 target = target.parentNode; 217 } 218 this.show_listmenu(false); 219 } 192 220 else if (this.searchmenu && this.searchmenu.is(':visible') && rcube_event.get_target(evt) != rcube_find_object('searchmod')) { 193 221 var menu = rcube_find_object('searchmenu'); … … 211 239 if (this.messagemenu && this.messagemenu.is(':visible')) 212 240 this.show_messagemenu(false); 241 if (this.listmenu && this.listmenu.is(':visible')) 242 this.show_listmenu(false); 213 243 } 214 244 } -
branches/devel-threads/skins/default/mail.css
r3058 r3106 167 167 #markmessagemenu, 168 168 #searchmenu, 169 #messagemenu 169 #messagemenu, 170 #listmenu 170 171 { 171 172 position: absolute; … … 627 628 } 628 629 630 #listcontrols a.expand-all { 631 background-position: -90px 0; 632 } 633 634 #listcontrols a.expand-allsel { 635 background-position: -90px -15px; 636 } 637 638 #listcontrols a.collapse-all { 639 background-position: -105px 0; 640 } 641 642 #listcontrols a.collapse-allsel { 643 background-position: -105px -15px; 644 } 645 629 646 #countcontrols 630 647 { … … 750 767 } 751 768 769 #messagelist tr td div 770 { 771 display: table-cell; /* For FireFox and Opera */ 772 display: inline-block; /* For Opera and IE */ 773 width: 15px; 774 height: 15px; 775 } 776 777 #messagelist tr td.subject img 778 { 779 vertical-align: middle; 780 } 781 782 #messagelist tr td div.collapsed, 783 #messagelist tr td div.expanded, 784 #messagelist tr td img 785 { 786 cursor: pointer; 787 } 788 789 #messagelist tr td div.collapsed 790 { 791 background: url(images/icons/collapsed.png) center 5px no-repeat; 792 } 793 794 #messagelist tr td div.expanded 795 { 796 background: url(images/icons/expanded.png) center 5px no-repeat; 797 } 798 752 799 #messagelist tbody tr td.flag img:hover, 753 800 #messagelist thead tr td.flag img -
branches/devel-threads/skins/default/templates/mail.html
r3058 r3106 60 60 attachmentIcon="/images/icons/attachment.png" 61 61 flaggedIcon="/images/icons/flagged.png" 62 unflaggedIcon="/images/icons/blank.gif" /> 62 unflaggedIcon="/images/icons/blank.gif" 63 unreadchildrenIcon="/images/icons/unread_children.png" 64 optionsmenuIcon="/images/icons/columnpicker.gif" /> 63 65 </div> 64 66 … … 83 85 <roundcube:button command="select-all" type="link" prop="invert" title="invert" class="buttonPas invert" classAct="button invert" classSel="button invertsel" content=" " /> 84 86 <roundcube:button command="select-none" type="link" title="none" class="buttonPas none" classAct="button none" classSel="button nonesel" content=" " /> 87 <span style="margin-left: 20px"><roundcube:label name="threads" />: </span> 88 <roundcube:button command="expand-all" type="link" title="expand-all" class="buttonPas expand-all" classAct="button expand-all" classSel="button expand-allsel" content=" " /> 89 <roundcube:button command="collapse-all" type="link" title="collapse-all" class="buttonPas collapse-all" classAct="button collapse-all" classSel="button collapse-allsel" content=" " /> 85 90 <roundcube:container name="listcontrols" id="listcontrols" /> 86 91 <roundcube:if condition="env:quota" /> … … 147 152 </div> 148 153 154 <div id="listmenu"> 155 <div><roundcube:label name="listcolumns" /></div> 156 <ul class="toolbarmenu"> 157 <li><input type="checkbox" name="list_cols[]" value="subject" id="cols_subject" checked="checked" disabled="disabled" /><label for="cols_subject"><roundcube:label name="subject" /></label></li> 158 <li><input type="checkbox" name="list_cols[]" value="from" id="cols_fromto" /><label for="cols_fromto"><roundcube:label name="fromto" /></label></li> 159 <li><input type="checkbox" name="list_cols[]" value="replyto" id="cols_replyto" /><label for="cols_replyto"><roundcube:label name="replyto" /></label></li> 160 <li><input type="checkbox" name="list_cols[]" value="cc" id="cols_cc" /><label for="cols_cc"><roundcube:label name="cc" /></label></li> 161 <li><input type="checkbox" name="list_cols[]" value="date" id="cols_date" /><label for="cols_date"><roundcube:label name="date" /></label></li> 162 <li><input type="checkbox" name="list_cols[]" value="size" id="cols_size" /><label for="cols_size"><roundcube:label name="size" /></label></li> 163 <li><input type="checkbox" name="list_cols[]" value="flag" id="cols_flag" /><label for="cols_flag"><roundcube:label name="flag" /></label></li> 164 <li><input type="checkbox" name="list_cols[]" value="attachment" id="cols_attachment" /><label for="cols_attachment"><roundcube:label name="attachment" /></label></li> 165 </ul> 166 <div><roundcube:label name="listsorting" /></div> 167 <ul class="toolbarmenu"> 168 <li><input type="radio" name="sort_cols" value="" id="sort_default" /><label for="sort_default"><roundcube:label name="nonedefault" /></label></li> 169 <li><input type="radio" name="sort_cols" value="arrival" id="sort_arrival" /><label for="sort_arrival"><roundcube:label name="arrival" /></label></li> 170 <li><input type="radio" name="sort_cols" value="date" id="sort_date" /><label for="sort_date"><roundcube:label name="sentdate" /></label></li> 171 <li><input type="radio" name="sort_cols" value="subject" id="sort_subject" /><label for="sort_subject"><roundcube:label name="subject" /></label></li> 172 <li><input type="radio" name="sort_cols" value="from" id="sort_fromto" /><label for="sort_fromto"><roundcube:label name="fromto" /></label></li> 173 <li><input type="radio" name="sort_cols" value="to" id="sort_replyto" /><label for="sort_replyto"><roundcube:label name="replyto" /></label></li> 174 <li><input type="radio" name="sort_cols" value="cc" id="sort_cc" /><label for="sort_cc"><roundcube:label name="cc" /></label></li> 175 <li><input type="radio" name="sort_cols" value="size" id="sort_size" /><label for="sort_size"><roundcube:label name="size" /></label></li> 176 </ul> 177 <div><roundcube:label name="listorder" /></div> 178 <ul class="toolbarmenu"> 179 <li><input type="radio" name="sort_ord" value="" id="sort_asc" /><label for="sort_default"><roundcube:label name="asc" /></label></li> 180 <li><input type="radio" name="sort_ord" value="subject" id="sort_desc" /><label for="sort_subject"><roundcube:label name="desc" /></label></li> 181 </ul> 182 <div><roundcube:label name="listgroups" /></div> 183 <ul class="toolbarmenu"> 184 <li><input type="radio" name="view" value="" id="view_default" /><label for="view_default"><roundcube:label name="nonedefault" /></label></li> 185 <li><input type="radio" name="view" value="thread" id="view_thread" /><label for="view_thread"><roundcube:label name="threaded" /></label></li> 186 </ul> 187 </div> 188 149 189 </body> 150 190 </html>
Note: See TracChangeset
for help on using the changeset viewer.
