Changeset 3106 in subversion


Ignore:
Timestamp:
Nov 13, 2009 9:27:04 AM (4 years ago)
Author:
alec
Message:
  • Threads (just a beginning)
Location:
branches/devel-threads
Files:
3 added
19 edited

Legend:

Unmodified
Added
Removed
  • branches/devel-threads/config/main.inc.php.dist

    r3049 r3106  
    7777$rcmail_config['imap_root'] = null; 
    7878$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; 
    7989 
    8090// Automatically add this domain to user names for login 
     
    342352 
    343353// default sort col 
    344 $rcmail_config['message_sort_col'] = 'date'; 
     354$rcmail_config['message_sort_col'] = 'default'; 
    345355 
    346356// default sort order 
  • branches/devel-threads/index.php

    r3063 r3106  
    227227    'subscribe'     => 'manage_folders.inc', 
    228228    'unsubscribe'   => 'manage_folders.inc', 
     229    'enable-threading'  => 'manage_folders.inc', 
     230    'disable-threading' => 'manage_folders.inc', 
    229231    'add-identity'  => 'edit_identity.inc', 
    230232  ) 
  • branches/devel-threads/program/include/rcube_imap.php

    r3048 r3106  
    4949  var $list_page = 1; 
    5050  var $page_size = 10; 
    51   var $sort_field = 'date'; 
     51  var $sort_field = ''; 
    5252  var $sort_order = 'DESC'; 
    53   var $index_sort = true; 
    5453  var $delimiter = NULL; 
    55   var $caching_enabled = FALSE; 
     54  var $threading = false; 
     55  var $caching_enabled = false; 
    5656  var $default_charset = 'ISO-8859-1'; 
    5757  var $struct_charset = NULL; 
     
    6464  var $uid_id_map = array(); 
    6565  var $msg_headers = array(); 
    66   var $skip_deleted = FALSE; 
     66  var $skip_deleted = false; 
    6767  var $search_set = NULL; 
    6868  var $search_string = ''; 
    6969  var $search_charset = ''; 
    70   var $search_sort_field = '';   
     70  var $search_sort_field = ''; 
     71  var $search_threads = false; 
    7172  var $debug_level = 1; 
    7273  var $error_code = 0; 
     74  var $db_header_fields = array('idx', 'uid', 'subject', 'from', 'to', 'cc', 'date', 'size'); 
    7375  var $options = array('auth_method' => 'check'); 
    7476   
     
    309311   * @param  string  Sorting field 
    310312   */ 
    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) 
    312314    { 
    313315    if (is_array($str) && $msgs == null) 
     
    320322    $this->search_charset = $charset; 
    321323    $this->search_sort_field = $sort_field; 
     324    $this->search_threads = $threads; 
    322325    } 
    323326 
     
    329332  function get_search_set() 
    330333    { 
    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        ); 
    332340    } 
    333341 
     
    355363    { 
    356364    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; 
    357389    } 
    358390 
     
    490522    if (empty($mailbox)) 
    491523      $mailbox = $this->mailbox; 
    492        
     524 
    493525    // 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     
    497533    $a_mailbox_cache = $this->get_cache('messagecount'); 
    498534     
     
    501537      return $a_mailbox_cache[$mailbox][$mode]; 
    502538 
     539    if ($mode == 'THREADS') 
     540      $count = $this->_threadcount($mailbox); 
     541 
    503542    // RECENT count is fetched a bit different 
    504     if ($mode == 'RECENT') 
     543    else if ($mode == 'RECENT') 
    505544       $count = iil_C_CheckForRecent($this->conn, $mailbox); 
    506545 
     
    536575 
    537576    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);   
    538595    } 
    539596 
     
    573630      return $this->_list_header_set($mailbox, $page, $sort_field, $sort_order, $slice); 
    574631 
     632    if ($this->threading) 
     633      return $this->_list_thread_headers($mailbox, $page, $sort_field, $sort_order, $recursive, $slice); 
     634 
    575635    $this->_set_sort_order($sort_field, $sort_order); 
    576636 
     
    599659    $a_msg_headers = array(); 
    600660 
    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) 
    603663      { 
    604664        if ($this->skip_deleted) { 
     
    675735 
    676736  /** 
     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  /** 
    677872   * Private method for listing a set of message headers (search results) 
    678873   * 
     
    691886      return array(); 
    692887 
     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 
    693892    $msgs = $this->search_set; 
    694893    $a_msg_headers = array(); 
     
    698897    $this->_set_sort_order($sort_field, $sort_order); 
    699898 
    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) 
    702901      { 
    703902      if ($sort_order == 'DESC') 
     
    720919      return array_values($a_msg_headers); 
    721920      } 
     921 
    722922    // 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 result 
     923    if ($this->get_capability('sort')) // SORT searching result 
    724924      { 
    725925      // reset search set if sorting field has been changed 
     
    749949      return array_values($a_msg_headers); 
    750950      } 
    751     else { // SEARCH searching result, need sorting 
     951    else { // SEARCH result, need sorting 
    752952      $cnt = count($msgs); 
    753953      // 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) { 
    755955        // use memory less expensive (and quick) method for big result set 
    756956        $a_index = $this->message_index('', $this->sort_field, $this->sort_order); 
     
    791991        } 
    792992      } 
     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); 
    7931023    } 
    7941024 
     
    8911121      $this->cache[$key] = array(); 
    8921122       
    893       // use message index sort for sorting by Date 
    894       if ($this->index_sort && $this->sort_field == 'date') 
     1123      // use message index sort as default sorting 
     1124      if (!$this->sort_field) 
    8951125        { 
    8961126        $msgs = $this->search_set; 
     
    9431173      } 
    9441174 
    945     // use message index sort for sorting by Date 
    946     if ($this->index_sort && $this->sort_field == 'date') 
     1175    // use message index sort as default sorting 
     1176    if (!$this->sort_field) 
    9471177      { 
    9481178      if ($this->skip_deleted) { 
     
    10821312      } 
    10831313 
    1084     $this->set_search_set($str, $results, $charset, $sort_field); 
     1314    $this->set_search_set($str, $results, $charset, $sort_field, (bool) $this->threading); 
    10851315 
    10861316    return $results; 
     
    11021332      $criteria = 'UNDELETED '.$criteria; 
    11031333 
    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')) { 
    11051345      $charset = $charset ? $charset : $this->default_charset; 
    11061346      $a_messages = iil_C_Sort($this->conn, $mailbox, $sort_field, $criteria, FALSE, $charset); 
     
    11151355     
    11161356        // I didn't found that SEARCH always returns sorted IDs 
    1117         if ($this->index_sort && $this->sort_field == 'date') 
     1357        if (!$this->sort_field) 
    11181358          sort($a_messages); 
    11191359        } 
     
    11291369   
    11301370  /** 
     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  /** 
    11311471   * Refresh saved search set 
    11321472   * 
     
    11361476    { 
    11371477    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); 
    11391480       
    11401481    return $this->get_search_set(); 
     
    23652706    { 
    23662707    $cache_key = "$key:$from:$to:$sort_field:$sort_order"; 
    2367     $db_header_fields = array('idx', 'uid', 'subject', 'from', 'to', 'cc', 'date', 'size'); 
    23682708     
    23692709    $config = rcmail::get_instance()->config; 
    23702710 
    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)) { 
    23742713      $sort_field = 'idx'; 
    23752714      } 
     
    24482787      return $sa_message_index[$key]; 
    24492788 
    2450     // use idx sort for sorting by Date with index_sort=true 
    2451     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)) 
    24522791      $sort_field = 'idx'; 
    24532792     
  • branches/devel-threads/program/include/rcube_shared.inc

    r3063 r3106  
    682682 
    683683/** 
     684 * Get all keys from array (recursive) 
     685 *  
     686 * @param array Input array 
     687 * @return array 
     688 */ 
     689function 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/** 
    684704 * mbstring replacement functions 
    685705 */ 
  • branches/devel-threads/program/js/app.js

    r3059 r3106  
    163163        if (this.gui_objects.messagelist) 
    164164          { 
    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}); 
    166167          this.message_list.row_init = function(o){ p.init_message_row(o); }; 
    167168          this.message_list.addEventListener('dblclick', function(o){ p.msglist_dbl_click(o); }); 
     
    180181          else 
    181182            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); 
    182189          } 
    183            
     190 
    184191        if (this.env.coltypes) 
    185192          this.set_message_coltypes(this.env.coltypes); 
     
    244251          } 
    245252 
    246         if (this.env.messagecount) 
     253        if (this.env.messagecount) { 
    247254          this.enable_command('select-all', 'select-none', 'expunge', true); 
     255          this.enable_command('expand-all', 'collapse-all', this.env.threading); 
     256        } 
    248257 
    249258        if (this.purge_mailbox_test()) 
     
    334343        } 
    335344        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); 
    337346 
    338347        if (this.gui_objects.identitieslist) 
     
    425434      row.flagged = this.env.messages[uid].flagged ? true : false; 
    426435      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; 
    427441      } 
    428442 
     
    441455      var found; 
    442456      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); 
    444458      } 
    445459 
     
    454468       
    455469    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      } 
    456479  }; 
    457480 
     
    617640        var sort_order, sort_col = props; 
    618641 
    619         if (this.env.sort_col==sort_col) 
     642        if (this.env.sort_col==sort_col) 
    620643          sort_order = this.env.sort_order=='ASC' ? 'DESC' : 'ASC'; 
    621         else 
     644        else 
    622645          sort_order = 'ASC'; 
    623          
     646                      
    624647        // set table header class 
    625648        $('#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); 
    627651 
    628652        // save new sort properties 
     
    744768        // mail task 
    745769        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        } 
    747780        // addressbook task 
    748781        else if (this.task=='addressbook') 
     
    758791      case 'moveto': 
    759792        if (this.task == 'mail') 
     793        { 
    760794          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        } 
    761801        else if (this.task == 'addressbook' && this.drag_active) 
    762802          this.copy_contact(null, props); 
     
    846886      case 'select-none': 
    847887        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(); 
    848896        break; 
    849897 
     
    11091157        this.unsubscribe_folder(props); 
    11101158        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 
    11121168      case 'create-folder': 
    11131169        this.create_folder(props); 
     
    14431499      clearTimeout(this.preview_timer); 
    14441500 
    1445     var selected = list.selection.length==1; 
     1501    var selected = list.get_single_selection() != null; 
    14461502 
    14471503    // Hide certain command buttons when Drafts folder is selected 
     
    15471603        { 
    15481604        this.set_message(id, 'unread', false); 
     1605        this.update_parents(id, 'read'); 
    15491606        if (this.env.unread_counts[this.env.mailbox]) 
    15501607          { 
     
    17321789  }; 
    17331790 
     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 
    17341809  // set message icon 
    17351810  this.set_message_icon = function(uid) 
     
    17401815    if (!rows[uid]) 
    17411816      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) 
    17441821      icn_src = this.env.deletedicon; 
    17451822    else if (rows[uid].replied && this.env.repliedicon) 
     
    17891866      rows[uid].flagged = status; 
    17901867 
    1791     this.env.messages[uid] = rows[uid]; 
     1868//    this.env.messages[uid] = rows[uid]; 
    17921869    } 
    17931870 
     
    18741951 
    18751952    // if config is set to flag for deletion 
    1876     if (this.env.flag_for_deletion) 
     1953    if (this.env.flag_for_deletion) { 
    18771954      this.mark_message('delete'); 
     1955      return false; 
     1956      } 
    18781957    // if there isn't a defined trash mailbox or we are in it 
    18791958    else if (!this.env.trash_mailbox || String(this.env.mailbox).toLowerCase() == String(this.env.trash_mailbox).toLowerCase())  
     
    18901969        this.move_messages(this.env.trash_mailbox); 
    18911970      } 
     1971 
     1972    return true; 
    18921973  }; 
    18931974 
     
    19422023    var a_uids = new Array(); 
    19432024    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(); 
    19452026 
    19462027    if (uid) 
     
    20022083 
    20032084    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); 
    20042088  }; 
    20052089 
     
    21192203      } 
    21202204  }; 
    2121    
     2205 
    21222206   
    21232207  /*********************************************************/ 
     
    34693553      this.http_post('unsubscribe', '_mbox='+urlencode(folder)); 
    34703554    }; 
    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 
    34723569  // helper method to find a specific mailbox row ID 
    34733570  this.get_folder_row_id = function(folder) 
     
    37853882      { 
    37863883      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')) 
    37883885        { 
    37893886        // if we have links for sorting, it's a bit more complicated... 
     
    38003897        } 
    38013898      else if (col == 'subject' && this.message_list) 
    3802         this.message_list.subject_col = n+1; 
     3899        this.message_list.subject_col = n; 
    38033900      } 
    38043901  }; 
     
    38183915    var even = rowcount%2; 
    38193916     
    3820     this.env.messages[uid] = { 
     3917    var message = this.env.messages[uid] = { 
    38213918      deleted: flags.deleted?1:0, 
    38223919      replied: flags.replied?1:0, 
    38233920      unread: flags.unread?1:0, 
    38243921      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    } 
    38273928 
    38283929    var css_class = 'message' 
     
    38393940     
    38403941    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) 
    38423945      icon = this.env.deletedicon; 
    38433946    else if (flags.replied && this.env.repliedicon) 
     
    38523955    else if(flags.unread && this.env.unreadicon) 
    38533956      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;">&nbsp</div>'; 
     3970      if (message.has_children && !message.depth) 
     3971        tree += '<div id="rcmexpando' + uid + '" class="' + (message.expanded ? 'expanded' : 'collapsed') + '">&nbsp;</div>'; 
     3972      else 
     3973        tree += '<div class="leaf">&nbsp;</div>'; 
     3974      if (message.depth) 
     3975        row.style.display = 'none'; 
     3976      } 
     3977 
     3978    tree += icon ? '<img src="'+icon+'" alt="" />' : ''; 
     3979 
    38613980    // add each submitted col 
    38623981    for (var n = 0; n < this.coltypes.length; n++) { 
     
    38643983      col = document.createElement('td'); 
    38653984      col.className = String(c).toLowerCase(); 
    3866              
    3867       if (c=='flag') { 
     3985 
     3986      var html; 
     3987      if (c=='flag') 
     3988        { 
    38683989        if (flags.flagged && this.env.flaggedicon) 
    3869           col.innerHTML = '<img src="'+this.env.flaggedicon+'" alt="" />'; 
     3990          html = '<img src="'+this.env.flaggedicon+'" alt="" />'; 
    38703991        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      } 
    38733994      else if (c=='attachment') 
    3874         col.innerHTML = (attachment && this.env.attachmenticon ? '<img src="'+this.env.attachmenticon+'" alt="" />' : '&nbsp;'); 
     3995        html = attachment && this.env.attachmenticon ? '<img src="'+this.env.attachmenticon+'" alt="" />' : '&nbsp;'; 
    38753996      else 
    3876         col.innerHTML = cols[c]; 
     3997        html = cols[c]; 
     3998        if (n == 0) 
     3999          html = tree + html; 
     4000      col.innerHTML = html; 
    38774001 
    38784002      row.appendChild(col); 
     
    38884012      } 
    38894013    }; 
     4014 
    38904015 
    38914016  // messages list handling in background (for performance) 
     
    42384363          this.enable_command('show', 'reply', 'reply-all', 'forward', 'moveto', 'delete',  
    42394364            '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); 
    42414367        } 
    42424368        break; 
     
    42504376          this.enable_command('show', 'expunge', 'select-all', 'select-none', 'sort', (this.env.messagecount > 0)); 
    42514377          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 
    42534381          if (response.action == 'list') 
    42544382            this.triggerEvent('listupdate', { folder:this.env.mailbox, rowcount:this.message_list.rowcount }); 
     
    43814509}  // end object rcube_webmail 
    43824510 
    4383  
    43844511// copy event engine prototype 
    43854512rcube_webmail.prototype.addEventListener = rcube_event_engine.prototype.addEventListener; 
  • branches/devel-threads/program/js/list.js

    r3055 r3106  
    3838  this.shiftkey = false; 
    3939  this.multiselect = false; 
     40  this.multiexpand = false; 
    4041  this.multi_selecting = false; 
    4142  this.draggable = false; 
     
    7778    { 
    7879      row = this.list.tBodies[0].childNodes[r]; 
    79       while (row && (row.nodeType != 1 || row.style.display == 'none')) 
     80      while (row && row.nodeType != 1) 
    8081      { 
    8182        row = row.nextSibling; 
     
    320321 
    321322 
     323expand_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 
     350collapse: 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 
     373expand: 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 
     426collapse_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 
     468expand_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 
     507expand_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 
    322542/** 
    323543 * get first/next/previous/last rows that are not hidden 
     
    496716    if ((this.rows[n].obj.rowIndex >= i) && (this.rows[n].obj.rowIndex <= j)) 
    497717    { 
    498       if (!this.in_selection(n)) 
     718      if (!this.in_selection(n)) { 
    499719        this.highlight_row(n, true); 
     720      } 
    500721    } 
    501722    else 
    502723    { 
    503       if  (this.in_selection(n) && !control) 
     724      if  (this.in_selection(n) && !control) { 
    504725        this.highlight_row(n, true); 
     726      } 
    505727    } 
    506728  } 
     
    517739      return true; 
    518740 
    519   return false;     
     741  return false; 
    520742}, 
    521743 
     
    568790   
    569791  for (var n in this.rows) 
    570     this.highlight_row(n, true);     
     792    this.highlight_row(n, true); 
    571793 
    572794  // trigger event if selection changed 
     
    686908      rcube_event.cancel(e); 
    687909      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; 
    688920    default: 
    689921      this.shiftkey = e.shiftKey; 
     
    713945    case 63233: 
    714946    case 63232: 
     947    case 61: 
     948    case 107: 
     949    case 109: 
     950    case 32: 
    715951      if (!rcube_event.get_modifier(e) && this.focused) 
    716952        return rcube_event.cancel(e); 
     
    747983 
    748984/** 
     985 * Special handling method for +/- keys 
     986 */ 
     987use_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/** 
    7491015 * Try to scroll the list to make the specified row visible 
    7501016 */ 
     
    7801046      this.draglayer = $('<div>').attr('id', 'rcmdraglayer').css({ position:'absolute', display:'none', 'z-index':2000 }).appendTo(document.body); 
    7811047 
    782     // get subjects of selectedd messages 
     1048    // get subjects of selected messages 
    7831049    var names = ''; 
    784     var c, i, node, subject, obj; 
     1050    var c, i, subject, obj; 
    7851051    for(var n=0; n<this.selection.length; n++) 
    7861052    { 
     
    7911057      } 
    7921058 
    793       if (this.rows[this.selection[n]].obj) 
     1059      if (obj = this.rows[this.selection[n]].obj) 
    7941060      { 
    795         obj = this.rows[this.selection[n]].obj; 
    7961061        subject = ''; 
    7971062 
    798         for(c=0, i=0; i<obj.childNodes.length; i++) 
     1063        for (c=0, i=0; i<obj.childNodes.length; i++) 
    7991064        { 
    800           if (obj.childNodes[i].nodeName == 'TD') 
     1065          if (obj.childNodes[i].nodeName == 'TD') 
    8011066          { 
    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; 
    8101077              } 
     1078               
     1079              if (!node) 
     1080                break; 
     1081 
    8111082              subject = node.nodeType==3 ? node.data : node.innerHTML; 
    8121083              // remove leading spaces 
  • branches/devel-threads/program/lib/imap.inc

    r3018 r3106  
    174174        var $junk = false; 
    175175        var $flagged = false; 
     176        var $has_children = false; 
     177        var $depth = 0; 
     178        var $unread_children = 0; 
    176179        var $others = array(); 
    177 } 
    178  
    179 /** 
    180  * @todo Change class vars to public/private 
    181  */ 
    182 class iilThreadHeader 
    183 { 
    184         var $id; 
    185         var $sbj; 
    186         var $irt; 
    187         var $mid; 
    188180} 
    189181 
     
    865857        $fields = array('ARRIVAL' => 1,'CC' => 1,'DATE' => 1, 
    866858        'FROM' => 1, 'SIZE' => 1, 'SUBJECT' => 1, 'TO' => 1); 
    867          
     859 
    868860        if (!$fields[$field]) { 
    869861            return false; 
     
    877869        $is_uid = $is_uid ? 'UID ' : ''; 
    878870         
    879         if (!empty($add)) { 
     871        // message IDs 
     872        if (is_array($add)) 
     873                $add = iil_CompressMessageSet(join(',', $add)); 
     874 
     875        if (!empty($add)) 
    880876            $add = " $add"; 
    881         } 
    882877 
    883878        $command  = 's ' . $is_uid . 'SORT (' . $field . ') '; 
     
    909904function iil_C_FetchHeaderIndex(&$conn, $mailbox, $message_set, $index_field='', $skip_deleted=true) { 
    910905 
    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         
    917917        $index_field = empty($index_field) ? 'DATE' : strtoupper($index_field); 
    918918         
     
    10121012        } while (!iil_StartsWith($line, $key, true)); 
    10131013 
    1014 /* 
    1015         //check number of elements... 
    1016         if (is_numeric($from_idx) && is_numeric($to_idx)) { 
    1017                 //count how many we should have 
    1018                 $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 */ 
    10301014        return $result;  
    10311015} 
     
    11031087} 
    11041088 
    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 root  
    1277                                 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 root 
    1287                                         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 root 
    1300                         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 threads 
    1317         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 table 
    1345         $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 position 
    1353         $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 message 
    1357                         $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 id 
    1365                                 $pos                 = $index[$mid]; 
    1366                                 if ($pos&&$pos<$min) { 
    1367                                     $min = $index[$mid];        //find smallest position 
    1368                                 } 
    1369                         } 
    1370                         $n = $min;      //smallest position of child is thread position 
    1371                          
    1372                         //assign smallest position to root level key 
    1373                         //set children array to one created above 
    1374                         ksort($new_a); 
    1375                         $itree[$n] = $new_a; 
    1376                 } 
    1377         } 
    1378          
    1379         //sort by key, this basically sorts all threads 
    1380         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  
    14061089function iil_C_FetchHeaders(&$conn, $mailbox, $message_set, $uidfetch=false, $bodystr=false, $add='') 
    14071090{ 
     
    14161099                return false; 
    14171100        } 
     1101 
     1102        if (is_array($message_set)) 
     1103                $message_set = join(',', $message_set); 
    14181104 
    14191105        $message_set = iil_CompressMessageSet($message_set); 
     
    18581544} 
    18591545 
     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 
     1549function 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 
     1596function 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 
    18601627function iil_C_Search(&$conn, $folder, $criteria) { 
    18611628 
  • branches/devel-threads/program/localization/en_US/labels.inc

    r2983 r3106  
    5757$labels['mailboxlist'] = 'Folders'; 
    5858$labels['messagesfromto'] = 'Messages $from to $to of $count'; 
     59$labels['threadsfromto'] = 'Threads $from to $to of $count'; 
    5960$labels['messagenrof'] = 'Message $nr of $count'; 
    6061 
     
    154155$labels['invert'] = 'Invert'; 
    155156$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'; 
    156166 
    157167$labels['compact'] = 'Compact'; 
     
    319329$labels['subscribed']  = 'Subscribed'; 
    320330$labels['messagecount'] = 'Messages'; 
     331$labels['threaded'] = 'Threaded'; 
    321332$labels['create']  = 'Create'; 
    322333$labels['createfolder']  = 'Create new folder'; 
  • branches/devel-threads/program/steps/mail/check_recent.inc

    r2962 r3106  
    2929      if (($search_request = get_input_value('_search', RCUBE_INPUT_GPC)) && isset($_SESSION['search'][$search_request])) { 
    3030        $_SESSION['search'][$search_request] = $IMAP->refresh_search(); 
    31         $all_count = $IMAP->messagecount(); 
     31        $all_count = $IMAP->messagecount(NULL, $IMAP->threading ? 'THREADS' : 'ALL'); 
    3232      } else { 
    33         $all_count = $IMAP->messagecount(NULL, 'ALL', TRUE); 
     33        $all_count = $IMAP->messagecount(NULL, $IMAP->threading ? 'THREADS' : 'ALL', TRUE); 
    3434      } 
    3535       
     
    5656        continue; 
    5757 
    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; 
    6270 
    63       $result = $IMAP->search($mbox_name, $search_str, NULL, 'date'); 
     71          $result = $IMAP->search($mbox_name, $search_str, NULL, 'date'); 
    6472 
    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          } 
    7079      } 
    7180    } 
  • branches/devel-threads/program/steps/mail/func.inc

    r3058 r3106  
    5353  $_SESSION['sort_order'] = $CONFIG['message_sort_order']; 
    5454 
     55// enable threads mode 
     56$a_message_threading = $RCMAIL->config->get('message_threading', array()); 
     57$IMAP->set_threading($a_message_threading[$_SESSION['mbox']]); 
     58 
    5559// set message set for search result 
    5660if (!empty($_REQUEST['_search']) && isset($_SESSION['search'][$_REQUEST['_search']])) 
     
    8084      $OUTPUT->set_env('search_mods', $_SESSION['search_mods'] ? $_SESSION['search_mods'] : array('subject'=>'subject')); 
    8185      // 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); 
    8387    } 
    8488         
     
    8791  $OUTPUT->set_env('quota', $IMAP->get_capability('quota')); 
    8892  $OUTPUT->set_env('delimiter', $IMAP->get_hierarchy_delimiter()); 
     93  $OUTPUT->set_env('threading', (bool) $IMAP->threading); 
    8994 
    9095  if ($CONFIG['flag_for_deletion']) 
     
    120125  $skin_path = $CONFIG['skin_path']; 
    121126  $image_tag = '<img src="%s%s" alt="%s" />'; 
    122  
    123127  // check to see if we have some settings for sorting 
    124128  $sort_col   = $_SESSION['sort_col']; 
     
    162166  // add col definition 
    163167  $out .= '<colgroup>'; 
    164   $out .= '<col class="icon" />'; 
    165168 
    166169  foreach ($a_show_cols as $col) 
     
    170173 
    171174  // add table title 
    172   $out .= "<thead><tr>\n<td class=\"icon\">&nbsp;</td>\n"; 
     175  $out .= "<thead><tr>\n"; 
    173176 
    174177  $javascript = ''; 
     
    234237 
    235238    // 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      } 
    238256    else     
    239257      $out .= '<td class="icon" id="rcm'.$col.'">' . "$col_name$sort</td>\n"; 
     
    266284    if ($header->flagged) 
    267285      $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) 
    271299      $message_icon = $attrib['deletedicon']; 
    272300    else if ($attrib['repliedicon'] && $header->answered) 
     
    293321      $attach_icon = $attrib['attachmenticon']; 
    294322         
    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", 
    296324                    $header->uid, 
    297325                    $header->seen ? '' : ' unread', 
    298326                    $header->deleted ? ' deleted' : '', 
    299327                    $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;">&nbsp</div>'; 
     342      if ($header->has_children && !$header->depth) 
     343        $tree .= '<div id="rcmexpando' . $header->uid . '" class="collapsed">&nbsp;</div>'; 
     344      else 
     345        $tree .= '<div class="leaf">&nbsp;</div>'; 
     346      } 
     347 
     348    $tree .= $message_icon ? sprintf($image_tag, $skin_path, $message_icon, '') : ''; 
    303349 
    304350    $IMAP->set_charset(!empty($header->charset) ? $header->charset : $CONFIG['default_charset']); 
    305351   
    306352    // format each col 
     353    $first = true; 
    307354    foreach ($a_show_cols as $col) 
    308355      { 
     
    326373        $cont = Q($header->$col); 
    327374         
     375      if ($first) { 
     376        $first = false; 
     377        $cont = $tree . $cont; 
     378      } 
    328379      if ($col!='attachment') 
    329380        $out .= '<td class="'.$col.'">' . $cont . "</td>\n"; 
     
    341392  $out .= "</tbody></table>\n"; 
    342393   
    343   $message_count = $IMAP->messagecount(); 
     394  $message_count = $IMAP->messagecount(NULL, $IMAP->threading ? 'THREADS' : 'ALL'); 
    344395   
    345396  // set client env 
    346397  $OUTPUT->add_gui_object('mailcontframe', 'mailcontframe'); 
    347398  $OUTPUT->add_gui_object('messagelist', $attrib['id']); 
     399  $OUTPUT->set_env('autoexpand_threads', $CONFIG['autoexpand_threads']); 
    348400  $OUTPUT->set_env('messagecount', $message_count); 
    349401  $OUTPUT->set_env('current_page', $IMAP->list_page); 
     
    370422  if ($attrib['unflaggedicon']) 
    371423    $OUTPUT->set_env('unflaggedicon', $skin_path . $attrib['unflaggedicon']); 
     424  if ($attrib['unreadchildrenicon']) 
     425    $OUTPUT->set_env('unreadchildrenicon', $skin_path . $attrib['unreadchildrenicon']); 
    372426   
    373427  $OUTPUT->set_env('messages', $a_js_message_arr); 
     
    413467  if ($browser->ie && $replace) 
    414468    $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]); 
    415475 
    416476  // loop through message headers 
     
    448508      } 
    449509 
     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; 
    450518    if ($header->deleted) 
    451519      $a_msg_flags['deleted'] = 1; 
     
    575643    { 
    576644    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 
    579647    } 
    580648 
     
    583651     
    584652  $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'); 
    586654 
    587655  if ($max==0) 
    588656    $out = rcube_label('mailboxempty'); 
    589657  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))); 
    594662 
    595663  return Q($out); 
  • branches/devel-threads/program/steps/mail/list.inc

    r2983 r3106  
    5656 
    5757// fetch message headers 
    58 if ($count = $IMAP->messagecount($mbox_name, 'ALL', !empty($_REQUEST['_refresh']))) 
     58if ($count = $IMAP->messagecount($mbox_name, $IMAP->threading ? 'THREADS' : 'ALL', !empty($_REQUEST['_refresh']))) 
    5959  $a_headers = $IMAP->list_headers($mbox_name, NULL, $sort_col, $sort_order); 
    6060 
     
    6666$OUTPUT->set_env('messagecount', $count); 
    6767$OUTPUT->set_env('pagecount', $pages); 
     68$OUTPUT->set_env('threading', (bool) $IMAP->threading); 
    6869$OUTPUT->command('set_rowcount', rcmail_get_messagecount_text($count)); 
    6970$OUTPUT->command('set_mailboxname', rcmail_get_mailbox_name_text()); 
     
    8182  $OUTPUT->show_message('nomessagesfound', 'notice'); 
    8283 
     84// deal with threaded view 
     85if ($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 
    8399// send response 
    84100$OUTPUT->send(); 
  • branches/devel-threads/program/steps/mail/move_del.inc

    r2960 r3106  
    2525 
    2626// count messages before changing anything 
    27 $old_count = $IMAP->messagecount(); 
     27$old_count = $IMAP->messagecount(NULL, $IMAP->threading ? 'THREADS' : 'ALL'); 
    2828$old_pages = ceil($old_count / $IMAP->page_size); 
    2929 
     
    8383else 
    8484{ 
    85   $msg_count      = $IMAP->messagecount(); 
     85  $msg_count      = $IMAP->messagecount(NULL, $IMAP->threading ? 'THREADS' : 'ALL'); 
    8686  $pages          = ceil($msg_count / $IMAP->page_size); 
    8787  $nextpage_count = $old_count - $IMAP->page_size * $IMAP->list_page; 
     
    123123 
    124124    $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); 
    125130 
    126131    rcmail_js_message_list($a_headers, false, false); 
  • branches/devel-threads/program/steps/mail/search.inc

    r2949 r3106  
    100100// Get the headers 
    101101$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'); 
    103103 
    104104// save search results in session 
     
    116116  rcmail_js_message_list($result_h); 
    117117  if ($search_str) 
    118     $OUTPUT->show_message('searchsuccessful', 'confirmation', array('nr' => $count)); 
     118    $OUTPUT->show_message('searchsuccessful', 'confirmation', array('nr' => $IMAP->messagecount(NULL, 'ALL'))); 
    119119} 
    120120else 
  • branches/devel-threads/program/steps/settings/func.inc

    r3055 r3106  
    312312    } 
    313313 
     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 
    314327    if (!isset($no_override['focus_on_new_message'])) { 
    315328      $field_id = 'rcmfd_focus_on_new_message'; 
  • branches/devel-threads/program/steps/settings/manage_folders.inc

    r2505 r3106  
    3939  } 
    4040 
     41// enable threading for one or more mailboxes 
     42else 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 
     53else 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 
    4163// create a new mailbox 
    4264else if ($RCMAIL->action=='create-folder') 
     
    160182function rcube_subscription_form($attrib) 
    161183  { 
    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'); 
    163189 
    164190  list($form_start, $form_end) = get_form_tags($attrib, 'folders'); 
     
    174200  $table->add_header('msgcount', rcube_label('messagecount')); 
    175201  $table->add_header('subscribed', rcube_label('subscribed')); 
     202  if ($threading_supported) 
     203    $table->add_header('threaded', rcube_label('threaded')); 
    176204  $table->add_header('rename', '&nbsp;'); 
    177205  $table->add_header('delete', '&nbsp;'); 
    178206 
    179  
    180207  // get folders from server 
    181208  $IMAP->clear_cache('mailboxes'); 
     
    183210  $a_unsubscribed = $IMAP->list_unsubscribed(); 
    184211  $a_subscribed = $IMAP->list_mailboxes(); 
     212  $a_threaded = $RCMAIL->config->get('message_threading', array());  
    185213  $delimiter = $IMAP->get_hierarchy_delimiter(); 
    186214  $a_js_folders = $seen_folders = $list_folders = array(); 
     
    212240    'onclick' => JS_OBJECT_NAME.".command(this.checked?'subscribe':'unsubscribe',this.value)", 
    213241  )); 
     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  )); 
    214246   
    215247  if (!empty($attrib['deleteicon'])) 
     
    227259    $idx = $i + 1; 
    228260    $subscribed = in_array($folder['id'], $a_subscribed); 
     261    $threaded = $a_threaded[$folder['id']]; 
    229262    $protected = ($CONFIG['protect_default_folders'] == true && in_array($folder['id'], $CONFIG['default_imap_folders'])); 
    230263    $classes = array($i%2 ? 'even' : 'odd'); 
     
    239272     
    240273    $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? 
    242275    $table->add('subscribed', ($protected || $folder['virtual']) ? ($subscribed ? '&nbsp;&#x2022;' : '&nbsp;') : 
    243276        $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    } 
    244281     
    245282    // add rename and delete buttons 
  • branches/devel-threads/program/steps/settings/save_prefs.inc

    r2983 r3106  
    4343      'focus_on_new_message' => isset($_POST['_focus_on_new_message']) ? TRUE : FALSE, 
    4444      'preview_pane'         => isset($_POST['_preview_pane']) ? TRUE : FALSE, 
     45      'autoexpand_threads'   => isset($_POST['_autoexpand_threads']) ? intval($_POST['_autoexpand_threads']) : 0, 
    4546      'mdn_requests'         => isset($_POST['_mdn_requests']) ? intval($_POST['_mdn_requests']) : 0, 
    4647      'keep_alive'           => isset($_POST['_keep_alive']) ? intval($_POST['_keep_alive'])*60 : $CONFIG['keep_alive'], 
  • branches/devel-threads/skins/default/functions.js

    r3060 r3106  
    125125  this.searchmenu = $('#searchmenu'); 
    126126  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() }; 
    127132} 
    128133 
     
    184189}, 
    185190 
     191show_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 
    186204body_mouseup: function(evt, p) 
    187205{ 
     
    190208  else if (this.messagemenu && this.messagemenu.is(':visible') && rcube_event.get_target(evt) != rcube_find_object('messagemenulink')) 
    191209    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  } 
    192220  else if (this.searchmenu && this.searchmenu.is(':visible') && rcube_event.get_target(evt) != rcube_find_object('searchmod')) { 
    193221    var menu = rcube_find_object('searchmenu'); 
     
    211239    if (this.messagemenu && this.messagemenu.is(':visible')) 
    212240      this.show_messagemenu(false); 
     241    if (this.listmenu && this.listmenu.is(':visible')) 
     242      this.show_listmenu(false); 
    213243  } 
    214244} 
  • branches/devel-threads/skins/default/mail.css

    r3058 r3106  
    167167#markmessagemenu, 
    168168#searchmenu, 
    169 #messagemenu 
     169#messagemenu, 
     170#listmenu 
    170171{ 
    171172  position: absolute; 
     
    627628} 
    628629 
     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 
    629646#countcontrols 
    630647{ 
     
    750767} 
    751768 
     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 
    752799#messagelist tbody tr td.flag img:hover, 
    753800#messagelist thead tr td.flag img 
  • branches/devel-threads/skins/default/templates/mail.html

    r3058 r3106  
    6060  attachmentIcon="/images/icons/attachment.png" 
    6161  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" /> 
    6365</div> 
    6466 
     
    8385      <roundcube:button command="select-all" type="link" prop="invert" title="invert" class="buttonPas invert" classAct="button invert" classSel="button invertsel" content=" " /> 
    8486      <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" />:&nbsp;</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=" " /> 
    8590      <roundcube:container name="listcontrols" id="listcontrols" /> 
    8691  <roundcube:if condition="env:quota" /> 
     
    147152</div> 
    148153 
     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 
    149189</body> 
    150190</html> 
Note: See TracChangeset for help on using the changeset viewer.