Changeset 3367 in subversion


Ignore:
Timestamp:
Mar 17, 2010 8:24:09 AM (3 years ago)
Author:
thomasb
Message:

Merged devel-threads branch (r3066:3364) back into trunk

Location:
trunk/roundcubemail
Files:
38 edited
3 copied

Legend:

Unmodified
Added
Removed
  • trunk/roundcubemail

  • trunk/roundcubemail/CHANGELOG

    r3359 r3367  
    22=========================== 
    33 
     4- Threaded message listing now available 
     5- Added sorting by ARRIVAL and CC 
     6- Message list columns configurable by the user 
     7- Removed 'index_sort' option, now we're using empty 'message_sort_col' for this 
    48- virtuser_query: support other identity data (#1486148) 
    59- Options virtuser_* replaced with virtuser_* plugins 
  • trunk/roundcubemail/THREADS

    r3364 r3367  
    1212    - on deleting messages the whole list isn't refreshed 
    1313    - added 'expand unread' button 
    14  
    15 CHANGES IN RELATION TO TRUNK (for pasting into CHANGELOG after merge) 
    16     - removed 'index_sort' option, now we're using empty 'message_sort_col' for 
    17       this purpose 
    18     - popup menu for messages list column/sorting/view selection 
    19     - added sorting by ARRIVAL 
    20     - added sorting by CC 
    2114 
    2215TODO (must have): 
     
    4538KNOWN ISSUES: 
    4639    - on new message (check_recent) the whole list is reloaded 
    47     - table header replacement doesn't work on IE 
     40    + table header replacement doesn't work on IE 
    4841    - css issues on IE6 
    4942    + css issues on IE7 
  • trunk/roundcubemail/bin/msgexport.sh

    r2229 r3367  
    9090                 
    9191                fwrite($out, sprintf("From %s %s UID %d\n", $from['mailto'], $headers->date, $headers->uid)); 
    92                 fwrite($out, iil_C_FetchPartHeader($IMAP->conn, $IMAP->mailbox, $i, null)); 
    93                 fwrite($out, iil_C_HandlePartBody($IMAP->conn, $IMAP->mailbox, $i, null, 1)); 
     92                fwrite($out, iil_C_FetchPartHeader($IMAP->conn, $mbox, $i, null)); 
     93                fwrite($out, iil_C_HandlePartBody($IMAP->conn, $mbox, $i, null, 1)); 
    9494                fwrite($out, "\n\n\n"); 
    9595                 
  • trunk/roundcubemail/config/main.inc.php.dist

    r3352 r3367  
    233233// ---------------------------------- 
    234234 
    235 // default sort col 
    236 $rcmail_config['message_sort_col'] = 'date'; 
    237  
    238 // default sort order 
     235// default messages sort column. Use empty value for default server's sorting,  
     236// or 'arrival', 'date', 'subject', 'from', 'to', 'size', 'cc' 
     237$rcmail_config['message_sort_col'] = ''; 
     238 
     239// default messages sort order 
    239240$rcmail_config['message_sort_order'] = 'DESC'; 
    240241 
     
    462463$rcmail_config['display_next'] = false; 
    463464 
    464 // If true, messages list will be sorted by message index instead of message date 
    465 $rcmail_config['index_sort'] = true; 
     465// 0 - Do not expand threads  
     466// 1 - Expand all threads automatically  
     467// 2 - Expand only threads with unread messages  
     468$rcmail_config['autoexpand_threads'] = 0; 
    466469 
    467470// When replying place cursor above original message (top posting) 
  • trunk/roundcubemail/index.php

    r3296 r3367  
    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  ) 
  • trunk/roundcubemail/program/include/html.php

    r3212 r3367  
    66 |                                                                       | 
    77 | This file is part of the RoundCube Webmail client                     | 
    8  | Copyright (C) 2005-2009, RoundCube Dev, - Switzerland                 | 
     8 | Copyright (C) 2005-2010, RoundCube Dev, - Switzerland                 | 
    99 | Licensed under the GNU GPL                                            | 
    1010 |                                                                       | 
     
    3535    public static $lc_tags = true; 
    3636    public static $common_attrib = array('id','class','style','title','align'); 
    37     public static $containers = array('iframe','div','span','p','h1','h2','h3', 
    38         'form','textarea','table','tr','th','td','style','script'); 
     37    public static $containers = array('iframe','div','span','p','h1','h2','h3','form','textarea','table','thead','tbody','tr','th','td','style','script'); 
    3938 
    4039    /** 
  • trunk/roundcubemail/program/include/rcmail.php

    r3302 r3367  
    411411    $this->imap->debug_level = $this->config->get('debug_level'); 
    412412    $this->imap->skip_deleted = $this->config->get('skip_deleted'); 
    413     $this->imap->index_sort = $this->config->get('index_sort', true); 
    414413 
    415414    // enable caching of imap data 
  • trunk/roundcubemail/program/include/rcube_imap.php

    r3358 r3367  
    66 |                                                                       | 
    77 | This file is part of the RoundCube Webmail client                     | 
    8  | Copyright (C) 2005-2009, RoundCube Dev. - Switzerland                 | 
     8 | Copyright (C) 2005-2010, RoundCube Dev. - Switzerland                 | 
    99 | Licensed under the GNU GPL                                            | 
    1010 |                                                                       | 
     
    4242class rcube_imap 
    4343{ 
    44   var $db; 
    45   var $conn; 
    46   var $root_dir = ''; 
    47   var $mailbox = 'INBOX'; 
    48   var $list_page = 1; 
    49   var $page_size = 10; 
    50   var $sort_field = 'date'; 
    51   var $sort_order = 'DESC'; 
    52   var $index_sort = true; 
    53   var $delimiter = NULL; 
    54   var $caching_enabled = FALSE; 
    55   var $default_charset = 'ISO-8859-1'; 
    56   var $struct_charset = NULL; 
    57   var $default_folders = array('INBOX'); 
    58   var $fetch_add_headers = ''; 
    59   var $cache = array(); 
    60   var $cache_keys = array();   
    61   var $cache_changes = array(); 
    62   var $uid_id_map = array(); 
    63   var $msg_headers = array(); 
    64   var $skip_deleted = FALSE; 
    65   var $search_set = NULL; 
    66   var $search_string = ''; 
    67   var $search_charset = ''; 
    68   var $search_sort_field = '';   
    69   var $debug_level = 1; 
    70   var $error_code = 0; 
    71   var $options = array('auth_method' => 'check'); 
    72    
     44  public $debug_level = 1; 
     45  public $error_code = 0; 
     46  public $skip_deleted = false; 
     47  public $root_dir = ''; 
     48  public $page_size = 10; 
     49  public $list_page = 1; 
     50  public $delimiter = NULL; 
     51  public $threading = false; 
     52  public $fetch_add_headers = ''; 
     53  public $conn; 
     54 
     55  private $db; 
     56  private $root_ns = ''; 
     57  private $mailbox = 'INBOX'; 
     58  private $sort_field = ''; 
     59  private $sort_order = 'DESC'; 
     60  private $caching_enabled = false; 
     61  private $default_charset = 'ISO-8859-1'; 
     62  private $struct_charset = NULL; 
     63  private $default_folders = array('INBOX'); 
     64  private $default_folders_lc = array('inbox'); 
     65  private $icache = array(); 
     66  private $cache = array(); 
     67  private $cache_keys = array();   
     68  private $cache_changes = array(); 
     69  private $uid_id_map = array(); 
     70  private $msg_headers = array(); 
     71  public  $search_set = NULL; 
     72  public  $search_string = ''; 
     73  private $search_charset = ''; 
     74  private $search_sort_field = ''; 
     75  private $search_threads = false; 
     76  private $db_header_fields = array('idx', 'uid', 'subject', 'from', 'to', 'cc', 'date', 'size'); 
     77  private $options = array('auth_method' => 'check'); 
    7378  private $host, $user, $pass, $port, $ssl; 
    7479 
     
    300305   * @param  string  Sorting field 
    301306   */ 
    302   function set_search_set($str=null, $msgs=null, $charset=null, $sort_field=null) 
     307  function set_search_set($str=null, $msgs=null, $charset=null, $sort_field=null, $threads=false) 
    303308    { 
    304309    if (is_array($str) && $msgs == null) 
    305       list($str, $msgs, $charset, $sort_field) = $str; 
     310      list($str, $msgs, $charset, $sort_field, $threads) = $str; 
    306311    if ($msgs != null && !is_array($msgs)) 
    307312      $msgs = explode(',', $msgs); 
    308        
     313 
    309314    $this->search_string = $str; 
    310315    $this->search_set = $msgs; 
    311316    $this->search_charset = $charset; 
    312317    $this->search_sort_field = $sort_field; 
     318    $this->search_threads = $threads; 
    313319    } 
    314320 
     
    320326  function get_search_set() 
    321327    { 
    322     return array($this->search_string, $this->search_set, $this->search_charset, $this->search_sort_field); 
     328    return array($this->search_string, 
     329        $this->search_set, 
     330        $this->search_charset, 
     331        $this->search_sort_field, 
     332        $this->search_threads, 
     333        ); 
    323334    } 
    324335 
     
    346357    { 
    347358    return iil_C_GetCapability($this->conn, strtoupper($cap)); 
     359    } 
     360 
     361 
     362  /** 
     363   * Sets threading flag to the best supported THREAD algorithm 
     364   * 
     365   * @param  boolean  TRUE to enable and FALSE 
     366   * @return string   Algorithm or false if THREAD is not supported 
     367   * @access public 
     368   */ 
     369  function set_threading($enable=false) 
     370    { 
     371    $this->threading = false; 
     372     
     373    if ($enable) { 
     374      if ($this->get_capability('THREAD=REFS')) 
     375        $this->threading = 'REFS'; 
     376      else if ($this->get_capability('THREAD=REFERENCES')) 
     377        $this->threading = 'REFERENCES'; 
     378      else if ($this->get_capability('THREAD=ORDEREDSUBJECT')) 
     379        $this->threading = 'ORDEREDSUBJECT'; 
     380      } 
     381       
     382    return $this->threading; 
    348383    } 
    349384 
     
    481516    if (empty($mailbox)) 
    482517      $mailbox = $this->mailbox; 
    483        
     518 
    484519    // count search set 
    485     if ($this->search_string && $mailbox == $this->mailbox && $mode == 'ALL' && !$force) 
    486       return count((array)$this->search_set); 
    487  
     520    if ($this->search_string && $mailbox == $this->mailbox && ($mode == 'ALL' || $mode == 'THREADS') && !$force) { 
     521      if ($this->search_threads) 
     522        return $mode == 'ALL' ? count((array)$this->search_set['depth']) : count((array)$this->search_set['tree']); 
     523      else 
     524        return count((array)$this->search_set); 
     525      } 
     526     
    488527    $a_mailbox_cache = $this->get_cache('messagecount'); 
    489528     
     
    495534      $a_mailbox_cache[$mailbox] = array(); 
    496535 
     536    if ($mode == 'THREADS') 
     537      $count = $this->_threadcount($mailbox); 
     538 
    497539    // RECENT count is fetched a bit different 
    498     if ($mode == 'RECENT') 
     540    else if ($mode == 'RECENT') 
    499541       $count = iil_C_CheckForRecent($this->conn, $mailbox); 
    500542 
     
    531573 
    532574    return (int)$count; 
     575    } 
     576 
     577 
     578  /** 
     579   * Private method for getting nr of threads 
     580   * 
     581   * @access  private 
     582   * @see     rcube_imap::messagecount() 
     583   */ 
     584  private function _threadcount($mailbox) 
     585    { 
     586    if (!empty($this->icache['threads'])) 
     587      return count($this->icache['threads']['tree']); 
     588     
     589    list ($thread_tree, $msg_depth, $has_children) = $this->_fetch_threads($mailbox); 
     590 
     591//    $this->update_thread_cache($mailbox, $thread_tree, $msg_depth, $has_children); 
     592    return count($thread_tree);   
    533593    } 
    534594 
     
    568628      return $this->_list_header_set($mailbox, $page, $sort_field, $sort_order, $slice); 
    569629 
     630    if ($this->threading) 
     631      return $this->_list_thread_headers($mailbox, $page, $sort_field, $sort_order, $recursive, $slice); 
     632 
    570633    $this->_set_sort_order($sort_field, $sort_order); 
    571634 
     
    594657    $a_msg_headers = array(); 
    595658 
    596     // use message index sort for sorting by Date (for better performance) 
    597     if ($this->index_sort && $this->sort_field == 'date') 
     659    // use message index sort as default sorting (for better performance) 
     660    if (!$this->sort_field) 
    598661      { 
    599662        if ($this->skip_deleted) { 
     
    672735 
    673736  /** 
     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, $slice); 
     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->icache['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->icache['threads'] = array(); 
     779      $this->icache['threads']['tree'] = $thread_tree; 
     780      $this->icache['threads']['depth'] = $msg_depth; 
     781      $this->icache['threads']['has_children'] = $has_children; 
     782      } 
     783 
     784    return array( 
     785      $this->icache['threads']['tree'], 
     786      $this->icache['threads']['depth'], 
     787      $this->icache['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    list($begin, $end) = $this->_get_message_range(count($msg_index), $page); 
     802    $msg_index = array_slice($msg_index, $begin, $end-$begin); 
     803 
     804    if ($slice) 
     805      $msg_index = array_slice($msg_index, ($this->sort_order == 'DESC' ? 0 : -$slice), $slice); 
     806 
     807    if ($this->sort_order == 'DESC') 
     808      $msg_index = array_reverse($msg_index); 
     809 
     810    // flatten threads array 
     811    // @TODO: fetch children only in expanded mode 
     812    $all_ids = array(); 
     813    foreach($msg_index as $root) { 
     814      $all_ids[] = $root; 
     815      if (!empty($thread_tree[$root])) 
     816        $all_ids = array_merge($all_ids, array_keys_recursive($thread_tree[$root])); 
     817      } 
     818 
     819    // fetch reqested headers from server 
     820    $this->_fetch_headers($mailbox, $all_ids, $a_msg_headers, $cache_key); 
     821 
     822    // return empty array if no messages found 
     823    if (!is_array($a_msg_headers) || empty($a_msg_headers)) 
     824      return array(); 
     825     
     826    // use this class for message sorting 
     827    $sorter = new rcube_header_sorter(); 
     828    $sorter->set_sequence_numbers($all_ids); 
     829    $sorter->sort_headers($a_msg_headers); 
     830 
     831    // Set depth, has_children and unread_children fields in headers 
     832    $this->_set_thread_flags($a_msg_headers, $msg_depth, $has_children); 
     833 
     834    return array_values($a_msg_headers); 
     835    } 
     836 
     837 
     838  /** 
     839   * Private method for setting threaded messages flags: 
     840   * depth, has_children and unread_children 
     841   * 
     842   * @param  array   Reference to headers array indexed by message ID 
     843   * @param  array   Array of messages depth indexed by message ID 
     844   * @param  array   Array of messages children flags indexed by message ID 
     845   * @return array   Message headers array indexed by message ID 
     846   * @access private 
     847   */ 
     848  private function _set_thread_flags(&$headers, $msg_depth, $msg_children) 
     849    { 
     850    $parents = array(); 
     851 
     852    foreach ($headers as $idx => $header) { 
     853      $id = $header->id; 
     854      $depth = $msg_depth[$id]; 
     855      $parents = array_slice($parents, 0, $depth); 
     856 
     857      if (!empty($parents)) { 
     858        $headers[$idx]->parent_uid = end($parents); 
     859        if (!$header->seen) 
     860          $headers[$parents[0]]->unread_children++; 
     861        } 
     862      array_push($parents, $header->uid); 
     863 
     864      $headers[$idx]->depth = $depth; 
     865      $headers[$idx]->has_children = $msg_children[$id]; 
     866      } 
     867    } 
     868 
     869 
     870  /** 
    674871   * Private method for listing a set of message headers (search results) 
    675872   * 
     
    688885      return array(); 
    689886 
     887    // use saved messages from searching 
     888    if ($this->threading) 
     889      return $this->_list_thread_header_set($mailbox, $page, $sort_field, $sort_order, $slice); 
     890 
     891    // search set is threaded, we need a new one 
     892    if ($this->search_threads) 
     893      $this->search('', $this->search_string, $this->search_charset, $sort_field); 
     894     
    690895    $msgs = $this->search_set; 
    691896    $a_msg_headers = array(); 
     
    695900    $this->_set_sort_order($sort_field, $sort_order); 
    696901 
    697     // quickest method 
    698     if ($this->index_sort && $this->search_sort_field == 'date' && $this->sort_field == 'date') 
     902    // quickest method (default sorting) 
     903    if (!$this->search_sort_field && !$this->sort_field) 
    699904      { 
    700905      if ($sort_order == 'DESC') 
     
    717922      return array_values($a_msg_headers); 
    718923      } 
     924 
    719925    // sorted messages, so we can first slice array and then fetch only wanted headers 
    720     if ($this->get_capability('sort') && (!$this->index_sort || $this->sort_field != 'date')) // SORT searching result 
     926    if ($this->get_capability('sort')) // SORT searching result 
    721927      { 
    722928      // reset search set if sorting field has been changed 
     
    746952      return array_values($a_msg_headers); 
    747953      } 
    748     else { // SEARCH searching result, need sorting 
     954    else { // SEARCH result, need sorting 
    749955      $cnt = count($msgs); 
    750956      // 300: experimantal value for best result 
    751       if (($cnt > 300 && $cnt > $this->page_size) || ($this->index_sort && $this->sort_field == 'date')) { 
     957      if (($cnt > 300 && $cnt > $this->page_size) || !$this->sort_field) { 
    752958        // use memory less expensive (and quick) method for big result set 
    753959        $a_index = $this->message_index('', $this->sort_field, $this->sort_order); 
     
    788994        } 
    789995      } 
     996    } 
     997 
     998 
     999  /** 
     1000   * Private method for listing a set of threaded message headers (search results) 
     1001   * 
     1002   * @param   string   Mailbox/folder name 
     1003   * @param   int      Current page to list 
     1004   * @param   string   Header field to sort by 
     1005   * @param   string   Sort order [ASC|DESC] 
     1006   * @param   boolean  Number of slice items to extract from result array 
     1007   * @return  array    Indexed array with message header objects 
     1008   * @access  private 
     1009   * @see     rcube_imap::list_header_set() 
     1010   */ 
     1011  private function _list_thread_header_set($mailbox, $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0) 
     1012    { 
     1013    // update search_set if previous data was fetched with disabled threading 
     1014    if (!$this->search_threads) 
     1015      $this->search('', $this->search_string, $this->search_charset, $sort_field); 
     1016 
     1017    $thread_tree = $this->search_set['tree']; 
     1018    $msg_depth = $this->search_set['depth']; 
     1019    $has_children = $this->search_set['children']; 
     1020    $a_msg_headers = array(); 
     1021 
     1022    $page = $page ? $page : $this->list_page; 
     1023    $start_msg = ($page-1) * $this->page_size; 
     1024 
     1025    $this->_set_sort_order($sort_field, $sort_order); 
     1026 
     1027    $msg_index = $this->_sort_threads($mailbox, $thread_tree, array_keys($msg_depth)); 
     1028 
     1029    return $this->_fetch_thread_headers($mailbox, $thread_tree, $msg_depth, $has_children, $msg_index, $page, $slice=0); 
    7901030    } 
    7911031 
     
    9021142  function message_index($mbox_name='', $sort_field=NULL, $sort_order=NULL) 
    9031143    { 
     1144    if ($this->threading) 
     1145      return $this->thread_index($mbox_name, $sort_field, $sort_order); 
     1146 
    9041147    $this->_set_sort_order($sort_field, $sort_order); 
    9051148 
     
    9081151 
    9091152    // we have a saved search result, get index from there 
    910     if (!isset($this->cache[$key]) && $this->search_string && $mailbox == $this->mailbox) 
     1153    if (!isset($this->cache[$key]) && $this->search_string 
     1154      && !$this->search_threads && $mailbox == $this->mailbox) 
    9111155    { 
    9121156      $this->cache[$key] = array(); 
    9131157       
    914       // use message index sort for sorting by Date 
    915       if ($this->index_sort && $this->sort_field == 'date') 
     1158      // use message index sort as default sorting 
     1159      if (!$this->sort_field) 
    9161160      { 
    9171161        $msgs = $this->search_set; 
     
    9381182      else 
    9391183      { 
    940         $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, join(',', $this->search_set), $this->sort_field, $this->skip_deleted); 
     1184        $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, 
     1185          join(',', $this->search_set), $this->sort_field, $this->skip_deleted); 
    9411186 
    9421187        if ($this->sort_order=="ASC") 
     
    9641209      } 
    9651210 
    966     // use message index sort for sorting by Date 
    967     if ($this->index_sort && $this->sort_field == 'date') 
     1211    // use message index sort as default sorting 
     1212    if (!$this->sort_field) 
    9681213      { 
    9691214      if ($this->skip_deleted) { 
     
    10031248 
    10041249  /** 
     1250   * Return sorted array of threaded message IDs (not UIDs) 
     1251   * 
     1252   * @param string Mailbox to get index from 
     1253   * @param string Sort column 
     1254   * @param string Sort order [ASC, DESC] 
     1255   * @return array Indexed array with message IDs 
     1256   */ 
     1257  function thread_index($mbox_name='', $sort_field=NULL, $sort_order=NULL) 
     1258    { 
     1259    $this->_set_sort_order($sort_field, $sort_order); 
     1260 
     1261    $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; 
     1262    $key = "{$mailbox}:{$this->sort_field}:{$this->sort_order}:{$this->search_string}.thi"; 
     1263 
     1264    // we have a saved search result, get index from there 
     1265    if (!isset($this->cache[$key]) && $this->search_string 
     1266      && $this->search_threads && $mailbox == $this->mailbox) 
     1267    { 
     1268      // use message IDs for better performance 
     1269      $ids = array_keys_recursive($this->search_set['tree']); 
     1270      $this->cache[$key] = $this->_flatten_threads($mailbox, $this->search_set['tree'], $ids); 
     1271    } 
     1272 
     1273    // have stored it in RAM 
     1274    if (isset($this->cache[$key])) 
     1275      return $this->cache[$key]; 
     1276/* 
     1277    // check local cache 
     1278    $cache_key = $mailbox.'.msg'; 
     1279    $cache_status = $this->check_cache_status($mailbox, $cache_key); 
     1280 
     1281    // cache is OK 
     1282    if ($cache_status>0) 
     1283      { 
     1284      $a_index = $this->get_message_cache_index($cache_key, TRUE, $this->sort_field, $this->sort_order); 
     1285      return array_keys($a_index); 
     1286      } 
     1287*/ 
     1288    // get all threads (default sort order) 
     1289    list ($thread_tree) = $this->_fetch_threads($mailbox); 
     1290 
     1291    $this->cache[$key] = $this->_flatten_threads($mailbox, $thread_tree); 
     1292     
     1293    return $this->cache[$key]; 
     1294    } 
     1295 
     1296 
     1297  /** 
     1298   * Return array of threaded messages (all, not only roots) 
     1299   * 
     1300   * @param string Mailbox to get index from 
     1301   * @param array  Threaded messages array (see _fetch_threads()) 
     1302   * @param array  Message IDs if we know what we need (e.g. search result) 
     1303   *               for better performance 
     1304   * @return array Indexed array with message IDs 
     1305   * 
    10051306   * @access private 
    10061307   */ 
    1007   function sync_header_index($mailbox) 
     1308  private function _flatten_threads($mailbox, $thread_tree, $ids=null) 
     1309    { 
     1310    if (empty($thread_tree)) 
     1311      return array(); 
     1312 
     1313    $msg_index = $this->_sort_threads($mailbox, $thread_tree, $ids); 
     1314 
     1315    if ($this->sort_order == 'DESC') 
     1316      $msg_index = array_reverse($msg_index); 
     1317           
     1318    // flatten threads array 
     1319    $all_ids = array(); 
     1320    foreach($msg_index as $root) { 
     1321      $all_ids[] = $root; 
     1322      if (!empty($thread_tree[$root])) 
     1323        $all_ids = array_merge($all_ids, array_keys_recursive($thread_tree[$root])); 
     1324      } 
     1325 
     1326    return $all_ids; 
     1327    } 
     1328 
     1329 
     1330  /** 
     1331   * @access private 
     1332   */ 
     1333  private function sync_header_index($mailbox) 
    10081334    { 
    10091335    $cache_key = $mailbox.'.msg'; 
     
    11031429    } 
    11041430 
    1105     $this->set_search_set($str, $results, $charset, $sort_field); 
     1431    $this->set_search_set($str, $results, $charset, $sort_field, (bool)$this->threading); 
    11061432 
    11071433    return $results; 
     
    11231449      $criteria = 'UNDELETED '.$criteria; 
    11241450 
    1125     if ($sort_field && $this->get_capability('sort') && (!$this->index_sort || $sort_field != 'date')) { 
     1451    if ($this->threading) { 
     1452      list ($thread_tree, $msg_depth, $has_children) = iil_C_Thread($this->conn, 
     1453            $mailbox, $this->threading, $criteria, $charset); 
     1454 
     1455      $a_messages = array( 
     1456        'tree'  => $thread_tree, 
     1457        'depth' => $msg_depth, 
     1458        'children' => $has_children 
     1459        ); 
     1460      } 
     1461    else if ($sort_field && $this->get_capability('sort')) { 
    11261462      $charset = $charset ? $charset : $this->default_charset; 
    11271463      $a_messages = iil_C_Sort($this->conn, $mailbox, $sort_field, $criteria, FALSE, $charset); 
     
    11361472     
    11371473        // I didn't found that SEARCH always returns sorted IDs 
    1138         if ($this->index_sort && $this->sort_field == 'date') 
     1474        if (!$this->sort_field) 
    11391475          sort($a_messages); 
    11401476        } 
     
    11501486   
    11511487  /** 
     1488   * Sort thread 
     1489   * 
     1490   * @param string Mailbox name 
     1491   * @param  array Unsorted thread tree (iil_C_Thread() result) 
     1492   * @param  array Message IDs if we know what we need (e.g. search result) 
     1493   * @return array Sorted roots IDs 
     1494   * @access private 
     1495   */ 
     1496  private function _sort_threads($mailbox, $thread_tree, $ids=NULL) 
     1497    { 
     1498    // THREAD=ORDEREDSUBJECT:   sorting by sent date of root message 
     1499    // THREAD=REFERENCES:       sorting by sent date of root message 
     1500    // THREAD=REFS:             sorting by the most recent date in each thread 
     1501    // default sorting 
     1502    if (!$this->sort_field || ($this->sort_field == 'date' && $this->threading == 'REFS')) { 
     1503        return array_keys($thread_tree); 
     1504      } 
     1505    // here we'll implement REFS sorting, for performance reason 
     1506    else { // ($sort_field == 'date' && $this->threading != 'REFS') 
     1507      // use SORT command 
     1508      if ($this->get_capability('sort')) { 
     1509        $a_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, 
     1510            !empty($ids) ? $ids : ($this->skip_deleted ? 'UNDELETED' : '')); 
     1511        } 
     1512      else { 
     1513        // fetch specified headers for all messages and sort them 
     1514        $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, !empty($ids) ? $ids : "1:*", 
     1515            $this->sort_field, $this->skip_deleted); 
     1516        asort($a_index); // ASC 
     1517        $a_index = array_values($a_index); 
     1518        } 
     1519 
     1520        return $this->_sort_thread_refs($thread_tree, $a_index); 
     1521      } 
     1522    } 
     1523 
     1524 
     1525  /** 
     1526   * THREAD=REFS sorting implementation 
     1527   * 
     1528   * @param  array   Thread tree array (message identifiers as keys) 
     1529   * @param  array   Array of sorted message identifiers 
     1530   * @return array   Array of sorted roots messages 
     1531   * @access private 
     1532   */ 
     1533  private function _sort_thread_refs($tree, $index) 
     1534    { 
     1535    if (empty($tree)) 
     1536      return array(); 
     1537     
     1538    $index = array_combine(array_values($index), $index); 
     1539 
     1540    // assign roots 
     1541    foreach ($tree as $idx => $val) { 
     1542      $index[$idx] = $idx; 
     1543      if (!empty($val)) { 
     1544        $idx_arr = array_keys_recursive($tree[$idx]); 
     1545        foreach ($idx_arr as $subidx) 
     1546          $index[$subidx] = $idx; 
     1547        } 
     1548      } 
     1549 
     1550    $index = array_values($index);   
     1551 
     1552    // create sorted array of roots 
     1553    $msg_index = array(); 
     1554    if ($this->sort_order != 'DESC') { 
     1555      foreach ($index as $idx) 
     1556        if (!isset($msg_index[$idx])) 
     1557          $msg_index[$idx] = $idx; 
     1558      $msg_index = array_values($msg_index); 
     1559      } 
     1560    else { 
     1561      for ($x=count($index)-1; $x>=0; $x--) 
     1562        if (!isset($msg_index[$index[$x]])) 
     1563          $msg_index[$index[$x]] = $index[$x]; 
     1564      $msg_index = array_reverse($msg_index); 
     1565      } 
     1566 
     1567    return $msg_index; 
     1568    } 
     1569 
     1570 
     1571  /** 
    11521572   * Refresh saved search set 
    11531573   * 
     
    11571577    { 
    11581578    if (!empty($this->search_string)) 
    1159       $this->search_set = $this->search('', $this->search_string, $this->search_charset, $this->search_sort_field); 
     1579      $this->search_set = $this->search('', $this->search_string, $this->search_charset, 
     1580            $this->search_sort_field, $this->search_threads); 
    11601581       
    11611582    return $this->get_search_set(); 
     
    17612182   */ 
    17622183  function move_message($uids, $to_mbox, $from_mbox='') 
    1763     { 
     2184  { 
    17642185    $fbox = $from_mbox; 
    17652186    $tbox = $to_mbox; 
     
    18032224    // moving failed 
    18042225    else if ($config->get('delete_always', false) && $tbox == $config->get('trash_mbox')) { 
    1805       return $this->delete_message($a_uids, $fbox); 
    1806     } 
    1807  
    1808     // remove message ids from search set 
    1809     if ($moved && $this->search_set && $from_mbox == $this->mailbox) { 
    1810       foreach ($a_uids as $uid) 
    1811         $a_mids[] = $this->_uid2id($uid, $from_mbox); 
    1812       $this->search_set = array_diff($this->search_set, $a_mids); 
    1813     } 
    1814  
    1815     // update cached message headers 
    1816     $cache_key = $from_mbox.'.msg'; 
    1817     if ($moved && $start_index = $this->get_message_cache_index_min($cache_key, $a_uids)) { 
    1818       // clear cache from the lowest index on 
    1819       $this->clear_message_cache($cache_key, $start_index); 
    1820       } 
     2226      $moved = $this->delete_message($a_uids, $fbox); 
     2227    } 
     2228 
     2229    if ($moved) { 
     2230      // unset threads internal cache 
     2231      unset($this->icache['threads']); 
     2232 
     2233      // remove message ids from search set 
     2234      if ($this->search_set && $from_mbox == $this->mailbox) { 
     2235        // threads are too complicated to just remove messages from set 
     2236        if ($this->search_threads) 
     2237          $this->refresh_search(); 
     2238        else { 
     2239          foreach ($a_uids as $uid) 
     2240            $a_mids[] = $this->_uid2id($uid, $from_mbox); 
     2241          $this->search_set = array_diff($this->search_set, $a_mids); 
     2242          } 
     2243        } 
     2244 
     2245      // update cached message headers 
     2246      $cache_key = $from_mbox.'.msg'; 
     2247      if ($start_index = $this->get_message_cache_index_min($cache_key, $a_uids)) { 
     2248        // clear cache from the lowest index on 
     2249        $this->clear_message_cache($cache_key, $start_index); 
     2250       } 
     2251    } 
    18212252 
    18222253    return $moved; 
    1823     } 
     2254  } 
    18242255 
    18252256 
     
    18322263   */ 
    18332264  function delete_message($uids, $mbox_name='') 
    1834     { 
     2265  { 
    18352266    $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; 
    18362267 
     
    18442275    $deleted = iil_C_Delete($this->conn, $mailbox, join(',', $a_uids)); 
    18452276 
    1846     // send expunge command in order to have the deleted message 
    1847     // really deleted from the mailbox 
    1848     if ($deleted) 
    1849       { 
     2277    if ($deleted) { 
     2278      // send expunge command in order to have the deleted message 
     2279      // really deleted from the mailbox 
    18502280      $this->_expunge($mailbox, FALSE, $a_uids); 
    18512281      $this->_clear_messagecount($mailbox); 
    18522282      unset($this->uid_id_map[$mailbox]); 
    1853       } 
    1854  
    1855     // remove message ids from search set 
    1856     if ($deleted && $this->search_set && $mailbox == $this->mailbox) { 
    1857       foreach ($a_uids as $uid) 
    1858         $a_mids[] = $this->_uid2id($uid, $mailbox); 
    1859       $this->search_set = array_diff($this->search_set, $a_mids); 
    1860     } 
    1861  
    1862     // remove deleted messages from cache 
    1863     $cache_key = $mailbox.'.msg'; 
    1864     if ($deleted && $start_index = $this->get_message_cache_index_min($cache_key, $a_uids)) { 
    1865       // clear cache from the lowest index on 
    1866       $this->clear_message_cache($cache_key, $start_index); 
    1867       } 
     2283 
     2284      // unset threads internal cache 
     2285      unset($this->icache['threads']); 
     2286       
     2287      // remove message ids from search set 
     2288      if ($this->search_set && $mailbox == $this->mailbox) { 
     2289        // threads are too complicated to just remove messages from set 
     2290        if ($this->search_threads) 
     2291          $this->refresh_search(); 
     2292        else { 
     2293          foreach ($a_uids as $uid) 
     2294            $a_mids[] = $this->_uid2id($uid, $mailbox); 
     2295          $this->search_set = array_diff($this->search_set, $a_mids); 
     2296          } 
     2297        } 
     2298 
     2299      // remove deleted messages from cache 
     2300      $cache_key = $mailbox.'.msg'; 
     2301      if ($start_index = $this->get_message_cache_index_min($cache_key, $a_uids)) { 
     2302        // clear cache from the lowest index on 
     2303        $this->clear_message_cache($cache_key, $start_index); 
     2304      } 
     2305    } 
    18682306 
    18692307    return $deleted; 
    1870     } 
     2308  } 
    18712309 
    18722310 
     
    24232861    { 
    24242862    $cache_key = "$key:$from:$to:$sort_field:$sort_order"; 
    2425     $db_header_fields = array('idx', 'uid', 'subject', 'from', 'to', 'cc', 'date', 'size'); 
    24262863     
    24272864    $config = rcmail::get_instance()->config; 
    24282865 
    2429     // use idx sort for sorting by Date with index_sort=true or for unknown field 
    2430     if (($sort_field == 'date' && $this->index_sort) 
    2431       || !in_array($sort_field, $db_header_fields)) { 
     2866    // use idx sort as default sorting 
     2867    if (!$sort_field || !in_array($sort_field, $this->db_header_fields)) { 
    24322868      $sort_field = 'idx'; 
    24332869      } 
     
    24662902  private function &get_cached_message($key, $uid) 
    24672903    { 
    2468     $internal_key = '__single_msg'; 
    2469      
    2470     if ($this->caching_enabled && !isset($this->cache[$internal_key][$uid])) 
     2904    $internal_key = 'message'; 
     2905     
     2906    if ($this->caching_enabled && !isset($this->icache[$internal_key][$uid])) 
    24712907      { 
    24722908      $sql_result = $this->db->query( 
     
    24832919        { 
    24842920        $this->uid_id_map[preg_replace('/\.msg$/', '', $key)][$uid] = $sql_arr['idx']; 
    2485         $this->cache[$internal_key][$uid] = $this->db->decode(unserialize($sql_arr['headers'])); 
    2486         if (is_object($this->cache[$internal_key][$uid]) && !empty($sql_arr['structure'])) 
    2487           $this->cache[$internal_key][$uid]->structure = $this->db->decode(unserialize($sql_arr['structure'])); 
     2921        $this->icache[$internal_key][$uid] = $this->db->decode(unserialize($sql_arr['headers'])); 
     2922        if (is_object($this->icache[$internal_key][$uid]) && !empty($sql_arr['structure'])) 
     2923          $this->icache[$internal_key][$uid]->structure = $this->db->decode(unserialize($sql_arr['structure'])); 
    24882924        } 
    24892925      } 
    24902926 
    2491     return $this->cache[$internal_key][$uid]; 
     2927    return $this->icache[$internal_key][$uid]; 
    24922928    } 
    24932929 
     
    25062942      return $sa_message_index[$key]; 
    25072943 
    2508     // use idx sort for sorting by Date with index_sort=true 
    2509     if ($sort_field == 'date' && $this->index_sort) 
     2944    // use idx sort as default 
     2945    if (!$sort_field || !in_array($sort_field, $this->db_header_fields)) 
    25102946      $sort_field = 'idx'; 
    25112947     
     
    25352971 
    25362972    // add to internal (fast) cache 
    2537     $this->cache['__single_msg'][$headers->uid] = clone $headers; 
    2538     $this->cache['__single_msg'][$headers->uid]->structure = $struct; 
     2973    $this->icache['message'][$headers->uid] = clone $headers; 
     2974    $this->icache['message'][$headers->uid]->structure = $struct; 
    25392975 
    25402976    // no further caching 
  • trunk/roundcubemail/program/include/rcube_shared.inc

    r3293 r3367  
    609609 
    610610/** 
     611 * Get all keys from array (recursive) 
     612 *  
     613 * @param array Input array 
     614 * @return array 
     615 */ 
     616function array_keys_recursive($array) 
     617{ 
     618  $keys = array(); 
     619   
     620  if (!empty($array)) 
     621    foreach ($array as $key => $child) { 
     622      $keys[] = $key; 
     623      if ($children = array_keys_recursive($child)) 
     624        $keys = array_merge($keys, $children); 
     625    } 
     626  return $keys; 
     627} 
     628 
     629 
     630/** 
    611631 * mbstring replacement functions 
    612632 */ 
  • trunk/roundcubemail/program/include/rcube_user.php

    r3359 r3367  
    112112        unset($save_prefs[$key]); 
    113113    } 
    114      
     114 
     115    $save_prefs = serialize($save_prefs); 
     116 
    115117    $this->db->query( 
    116118      "UPDATE ".get_table_name('users')." 
     
    118120              language=? 
    119121       WHERE  user_id=?", 
    120       serialize($save_prefs), 
     122      $save_prefs, 
    121123      $_SESSION['language'], 
    122124      $this->ID); 
     
    125127    if ($this->db->affected_rows()) { 
    126128      $config->set_user_prefs($a_user_prefs); 
     129      $this->data['preferences'] = $save_prefs; 
    127130      return true; 
    128131    } 
  • trunk/roundcubemail/program/js/app.js

    r3343 r3367  
    44 |                                                                       | 
    55 | This file is part of the RoundCube Webmail client                     | 
    6  | Copyright (C) 2005-2009, RoundCube Dev, - Switzerland                 | 
     6 | Copyright (C) 2005-2010, RoundCube Dev, - Switzerland                 | 
    77 | Licensed under the GNU GPL                                            | 
    88 |                                                                       | 
    99 +-----------------------------------------------------------------------+ 
    1010 | Authors: Thomas Bruederli <roundcube@gmail.com>                       | 
     11 |          Aleksander 'A.L.E.C' Machniak <alec@alec.pl>                 | 
    1112 |          Charles McNulty <charles@charlesmcnulty.com>                 | 
    1213 +-----------------------------------------------------------------------+ 
     
    161162      { 
    162163      case 'mail': 
     164        // enable mail commands 
     165        this.enable_command('list', 'checkmail', 'compose', 'add-contact', 'search', 'reset-search', 'collapse-folder', true); 
     166       
    163167        if (this.gui_objects.messagelist) 
    164168          { 
    165           this.message_list = new rcube_list_widget(this.gui_objects.messagelist, {multiselect:true, draggable:true, keyboard:true, dblclick_time:this.dblclick_time}); 
     169          this.message_list = new rcube_list_widget(this.gui_objects.messagelist, 
     170            {multiselect:true, multiexpand:true, draggable:true, keyboard:true, dblclick_time:this.dblclick_time}); 
    166171          this.message_list.row_init = function(o){ p.init_message_row(o); }; 
    167172          this.message_list.addEventListener('dblclick', function(o){ p.msglist_dbl_click(o); }); 
     
    171176          this.message_list.addEventListener('dragmove', function(e){ p.drag_move(e); }); 
    172177          this.message_list.addEventListener('dragend', function(e){ p.drag_end(e); }); 
     178          this.message_list.addEventListener('expandcollapse', function(e){ p.msglist_expand(e); }); 
    173179          document.onmouseup = function(e){ return p.doc_mouse_up(e); }; 
    174180 
     181          this.set_message_coltypes(this.env.coltypes); 
    175182          this.message_list.init(); 
    176           this.enable_command('toggle_status', 'toggle_flag', true); 
     183          this.enable_command('toggle_status', 'toggle_flag', 'menu-open', 'menu-save', true); 
    177184           
    178185          if (this.gui_objects.mailcontframe) 
     
    180187          else 
    181188            this.message_list.focus(); 
     189           
     190          // load messages 
     191          if (this.env.messagecount) 
     192            this.command('list'); 
    182193          } 
    183            
    184         if (this.env.coltypes) 
    185           this.set_message_coltypes(this.env.coltypes); 
    186  
    187         // enable mail commands 
    188         this.enable_command('list', 'checkmail', 'compose', 'add-contact', 'search', 'reset-search', 'collapse-folder', true); 
    189194 
    190195        if (this.env.search_text != null && document.getElementById('quicksearchbox') != null) 
     
    244249          } 
    245250 
    246         if (this.env.messagecount) 
     251        if (this.env.messagecount) { 
    247252          this.enable_command('select-all', 'select-none', 'expunge', true); 
     253          this.enable_command('expand-all', 'expand-unread', 'collapse-all', this.env.threading); 
     254        } 
    248255 
    249256        if (this.purge_mailbox_test()) 
     
    334341        } 
    335342        else if (this.env.action=='folders') 
    336           this.enable_command('subscribe', 'unsubscribe', 'create-folder', 'rename-folder', 'delete-folder', true); 
     343          this.enable_command('subscribe', 'unsubscribe', 'create-folder', 'rename-folder', 'delete-folder', 'enable-threading', 'disable-threading', true); 
    337344 
    338345        if (this.gui_objects.identitieslist) 
     
    352359          this.sections_list.init(); 
    353360          this.sections_list.focus(); 
     361          this.sections_list.select_first(); 
    354362        } 
    355363        else if (this.gui_objects.subscriptionlist) 
     
    405413  }; 
    406414 
    407   // start interval for keep-alive/recent_check signal 
    408   this.start_keepalive = function() 
    409     { 
    410     if (this.env.keep_alive && !this.env.framed && this.task=='mail' && this.gui_objects.mailboxlist) 
    411       this._int = setInterval(function(){ ref.check_for_recent(false); }, this.env.keep_alive * 1000); 
    412     else if (this.env.keep_alive && !this.env.framed && this.task!='login') 
    413       this._int = setInterval(function(){ ref.send_keep_alive(); }, this.env.keep_alive * 1000); 
    414     } 
    415  
    416   this.init_message_row = function(row) 
    417   { 
    418     var uid = row.uid; 
    419     if (uid && this.env.messages[uid]) 
    420       { 
    421       row.deleted = this.env.messages[uid].deleted ? true : false; 
    422       row.unread = this.env.messages[uid].unread ? true : false; 
    423       row.replied = this.env.messages[uid].replied ? true : false; 
    424       row.flagged = this.env.messages[uid].flagged ? true : false; 
    425       row.forwarded = this.env.messages[uid].forwarded ? true : false; 
    426       } 
    427  
    428     // set eventhandler to message icon 
    429     if (row.icon = row.obj.getElementsByTagName('td')[0].getElementsByTagName('img')[0]) 
    430       { 
    431       var p = this; 
    432       row.icon.id = 'msgicn_'+row.uid; 
    433       row.icon._row = row.obj; 
    434       row.icon.onmousedown = function(e) { p.command('toggle_status', this); }; 
    435       } 
    436  
    437     // global variable 'flagged_col' may be not defined yet 
    438     if (!this.env.flagged_col && this.env.coltypes) 
    439       { 
    440       var found; 
    441       if((found = find_in_array('flag', this.env.coltypes)) >= 0) 
    442         this.set_env('flagged_col', found+1); 
    443       } 
    444  
    445     // set eventhandler to flag icon, if icon found 
    446     if (this.env.flagged_col && (row.flagged_icon = row.obj.getElementsByTagName('td')[this.env.flagged_col].getElementsByTagName('img')[0])) 
    447       { 
    448       var p = this; 
    449       row.flagged_icon.id = 'flaggedicn_'+row.uid; 
    450       row.flagged_icon._row = row.obj; 
    451       row.flagged_icon.onmousedown = function(e) { p.command('toggle_flag', this); }; 
    452       } 
    453        
    454     this.triggerEvent('insertrow', { uid:uid, row:row }); 
    455   }; 
    456  
    457   // init message compose form: set focus and eventhandlers 
    458   this.init_messageform = function() 
    459     { 
    460     if (!this.gui_objects.messageform) 
    461       return false; 
    462      
    463     //this.messageform = this.gui_objects.messageform; 
    464     var input_from = $("[name='_from']"); 
    465     var input_to = $("[name='_to']"); 
    466     var input_subject = $("input[name='_subject']"); 
    467     var input_message = $("[name='_message']").get(0); 
    468     var html_mode = $("input[name='_is_html']").val() == '1'; 
    469  
    470     // init live search events 
    471     this.init_address_input_events(input_to); 
    472     this.init_address_input_events($("[name='_cc']")); 
    473     this.init_address_input_events($("[name='_bcc']")); 
    474      
    475     if (!html_mode) 
    476       this.set_caret_pos(input_message, this.env.top_posting ? 0 : $(input_message).val().length); 
    477  
    478     // add signature according to selected identity 
    479     if (input_from.attr('type') == 'select-one' && $("input[name='_draft_saveid']").val() == '' 
    480         && !html_mode) {  // if we have HTML editor, signature is added in callback 
    481       this.change_identity(input_from[0]); 
    482     } 
    483     else if (!html_mode)  
    484       this.set_caret_pos(input_message, this.env.top_posting ? 0 : $(input_message).val().length); 
    485  
    486     if (input_to.val() == '') 
    487       input_to.focus(); 
    488     else if (input_subject.val() == '') 
    489       input_subject.focus(); 
    490     else if (input_message && !html_mode) 
    491       input_message.focus(); 
    492  
    493     // get summary of all field values 
    494     this.compose_field_hash(true); 
    495   
    496     // start the auto-save timer 
    497     this.auto_save_start(); 
    498     }; 
    499  
    500   this.init_address_input_events = function(obj) 
    501     { 
    502     var handler = function(e){ return ref.ksearch_keypress(e,this); }; 
    503     obj.bind((bw.safari || bw.ie ? 'keydown' : 'keypress'), handler); 
    504     obj.attr('autocomplete', 'off'); 
    505     }; 
    506  
    507415 
    508416  /*********************************************************/ 
     
    581489        break; 
    582490 
     491      case 'menu-open': 
     492      case 'menu-save': 
     493        this.triggerEvent(command, {props:props}); 
     494        return false; 
     495        break; 
     496 
    583497      case 'open': 
    584498        var uid; 
     
    626540          sort_order = 'ASC'; 
    627541 
    628         // set table header class 
    629         $('#rcm'+this.env.sort_col).removeClass('sorted'+(this.env.sort_order.toUpperCase())); 
    630         $('#rcm'+sort_col).addClass('sorted'+sort_order); 
    631  
    632         // save new sort properties 
    633         this.env.sort_col = sort_col; 
    634         this.env.sort_order = sort_order; 
     542        // set table header and update env 
     543        this.set_list_sorting(sort_col, sort_order); 
    635544 
    636545        // reload message list 
     
    757666        break; 
    758667 
    759  
    760668      // mail task commands 
    761669      case 'move': 
     
    850758      case 'select-none': 
    851759        this.message_list.clear_selection(); 
     760        break; 
     761 
     762      case 'expand-all': 
     763        this.env.autoexpand_threads = 1; 
     764        this.message_list.expand_all(); 
     765        break; 
     766 
     767      case 'expand-unread': 
     768        this.env.autoexpand_threads = 2; 
     769        this.message_list.collapse_all(); 
     770        this.expand_unread(); 
     771        break; 
     772 
     773      case 'collapse-all': 
     774        this.env.autoexpand_threads = 0; 
     775        this.message_list.collapse_all(); 
    852776        break; 
    853777 
     
    11171041        this.unsubscribe_folder(props); 
    11181042        break; 
    1119          
     1043 
     1044      case 'enable-threading': 
     1045        this.enable_threading(props); 
     1046        break; 
     1047 
     1048      case 'disable-threading': 
     1049        this.disable_threading(props); 
     1050        break; 
     1051 
    11201052      case 'create-folder': 
    11211053        this.create_folder(props); 
     
    14511383      clearTimeout(this.preview_timer); 
    14521384 
    1453     var selected = list.selection.length==1; 
     1385    var selected = list.get_single_selection() != null; 
    14541386 
    14551387    // Hide certain command buttons when Drafts folder is selected 
     
    15061438  }; 
    15071439   
     1440  this.msglist_expand = function(row) 
     1441  { 
     1442    if (this.env.messages[row.uid]) 
     1443      this.env.messages[row.uid].expanded = row.expanded; 
     1444  }; 
     1445   
    15081446  this.check_droptarget = function(id) 
    15091447  { 
     
    15211459  /*********************************************************/ 
    15221460 
     1461  this.init_message_row = function(row) 
     1462  { 
     1463    var self = this; 
     1464    var uid = row.uid; 
     1465     
     1466    if (uid && this.env.messages[uid]) 
     1467      $.extend(row, this.env.messages[uid]); 
     1468 
     1469    // set eventhandler to message icon 
     1470    if (this.env.subject_col != null && (row.icon = document.getElementById('msgicn'+row.uid))) { 
     1471      row.icon._row = row.obj; 
     1472      row.icon.onmousedown = function(e) { self.command('toggle_status', this); }; 
     1473    } 
     1474 
     1475    // set eventhandler to flag icon, if icon found 
     1476    if (this.env.flagged_col != null && (row.flagged_icon = document.getElementById('flaggedicn'+row.uid))) { 
     1477      row.flagged_icon._row = row.obj; 
     1478      row.flagged_icon.onmousedown = function(e) { self.command('toggle_flag', this); }; 
     1479    } 
     1480 
     1481    var expando; 
     1482    if (!row.depth && row.has_children && (expando = document.getElementById('rcmexpando'+row.uid))) { 
     1483      expando.onmousedown = function(e) { return self.expand_message_row(e, uid); }; 
     1484    } 
     1485 
     1486    this.triggerEvent('insertrow', { uid:uid, row:row }); 
     1487  }; 
     1488 
     1489  // create a table row in the message list 
     1490  this.add_message_row = function(uid, cols, flags, attop) 
     1491  { 
     1492    if (!this.gui_objects.messagelist || !this.message_list) 
     1493      return false; 
     1494 
     1495    if (this.message_list.background) 
     1496      var tbody = this.message_list.background; 
     1497    else 
     1498      var tbody = this.gui_objects.messagelist.tBodies[0]; 
     1499     
     1500    var rows = this.message_list.rows; 
     1501    var rowcount = tbody.rows.length; 
     1502    var even = rowcount%2; 
     1503     
     1504    if (!this.env.messages[uid]) 
     1505      this.env.messages[uid] = {}; 
     1506     
     1507    // merge flags over local message object 
     1508    $.extend(this.env.messages[uid], { 
     1509      deleted: flags.deleted?1:0, 
     1510      replied: flags.replied?1:0, 
     1511      unread: flags.unread?1:0, 
     1512      forwarded: flags.forwarded?1:0, 
     1513      flagged: flags.flagged?1:0, 
     1514      has_children: flags.has_children?1:0, 
     1515      depth: flags.depth?flags.depth:0, 
     1516      unread_children: flags.unread_children, 
     1517      parent_uid: flags.parent_uid 
     1518    }); 
     1519 
     1520    var message = this.env.messages[uid]; 
     1521 
     1522    var css_class = 'message' 
     1523        + (even ? ' even' : ' odd') 
     1524        + (flags.unread ? ' unread' : '') 
     1525        + (flags.deleted ? ' deleted' : '') 
     1526        + (flags.flagged ? ' flagged' : '') 
     1527        + (flags.unread_children && !flags.unread ? ' unroot' : '') 
     1528        + (this.message_list.in_selection(uid) ? ' selected' : ''); 
     1529 
     1530    // for performance use DOM instead of jQuery here 
     1531    var row = document.createElement('tr'); 
     1532    row.id = 'rcmrow'+uid; 
     1533    row.className = css_class; 
     1534     
     1535    var icon = this.env.messageicon; 
     1536    if (!flags.unread && flags.unread_children > 0 && this.env.unreadchildrenicon) 
     1537      icon = this.env.unreadchildrenicon; 
     1538    else if (flags.deleted && this.env.deletedicon) 
     1539      icon = this.env.deletedicon; 
     1540    else if (flags.replied && this.env.repliedicon) 
     1541      { 
     1542      if (flags.forwarded && this.env.forwardedrepliedicon) 
     1543        icon = this.env.forwardedrepliedicon; 
     1544      else 
     1545        icon = this.env.repliedicon; 
     1546      } 
     1547    else if (flags.forwarded && this.env.forwardedicon) 
     1548      icon = this.env.forwardedicon; 
     1549    else if(flags.unread && this.env.unreadicon) 
     1550      icon = this.env.unreadicon; 
     1551  
     1552    var tree = expando = ''; 
     1553 
     1554    if (this.env.threading) 
     1555      { 
     1556      // This assumes that div width is hardcoded to 15px, 
     1557      var width = message.depth * 15; 
     1558      if (message.depth) { 
     1559        if ((this.env.autoexpand_threads == 0 || this.env.autoexpand_threads == 2) && 
     1560            (!rows[message.parent_uid] || !rows[message.parent_uid].expanded)) { 
     1561          row.style.display = 'none'; 
     1562          message.expanded = false; 
     1563        } 
     1564        else 
     1565          message.expanded = true; 
     1566      } 
     1567      else if (message.has_children) { 
     1568        if (typeof(message.expanded) == 'undefined' && (this.env.autoexpand_threads == 1 || (this.env.autoexpand_threads == 2 && message.unread_children))) { 
     1569          message.expanded = true; 
     1570        } 
     1571      } 
     1572 
     1573      if (width) 
     1574        tree += '<span id="rcmtab' + uid + '" class="branch" style="width:' + width + 'px;">&nbsp;&nbsp;</span>'; 
     1575 
     1576      if (message.has_children && !message.depth) 
     1577        expando = '<div id="rcmexpando' + uid + '" class="' + (message.expanded ? 'expanded' : 'collapsed') + '">&nbsp;&nbsp;</div>'; 
     1578      } 
     1579 
     1580    tree += icon ? '<img id="msgicn'+uid+'" src="'+icon+'" alt="" class="msgicon" />' : ''; 
     1581     
     1582    // first col is always there 
     1583    var col = document.createElement('td'); 
     1584    col.className = 'threads'; 
     1585    col.innerHTML = expando; 
     1586    row.appendChild(col); 
     1587     
     1588    // build subject link  
     1589    if (!bw.ie && cols.subject) { 
     1590      var action = flags.mbox == this.env.drafts_mailbox ? 'compose' : 'show'; 
     1591      var uid_param = flags.mbox == this.env.drafts_mailbox ? '_draft_uid' : '_uid'; 
     1592      cols.subject = '<a href="./?_task=mail&_action='+action+'&_mbox='+urlencode(flags.mbox)+'&'+uid_param+'='+uid+'"'+ 
     1593        ' onclick="return rcube_event.cancel(event)">'+cols.subject+'</a>'; 
     1594    } 
     1595 
     1596    // add each submitted col 
     1597    for (var n = 0; n < this.env.coltypes.length; n++) { 
     1598      var c = this.env.coltypes[n]; 
     1599      col = document.createElement('td'); 
     1600      col.className = String(c).toLowerCase(); 
     1601 
     1602      var html; 
     1603      if (c=='flag') { 
     1604        if (flags.flagged && this.env.flaggedicon) 
     1605          html = '<img id="flaggedicn'+uid+'" src="'+this.env.flaggedicon+'" class="flagicon" alt="" />'; 
     1606        else if(!flags.flagged && this.env.unflaggedicon) 
     1607          html = '<img id="flaggedicn'+uid+'" src="'+this.env.unflaggedicon+'" class="flagicon" alt="" />'; 
     1608      } 
     1609      else if (c=='attachment') 
     1610        html = flags.attachment && this.env.attachmenticon ? '<img src="'+this.env.attachmenticon+'" alt="" />' : '&nbsp;'; 
     1611      else if (c=='subject') 
     1612        html = tree + cols[c]; 
     1613      else 
     1614        html = cols[c]; 
     1615 
     1616      col.innerHTML = html; 
     1617 
     1618      row.appendChild(col); 
     1619    } 
     1620 
     1621    this.message_list.insert_row(row, attop); 
     1622 
     1623    // remove 'old' row 
     1624    if (attop && this.env.pagesize && this.message_list.rowcount > this.env.pagesize) { 
     1625      var uid = this.message_list.get_last_row(); 
     1626      this.message_list.remove_row(uid); 
     1627      this.message_list.clear_selection(uid); 
     1628    } 
     1629  }; 
     1630 
     1631  // messages list handling in background (for performance) 
     1632  this.offline_message_list = function(flag) 
     1633    { 
     1634      if (this.message_list) 
     1635        this.message_list.set_background_mode(flag); 
     1636    }; 
     1637 
     1638  this.set_list_sorting = function(sort_col, sort_order) 
     1639    { 
     1640    // set table header class 
     1641    $('#rcm'+this.env.sort_col).removeClass('sorted'+(this.env.sort_order.toUpperCase())); 
     1642    if (sort_col) 
     1643      $('#rcm'+sort_col).addClass('sorted'+sort_order); 
     1644     
     1645    this.env.sort_col = sort_col; 
     1646    this.env.sort_order = sort_order; 
     1647    } 
     1648 
     1649  this.set_list_options = function(cols, sort_col, sort_order, threads) 
     1650    { 
     1651    var update, add_url = ''; 
     1652 
     1653    if (this.env.sort_col != sort_col || this.env.sort_order != sort_order) { 
     1654      update = 1; 
     1655      this.set_list_sorting(sort_col, sort_order); 
     1656      } 
     1657     
     1658    if (this.env.threading != threads) { 
     1659      update = 1; 
     1660      add_url += '&_threads=' + threads;      
     1661      } 
     1662 
     1663    if (cols.join() != this.env.coltypes.join()) { 
     1664      update = 1; 
     1665      add_url += '&_cols=' + cols.join(','); 
     1666      } 
     1667 
     1668    if (update) 
     1669      this.list_mailbox('', '', sort_col+'_'+sort_order, add_url); 
     1670    } 
     1671 
    15231672  // when user doble-clicks on a row 
    15241673  this.show_message = function(id, safe, preview) 
     
    15551704        { 
    15561705        this.set_message(id, 'unread', false); 
     1706        this.update_thread_root(id, 'read'); 
    15571707        if (this.env.unread_counts[this.env.mailbox]) 
    15581708          { 
     
    16221772    } 
    16231773 
    1624  
    16251774  // list messages of a specific mailbox 
    1626   this.list_mailbox = function(mbox, page, sort) 
    1627     { 
    1628     var add_url = ''; 
     1775  this.list_mailbox = function(mbox, page, sort, add_url) 
     1776    { 
     1777    var url = ''; 
    16291778    var target = window; 
    16301779 
     
    16321781      mbox = this.env.mailbox; 
    16331782 
     1783    if (add_url) 
     1784      url += add_url; 
     1785 
    16341786    // add sort to url if set 
    16351787    if (sort) 
    1636       add_url += '&_sort=' + sort; 
     1788      url += '&_sort=' + sort; 
    16371789 
    16381790    // also send search request to get the right messages 
    16391791    if (this.env.search_request) 
    1640       add_url += '&_search='+this.env.search_request; 
     1792      url += '&_search='+this.env.search_request; 
    16411793 
    16421794    // set page=1 if changeing to another mailbox 
     
    16491801 
    16501802    if (mbox != this.env.mailbox || (mbox == this.env.mailbox && !page && !sort)) 
    1651       add_url += '&_refresh=1'; 
     1803      url += '&_refresh=1'; 
    16521804 
    16531805    // unselect selected messages 
     
    16621814    if (this.gui_objects.messagelist) 
    16631815      { 
    1664       this.list_mailbox_remote(mbox, page, add_url); 
     1816      this.list_mailbox_remote(mbox, page, url); 
    16651817      return; 
    16661818      } 
     
    16691821      { 
    16701822      target = window.frames[this.env.contentframe]; 
    1671       add_url += '&_framed=1'; 
     1823      url += '&_framed=1'; 
    16721824      } 
    16731825 
     
    16761828      { 
    16771829      this.set_busy(true, 'loading'); 
    1678       target.location.href = this.env.comm_path+'&_mbox='+urlencode(mbox)+(page ? '&_page='+page : '')+add_url; 
     1830      target.location.href = this.env.comm_path+'&_mbox='+urlencode(mbox)+(page ? '&_page='+page : '')+url; 
    16791831      } 
    16801832    }; 
     
    16921844    }; 
    16931845 
    1694   this.expunge_mailbox = function(mbox) 
    1695     { 
    1696     var lock = false; 
    1697     var add_url = ''; 
    1698      
    1699     // lock interface if it's the active mailbox 
    1700     if (mbox == this.env.mailbox) 
    1701        { 
    1702        lock = true; 
    1703        this.set_busy(true, 'loading'); 
    1704        add_url = '&_reload=1'; 
    1705        } 
    1706  
    1707     // send request to server 
    1708     var url = '_mbox='+urlencode(mbox); 
    1709     this.http_post('expunge', url+add_url, lock); 
    1710     }; 
    1711  
    1712   this.purge_mailbox = function(mbox) 
    1713     { 
    1714     var lock = false; 
    1715     var add_url = ''; 
    1716      
    1717     if (!confirm(this.get_label('purgefolderconfirm'))) 
    1718       return false; 
    1719      
    1720     // lock interface if it's the active mailbox 
    1721     if (mbox == this.env.mailbox) 
    1722        { 
    1723        lock = true; 
    1724        this.set_busy(true, 'loading'); 
    1725        add_url = '&_reload=1'; 
    1726        } 
    1727  
    1728     // send request to server 
    1729     var url = '_mbox='+urlencode(mbox); 
    1730     this.http_post('purge', url+add_url, lock); 
    1731     return true; 
    1732     }; 
    1733  
    1734   // test if purge command is allowed 
    1735   this.purge_mailbox_test = function() 
    1736   { 
    1737     return (this.env.messagecount && (this.env.mailbox == this.env.trash_mailbox || this.env.mailbox == this.env.junk_mailbox  
    1738       || this.env.mailbox.match('^' + RegExp.escape(this.env.trash_mailbox) + RegExp.escape(this.env.delimiter))  
    1739       || this.env.mailbox.match('^' + RegExp.escape(this.env.junk_mailbox) + RegExp.escape(this.env.delimiter)))); 
    1740   }; 
     1846  // expand all threads with unread children 
     1847  this.expand_unread = function() 
     1848    { 
     1849    var tbody = this.gui_objects.messagelist.tBodies[0]; 
     1850    var new_row = tbody.firstChild; 
     1851    var r; 
     1852     
     1853    while (new_row) { 
     1854      if (new_row.nodeType == 1 && (r = this.message_list.rows[new_row.uid]) 
     1855            && r.unread_children) { 
     1856        this.message_list.expand_all(r); 
     1857        var expando = document.getElementById('rcmexpando' + r.uid); 
     1858        if (expando) 
     1859          expando.className = 'expanded'; 
     1860        this.set_unread_children(r.uid); 
     1861        } 
     1862      new_row = new_row.nextSibling; 
     1863      } 
     1864    return false; 
     1865    }; 
     1866 
     1867  // thread expanding/collapsing handler     
     1868  this.expand_message_row = function(e, uid) 
     1869    { 
     1870    var row = this.message_list.rows[uid]; 
     1871 
     1872    // handle unread_children mark 
     1873    row.expanded = !row.expanded; 
     1874    this.set_unread_children(uid); 
     1875    row.expanded = !row.expanded; 
     1876 
     1877    this.message_list.expand_row(e, uid); 
     1878    }; 
     1879 
     1880  // message list expanding 
     1881  this.expand_threads = function() 
     1882    {     
     1883    if (!this.env.threading || !this.env.autoexpand_threads || !this.message_list) 
     1884      return; 
     1885     
     1886    switch (this.env.autoexpand_threads) { 
     1887      case 2: this.expand_unread(); break; 
     1888      case 1: this.message_list.expand_all(); break; 
     1889      } 
     1890    //  this.message_list.expand(null); 
     1891    } 
     1892 
     1893  // update parent in a thread 
     1894  this.update_thread_root = function(uid, flag) 
     1895  { 
     1896    if (!this.env.threading) 
     1897      return; 
     1898 
     1899    var root = this.find_thread_root(uid); 
     1900     
     1901    if (uid == root) 
     1902      return; 
     1903 
     1904    var p = this.message_list.rows[root]; 
     1905 
     1906    if (flag == 'read' && p.unread_children) { 
     1907      p.unread_children--; 
     1908    } else if (flag == 'unread' && p.has_children) { 
     1909      // unread_children may be undefined 
     1910      p.unread_children = p.unread_children ? p.unread_children + 1 : 1; 
     1911    } else { 
     1912      return; 
     1913    } 
     1914 
     1915    this.set_message_icon(root); 
     1916    this.set_unread_children(root); 
     1917  }; 
     1918 
     1919  // finds root message for specified thread 
     1920  this.find_thread_root = function(uid) 
     1921  { 
     1922    var r = this.message_list.rows[uid]; 
     1923   
     1924    if (r.parent_uid) 
     1925      return this.find_thread_root(r.parent_uid); 
     1926    else 
     1927      return uid; 
     1928  } 
     1929 
     1930  // update thread indicators for all messages in a thread below the specified message 
     1931  // return number of removed/added root level messages 
     1932  this.update_thread = function (uid) 
     1933  { 
     1934    if (!this.env.threading) 
     1935      return 0; 
     1936 
     1937    var rows = this.message_list.rows; 
     1938    var row = rows[uid] 
     1939    var depth = rows[uid].depth; 
     1940    var r, parent, count = 0; 
     1941    var roots = new Array(); 
     1942 
     1943    if (!row.depth) // root message: decrease roots count 
     1944      count--; 
     1945    else if (row.unread) { 
     1946      // update unread_children for thread root 
     1947      var parent = this.find_thread_root(uid); 
     1948      rows[parent].unread_children--; 
     1949      this.set_unread_children(parent); 
     1950      } 
     1951 
     1952    parent = row.parent_uid; 
     1953 
     1954    // childrens 
     1955    row = row.obj.nextSibling; 
     1956    while (row) { 
     1957      if (row.nodeType == 1 && (r = rows[row.uid])) { 
     1958        if (!r.depth || r.depth <= depth) 
     1959          break; 
     1960 
     1961        r.depth--; // move left 
     1962        $('#rcmtab'+r.uid).width(r.depth * 15); 
     1963        if (!r.depth) { // a new root 
     1964          count++; // increase roots count 
     1965          r.parent_uid = 0; 
     1966          if (r.has_children) { 
     1967            // replace 'leaf' with 'collapsed' 
     1968            $('#rcmrow'+r.uid+' '+'.leaf:first') 
     1969              .attr('id', 'rcmexpando' + r.uid) 
     1970              .attr('class', (r.obj.style.display != 'none' ? 'expanded' : 'collapsed')) 
     1971              .bind('mousedown', {uid:r.uid, p:this}, 
     1972                function(e) { return e.data.p.expand_message_row(e, e.data.uid); }); 
     1973 
     1974            r.unread_children = 0; 
     1975            roots[roots.length] = r; 
     1976            } 
     1977          // show if it was hidden 
     1978          if (r.obj.style.display == 'none') 
     1979            $(r.obj).show(); 
     1980          } 
     1981        else { 
     1982          if (r.depth == depth) 
     1983            r.parent_uid = parent; 
     1984          if (r.unread && roots.length) { 
     1985            roots[roots.length-1].unread_children++; 
     1986            } 
     1987          } 
     1988        } 
     1989        row = row.nextSibling; 
     1990      } 
     1991     
     1992    // update unread_children for roots 
     1993    for (var i=0; i<roots.length; i++) 
     1994      this.set_unread_children(roots[i].uid); 
     1995 
     1996    return count; 
     1997  }; 
     1998 
     1999  this.delete_excessive_thread_rows = function() 
     2000  { 
     2001    var rows = this.message_list.rows;     
     2002    var tbody = this.message_list.list.tBodies[0]; 
     2003    var row = tbody.firstChild; 
     2004    var cnt = this.env.pagesize + 1; 
     2005     
     2006    while (row) { 
     2007      if (row.nodeType == 1 && (r = rows[row.uid])) { 
     2008        if (!r.depth && cnt) 
     2009          cnt--; 
     2010 
     2011        if (!cnt) 
     2012          this.message_list.remove_row(row.uid); 
     2013        } 
     2014        row = row.nextSibling; 
     2015      } 
     2016  } 
    17412017 
    17422018  // set message icon 
     
    17482024    if (!rows[uid]) 
    17492025      return false; 
    1750  
    1751     if (rows[uid].deleted && this.env.deletedicon) 
     2026    if (!rows[uid].unread && rows[uid].unread_children && this.env.unreadchildrenicon) { 
     2027      icn_src = this.env.unreadchildrenicon; 
     2028    } 
     2029    else if (rows[uid].deleted && this.env.deletedicon) 
    17522030      icn_src = this.env.deletedicon; 
    17532031    else if (rows[uid].replied && this.env.repliedicon) 
     
    17742052    else if (!rows[uid].flagged && this.env.unflaggedicon) 
    17752053      icn_src = this.env.unflaggedicon; 
    1776  
    17772054    if (rows[uid].flagged_icon && icn_src) 
    17782055      rows[uid].flagged_icon.src = icn_src; 
     
    17972074      rows[uid].flagged = status; 
    17982075 
    1799     this.env.messages[uid] = rows[uid]; 
     2076//    this.env.messages[uid] = rows[uid]; 
    18002077    } 
    18012078 
     
    18092086    if (flag) 
    18102087      this.set_message_status(uid, flag, status); 
    1811      
     2088 
    18122089    var rowobj = $(rows[uid].obj); 
    1813     if (rows[uid].unread && rows[uid].classname.indexOf('unread')<0) 
    1814       { 
    1815       rows[uid].classname += ' unread'; 
     2090 
     2091    if (rows[uid].unread && !rowobj.hasClass('unread')) 
    18162092      rowobj.addClass('unread'); 
    1817       } 
    1818     else if (!rows[uid].unread && rows[uid].classname.indexOf('unread')>=0) 
    1819       { 
    1820       rows[uid].classname = rows[uid].classname.replace(/\s*unread/, ''); 
     2093    else if (!rows[uid].unread && rowobj.hasClass('unread')) 
    18212094      rowobj.removeClass('unread'); 
    1822       } 
    1823      
    1824     if (rows[uid].deleted && rows[uid].classname.indexOf('deleted')<0) 
    1825       { 
    1826       rows[uid].classname += ' deleted'; 
     2095     
     2096    if (rows[uid].deleted && !rowobj.hasClass('deleted')) 
    18272097      rowobj.addClass('deleted'); 
    1828       } 
    1829     else if (!rows[uid].deleted && rows[uid].classname.indexOf('deleted')>=0) 
    1830       { 
    1831       rows[uid].classname = rows[uid].classname.replace(/\s*deleted/, ''); 
     2098    else if (!rows[uid].deleted && rowobj.hasClass('deleted')) 
    18322099      rowobj.removeClass('deleted'); 
    1833       } 
    1834  
    1835     if (rows[uid].flagged && rows[uid].classname.indexOf('flagged')<0) 
    1836       { 
    1837       rows[uid].classname += ' flagged'; 
     2100 
     2101    if (rows[uid].flagged && !rowobj.hasClass('flagged')) 
    18382102      rowobj.addClass('flagged'); 
    1839       } 
    1840     else if (!rows[uid].flagged && rows[uid].classname.indexOf('flagged')>=0) 
    1841       { 
    1842       rows[uid].classname = rows[uid].classname.replace(/\s*flagged/, ''); 
     2103    else if (!rows[uid].flagged && rowobj.hasClass('flagged')) 
    18432104      rowobj.removeClass('flagged'); 
    1844       } 
    1845  
     2105 
     2106    this.set_unread_children(uid); 
    18462107    this.set_message_icon(uid); 
    1847     } 
     2108    }; 
     2109 
     2110  // sets unroot (unread_children) class of parent row 
     2111  this.set_unread_children = function(uid) 
     2112    { 
     2113    var row = this.message_list.rows[uid]; 
     2114     
     2115    if (row.parent_uid || !row.has_children) 
     2116      return; 
     2117 
     2118    if (!row.unread && row.unread_children && !row.expanded) 
     2119      $(row.obj).addClass('unroot'); 
     2120    else 
     2121      $(row.obj).removeClass('unroot'); 
     2122    }; 
    18482123 
    18492124  // move selected messages to the specified mailbox 
     
    18822157 
    18832158    // if config is set to flag for deletion 
    1884     if (this.env.flag_for_deletion) 
     2159    if (this.env.flag_for_deletion) { 
    18852160      this.mark_message('delete'); 
     2161      return false; 
     2162      } 
    18862163    // if there isn't a defined trash mailbox or we are in it 
    18872164    else if (!this.env.trash_mailbox || this.env.mailbox == this.env.trash_mailbox)  
     
    18982175        this.move_messages(this.env.trash_mailbox); 
    18992176      } 
     2177 
     2178    return true; 
    19002179  }; 
    19012180 
     
    19112190    }; 
    19122191 
    1913   // Send a specifc request with UIDs of all selected messages 
     2192  // Send a specifc moveto/delete request with UIDs of all selected messages 
    19142193  // @private 
    1915   this._with_selected_messages = function(action, lock, add_url, remove) 
     2194  this._with_selected_messages = function(action, lock, add_url) 
    19162195  { 
    19172196    var a_uids = new Array(); 
     2197    var count = 0; 
    19182198 
    19192199    if (this.env.uid) 
     
    19222202    { 
    19232203      var selection = this.message_list.get_selection(); 
    1924       var rows = this.message_list.rows; 
    19252204      var id; 
    19262205      for (var n=0; n<selection.length; n++) { 
    19272206        id = selection[n]; 
    19282207        a_uids[a_uids.length] = id; 
     2208        count += this.update_thread(id); 
    19292209        this.message_list.remove_row(id, (this.env.display_next && n == selection.length-1)); 
    19302210      } 
     
    19402220    if (this.env.display_next && this.env.next_uid) 
    19412221      add_url += '&_next_uid='+this.env.next_uid; 
     2222 
     2223    if (count < 0) 
     2224      add_url += '&_count='+(count*-1); 
     2225    else if (count > 0)  
     2226      // remove threads from the end of the list 
     2227      this.delete_excessive_thread_rows(); 
    19422228 
    19432229    // send request to server 
     
    20102296 
    20112297    this.http_post('mark', '_uid='+a_uids.join(',')+'&_flag='+flag); 
     2298 
     2299    for (var i=0; i<a_uids.length; i++) 
     2300      this.update_thread_root(a_uids[i], flag); 
    20122301  }; 
    20132302 
     
    20722361    var r_uids = new Array(); 
    20732362    var rows = this.message_list ? this.message_list.rows : new Array(); 
    2074      
     2363    var count = 0; 
     2364 
    20752365    for (var i=0; i<a_uids.length; i++) 
    20762366      { 
     
    20812371          r_uids[r_uids.length] = uid; 
    20822372 
    2083         if (this.env.skip_deleted) 
     2373        if (this.env.skip_deleted) { 
     2374          count += this.update_thread(uid); 
    20842375          this.message_list.remove_row(uid, (this.env.display_next && i == this.message_list.selection.length-1)); 
     2376          } 
    20852377        else 
    20862378          this.set_message(uid, 'deleted', true); 
     
    20892381 
    20902382    // make sure there are no selected rows 
    2091     if (this.env.skip_deleted && !this.env.display_next && this.message_list) 
     2383    if (this.env.skip_deleted && this.message_list) { 
     2384      if(!this.env.display_next) 
    20922385      this.message_list.clear_selection(); 
     2386      if (count < 0) 
     2387        add_url += '&_count='+(count*-1); 
     2388      else if (count > 0)  
     2389        // remove threads from the end of the list 
     2390        this.delete_excessive_thread_rows(); 
     2391      } 
    20932392 
    20942393    add_url = '&_from='+(this.env.action ? this.env.action : ''); 
     
    21272426      } 
    21282427  }; 
    2129    
     2428 
     2429 
     2430  /*********************************************************/ 
     2431  /*********       mailbox folders methods         *********/ 
     2432  /*********************************************************/ 
     2433 
     2434  this.expunge_mailbox = function(mbox) 
     2435    { 
     2436    var lock = false; 
     2437    var add_url = ''; 
     2438     
     2439    // lock interface if it's the active mailbox 
     2440    if (mbox == this.env.mailbox) 
     2441       { 
     2442       lock = true; 
     2443       this.set_busy(true, 'loading'); 
     2444       add_url = '&_reload=1'; 
     2445       } 
     2446 
     2447    // send request to server 
     2448    var url = '_mbox='+urlencode(mbox); 
     2449    this.http_post('expunge', url+add_url, lock); 
     2450    }; 
     2451 
     2452  this.purge_mailbox = function(mbox) 
     2453    { 
     2454    var lock = false; 
     2455    var add_url = ''; 
     2456     
     2457    if (!confirm(this.get_label('purgefolderconfirm'))) 
     2458      return false; 
     2459     
     2460    // lock interface if it's the active mailbox 
     2461    if (mbox == this.env.mailbox) 
     2462       { 
     2463       lock = true; 
     2464       this.set_busy(true, 'loading'); 
     2465       add_url = '&_reload=1'; 
     2466       } 
     2467 
     2468    // send request to server 
     2469    var url = '_mbox='+urlencode(mbox); 
     2470    this.http_post('purge', url+add_url, lock); 
     2471    return true; 
     2472    }; 
     2473 
     2474  // test if purge command is allowed 
     2475  this.purge_mailbox_test = function() 
     2476  { 
     2477    return (this.env.messagecount && (this.env.mailbox == this.env.trash_mailbox || this.env.mailbox == this.env.junk_mailbox  
     2478      || this.env.mailbox.match('^' + RegExp.escape(this.env.trash_mailbox) + RegExp.escape(this.env.delimiter))  
     2479      || this.env.mailbox.match('^' + RegExp.escape(this.env.junk_mailbox) + RegExp.escape(this.env.delimiter)))); 
     2480  }; 
     2481 
    21302482   
    21312483  /*********************************************************/ 
     
    21532505  /*********************************************************/ 
    21542506   
     2507  // init message compose form: set focus and eventhandlers 
     2508  this.init_messageform = function() 
     2509  { 
     2510    if (!this.gui_objects.messageform) 
     2511      return false; 
     2512     
     2513    //this.messageform = this.gui_objects.messageform; 
     2514    var input_from = $("[name='_from']"); 
     2515    var input_to = $("[name='_to']"); 
     2516    var input_subject = $("input[name='_subject']"); 
     2517    var input_message = $("[name='_message']").get(0); 
     2518    var html_mode = $("input[name='_is_html']").val() == '1'; 
     2519 
     2520    // init live search events 
     2521    this.init_address_input_events(input_to); 
     2522    this.init_address_input_events($("[name='_cc']")); 
     2523    this.init_address_input_events($("[name='_bcc']")); 
     2524     
     2525    if (!html_mode) 
     2526      this.set_caret_pos(input_message, this.env.top_posting ? 0 : $(input_message).val().length); 
     2527 
     2528    // add signature according to selected identity 
     2529    if (input_from.attr('type') == 'select-one' && $("input[name='_draft_saveid']").val() == '' 
     2530        && !html_mode) {  // if we have HTML editor, signature is added in callback 
     2531      this.change_identity(input_from[0]); 
     2532    } 
     2533    else if (!html_mode)  
     2534      this.set_caret_pos(input_message, this.env.top_posting ? 0 : $(input_message).val().length); 
     2535 
     2536    if (input_to.val() == '') 
     2537      input_to.focus(); 
     2538    else if (input_subject.val() == '') 
     2539      input_subject.focus(); 
     2540    else if (input_message && !html_mode) 
     2541      input_message.focus(); 
     2542 
     2543    // get summary of all field values 
     2544    this.compose_field_hash(true); 
     2545  
     2546    // start the auto-save timer 
     2547    this.auto_save_start(); 
     2548  }; 
     2549 
     2550  this.init_address_input_events = function(obj) 
     2551  { 
     2552    var handler = function(e){ return ref.ksearch_keypress(e,this); }; 
     2553    obj.bind((bw.safari || bw.ie ? 'keydown' : 'keypress'), handler); 
     2554    obj.attr('autocomplete', 'off'); 
     2555  }; 
     2556 
    21552557  // checks the input fields before sending a message 
    21562558  this.check_compose_input = function() 
    2157     { 
     2559  { 
    21582560    // check input fields 
    21592561    var input_to = $("[name='_to']"); 
     
    21902592     
    21912593    // display localized warning for missing subject 
    2192     if (input_subject.val() == '') 
    2193       { 
     2594    if (input_subject.val() == '') { 
    21942595      var subject = prompt(this.get_label('nosubjectwarning'), this.get_label('nosubject')); 
    21952596 
    21962597      // user hit cancel, so don't send 
    2197       if (!subject && subject !== '') 
    2198         { 
     2598      if (!subject && subject !== '') { 
    21992599        input_subject.focus(); 
    22002600        return false; 
    2201         } 
     2601      } 
    22022602      else 
    2203         { 
    22042603        input_subject.val((subject ? subject : this.get_label('nosubject'))); 
    2205         } 
    2206       } 
     2604    } 
    22072605 
    22082606    // check for empty body 
    22092607    if ((!window.tinyMCE || !tinyMCE.get(this.env.composebody)) 
    2210         && input_message.val() == '' && !confirm(this.get_label('nobodywarning'))) 
    2211       { 
     2608        && input_message.val() == '' && !confirm(this.get_label('nobodywarning'))) { 
    22122609      input_message.focus(); 
    22132610      return false; 
    2214       } 
     2611    } 
    22152612    else if (window.tinyMCE && tinyMCE.get(this.env.composebody) 
    2216         && !tinyMCE.get(this.env.composebody).getContent() 
    2217         && !confirm(this.get_label('nobodywarning'))) 
    2218       { 
     2613        && !tinyMCE.get(this.env.composebody).getContent() 
     2614        && !confirm(this.get_label('nobodywarning'))) { 
    22192615      tinyMCE.get(this.env.composebody).focus(); 
    22202616      return false; 
    2221       } 
     2617    } 
    22222618 
    22232619    // Apply spellcheck changes if spell checker is active 
     
    22292625 
    22302626    return true; 
    2231     }; 
     2627  }; 
    22322628 
    22332629  this.stop_spellchecking = function() 
    2234     { 
     2630  { 
    22352631    if (this.env.spellcheck && !this.spellcheck_ready) { 
    22362632      $(this.env.spellcheck.spell_span).trigger('click'); 
    22372633      this.set_spellcheck_state('ready'); 
    2238       } 
    2239     }; 
     2634    } 
     2635  }; 
    22402636 
    22412637  this.display_spellcheck_controls = function(vis) 
    2242     { 
     2638  { 
    22432639    if (this.env.spellcheck) { 
    22442640      // stop spellchecking process 
    22452641      if (!vis) 
    2246         this.stop_spellchecking(); 
     2642        this.stop_spellchecking(); 
    22472643 
    22482644      $(this.env.spellcheck.spell_container).css('visibility', vis ? 'visible' : 'hidden'); 
    22492645      } 
    2250     }; 
     2646  }; 
    22512647 
    22522648  this.set_spellcheck_state = function(s) 
     
    35123908      this.http_post('unsubscribe', '_mbox='+urlencode(folder)); 
    35133909    }; 
    3514      
     3910 
     3911  this.enable_threading = function(folder) 
     3912    { 
     3913    if (folder) 
     3914      this.http_post('enable-threading', '_mbox='+urlencode(folder)); 
     3915    }; 
     3916 
     3917  this.disable_threading = function(folder) 
     3918    { 
     3919    if (folder) 
     3920      this.http_post('disable-threading', '_mbox='+urlencode(folder)); 
     3921    }; 
     3922     
     3923 
    35153924  // helper method to find a specific mailbox row ID 
    35163925  this.get_folder_row_id = function(folder) 
     
    38174226  }; 
    38184227 
    3819   // for reordering column array, Konqueror workaround 
    3820   this.set_message_coltypes = function(coltypes)  
     4228  // for reordering column array (Konqueror workaround) 
     4229  // and for setting some message list global variables 
     4230  this.set_message_coltypes = function(coltypes, repl) 
    38214231  {  
    3822     this.coltypes = coltypes; 
     4232    this.env.coltypes = coltypes; 
    38234233     
    38244234    // set correct list titles 
    3825     var cell, col; 
    38264235    var thead = this.gui_objects.messagelist ? this.gui_objects.messagelist.tHead : null; 
    3827     for (var n=0; thead && n<this.coltypes.length; n++)  
    3828       { 
    3829       col = this.coltypes[n]; 
     4236 
     4237    // replace old column headers 
     4238    if (thead && repl) { 
     4239      for (var cell, c=0; c < repl.length; c++) { 
     4240        cell = thead.rows[0].cells[c]; 
     4241        if (!cell) { 
     4242          cell = document.createElement('td'); 
     4243          thead.rows[0].appendChild(cell); 
     4244        } 
     4245        cell.innerHTML = repl[c].html; 
     4246        if (repl[c].id) cell.id = repl[c].id; 
     4247        if (repl[c].className) cell.className = repl[c].className; 
     4248      } 
     4249    } 
     4250 
     4251    var cell, col, n; 
     4252    for (n=0; thead && n<this.env.coltypes.length; n++) 
     4253      { 
     4254      col = this.env.coltypes[n]; 
    38304255      if ((cell = thead.rows[0].cells[n+1]) && (col=='from' || col=='to')) 
    38314256        { 
     
    38334258        if (cell.firstChild && cell.firstChild.tagName.toLowerCase()=='a') 
    38344259          { 
    3835           cell.firstChild.innerHTML = this.get_label(this.coltypes[n]); 
     4260          cell.firstChild.innerHTML = this.get_label(this.env.coltypes[n]); 
    38364261          cell.firstChild.onclick = function(){ return rcmail.command('sort', this.__col, this); }; 
    38374262          cell.firstChild.__col = col; 
    38384263          } 
    38394264        else 
    3840           cell.innerHTML = this.get_label(this.coltypes[n]); 
     4265          cell.innerHTML = this.get_label(this.env.coltypes[n]); 
    38414266 
    38424267        cell.id = 'rcm'+col; 
    38434268        } 
    3844       else if (col == 'subject' && this.message_list) 
    3845         this.message_list.subject_col = n+1; 
    3846       } 
    3847   }; 
    3848  
    3849   // create a table row in the message list 
    3850   this.add_message_row = function(uid, cols, flags, attachment, attop) 
    3851     { 
    3852     if (!this.gui_objects.messagelist || !this.message_list) 
    3853       return false; 
    3854  
    3855     if (this.message_list.background) 
    3856       var tbody = this.message_list.background; 
    3857     else 
    3858       var tbody = this.gui_objects.messagelist.tBodies[0]; 
    3859      
    3860     var rowcount = tbody.rows.length; 
    3861     var even = rowcount%2; 
    3862      
    3863     this.env.messages[uid] = { 
    3864       deleted: flags.deleted?1:0, 
    3865       replied: flags.replied?1:0, 
    3866       unread: flags.unread?1:0, 
    3867       forwarded: flags.forwarded?1:0, 
    3868       flagged:flags.flagged?1:0 
    3869     }; 
    3870  
    3871     var css_class = 'message' 
    3872         + (even ? ' even' : ' odd') 
    3873         + (flags.unread ? ' unread' : '') 
    3874         + (flags.deleted ? ' deleted' : '') 
    3875         + (flags.flagged ? ' flagged' : '') 
    3876         + (this.message_list.in_selection(uid) ? ' selected' : ''); 
    3877  
    3878     // for performance use DOM instead of jQuery here 
    3879     var row = document.createElement('tr'); 
    3880     row.id = 'rcmrow'+uid; 
    3881     row.className = css_class; 
    3882      
    3883     var icon = this.env.messageicon; 
    3884     if (flags.deleted && this.env.deletedicon) 
    3885       icon = this.env.deletedicon; 
    3886     else if (flags.replied && this.env.repliedicon) 
    3887       { 
    3888       if (flags.forwarded && this.env.forwardedrepliedicon) 
    3889         icon = this.env.forwardedrepliedicon; 
    3890       else 
    3891         icon = this.env.repliedicon; 
    3892       } 
    3893     else if (flags.forwarded && this.env.forwardedicon) 
    3894       icon = this.env.forwardedicon; 
    3895     else if(flags.unread && this.env.unreadicon) 
    3896       icon = this.env.unreadicon; 
    3897      
    3898     // add icon col 
    3899     var col = document.createElement('td'); 
    3900     col.className = 'icon'; 
    3901     col.innerHTML = icon ? '<img src="'+icon+'" alt="" />' : ''; 
    3902     row.appendChild(col); 
    3903  
    3904     // build subject link 
    3905     if (!bw.ie && cols.subject) { 
    3906       var action = cols.mbox == this.env.drafts_mailbox ? 'compose' : 'show'; 
    3907       var uid_param = cols.mbox == this.env.drafts_mailbox ? '_draft_uid' : '_uid'; 
    3908       cols.subject = '<a href="./?_task=mail&_action='+action+'&_mbox='+urlencode(cols.mbox)+'&'+uid_param+'='+uid+'"'+ 
    3909          ' onclick="return rcube_event.cancel(event)">'+cols.subject+'</a>'; 
    3910     } 
    3911  
    3912     // add each submitted col 
    3913     for (var n = 0; n < this.coltypes.length; n++) { 
    3914       var c = this.coltypes[n]; 
    3915       col = document.createElement('td'); 
    3916       col.className = String(c).toLowerCase(); 
    3917              
    3918       if (c=='flag') { 
    3919         if (flags.flagged && this.env.flaggedicon) 
    3920           col.innerHTML = '<img src="'+this.env.flaggedicon+'" alt="" />'; 
    3921         else if(!flags.flagged && this.env.unflaggedicon) 
    3922           col.innerHTML = '<img src="'+this.env.unflaggedicon+'" alt="" />'; 
    3923         } 
    3924       else if (c=='attachment') 
    3925         col.innerHTML = (attachment && this.env.attachmenticon ? '<img src="'+this.env.attachmenticon+'" alt="" />' : '&nbsp;'); 
    3926       else 
    3927         col.innerHTML = cols[c]; 
    3928  
    3929       row.appendChild(col); 
    3930       } 
    3931  
    3932     this.message_list.insert_row(row, attop); 
    3933  
    3934     // remove 'old' row 
    3935     if (attop && this.env.pagesize && this.message_list.rowcount > this.env.pagesize) { 
    3936       var uid = this.message_list.get_last_row(); 
    3937       this.message_list.remove_row(uid); 
    3938       this.message_list.clear_selection(uid); 
    3939       } 
    3940     }; 
    3941  
    3942   // messages list handling in background (for performance) 
    3943   this.offline_message_list = function(flag) 
    3944     { 
     4269      } 
     4270 
     4271    // remove excessive columns 
     4272    for (var i=n+1; thead && i<thead.rows[0].cells.length; i++) 
     4273      thead.rows[0].removeChild(thead.rows[0].cells[i]); 
     4274 
     4275    this.env.subject_col = null; 
     4276    this.env.flagged_col = null; 
     4277 
     4278    var found; 
     4279    if((found = find_in_array('subject', this.env.coltypes)) >= 0) { 
     4280      this.set_env('subject_col', found); 
    39454281      if (this.message_list) 
    3946         this.message_list.set_background_mode(flag); 
    3947     }; 
     4282        this.message_list.subject_col = found+1; 
     4283      } 
     4284    if((found = find_in_array('flag', this.env.coltypes)) >= 0) 
     4285      this.set_env('flagged_col', found); 
     4286  }; 
    39484287 
    39494288  // replace content of row count display 
     
    42614600        this.triggerEvent(response.callbacks[i][0], response.callbacks[i][1]); 
    42624601    } 
    4263   
     4602 
    42644603    // process the response data according to the sent action 
    42654604    switch (response.action) { 
     
    42894628          this.enable_command('show', 'reply', 'reply-all', 'forward', 'moveto', 'delete',  
    42904629            'mark', 'viewsource', 'open', 'edit', 'download', 'print', 'load-attachment',  
    4291             'purge', 'expunge', 'select-all', 'select-none', 'sort', false); 
     4630            'purge', 'expunge', 'select-all', 'select-none', 'sort', 
     4631            'expand-all', 'expand-unread', 'collapse-all', false); 
    42924632        } 
    42934633        break; 
     
    42954635      case 'check-recent': 
    42964636      case 'getunread': 
     4637      case 'search': 
    42974638      case 'list': 
    42984639        if (this.task == 'mail') { 
    4299           if (this.message_list && response.action == 'list') 
     4640          if (this.message_list && (response.action == 'list' || response.action == 'search')) { 
    43004641            this.msglist_select(this.message_list); 
     4642            this.expand_threads(); 
     4643          } 
    43014644          this.enable_command('show', 'expunge', 'select-all', 'select-none', 'sort', (this.env.messagecount > 0)); 
    43024645          this.enable_command('purge', this.purge_mailbox_test()); 
    4303            
     4646          
     4647          this.enable_command('expand-all', 'expand-unread', 'collapse-all', this.env.threading && this.env.messagecount); 
     4648 
    43044649          if (response.action == 'list') 
    43054650            this.triggerEvent('listupdate', { folder:this.env.mailbox, rowcount:this.message_list.rowcount }); 
     
    43334678    this.http_request('keep-alive', '_t='+d.getTime()); 
    43344679    }; 
     4680 
     4681  // start interval for keep-alive/recent_check signal 
     4682  this.start_keepalive = function() 
     4683    { 
     4684    if (this.env.keep_alive && !this.env.framed && this.task=='mail' && this.gui_objects.mailboxlist) 
     4685      this._int = setInterval(function(){ ref.check_for_recent(false); }, this.env.keep_alive * 1000); 
     4686    else if (this.env.keep_alive && !this.env.framed && this.task!='login') 
     4687      this._int = setInterval(function(){ ref.send_keep_alive(); }, this.env.keep_alive * 1000); 
     4688    } 
    43354689 
    43364690  // send periodic request to check for recent messages 
     
    44344788}  // end object rcube_webmail 
    44354789 
    4436  
    44374790// copy event engine prototype 
    44384791rcube_webmail.prototype.addEventListener = rcube_event_engine.prototype.addEventListener; 
  • trunk/roundcubemail/program/js/common.js

    r3310 r3367  
    293293  else if (typeof e == 'object') 
    294294    e.event = evt; 
    295    
     295 
    296296  if (this._events && this._events[evt] && !this._event_exec) { 
    297297    this._event_exec = true; 
  • trunk/roundcubemail/program/js/list.js

    r3055 r3367  
    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; 
     
    109110    var uid = RegExp.$1; 
    110111    row.uid = uid; 
    111     this.rows[uid] = {uid:uid, id:row.id, obj:row, classname:row.className}; 
     112    this.rows[uid] = {uid:uid, id:row.id, obj:row}; 
    112113 
    113114    // set eventhandlers to table row 
     
    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 
     329  // Don't select this message 
     330  this.dont_select = true; 
     331  // Don't treat double click on the expando as double click on the message. 
     332  row.clicked = 0; 
     333 
     334  if (row.expanded) { 
     335    evtarget.className = "collapsed"; 
     336    if (mod_key == CONTROL_KEY || this.multiexpand) 
     337      this.collapse_all(row); 
     338    else 
     339      this.collapse(row); 
     340  } 
     341  else { 
     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  this.triggerEvent('expandcollapse', { uid:row.uid, expanded:row.expanded }); 
     354  var depth = row.depth; 
     355  var new_row = row ? row.obj.nextSibling : null; 
     356  var r; 
     357 
     358  while (new_row) { 
     359    if (new_row.nodeType == 1) { 
     360      var r = this.rows[new_row.uid]; 
     361      if (r && r.depth <= depth) 
     362        break; 
     363      $(new_row).hide(); 
     364      r.expanded = false; 
     365      this.triggerEvent('expandcollapse', { uid:r.uid, expanded:r.expanded }); 
     366    } 
     367    new_row = new_row.nextSibling; 
     368  } 
     369 
     370  return false; 
     371}, 
     372 
     373expand: function(row) 
     374{ 
     375  var depth, new_row; 
     376  var last_expanded_parent_depth; 
     377 
     378  if (row) { 
     379    row.expanded = true; 
     380    depth = row.depth; 
     381    new_row = row.obj.nextSibling; 
     382    this.triggerEvent('expandcollapse', { uid:row.uid, expanded:row.expanded }); 
     383  } 
     384  else { 
     385    var tbody = this.list.tBodies[0]; 
     386    new_row = tbody.firstChild; 
     387    depth = 0; 
     388    last_expanded_parent_depth = 0; 
     389  } 
     390 
     391  while (new_row) { 
     392    if (new_row.nodeType == 1) { 
     393      var r = this.rows[new_row.uid]; 
     394      if (r) { 
     395        if (row && (!r.depth || r.depth <= depth)) 
     396          break; 
     397 
     398        if (r.parent_uid) { 
     399          var p = this.rows[r.parent_uid]; 
     400          if (p && p.expanded) { 
     401            if ((row && p == row) || last_expanded_parent_depth >= p.depth - 1) { 
     402              last_expanded_parent_depth = p.depth; 
     403              $(new_row).show(); 
     404              r.expanded = true; 
     405              this.triggerEvent('expandcollapse', { uid:r.uid, expanded:r.expanded }); 
     406            } 
     407          } 
     408          else 
     409            if (row && (! p || p.depth <= depth)) 
     410              break; 
     411        } 
     412      } 
     413    } 
     414    new_row = new_row.nextSibling; 
     415  } 
     416 
     417  return false; 
     418}, 
     419 
     420 
     421collapse_all: function(row) 
     422{ 
     423  var depth, new_row; 
     424  var r; 
     425 
     426  if (row) { 
     427    row.expanded = false; 
     428    depth = row.depth; 
     429    new_row = row.obj.nextSibling; 
     430    this.triggerEvent('expandcollapse', { uid:row.uid, expanded:row.expanded }); 
     431     
     432    // don't collapse sub-root tree in multiexpand mode  
     433    if (depth && this.multiexpand) 
     434      return false;  
     435  } 
     436  else { 
     437    var tbody = this.list.tBodies[0]; 
     438    new_row = tbody.firstChild; 
     439    depth = 0; 
     440  } 
     441 
     442  while (new_row) { 
     443    if (new_row.nodeType == 1) { 
     444      var r = this.rows[new_row.uid]; 
     445      if (r) { 
     446        if (row && (!r.depth || r.depth <= depth)) 
     447          break; 
     448 
     449        if (row || r.depth) 
     450          $(new_row).hide(); 
     451        if (r.has_children) { 
     452          r.expanded = false; 
     453          var expando = document.getElementById('rcmexpando' + r.uid); 
     454          if (expando) 
     455            expando.className = 'collapsed'; 
     456          this.triggerEvent('expandcollapse', { uid:r.uid, expanded:r.expanded }); 
     457        } 
     458      } 
     459    } 
     460    new_row = new_row.nextSibling; 
     461  } 
     462 
     463  return false; 
     464}, 
     465 
     466expand_all: function(row) 
     467{ 
     468  var depth, new_row; 
     469  var r; 
     470 
     471  if (row) { 
     472    row.expanded = true; 
     473    depth = row.depth; 
     474    new_row = row.obj.nextSibling; 
     475    this.triggerEvent('expandcollapse', { uid:row.uid, expanded:row.expanded }); 
     476  } 
     477  else { 
     478    var tbody = this.list.tBodies[0]; 
     479    new_row = tbody.firstChild; 
     480    depth = 0; 
     481  } 
     482 
     483  while (new_row) { 
     484    if (new_row.nodeType == 1) { 
     485      var r = this.rows[new_row.uid]; 
     486      if (r) { 
     487        if (row && r.depth <= depth) 
     488          break; 
     489 
     490        $(new_row).show(); 
     491        if (r.has_children) { 
     492          r.expanded = true; 
     493          var expando = document.getElementById('rcmexpando' + r.uid); 
     494          if (expando) 
     495            expando.className = 'expanded'; 
     496          this.triggerEvent('expandcollapse', { uid:r.uid, expanded:r.expanded }); 
     497        } 
     498      } 
     499    } 
     500    new_row = new_row.nextSibling; 
     501  } 
     502  return false; 
     503}, 
     504 
    322505/** 
    323506 * get first/next/previous/last rows that are not hidden 
     
    496679    if ((this.rows[n].obj.rowIndex >= i) && (this.rows[n].obj.rowIndex <= j)) 
    497680    { 
    498       if (!this.in_selection(n)) 
     681      if (!this.in_selection(n)) { 
    499682        this.highlight_row(n, true); 
     683      } 
    500684    } 
    501685    else 
    502686    { 
    503       if  (this.in_selection(n) && !control) 
     687      if  (this.in_selection(n) && !control) { 
    504688        this.highlight_row(n, true); 
     689      } 
    505690    } 
    506691  } 
     
    517702      return true; 
    518703 
    519   return false;     
     704  return false; 
    520705}, 
    521706 
     
    568753   
    569754  for (var n in this.rows) 
    570     this.highlight_row(n, true);     
     755    this.highlight_row(n, true); 
    571756 
    572757  // trigger event if selection changed 
     
    686871      rcube_event.cancel(e); 
    687872      return this.use_arrow_key(keyCode, mod_key); 
     873    case 61: 
     874    case 107: // Plus sign on a numeric keypad (fc11 + firefox 3.5.2) 
     875    case 109: 
     876    case 32: 
     877      // Stop propagation 
     878      rcube_event.cancel(e); 
     879      var ret = this.use_plusminus_key(keyCode, mod_key); 
     880      this.key_pressed = keyCode; 
     881      this.triggerEvent('keypress'); 
     882      return ret; 
    688883    default: 
    689884      this.shiftkey = e.shiftKey; 
     
    713908    case 63233: 
    714909    case 63232: 
     910    case 61: 
     911    case 107: 
     912    case 109: 
     913    case 32: 
    715914      if (!rcube_event.get_modifier(e) && this.focused) 
    716915        return rcube_event.cancel(e); 
     
    747946 
    748947/** 
     948 * Special handling method for +/- keys 
     949 */ 
     950use_plusminus_key: function(keyCode, mod_key) 
     951{ 
     952  var selected_row = this.rows[this.last_selected]; 
     953  if (!selected_row) 
     954    return; 
     955 
     956  if (keyCode == 32) 
     957    keyCode = selected_row.expanded ? 109 : 61; 
     958  if (keyCode == 61 || keyCode == 107) 
     959    if (mod_key == CONTROL_KEY || this.multiexpand) 
     960      this.expand_all(selected_row); 
     961    else 
     962     this.expand(selected_row); 
     963  else 
     964    if (mod_key == CONTROL_KEY || this.multiexpand) 
     965      this.collapse_all(selected_row); 
     966    else 
     967      this.collapse(selected_row); 
     968 
     969  var expando = document.getElementById('rcmexpando' + selected_row.uid); 
     970  if (expando) 
     971    expando.className = selected_row.expanded?'expanded':'collapsed'; 
     972 
     973  return false; 
     974}, 
     975 
     976 
     977/** 
    749978 * Try to scroll the list to make the specified row visible 
    750979 */ 
     
    7801009      this.draglayer = $('<div>').attr('id', 'rcmdraglayer').css({ position:'absolute', display:'none', 'z-index':2000 }).appendTo(document.body); 
    7811010 
    782     // get subjects of selectedd messages 
     1011    // get subjects of selected messages 
    7831012    var names = ''; 
    784     var c, i, node, subject, obj; 
     1013    var c, i, subject, obj; 
    7851014    for(var n=0; n<this.selection.length; n++) 
    7861015    { 
     
    7911020      } 
    7921021 
    793       if (this.rows[this.selection[n]].obj) 
     1022      if (obj = this.rows[this.selection[n]].obj) 
    7941023      { 
    795         obj = this.rows[this.selection[n]].obj; 
    7961024        subject = ''; 
    7971025 
    798         for(c=0, i=0; i<obj.childNodes.length; i++) 
     1026        for (c=0, i=0; i<obj.childNodes.length; i++) 
    7991027        { 
    800           if (obj.childNodes[i].nodeName == 'TD') 
     1028          if (obj.childNodes[i].nodeName == 'TD') 
    8011029          { 
    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(); 
     1030            if (n == 0) 
     1031              this.drag_start_pos = $(obj.childNodes[i]).offset(); 
     1032 
     1033            if (this.subject_col < 0 || (this.subject_col >= 0 && this.subject_col == c)) 
     1034            { 
     1035              var node, tmp_node, nodes = obj.childNodes[i].childNodes; 
     1036              // find text node 
     1037              for (m=0; m<nodes.length; m++) { 
     1038                if ((tmp_node = obj.childNodes[i].childNodes[m]) && (tmp_node.nodeType==3 || tmp_node.nodeName=='A')) 
     1039                  node = tmp_node; 
    8101040              } 
     1041               
     1042              if (!node) 
     1043                break; 
     1044 
    8111045              subject = node.nodeType==3 ? node.data : node.innerHTML; 
    8121046              // remove leading spaces 
  • trunk/roundcubemail/program/lib/imap.inc

    r3353 r3367  
    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 
     
    874866        $fields = array('ARRIVAL' => 1,'CC' => 1,'DATE' => 1, 
    875867        'FROM' => 1, 'SIZE' => 1, 'SUBJECT' => 1, 'TO' => 1); 
    876          
     868 
    877869        if (!$fields[$field]) { 
    878870            return false; 
     
    886878        $is_uid = $is_uid ? 'UID ' : ''; 
    887879         
    888         if (!empty($add)) { 
     880        // message IDs 
     881        if (is_array($add)) 
     882                $add = iil_CompressMessageSet(join(',', $add)); 
     883 
     884        if (!empty($add)) 
    889885            $add = " $add"; 
    890         } 
    891886 
    892887        $command  = 's ' . $is_uid . 'SORT (' . $field . ') '; 
     
    918913function iil_C_FetchHeaderIndex(&$conn, $mailbox, $message_set, $index_field='', $skip_deleted=true, $uidfetch=false) { 
    919914 
    920         list($from_idx, $to_idx) = explode(':', $message_set); 
    921         if (empty($message_set) || 
    922                 (isset($to_idx) && $to_idx != '*' && (int)$from_idx > (int)$to_idx)) { 
    923                 return false; 
    924         } 
    925  
     915        if (is_array($message_set)) { 
     916                if (!($message_set = iil_CompressMessageSet(join(',', $message_set)))) 
     917                        return false; 
     918        } else { 
     919                list($from_idx, $to_idx) = explode(':', $message_set); 
     920                if (empty($message_set) || 
     921                        (isset($to_idx) && $to_idx != '*' && (int)$from_idx > (int)$to_idx)) { 
     922                        return false; 
     923                } 
     924        } 
     925         
    926926        $index_field = empty($index_field) ? 'DATE' : strtoupper($index_field); 
    927927         
    928928        $fields_a['DATE']         = 1; 
    929929        $fields_a['INTERNALDATE'] = 4; 
     930        $fields_a['ARRIVAL']      = 4; 
    930931        $fields_a['FROM']         = 1; 
    931932        $fields_a['REPLY-TO']     = 1; 
    932933        $fields_a['SENDER']       = 1; 
    933934        $fields_a['TO']           = 1; 
     935        $fields_a['CC']           = 1; 
    934936        $fields_a['SUBJECT']      = 1; 
    935937        $fields_a['UID']          = 2; 
     
    10321034        } while (!iil_StartsWith($line, $key, true)); 
    10331035 
    1034 /* 
    1035         //check number of elements... 
    1036         if (is_numeric($from_idx) && is_numeric($to_idx)) { 
    1037                 //count how many we should have 
    1038                 $should_have = $to_idx - $from_idx + 1; 
    1039                  
    1040                 //if we have less, try and fill in the "gaps" 
    1041                 if (count($result) < $should_have) { 
    1042                         for ($i=$from_idx; $i<=$to_idx; $i++) { 
    1043                                 if (!isset($result[$i])) { 
    1044                                         $result[$i] = ''; 
    1045                                 } 
    1046                         } 
    1047                 } 
    1048         } 
    1049 */ 
    10501036        return $result;  
    10511037} 
     
    11231109} 
    11241110 
    1125 function iil_SortThreadHeaders($headers, $index_a, $uids) { 
    1126         asort($index_a); 
    1127         $result = array(); 
    1128         foreach ($index_a as $mid=>$foobar) { 
    1129                 $uid = $uids[$mid]; 
    1130                 $result[$uid] = $headers[$uid]; 
    1131         } 
    1132         return $result; 
    1133 } 
    1134  
    1135 function iil_C_FetchThreadHeaders(&$conn, $mailbox, $message_set) { 
    1136         global $clock; 
    1137         global $index_a; 
    1138          
    1139         list($from_idx, $to_idx) = explode(':', $message_set); 
    1140         if (empty($message_set) || (isset($to_idx) 
    1141         && (int)$from_idx > (int)$to_idx)) { 
    1142                 return false; 
    1143         } 
    1144  
    1145         $result = array(); 
    1146         $uids   = iil_C_FetchUIDs($conn, $mailbox); 
    1147         $debug  = false; 
    1148          
    1149         $message_set = iil_CompressMessageSet($message_set); 
    1150      
    1151         /* if we're missing any, get them */ 
    1152         if ($message_set) { 
    1153                 /* FETCH date,from,subject headers */ 
    1154                 $key        = 'fh'; 
    1155                 $fp         = $conn->fp; 
    1156                 $request    = $key . " FETCH $message_set "; 
    1157                 $request   .= "(BODY.PEEK[HEADER.FIELDS (SUBJECT MESSAGE-ID IN-REPLY-TO)])"; 
    1158                 $mid_to_id  = array(); 
    1159                 if (!iil_PutLine($fp, $request)) { 
    1160                     return false; 
    1161                 } 
    1162                 do { 
    1163                         $line = chop(iil_ReadLine($fp, 1024)); 
    1164                         if ($debug) { 
    1165                             echo $line . "\n"; 
    1166                         } 
    1167                         if (preg_match('/\{[0-9]+\}$/', $line)) { 
    1168                                 $a       = explode(' ', $line); 
    1169                                 $new = array(); 
    1170  
    1171                                 $new_thhd = new iilThreadHeader; 
    1172                                 $new_thhd->id = $a[1]; 
    1173                                 do { 
    1174                                         $line = chop(iil_ReadLine($fp, 1024), "\r\n"); 
    1175                                         if (iil_StartsWithI($line, 'Message-ID:') 
    1176                                                 || (iil_StartsWithI($line,'In-Reply-To:')) 
    1177                                                 || (iil_StartsWithI($line,'SUBJECT:'))) { 
    1178  
    1179                                                 $pos        = strpos($line, ':'); 
    1180                                                 $field_name = substr($line, 0, $pos); 
    1181                                                 $field_val  = substr($line, $pos+1); 
    1182  
    1183                                                 $new[strtoupper($field_name)] = trim($field_val); 
    1184  
    1185                                         } else if (preg_match('/^\s+/', $line)) { 
    1186                                                 $new[strtoupper($field_name)] .= trim($line); 
    1187                                         } 
    1188                                 } while ($line[0] != ')'); 
    1189                  
    1190                                 $new_thhd->sbj = $new['SUBJECT']; 
    1191                                 $new_thhd->mid = substr($new['MESSAGE-ID'], 1, -1); 
    1192                                 $new_thhd->irt = substr($new['IN-REPLY-TO'], 1, -1); 
    1193                                  
    1194                                 $result[$uids[$new_thhd->id]] = $new_thhd; 
    1195                         } 
    1196                 } while (!iil_StartsWith($line, 'fh')); 
    1197         } 
    1198          
    1199         /* sort headers */ 
    1200         if (is_array($index_a)) { 
    1201                 $result = iil_SortThreadHeaders($result, $index_a, $uids);       
    1202         } 
    1203          
    1204         //echo 'iil_FetchThreadHeaders:'."\n"; 
    1205         //print_r($result); 
    1206          
    1207         return $result; 
    1208 } 
    1209  
    1210 function iil_C_BuildThreads2(&$conn, $mailbox, $message_set, &$clock) { 
    1211         global $index_a; 
    1212  
    1213         list($from_idx, $to_idx) = explode(':', $message_set); 
    1214         if (empty($message_set) || (isset($to_idx) 
    1215                 && (int)$from_idx > (int)$to_idx)) { 
    1216                 return false; 
    1217         } 
    1218      
    1219         $result    = array(); 
    1220         $roots     = array(); 
    1221         $root_mids = array(); 
    1222         $sub_mids  = array(); 
    1223         $strays    = array(); 
    1224         $messages  = array(); 
    1225         $fp        = $conn->fp; 
    1226         $debug     = false; 
    1227          
    1228         $sbj_filter_pat = '/[a-z]{2,3}(\[[0-9]*\])?:(\s*)/i'; 
    1229          
    1230         /*  Do "SELECT" command */ 
    1231         if (!iil_C_Select($conn, $mailbox)) { 
    1232             return false; 
    1233         } 
    1234      
    1235         /* FETCH date,from,subject headers */ 
    1236         $mid_to_id = array(); 
    1237         $messages  = array(); 
    1238         $headers   = iil_C_FetchThreadHeaders($conn, $mailbox, $message_set); 
    1239         if ($clock) { 
    1240             $clock->register('fetched headers'); 
    1241         } 
    1242      
    1243         if ($debug) { 
    1244             print_r($headers); 
    1245         } 
    1246      
    1247         /* go through header records */ 
    1248         foreach ($headers as $header) { 
    1249                 //$id = $header['i']; 
    1250                 //$new = array('id'=>$id, 'MESSAGE-ID'=>$header['m'],  
    1251                 //                      'IN-REPLY-TO'=>$header['r'], 'SUBJECT'=>$header['s']); 
    1252                 $id  = $header->id; 
    1253                 $new = array('id' => $id, 'MESSAGE-ID' => $header->mid,  
    1254                         'IN-REPLY-TO' => $header->irt, 'SUBJECT' => $header->sbj); 
    1255  
    1256                 /* add to message-id -> mid lookup table */ 
    1257                 $mid_to_id[$new['MESSAGE-ID']] = $id; 
    1258                  
    1259                 /* if no subject, use message-id */ 
    1260                 if (empty($new['SUBJECT'])) { 
    1261                     $new['SUBJECT'] = $new['MESSAGE-ID']; 
    1262                 } 
    1263          
    1264                 /* if subject contains 'RE:' or has in-reply-to header, it's a reply */ 
    1265                 $sbj_pre = ''; 
    1266                 $has_re = false; 
    1267                 if (preg_match($sbj_filter_pat, $new['SUBJECT'])) { 
    1268                     $has_re = true; 
    1269                 } 
    1270                 if ($has_re || $new['IN-REPLY-TO']) { 
    1271                     $sbj_pre = 'RE:'; 
    1272                 } 
    1273          
    1274                 /* strip out 're:', 'fw:' etc */ 
    1275                 if ($has_re) { 
    1276                     $sbj = preg_replace($sbj_filter_pat, '', $new['SUBJECT']); 
    1277                 } else { 
    1278                     $sbj = $new['SUBJECT']; 
    1279                 } 
    1280                 $new['SUBJECT'] = $sbj_pre.$sbj; 
    1281                  
    1282                  
    1283                 /* if subject not a known thread-root, add to list */ 
    1284                 if ($debug) { 
    1285                     echo $id . ' ' . $new['SUBJECT'] . "\t" . $new['MESSAGE-ID'] . "\n"; 
    1286                 } 
    1287                 $root_id = $roots[$sbj]; 
    1288                  
    1289                 if ($root_id && ($has_re || !$root_in_root[$root_id])) { 
    1290                         if ($debug) { 
    1291                             echo "\tfound root: $root_id\n"; 
    1292                         } 
    1293                         $sub_mids[$new['MESSAGE-ID']] = $root_id; 
    1294                         $result[$root_id][]           = $id; 
    1295                 } else if (!isset($roots[$sbj]) || (!$has_re && $root_in_root[$root_id])) { 
    1296                         /* try to use In-Reply-To header to find root  
    1297                                 unless subject contains 'Re:' */ 
    1298                         if ($has_re&&$new['IN-REPLY-TO']) { 
    1299                                 if ($debug) { 
    1300                                     echo "\tlooking: ".$new['IN-REPLY-TO']."\n"; 
    1301                                 } 
    1302                                 //reply to known message? 
    1303                                 $temp = $sub_mids[$new['IN-REPLY-TO']]; 
    1304                                  
    1305                                 if ($temp) { 
    1306                                         //found it, root:=parent's root 
    1307                                         if ($debug) { 
    1308                                             echo "\tfound parent: ".$new['SUBJECT']."\n"; 
    1309                                         } 
    1310                                         $result[$temp][]              = $id; 
    1311                                         $sub_mids[$new['MESSAGE-ID']] = $temp; 
    1312                                         $sbj                          = ''; 
    1313                                 } else { 
    1314                                         //if we can't find referenced parent, it's a "stray" 
    1315                                         $strays[$id] = $new['IN-REPLY-TO']; 
    1316                                 } 
    1317                         } 
    1318                          
    1319                         //add subject as root 
    1320                         if ($sbj) { 
    1321                                 if ($debug) { 
    1322                                     echo "\t added to root\n"; 
    1323                                 } 
    1324                                 $roots[$sbj]                  = $id; 
    1325                                 $root_in_root[$id]            = !$has_re; 
    1326                                 $sub_mids[$new['MESSAGE-ID']] = $id; 
    1327                                 $result[$id]                  = array($id); 
    1328                         } 
    1329                         if ($debug) { 
    1330                             echo $new['MESSAGE-ID'] . "\t" . $sbj . "\n"; 
    1331                         } 
    1332                 } 
    1333         } 
    1334          
    1335         //now that we've gone through all the messages, 
    1336         //go back and try and link up the stray threads 
    1337         if (count($strays) > 0) { 
    1338                 foreach ($strays as $id=>$irt) { 
    1339                         $root_id = $sub_mids[$irt]; 
    1340                         if (!$root_id || $root_id==$id) { 
    1341                             continue; 
    1342                         } 
    1343                         $result[$root_id] = array_merge($result[$root_id],$result[$id]); 
    1344                         unset($result[$id]); 
    1345                 } 
    1346         } 
    1347          
    1348         if ($clock) { 
    1349             $clock->register('data prepped'); 
    1350         } 
    1351      
    1352         if ($debug) { 
    1353             print_r($roots); 
    1354         } 
    1355  
    1356         return $result; 
    1357 } 
    1358  
    1359 function iil_SortThreads(&$tree, $index, $sort_order = 'ASC') { 
    1360         if (!is_array($tree) || !is_array($index)) { 
    1361             return false; 
    1362         } 
    1363      
    1364         //create an id to position lookup table 
    1365         $i = 0; 
    1366         foreach ($index as $id=>$val) { 
    1367                 $i++; 
    1368                 $index[$id] = $i; 
    1369         } 
    1370         $max = $i+1; 
    1371          
    1372         //for each tree, set array key to position 
    1373         $itree = array(); 
    1374         foreach ($tree as $id=>$node) { 
    1375                 if (count($tree[$id])<=1) { 
    1376                         //for "threads" with only one message, key is position of that message 
    1377                         $n         = $index[$id]; 
    1378                         $itree[$n] = array($n=>$id); 
    1379                 } else { 
    1380                         //for "threads" with multiple messages,  
    1381                         $min   = $max; 
    1382                         $new_a = array(); 
    1383                         foreach ($tree[$id] as $mid) { 
    1384                                 $new_a[$index[$mid]] = $mid;            //create new sub-array mapping position to id 
    1385                                 $pos                 = $index[$mid]; 
    1386                                 if ($pos&&$pos<$min) { 
    1387                                     $min = $index[$mid];        //find smallest position 
    1388                                 } 
    1389                         } 
    1390                         $n = $min;      //smallest position of child is thread position 
    1391                          
    1392                         //assign smallest position to root level key 
    1393                         //set children array to one created above 
    1394                         ksort($new_a); 
    1395                         $itree[$n] = $new_a; 
    1396                 } 
    1397         } 
    1398          
    1399         //sort by key, this basically sorts all threads 
    1400         ksort($itree); 
    1401         $i   = 0; 
    1402         $out = array(); 
    1403         foreach ($itree as $k=>$node) { 
    1404                 $out[$i] = $itree[$k]; 
    1405                 $i++; 
    1406         } 
    1407          
    1408         return $out; 
    1409 } 
    1410  
    1411 function iil_IndexThreads(&$tree) { 
    1412         /* creates array mapping mid to thread id */ 
    1413          
    1414         if (!is_array($tree)) { 
    1415             return false; 
    1416         } 
    1417      
    1418         $t_index = array(); 
    1419         foreach ($tree as $pos=>$kids) { 
    1420                 foreach ($kids as $kid) $t_index[$kid] = $pos; 
    1421         } 
    1422          
    1423         return $t_index; 
    1424 } 
    1425  
    14261111function iil_C_FetchHeaders(&$conn, $mailbox, $message_set, $uidfetch=false, $bodystr=false, $add='') 
    14271112{ 
     
    14361121                return false; 
    14371122        } 
     1123 
     1124        if (is_array($message_set)) 
     1125                $message_set = join(',', $message_set); 
    14381126 
    14391127        $message_set = iil_CompressMessageSet($message_set); 
     
    18791567} 
    18801568 
     1569// Don't be tempted to change $str to pass by reference to speed this up - it will slow it down by about 
     1570// 7 times instead :-) See comments on http://uk2.php.net/references and this article: 
     1571// http://derickrethans.nl/files/phparch-php-variables-article.pdf 
     1572function iil_ParseThread($str, $begin, $end, $root, $parent, $depth, &$depthmap, &$haschildren) { 
     1573        $node = array(); 
     1574        if ($str[$begin] != '(') { 
     1575                $stop = $begin + strspn($str, "1234567890", $begin, $end - $begin); 
     1576                $msg = substr($str, $begin, $stop - $begin); 
     1577                if ($msg == 0) 
     1578                    return $node; 
     1579                if (is_null($root)) 
     1580                        $root = $msg; 
     1581                $depthmap[$msg] = $depth; 
     1582                $haschildren[$msg] = false; 
     1583                if (!is_null($parent)) 
     1584                        $haschildren[$parent] = true; 
     1585                if ($stop + 1 < $end) 
     1586                        $node[$msg] = iil_ParseThread($str, $stop + 1, $end, $root, $msg, $depth + 1, $depthmap, $haschildren); 
     1587                else 
     1588                        $node[$msg] = array(); 
     1589        } else { 
     1590                $off = $begin; 
     1591                while ($off < $end) { 
     1592                        $start = $off; 
     1593                        $off++; 
     1594                        $n = 1; 
     1595                        while ($n > 0) { 
     1596                                $p = strpos($str, ')', $off); 
     1597                                if ($p === false) { 
     1598                                        error_log('Mismatched brackets parsing IMAP THREAD response:'); 
     1599                                        error_log(substr($str, ($begin < 10) ? 0 : ($begin - 10), $end - $begin + 20)); 
     1600                                        error_log(str_repeat(' ', $off - (($begin < 10) ? 0 : ($begin - 10)))); 
     1601                                        return $node; 
     1602                                } 
     1603                                $p1 = strpos($str, '(', $off); 
     1604                                if ($p1 !== false && $p1 < $p) { 
     1605                                        $off = $p1 + 1; 
     1606                                        $n++; 
     1607                                } else { 
     1608                                        $off = $p + 1; 
     1609                                        $n--; 
     1610                                } 
     1611                        } 
     1612                        $node += iil_ParseThread($str, $start + 1, $off - 1, $root, $parent, $depth, $depthmap, $haschildren); 
     1613                } 
     1614        } 
     1615         
     1616        return $node; 
     1617} 
     1618 
     1619function iil_C_Thread(&$conn, $folder, $algorithm='REFERENCES', $criteria='', 
     1620    $encoding='US-ASCII') { 
     1621 
     1622        if (iil_C_Select($conn, $folder)) { 
     1623         
     1624                $encoding = $encoding ? trim($encoding) : 'US-ASCII'; 
     1625                $algorithm = $algorithm ? trim($algorithm) : 'REFERENCES'; 
     1626                $criteria = $criteria ? 'ALL '.trim($criteria) : 'ALL'; 
     1627                 
     1628                iil_PutLineC($conn->fp, "thrd1 THREAD $algorithm $encoding $criteria"); 
     1629                do { 
     1630                        $line = trim(iil_ReadLine($conn->fp, 10000)); 
     1631                        if (preg_match('/^\* THREAD/', $line)) { 
     1632                                $str = trim(substr($line, 8)); 
     1633                                $depthmap = array(); 
     1634                                $haschildren = array(); 
     1635                                $tree = iil_ParseThread($str, 0, strlen($str), null, null, 0, $depthmap, $haschildren); 
     1636                        } 
     1637                } while (!iil_StartsWith($line, 'thrd1', true)); 
     1638 
     1639                $result_code = iil_ParseResult($line); 
     1640                if ($result_code == 0) { 
     1641                    return array($tree, $depthmap, $haschildren); 
     1642                } 
     1643                $conn->error = 'iil_C_Thread: ' . $line . "\n"; 
     1644                return false;    
     1645        } 
     1646        $conn->error = "iil_C_Thread: Couldn't select \"$folder\"\n"; 
     1647        return false; 
     1648} 
     1649 
    18811650function iil_C_Search(&$conn, $folder, $criteria) { 
    18821651 
  • trunk/roundcubemail/program/localization/de_CH/labels.inc

    r3116 r3367  
    128128$labels['invert'] = 'Umkehren'; 
    129129$labels['filter'] = 'Filter'; 
     130 
     131$labels['list'] = 'Liste'; 
     132$labels['threads'] = 'Konversationen'; 
     133$labels['expand-all'] = 'All aufklappen'; 
     134$labels['expand-unread'] = 'Ungelesene aufklappen'; 
     135$labels['collapse-all'] = 'Alle zuklappen'; 
     136$labels['threaded'] = 'Gruppiert'; 
     137 
     138$labels['autoexpand_threads'] = 'Konversationen aufklappen'; 
     139$labels['do_expand'] = 'alle'; 
     140$labels['expand_only_unread'] = 'nur ungelesene'; 
     141$labels['fromto'] = 'Sender/EmpfÀnger'; 
     142$labels['flag'] = 'Markierung'; 
     143$labels['attachment'] = 'Anhang'; 
     144$labels['nonesort'] = 'Keine'; 
     145$labels['sentdate'] = 'Sendedatum'; 
     146$labels['arrival'] = 'Empfangsdatum'; 
     147$labels['asc'] = 'aufsteigend'; 
     148$labels['desc'] = 'absteigend'; 
     149$labels['listcolumns'] = 'Spalten'; 
     150$labels['listsorting'] = 'Sortierung'; 
     151$labels['listorder'] = 'Ordnung'; 
     152$labels['listmode'] = 'Anzeigemodus'; 
     153 
    130154$labels['compact'] = 'Packen'; 
    131155$labels['empty'] = 'Leeren'; 
  • trunk/roundcubemail/program/localization/en_US/labels.inc

    r3327 r3367  
    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 
     
    150151$labels['invert'] = 'Invert'; 
    151152$labels['filter'] = 'Filter'; 
     153 
     154$labels['list'] = 'List'; 
     155$labels['threads'] = 'Threads'; 
     156$labels['expand-all'] = 'Expand All'; 
     157$labels['expand-unread'] = 'Expand Unread'; 
     158$labels['collapse-all'] = 'Collapse All'; 
     159$labels['threaded'] = 'Threaded'; 
     160 
     161$labels['autoexpand_threads'] = 'Expand message threads'; 
     162$labels['do_expand'] = 'all threads'; 
     163$labels['expand_only_unread'] = 'only with unread messages'; 
     164$labels['fromto'] = 'Sender/Recipient'; 
     165$labels['flag'] = 'Flag'; 
     166$labels['attachment'] = 'Attachment'; 
     167$labels['nonesort'] = 'None'; 
     168$labels['sentdate'] = 'Sent date'; 
     169$labels['arrival'] = 'Arrival date'; 
     170$labels['asc'] = 'ascending'; 
     171$labels['desc'] = 'descending'; 
     172$labels['listcolumns'] = 'List columns'; 
     173$labels['listsorting'] = 'Sorting column'; 
     174$labels['listorder'] = 'Sorting order'; 
     175$labels['listmode'] = 'List view mode'; 
    152176 
    153177$labels['compact'] = 'Compact'; 
     
    309333$labels['checkallfolders'] = 'Check all folders for new messages'; 
    310334$labels['displaynext'] = 'After message delete/move display the next message'; 
    311 $labels['indexsort'] = 'Use message index for sorting by date'; 
    312335$labels['mainoptions'] = 'Main Options'; 
    313336$labels['section'] = 'Section'; 
  • trunk/roundcubemail/program/localization/pl_PL/labels.inc

    r3327 r3367  
    141141$labels['select'] = 'Zaznacz'; 
    142142$labels['all'] = 'Wszystkie'; 
    143 $labels['none'] = 'Anuluj'; 
     143$labels['none'] = 'Brak'; 
    144144$labels['unread'] = 'Nieprzeczytane'; 
    145145$labels['flagged'] = 'Oznaczone'; 
  • trunk/roundcubemail/program/steps/mail/check_recent.inc

    r3308 r3367  
    3333       
    3434      // get overall message count; allow caching because rcube_imap::recent_uids() did a refresh 
    35       $all_count = $IMAP->messagecount(); 
     35      $all_count = $IMAP->messagecount(NULL, $IMAP->threading ? 'THREADS' : 'ALL'); 
    3636       
    3737      $unread_count = $IMAP->messagecount(NULL, 'UNSEEN', TRUE); 
     
    3939 
    4040      $OUTPUT->set_env('messagecount', $all_count); 
    41       $OUTPUT->set_env('pagesize', $IMAP->page_size); 
    4241      $OUTPUT->set_env('pagecount', ceil($all_count/$IMAP->page_size)); 
    4342      $OUTPUT->command('set_unread_count', $mbox_name, $unread_count, ($mbox_name == 'INBOX')); 
     
    5756        continue; 
    5857 
    59       // use SEARCH/SORT to find recent messages 
    60       $search_str = 'UID '.min($recents).':'.max($recents); 
    61       if ($search_request) 
    62         $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      } 
     66      else { 
     67        // use SEARCH/SORT to find recent messages 
     68        $search_str = 'UID '.min($recents).':'.max($recents); 
     69        if ($search_request) 
     70          $search_str .= ' '.$IMAP->search_string; 
    6371 
    64       if ($IMAP->search($mbox_name, $search_str, NULL, 'date')) { 
    65         // revert sort order 
    66         $order = $_SESSION['sort_col'] == 'date' && $_SESSION['sort_order'] == 'DESC' ? 'ASC' : 'DESC'; 
    67         // get the headers and add them to the list 
    68         $result_h = $IMAP->list_headers($mbox_name, 1, 'date', $order); 
    69         rcmail_js_message_list($result_h, true, false); 
     72        if ($IMAP->search($mbox_name, $search_str, NULL, 'date')) { 
     73          // revert sort order 
     74          $order = $_SESSION['sort_col'] == 'date' && $_SESSION['sort_order'] == 'DESC' ? 'ASC' : 'DESC'; 
     75          // get the headers and add them to the list 
     76          $result_h = $IMAP->list_headers($mbox_name, 1, 'date', $order); 
     77          rcmail_js_message_list($result_h, true, false); 
     78        } 
    7079      } 
    7180    } 
  • trunk/roundcubemail/program/steps/mail/func.inc

    r3358 r3367  
    6060  $_SESSION['sort_order'] = $CONFIG['message_sort_order']; 
    6161 
     62// set threads mode 
     63$a_threading = $RCMAIL->config->get('message_threading', array()); 
     64if (isset($_GET['_threads'])) { 
     65  if ($_GET['_threads']) 
     66    $a_threading[$_SESSION['mbox']] = true; 
     67  else 
     68    unset($a_threading[$_SESSION['mbox']]); 
     69  $RCMAIL->user->save_prefs(array('message_threading' => $a_threading)); 
     70} 
     71$IMAP->set_threading($a_threading[$_SESSION['mbox']]); 
     72 
    6273// set message set for search result 
    6374if (!empty($_REQUEST['_search']) && isset($_SESSION['search'][$_REQUEST['_search']])) 
     
    89100       
    90101      // make sure the message count is refreshed (for default view) 
    91       $IMAP->messagecount($mbox_name, 'ALL', true); 
     102      $IMAP->messagecount($mbox_name, $IMAP->threading ? 'THREADS' : 'ALL', true); 
    92103    } 
    93104         
    94   // set current mailbox in client environment 
     105  // set current mailbox and some other vars in client environment 
    95106  $OUTPUT->set_env('mailbox', $mbox_name); 
     107  $OUTPUT->set_env('pagesize', $IMAP->page_size); 
    96108  $OUTPUT->set_env('quota', $IMAP->get_capability('quota')); 
    97109  $OUTPUT->set_env('delimiter', $IMAP->get_hierarchy_delimiter()); 
     110  $OUTPUT->set_env('threading', (bool) $IMAP->threading); 
     111  $OUTPUT->set_env('threads', $IMAP->threading 
     112        || $IMAP->get_capability('thread=references') 
     113        || $IMAP->get_capability('thread=orderedsubject') 
     114        || $IMAP->get_capability('thread=refs')   
     115  ); 
    98116 
    99117  if ($CONFIG['flag_for_deletion']) 
     
    124142 */ 
    125143function rcmail_message_list($attrib) 
    126   { 
    127   global $IMAP, $CONFIG, $COMM_PATH, $OUTPUT; 
    128  
    129   $skin_path = $CONFIG['skin_path']; 
    130   $image_tag = '<img src="%s%s" alt="%s" />'; 
    131  
    132   // check to see if we have some settings for sorting 
    133   $sort_col   = $_SESSION['sort_col']; 
    134   $sort_order = $_SESSION['sort_order']; 
    135    
     144{ 
     145  global $IMAP, $CONFIG, $OUTPUT; 
     146 
    136147  // add some labels to client 
    137148  $OUTPUT->add_label('from', 'to'); 
    138  
    139   // get message headers 
    140   $a_headers = $IMAP->list_headers('', '', $sort_col, $sort_order); 
    141149 
    142150  // add id to message list table if not specified 
    143151  if (!strlen($attrib['id'])) 
    144152    $attrib['id'] = 'rcubemessagelist'; 
    145  
    146   // allow the following attributes to be added to the <table> tag 
    147   $attrib_str = create_attrib_string($attrib, array('style', 'class', 'id', 'cellpadding', 'cellspacing', 'border', 'summary')); 
    148  
    149   $out = '<table' . $attrib_str . ">\n"; 
    150153 
    151154  // define list of cols to be displayed based on parameter or config 
     
    155158      $a_show_cols = preg_split('/[\s,;]+/', strip_quotes($attrib['columns'])); 
    156159 
    157   // store column list in a session-variable 
     160  // save some variables for use in ajax list 
    158161  $_SESSION['list_columns'] = $a_show_cols; 
    159    
    160   // define sortable columns 
    161   $a_sort_cols = array('subject', 'date', 'from', 'to', 'size'); 
    162  
     162  $_SESSION['list_attrib'] = $attrib; 
     163   
    163164  $mbox = $IMAP->get_mailbox_name(); 
    164165  $delim = $IMAP->get_hierarchy_delimiter(); 
     
    168169      && (($f = array_search('from', $a_show_cols)) !== false) && array_search('to', $a_show_cols) === false) 
    169170    $a_show_cols[$f] = 'to'; 
    170    
    171   // add col definition 
    172   $out .= '<colgroup>'; 
    173   $out .= '<col class="icon" />'; 
    174  
    175   foreach ($a_show_cols as $col) 
    176     $out .= ($col!='attachment') ? sprintf('<col class="%s" />', $col) : '<col class="icon" />'; 
    177  
    178   $out .= "</colgroup>\n"; 
    179  
    180   // add table title 
    181   $out .= "<thead><tr>\n<td class=\"icon\">&nbsp;</td>\n"; 
    182  
    183   $javascript = ''; 
    184   foreach ($a_show_cols as $col) 
    185     { 
    186     // get column name 
    187     switch ($col) 
    188       { 
    189       case 'flag': 
    190         $col_name = sprintf($image_tag, $skin_path, $attrib['unflaggedicon'], ''); 
    191         break; 
    192       case 'attachment': 
    193         $col_name = sprintf($image_tag, $skin_path, $attrib['attachmenticon'], ''); 
    194         break; 
    195       default: 
    196         $col_name = Q(rcube_label($col)); 
    197     } 
    198  
    199     // make sort links 
    200     $sort = ''; 
    201     if (in_array($col, $a_sort_cols)) 
    202       { 
    203       // have buttons configured 
    204       if (!empty($attrib['sortdescbutton']) || !empty($attrib['sortascbutton'])) 
    205         { 
    206         $sort = '&nbsp;&nbsp;'; 
    207  
    208         // asc link 
    209         if (!empty($attrib['sortascbutton'])) 
    210           { 
    211           $sort .= $OUTPUT->button(array( 
    212             'command' => 'sort', 
    213             'prop' => $col.'_ASC', 
    214             'image' => $attrib['sortascbutton'], 
    215             'align' => 'absmiddle', 
    216             'title' => 'sortasc')); 
    217           }        
    218          
    219         // desc link 
    220         if (!empty($attrib['sortdescbutton'])) 
    221           { 
    222           $sort .= $OUTPUT->button(array( 
    223             'command' => 'sort', 
    224             'prop' => $col.'_DESC', 
    225             'image' => $attrib['sortdescbutton'], 
    226             'align' => 'absmiddle', 
    227             'title' => 'sortdesc')); 
    228           } 
    229         } 
    230       // just add a link tag to the header 
    231       else 
    232         { 
    233         $col_name = sprintf( 
    234           '<a href="./#sort" onclick="return %s.command(\'sort\',\'%s\',this)" title="%s">%s</a>', 
    235           JS_OBJECT_NAME, 
    236           $col, 
    237           rcube_label('sortby'), 
    238           $col_name); 
    239         } 
    240       } 
    241        
    242     $sort_class = $col==$sort_col ? " sorted$sort_order" : ''; 
    243  
    244     // put it all together 
    245     if ($col!='attachment') 
    246       $out .= '<td class="'.$col.$sort_class.'" id="rcm'.$col.'">' . "$col_name$sort</td>\n"; 
    247     else     
    248       $out .= '<td class="icon" id="rcm'.$col.'">' . "$col_name$sort</td>\n"; 
    249     } 
    250  
    251   $out .= "</tr></thead>\n<tbody>\n"; 
    252  
    253   // no messages in this mailbox 
    254   if (!sizeof($a_headers)) 
    255     $OUTPUT->show_message('nomessagesfound', 'notice'); 
    256  
    257   $a_js_message_arr = array(); 
    258  
    259   // create row for each message 
    260   foreach ($a_headers as $i => $header)  //while (list($i, $header) = each($a_headers)) 
    261     { 
    262     $message_icon = $attach_icon = $flagged_icon = ''; 
    263     $js_row_arr = array(); 
    264     $zebra_class = $i%2 ? ' even' : ' odd'; 
    265  
    266     // set messag attributes to javascript array 
    267     if ($header->deleted) 
    268       $js_row_arr['deleted'] = true; 
    269     if (!$header->seen) 
    270       $js_row_arr['unread'] = true; 
    271     if ($header->answered) 
    272       $js_row_arr['replied'] = true; 
    273     if ($header->forwarded) 
    274       $js_row_arr['forwarded'] = true; 
    275     if ($header->flagged) 
    276       $js_row_arr['flagged'] = true; 
    277  
    278     // set message icon   
    279     if ($attrib['deletedicon'] && $header->deleted) 
    280       $message_icon = $attrib['deletedicon']; 
    281     else if ($attrib['repliedicon'] && $header->answered) 
    282       { 
    283       if ($attrib['forwardedrepliedicon'] && $header->forwarded) 
    284         $message_icon = $attrib['forwardedrepliedicon']; 
    285       else 
    286         $message_icon = $attrib['repliedicon']; 
    287       } 
    288     else if ($attrib['forwardedicon'] && $header->forwarded) 
    289       $message_icon = $attrib['forwardedicon']; 
    290     else if ($attrib['unreadicon'] && !$header->seen) 
    291       $message_icon = $attrib['unreadicon']; 
    292     else if ($attrib['messageicon']) 
    293       $message_icon = $attrib['messageicon']; 
    294  
    295     if ($attrib['flaggedicon'] && $header->flagged) 
    296       $flagged_icon = $attrib['flaggedicon']; 
    297     else if ($attrib['unflaggedicon'] && !$header->flagged) 
    298       $flagged_icon = $attrib['unflaggedicon']; 
    299      
    300     // set attachment icon 
    301     if ($attrib['attachmenticon'] && preg_match("/multipart\/m/i", $header->ctype)) 
    302       $attach_icon = $attrib['attachmenticon']; 
    303          
    304     $out .= sprintf('<tr id="rcmrow%d" class="message%s%s%s%s">'."\n", 
    305                     $header->uid, 
    306                     $header->seen ? '' : ' unread', 
    307                     $header->deleted ? ' deleted' : '', 
    308                     $header->flagged ? ' flagged' : '', 
    309                     $zebra_class); 
    310      
    311     $out .= sprintf("<td class=\"icon\">%s</td>\n", $message_icon ? sprintf($image_tag, $skin_path, $message_icon, '') : ''); 
    312  
    313     $IMAP->set_charset(!empty($header->charset) ? $header->charset : $CONFIG['default_charset']); 
    314    
    315     // format each col 
    316     foreach ($a_show_cols as $col) 
    317       { 
    318       if (in_array($col, array('from', 'to', 'cc', 'replyto'))) 
    319         $cont = Q(rcmail_address_string($header->$col, 3, false, $attrib['addicon']), 'show'); 
    320       else if ($col=='subject') 
    321         { 
    322         $action = $mbox==$CONFIG['drafts_mbox'] ? 'compose' : 'show'; 
    323         $uid_param = $mbox==$CONFIG['drafts_mbox'] ? '_draft_uid' : '_uid'; 
    324         $cont = abbreviate_string(trim($IMAP->decode_header($header->$col)), 160); 
    325         if (empty($cont)) $cont = rcube_label('nosubject'); 
    326         $cont = $OUTPUT->browser->ie ? Q($cont) : sprintf('<a href="%s" onclick="return rcube_event.cancel(event)">%s</a>', Q(rcmail_url($action, array($uid_param=>$header->uid, '_mbox'=>$mbox))), Q($cont)); 
    327         } 
    328       else if ($col=='flag') 
    329         $cont = $flagged_icon ? sprintf($image_tag, $skin_path, $flagged_icon, '') : ''; 
    330       else if ($col=='size') 
    331         $cont = show_bytes($header->$col); 
    332       else if ($col=='date') 
    333         $cont = format_date($header->date); 
    334       else 
    335         $cont = Q($header->$col); 
    336          
    337       if ($col!='attachment') 
    338         $out .= '<td class="'.$col.'">' . $cont . "</td>\n"; 
    339       else 
    340         $out .= sprintf("<td class=\"icon\">%s</td>\n", $attach_icon ? sprintf($image_tag, $skin_path, $attach_icon, '') : '&nbsp;'); 
    341       } 
    342  
    343     $out .= "</tr>\n"; 
    344      
    345     if (sizeof($js_row_arr)) 
    346       $a_js_message_arr[$header->uid] = $js_row_arr; 
    347     } 
    348    
    349   // complete message table 
    350   $out .= "</tbody></table>\n"; 
    351    
    352   $message_count = $IMAP->messagecount(); 
     171 
     172  $skin_path = $_SESSION['skin_path'] = $CONFIG['skin_path']; 
     173  $message_count = $IMAP->messagecount(NULL, $IMAP->threading ? 'THREADS' : 'ALL'); 
    353174   
    354175  // set client env 
    355176  $OUTPUT->add_gui_object('mailcontframe', 'mailcontframe'); 
    356177  $OUTPUT->add_gui_object('messagelist', $attrib['id']); 
     178  $OUTPUT->set_env('autoexpand_threads', intval($CONFIG['autoexpand_threads'])); 
    357179  $OUTPUT->set_env('messagecount', $message_count); 
    358180  $OUTPUT->set_env('current_page', $IMAP->list_page); 
    359181  $OUTPUT->set_env('pagecount', ceil($message_count/$IMAP->page_size)); 
    360   $OUTPUT->set_env('sort_col', $sort_col); 
    361   $OUTPUT->set_env('sort_order', $sort_order); 
     182  $OUTPUT->set_env('sort_col', $_SESSION['sort_col']); 
     183  $OUTPUT->set_env('sort_order', $_SESSION['sort_order']); 
    362184   
    363185  if ($attrib['messageicon']) 
     
    379201  if ($attrib['unflaggedicon']) 
    380202    $OUTPUT->set_env('unflaggedicon', $skin_path . $attrib['unflaggedicon']); 
    381    
    382   $OUTPUT->set_env('messages', $a_js_message_arr); 
     203  if ($attrib['unreadchildrenicon']) 
     204    $OUTPUT->set_env('unreadchildrenicon', $skin_path . $attrib['unreadchildrenicon']); 
     205   
     206  $OUTPUT->set_env('messages', array()); 
    383207  $OUTPUT->set_env('coltypes', $a_show_cols); 
    384208   
     209  if (!$message_count) 
     210    $OUTPUT->show_message('nomessagesfound', 'notice'); 
     211   
    385212  $OUTPUT->include_script('list.js'); 
    386213   
    387   return $out; 
    388   } 
     214  $thead = ''; 
     215  foreach (rcmail_message_list_head($attrib, $a_show_cols) as $cell) 
     216    $thead .= html::tag('td', array('class' => $cell['className'], 'id' => $cell['id']), $cell['html']); 
     217   
     218  return html::tag('table', 
     219    $attrib, 
     220    html::tag('thead', null, html::tag('tr', null, $thead)) . 
     221      html::tag('tbody', null, ''), 
     222    array('style', 'class', 'id', 'cellpadding', 'cellspacing', 'border', 'summary')); 
     223} 
    389224 
    390225 
     
    393228 * or to replace the whole list (IE only) 
    394229 */ 
    395 function rcmail_js_message_list($a_headers, $insert_top=FALSE, $replace=TRUE) 
    396   { 
     230function rcmail_js_message_list($a_headers, $insert_top=FALSE, $replace=TRUE, $head_replace=FALSE) 
     231{ 
    397232  global $CONFIG, $IMAP, $OUTPUT; 
    398233 
     
    410245    $a_show_cols[$f] = 'to'; 
    411246 
    412   $browser = new rcube_browser; 
    413  
    414   $OUTPUT->command('set_message_coltypes', $a_show_cols); 
     247  $thead = $head_replace ? rcmail_message_list_head($_SESSION['list_attrib'], $a_show_cols) : NULL; 
     248   
     249  $OUTPUT->command('set_message_coltypes', $a_show_cols, $thead); 
     250 
     251  if (empty($a_headers)) 
     252    return; 
    415253 
    416254  // remove 'attachment' and 'flag' columns, we don't need them here 
     
    420258    unset($a_show_cols[$key]); 
    421259 
    422   if ($browser->ie && $replace) 
     260  if ($OUTPUT->browser->ie && $replace) 
    423261    $OUTPUT->command('offline_message_list', true); 
    424262 
     
    441279      else if ($col=='subject') 
    442280        { 
    443         $cont = abbreviate_string(trim($IMAP->decode_header($header->$col)), 160); 
     281        $cont = abbreviate_string(trim($IMAP->decode_header($header->$col)), 160); 
    444282        if (!$cont) $cont = rcube_label('nosubject'); 
    445         $cont = Q($cont); 
    446         $a_msg_cols['mbox'] = $mbox; 
     283        $cont = Q($cont); 
    447284        } 
    448285      else if ($col=='size') 
     
    456293      } 
    457294 
     295    if ($header->depth) 
     296      $a_msg_flags['depth'] = $header->depth; 
     297    if ($header->parent_uid) 
     298      $a_msg_flags['parent_uid'] = $header->parent_uid; 
     299    if ($header->has_children) 
     300      $a_msg_flags['has_children'] = $header->has_children; 
     301    if ($header->unread_children) 
     302      $a_msg_flags['unread_children'] = $header->unread_children; 
    458303    if ($header->deleted) 
    459304      $a_msg_flags['deleted'] = 1; 
     
    466311    if ($header->flagged) 
    467312      $a_msg_flags['flagged'] = 1; 
    468        
     313    if(preg_match("/multipart\/m/i", $header->ctype)) 
     314      $a_msg_flags['attachment'] = 1; 
     315    $a_msg_flags['mbox'] = $mbox; 
     316 
    469317    $OUTPUT->command('add_message_row', 
    470318      $header->uid, 
    471319      $a_msg_cols, 
    472320      $a_msg_flags, 
    473       preg_match("/multipart\/m/i", $header->ctype), 
    474321      $insert_top); 
    475322    } 
     
    478325    $OUTPUT->command('offline_message_list', false); 
    479326  } 
     327 
     328 
     329/* 
     330 * Creates <THEAD> for message list table 
     331 */ 
     332function rcmail_message_list_head($attrib, $a_show_cols) 
     333{ 
     334  global $CONFIG; 
     335 
     336  $skin_path = $_SESSION['skin_path']; 
     337  $image_tag = html::img(array('src' => "%s%s", 'alt' => "%s")); 
     338 
     339  // check to see if we have some settings for sorting 
     340  $sort_col   = $_SESSION['sort_col']; 
     341  $sort_order = $_SESSION['sort_order']; 
     342 
     343  // define sortable columns 
     344  $a_sort_cols = array('subject', 'date', 'from', 'to', 'size', 'cc'); 
     345   
     346  if (!empty($attrib['optionsmenuicon'])) 
     347    $list_menu = html::a( 
     348      array('href' => '#', 'onclick' => 'return '.JS_OBJECT_NAME.".command('menu-open', 'messagelistmenu')"), 
     349      html::img(array('src' => $skin_path . $attrib['optionsmenuicon'], 'id' => 'listmenulink', 'title' => rcube_label('listoptions'))) 
     350    ); 
     351  else 
     352    $list_menu = ''; 
     353 
     354  $cells = array(array('className' => 'threads', 'html' => $list_menu)); 
     355 
     356  foreach ($a_show_cols as $col) { 
     357    // get column name 
     358    switch ($col) { 
     359      case 'flag': 
     360        $col_name = sprintf($image_tag, $skin_path, $attrib['unflaggedicon'], ''); 
     361        break; 
     362      case 'attachment': 
     363        $col_name = sprintf($image_tag, $skin_path, $attrib['attachmenticon'], ''); 
     364        break; 
     365      default: 
     366        $col_name = Q(rcube_label($col)); 
     367    } 
     368 
     369    // make sort links 
     370    if (in_array($col, $a_sort_cols)) 
     371      $col_name = html::a(array('href'=>"./#sort", 'onclick' => 'return '.JS_OBJECT_NAME.".command('sort','".$col."',this)", 'title' => rcube_label('sortby')), $col_name); 
     372 
     373    $sort_class = $col == $sort_col ? " sorted$sort_order" : ''; 
     374    $class_name = $col == 'attachment' ? 'icon' : $col.$sort_class; 
     375 
     376    // put it all together 
     377    $cells[] = array('className' => $class_name, 'id' => "rcm$col", 'html' => $col_name); 
     378  } 
     379 
     380  return $cells; 
     381} 
    480382 
    481383 
     
    514416function rcmail_quota_display($attrib) 
    515417  { 
    516   global $OUTPUT, $COMM_PATH; 
     418  global $OUTPUT; 
    517419 
    518420  if (!$attrib['id']) 
     
    583485    { 
    584486    return rcube_label(array('name' => 'messagenrof', 
    585                              'vars' => array('nr'  => $MESSAGE->index+1, 
    586                                              'count' => $count!==NULL ? $count : $IMAP->messagecount()))); 
     487        'vars' => array('nr'  => $MESSAGE->index+1, 
     488        'count' => $count!==NULL ? $count : $IMAP->messagecount(NULL, 'ALL')))); // Only messages, no threads here 
    587489    } 
    588490 
     
    591493     
    592494  $start_msg = ($page-1) * $IMAP->page_size + 1; 
    593   $max = $count!==NULL ? $count : $IMAP->messagecount(); 
     495  $max = $count!==NULL ? $count : $IMAP->messagecount(NULL, $IMAP->threading ? 'THREADS' : 'ALL'); 
    594496 
    595497  if ($max==0) 
    596498    $out = rcube_label('mailboxempty'); 
    597499  else 
    598     $out = rcube_label(array('name' => 'messagesfromto', 
    599                               'vars' => array('from'  => $start_msg, 
    600                                               'to'    => min($max, $start_msg + $IMAP->page_size - 1), 
    601                                               'count' => $max))); 
     500    $out = rcube_label(array('name' => $IMAP->threading ? 'threadsfromto' : 'messagesfromto', 
     501            'vars' => array('from'  => $start_msg, 
     502            'to'    => min($max, $start_msg + $IMAP->page_size - 1), 
     503            'count' => $max))); 
    602504 
    603505  return Q($out); 
  • trunk/roundcubemail/program/steps/mail/list.inc

    r2983 r3367  
    3434  $_SESSION['sort_col'] = $save_arr['message_sort_col'] = $sort_col; 
    3535  $_SESSION['sort_order'] = $save_arr['message_sort_order'] = $sort_order; 
    36    
    37   $RCMAIL->user->save_prefs($save_arr); 
    3836} 
    3937else 
     
    4341  $sort_order = isset($_SESSION['sort_order']) ? $_SESSION['sort_order'] : $CONFIG['message_sort_order']; 
    4442} 
     43 
     44// is there a set of columns for this request? 
     45if ($cols = get_input_value('_cols', RCUBE_INPUT_GET)) 
     46{ 
     47  $save_arr = array(); 
     48  $_SESSION['list_columns'] = $save_arr['list_cols'] = explode(',', $cols); 
     49} 
     50 
     51if ($save_arr)   
     52  $RCMAIL->user->save_prefs($save_arr); 
    4553 
    4654$mbox_name = $IMAP->get_mailbox_name(); 
     
    5664 
    5765// fetch message headers 
    58 if ($count = $IMAP->messagecount($mbox_name, 'ALL', !empty($_REQUEST['_refresh']))) 
     66if ($count = $IMAP->messagecount($mbox_name, $IMAP->threading ? 'THREADS' : 'ALL', !empty($_REQUEST['_refresh']))) 
    5967  $a_headers = $IMAP->list_headers($mbox_name, NULL, $sort_col, $sort_order); 
     68 
     69// update search set (possible change of threading mode) 
     70if (!empty($_REQUEST['_search']) && isset($_SESSION['search'][$_REQUEST['_search']])) 
     71  $_SESSION['search'][$_REQUEST['_search']] = $IMAP->get_search_set(); 
    6072 
    6173// update mailboxlist 
     
    6678$OUTPUT->set_env('messagecount', $count); 
    6779$OUTPUT->set_env('pagecount', $pages); 
     80$OUTPUT->set_env('threading', (bool) $IMAP->threading); 
    6881$OUTPUT->command('set_rowcount', rcmail_get_messagecount_text($count)); 
    6982$OUTPUT->command('set_mailboxname', rcmail_get_mailbox_name_text()); 
    7083 
    7184// add message rows 
     85rcmail_js_message_list($a_headers, FALSE, TRUE, (bool) $cols); 
    7286if (isset($a_headers) && count($a_headers)) 
    7387{ 
    74   rcmail_js_message_list($a_headers); 
    7588  if ($search_request) 
    7689    $OUTPUT->show_message('searchsuccessful', 'confirmation', array('nr' => $count)); 
  • trunk/roundcubemail/program/steps/mail/mark.inc

    r3317 r3367  
    3737  if ($flag == 'DELETED' && $CONFIG['skip_deleted'] && $_POST['_from'] != 'show') { 
    3838    // count messages before changing anything 
    39     $old_count = $IMAP->messagecount(); 
     39    $old_count = $IMAP->messagecount(NULL, $IMAP->threading ? 'THREADS' : 'ALL'); 
    4040    $old_pages = ceil($old_count / $IMAP->page_size); 
    4141    $count = sizeof(explode(',', $uids)); 
     
    7676      } 
    7777 
    78       $msg_count      = $IMAP->messagecount(); 
     78      $msg_count      = $IMAP->messagecount(NULL, $IMAP->threading ? 'THREADS' : 'ALL'); 
    7979      $pages          = ceil($msg_count / $IMAP->page_size); 
    8080      $nextpage_count = $old_count - $IMAP->page_size * $IMAP->list_page; 
     
    104104      $OUTPUT->command('set_rowcount', rcmail_get_messagecount_text($msg_count)); 
    105105 
     106      if ($IMAP->threading) 
     107        $count = get_input_value('_count', RCUBE_INPUT_POST); 
     108 
    106109      // add new rows from next page (if any) 
    107       if (($jump_back || $nextpage_count > 0)) { 
     110      if ($count && ($jump_back || $nextpage_count > 0)) { 
    108111        $sort_col   = isset($_SESSION['sort_col'])   ? $_SESSION['sort_col']   : $CONFIG['message_sort_col']; 
    109112        $sort_order = isset($_SESSION['sort_order']) ? $_SESSION['sort_order'] : $CONFIG['message_sort_order']; 
  • trunk/roundcubemail/program/steps/mail/move_del.inc

    r3317 r3367  
    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 
     
    5151    $count = sizeof(explode(',', ($uids = get_input_value('_uid', RCUBE_INPUT_POST)))); 
    5252    $mbox = get_input_value('_mbox', RCUBE_INPUT_POST); 
     53 
    5354    $del = $IMAP->delete_message($uids, $mbox); 
    5455   
     
    8384else 
    8485{ 
    85   $msg_count      = $IMAP->messagecount(); 
     86  $msg_count      = $IMAP->messagecount(NULL, $IMAP->threading ? 'THREADS' : 'ALL'); 
    8687  $pages          = ceil($msg_count / $IMAP->page_size); 
    8788  $nextpage_count = $old_count - $IMAP->page_size * $IMAP->list_page; 
     
    117118  $OUTPUT->command('set_rowcount', rcmail_get_messagecount_text($msg_count)); 
    118119 
     120  if ($IMAP->threading) 
     121    $count = get_input_value('_count', RCUBE_INPUT_POST); 
     122 
    119123  // add new rows from next page (if any) 
    120   if ($addrows && ($jump_back || $nextpage_count > 0)) { 
     124  if ($addrows && $count && ($jump_back || $nextpage_count > 0)) { 
    121125    $sort_col   = isset($_SESSION['sort_col'])   ? $_SESSION['sort_col']   : $CONFIG['message_sort_col']; 
    122126    $sort_order = isset($_SESSION['sort_order']) ? $_SESSION['sort_order'] : $CONFIG['message_sort_order']; 
  • trunk/roundcubemail/program/steps/mail/search.inc

    r3313 r3367  
    105105// Get the headers 
    106106$result_h = $IMAP->list_headers($mbox, 1, $_SESSION['sort_col'], $_SESSION['sort_order']); 
    107 $count = $IMAP->messagecount(); 
     107$count = $IMAP->messagecount(NULL, $IMAP->threading ? 'THREADS' : 'ALL'); 
    108108 
    109109// save search results in session 
     
    121121  rcmail_js_message_list($result_h); 
    122122  if ($search_str) 
    123     $OUTPUT->show_message('searchsuccessful', 'confirmation', array('nr' => $count)); 
     123    $OUTPUT->show_message('searchsuccessful', 'confirmation', array('nr' => $IMAP->messagecount(NULL, 'ALL'))); 
    124124} 
    125125else 
  • trunk/roundcubemail/program/steps/mail/show.inc

    r3238 r3367  
    105105 
    106106    if ($_SESSION['sort_col'] == 'date' && $_SESSION['sort_order'] != 'DESC' 
    107         && empty($_REQUEST['_search']) && !$IMAP->skip_deleted) 
     107        && empty($_REQUEST['_search']) && !$CONFIG['skip_deleted'] && !$IMAP->threading) 
    108108      { 
    109109      // this assumes that we are sorted by date_DESC 
     
    143143  if (!$MESSAGE->headers->seen) 
    144144    $RCMAIL->plugins->exec_hook('message_read', array('uid' => $MESSAGE->uid, 
    145       'mailbox' => $IMAP->mailbox, 'message' => $MESSAGE)); 
     145      'mailbox' => $mbox_name, 'message' => $MESSAGE)); 
    146146} 
    147147 
  • trunk/roundcubemail/program/steps/mail/viewsource.inc

    r3231 r3367  
    2626{ 
    2727  $headers = $IMAP->get_headers($uid); 
    28   $charset = $headers->charset ? $headers->charset : $IMAP->default_charset; 
     28  $charset = $headers->charset ? $headers->charset : $CONFIG['default_charset']; 
    2929  header("Content-Type: text/plain; charset={$charset}"); 
    3030 
  • trunk/roundcubemail/program/steps/settings/func.inc

    r3327 r3367  
    249249    } 
    250250 
    251     // Show checkbox for toggling 'index_sort'  
    252     if (!isset($no_override['index_sort'])) { 
    253       $field_id = 'rcmfd_indexsort'; 
    254       $input_indexsort = new html_checkbox(array('name' => '_index_sort', 'id' => $field_id, 'value' => 1)); 
    255  
    256       $blocks['list']['options']['index_sort'] = array( 
    257         'title' => html::label($field_id, Q(rcube_label('indexsort'))), 
    258         'content' => $input_indexsort->show($config['index_sort']?1:0), 
    259       ); 
    260     } 
    261  
    262251    // show drop-down for available skins 
    263252    if (!isset($no_override['skin'])) { 
     
    309298        'title' => html::label($field_id, Q(rcube_label('mdnrequests'))), 
    310299        'content' => $select_mdn_requests->show($config['mdn_requests']), 
     300      ); 
     301    } 
     302 
     303    if (!isset($no_override['autoexpand_threads'])) { 
     304      $field_id = 'rcmfd_autoexpand_threads'; 
     305      $select_autoexpand_threads = new html_select(array('name' => '_autoexpand_threads', 'id' => $field_id)); 
     306      $select_autoexpand_threads->add(rcube_label('never'), 0); 
     307      $select_autoexpand_threads->add(rcube_label('do_expand'), 1); 
     308      $select_autoexpand_threads->add(rcube_label('expand_only_unread'), 2); 
     309       
     310      $blocks['main']['options']['autoexpand_threads'] = array( 
     311        'title' => html::label($field_id, Q(rcube_label('autoexpand_threads'))), 
     312        'content' => $select_autoexpand_threads->show($config['autoexpand_threads']), 
    311313      ); 
    312314    } 
  • trunk/roundcubemail/program/steps/settings/manage_folders.inc

    r3278 r3367  
    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    rcube_set_threading($mbox, true); 
     46  } 
     47 
     48// enable threading for one or more mailboxes 
     49else if ($RCMAIL->action=='disable-threading') 
     50  { 
     51  if ($mbox = get_input_value('_mbox', RCUBE_INPUT_POST, false, 'UTF7-IMAP')) 
     52    rcube_set_threading($mbox, false); 
     53  } 
     54 
    4155// create a new mailbox 
    4256else if ($RCMAIL->action=='create-folder') 
     
    7892 
    7993    $rename = $IMAP->rename_mailbox($oldname, $name); 
     94    } 
     95 
     96  // update per-folder options for modified folder and its subfolders 
     97  if ($rename) { 
     98    $a_threaded = $RCMAIL->config->get('message_threading', array());  
     99    $delimiter = $IMAP->get_hierarchy_delimiter(); 
     100    $oldprefix = '/^' . preg_quote($oldname . $delimiter, '/') . '/'; 
     101    foreach ($a_threaded as $key => $val) 
     102      if ($key == $oldname) { 
     103        unset($a_threaded[$key]); 
     104        $a_threaded[$name] = true; 
     105        } 
     106      else if (preg_match($oldprefix, $key)) { 
     107        unset($a_threaded[$key]); 
     108        $a_threaded[preg_replace($oldprefix, $name.$delimiter, $key)] = true;       
     109      } 
     110       
     111    $RCMAIL->user->save_prefs(array('message_threading' => $a_threaded)); 
    80112    } 
    81113 
     
    160192function rcube_subscription_form($attrib) 
    161193  { 
    162   global $IMAP, $CONFIG, $OUTPUT; 
     194  global $RCMAIL, $IMAP, $CONFIG, $OUTPUT; 
     195 
     196  $threading_supported = $IMAP->get_capability('thread=references') 
     197    || $IMAP->get_capability('thread=orderedsubject') 
     198    || $IMAP->get_capability('thread=refs'); 
    163199 
    164200  list($form_start, $form_end) = get_form_tags($attrib, 'folders'); 
     
    174210  $table->add_header('msgcount', rcube_label('messagecount')); 
    175211  $table->add_header('subscribed', rcube_label('subscribed')); 
     212  if ($threading_supported) 
     213    $table->add_header('threaded', rcube_label('threaded')); 
    176214  $table->add_header('rename', '&nbsp;'); 
    177215  $table->add_header('delete', '&nbsp;'); 
    178216 
    179  
    180217  // get folders from server 
    181218  $IMAP->clear_cache('mailboxes'); 
     
    183220  $a_unsubscribed = $IMAP->list_unsubscribed(); 
    184221  $a_subscribed = $IMAP->list_mailboxes(); 
     222  $a_threaded = $a_threaded_copy = $RCMAIL->config->get('message_threading', array());  
    185223  $delimiter = $IMAP->get_hierarchy_delimiter(); 
    186224  $a_js_folders = $seen_folders = $list_folders = array(); 
     
    204242    } 
    205243     
     244    unset($a_threaded_copy[$folder]); 
     245     
    206246    $list_folders[] = array('id' => $folder, 'name' => $name, 'level' => $level); 
    207247    $seen[$folder]++; 
     248  } 
     249 
     250  // remove 'message_threading' option for not existing folders 
     251  if ($a_threaded_copy) { 
     252    foreach ($a_threaded_copy as $key => $val) 
     253      unset($a_threaded[$key]); 
     254    unset($a_threaded_copy); 
     255    $RCMAIL->user->save_prefs(array('message_threading' => $a_threaded)); 
    208256  } 
    209257 
     
    212260    'onclick' => JS_OBJECT_NAME.".command(this.checked?'subscribe':'unsubscribe',this.value)", 
    213261  )); 
     262  $checkbox_threaded = new html_checkbox(array( 
     263    'name' => '_threaded[]', 
     264    'onclick' => JS_OBJECT_NAME.".command(this.checked?'enable-threading':'disable-threading',this.value)", 
     265  )); 
    214266   
    215267  if (!empty($attrib['deleteicon'])) 
     
    227279    $idx = $i + 1; 
    228280    $subscribed = in_array($folder['id'], $a_subscribed); 
     281    $threaded = $a_threaded[$folder['id']]; 
    229282    $protected = ($CONFIG['protect_default_folders'] == true && in_array($folder['id'], $CONFIG['default_imap_folders'])); 
    230283    $classes = array($i%2 ? 'even' : 'odd'); 
     
    239292     
    240293    $table->add('name', Q($display_folder)); 
    241     $table->add('msgcount', ($folder['virtual'] ? '' : $IMAP->messagecount($folder['id']))); 
     294    $table->add('msgcount', ($folder['virtual'] ? '' : $IMAP->messagecount($folder['id']))); // XXX: Use THREADS or ALL? 
    242295    $table->add('subscribed', ($protected || $folder['virtual']) ? ($subscribed ? '&nbsp;&#x2022;' : '&nbsp;') : 
    243296        $checkbox_subscribe->show(($subscribed ? $folder_utf8 : ''), array('value' => $folder_utf8))); 
     297    if ($IMAP->get_capability('thread=references')) { 
     298      $table->add('threaded', 
     299                  $checkbox_threaded->show(($threaded ? $folder_utf8 : ''), array('value' => $folder_utf8))); 
     300    } 
    244301     
    245302    // add rename and delete buttons 
     
    335392  return $out; 
    336393  } 
     394 
     395 
     396// (un)set 'threading' for selected folder 
     397function rcube_set_threading($mbox, $state=true) 
     398  { 
     399  global $RCMAIL; 
     400  $mbox = (array)$mbox; 
     401  $a_prefs = (array)$RCMAIL->config->get('message_threading'); 
     402 
     403  if ($state) { 
     404    foreach ($mbox as $box) 
     405      $a_prefs[$box] = true; 
     406    } 
     407  else { 
     408    foreach ($mbox as $box) 
     409      unset($a_prefs[$box]); 
     410    } 
     411 
     412  $RCMAIL->user->save_prefs(array('message_threading' => $a_prefs)); 
     413  } 
     414 
    337415 
    338416$OUTPUT->set_pagetitle(rcube_label('folders')); 
  • trunk/roundcubemail/program/steps/settings/save_prefs.inc

    r3327 r3367  
    3333      'dst_active'   => isset($_POST['_dst_active']) ? TRUE : FALSE, 
    3434      'pagesize'     => is_numeric($_POST['_pagesize']) ? max(2, intval($_POST['_pagesize'])) : $CONFIG['pagesize'], 
    35       'index_sort'   => isset($_POST['_index_sort']) ? TRUE : FALSE, 
    3635      'prettydate'   => isset($_POST['_pretty_date']) ? TRUE : FALSE, 
    3736      'skin'         => isset($_POST['_skin']) ? get_input_value('_skin', RCUBE_INPUT_POST) : $CONFIG['skin'], 
     
    4342      'focus_on_new_message' => isset($_POST['_focus_on_new_message']) ? TRUE : FALSE, 
    4443      'preview_pane'         => isset($_POST['_preview_pane']) ? TRUE : FALSE, 
     44      'autoexpand_threads'   => isset($_POST['_autoexpand_threads']) ? intval($_POST['_autoexpand_threads']) : 0, 
    4545      'mdn_requests'         => isset($_POST['_mdn_requests']) ? intval($_POST['_mdn_requests']) : 0, 
    4646      'keep_alive'           => isset($_POST['_keep_alive']) ? intval($_POST['_keep_alive'])*60 : $CONFIG['keep_alive'], 
  • trunk/roundcubemail/skins/default/common.css

    r3248 r3367  
    538538ul.toolbarmenu 
    539539{ 
    540   margin: 0; 
     540  margin: -4px 0 -4px 0; 
    541541  padding: 0; 
    542542  list-style: none; 
     
    548548  white-space: nowrap; 
    549549  min-width: 130px; 
     550  margin: 3px -4px; 
    550551} 
    551552 
     
    554555  display: block; 
    555556  color: #a0a0a0; 
    556   padding: 2px 8px 3px 22px; 
     557  padding: 2px 12px 3px 28px; 
    557558  text-decoration: none; 
    558559  min-height: 14px; 
     
    598599  padding-top: 2px; 
    599600} 
     601 
     602.disabled 
     603{ 
     604  color: #999; 
     605} 
  • trunk/roundcubemail/skins/default/functions.js

    r3337 r3367  
    125125  this.searchmenu = $('#searchmenu'); 
    126126  this.messagemenu = $('#messagemenu'); 
     127  this.listmenu = $('#listmenu'); 
    127128} 
    128129 
     
    187188}, 
    188189 
     190show_listmenu: function(show) 
     191{ 
     192  if (typeof show == 'undefined') 
     193    show = this.listmenu.is(':visible') ? false : true; 
     194 
     195  var ref = rcube_find_object('listmenulink'); 
     196  if (show && ref) { 
     197    var pos = $(ref).offset(); 
     198    this.listmenu.css({ left:pos.left, top:(pos.top + ref.offsetHeight + 2)}); 
     199    // set form values 
     200    $('input[name="sort_col"][value="'+rcmail.env.sort_col+'"]').attr('checked', 1); 
     201    $('input[name="sort_ord"][value="DESC"]').attr('checked', rcmail.env.sort_order=='DESC' ? 1 : 0); 
     202    $('input[name="sort_ord"][value="ASC"]').attr('checked', rcmail.env.sort_order=='DESC' ? 0 : 1); 
     203    $('input[name="view"][value="thread"]').attr('checked', rcmail.env.threading ? 1 : 0); 
     204    $('input[name="view"][value="list"]').attr('checked', rcmail.env.threading ? 0 : 1); 
     205    // list columns 
     206    var cols = $('input[name="list_col[]"]'); 
     207    for (var i=0; i<cols.length; i++) { 
     208      var found = 0; 
     209      if (cols[i].value != 'from') 
     210        found = jQuery.inArray(cols[i].value, rcmail.env.coltypes) != -1; 
     211      else 
     212        found = (jQuery.inArray('from', rcmail.env.coltypes) != -1 
     213            || jQuery.inArray('to', rcmail.env.coltypes) != -1); 
     214      $(cols[i]).attr('checked',found ? 1 : 0); 
     215    } 
     216  } 
     217 
     218  this.listmenu[show?'show':'hide'](); 
     219 
     220  if (show) { 
     221    var maxheight=0; 
     222    $('#listmenu fieldset').each(function() { 
     223      var height = $(this).height(); 
     224      if (height > maxheight) { 
     225        maxheight = height; 
     226      } 
     227    }); 
     228    $('#listmenu fieldset').css("min-height", maxheight+"px") 
     229    // IE6 complains if you set this attribute using either method: 
     230    //$('#listmenu fieldset').css({'height':'auto !important'}); 
     231    //$('#listmenu fieldset').css("height","auto !important"); 
     232      .height(maxheight); 
     233  }; 
     234}, 
     235 
     236open_listmenu: function(e) 
     237{ 
     238  this.show_listmenu(); 
     239}, 
     240 
     241save_listmenu: function() 
     242{ 
     243  this.show_listmenu(); 
     244 
     245  var sort = $('input[name="sort_col"]:checked').val(); 
     246  var ord = $('input[name="sort_ord"]:checked').val(); 
     247  var thread = $('input[name="view"]:checked').val(); 
     248  var cols = $('input[name="list_col[]"]:checked') 
     249    .map(function(){ return this.value; }).get(); 
     250 
     251  rcmail.set_list_options(cols, sort, ord, thread == 'thread' ? 1 : 0); 
     252}, 
     253 
    189254body_mouseup: function(evt, p) 
    190255{ 
    191   if (this.markmenu && this.markmenu.is(':visible') && rcube_event.get_target(evt) != rcube_find_object('markreadbutton')) 
     256  var target = rcube_event.get_target(evt); 
     257 
     258  if (this.markmenu && this.markmenu.is(':visible') && target != rcube_find_object('markreadbutton')) 
    192259    this.show_markmenu(false); 
    193   else if (this.messagemenu && this.messagemenu.is(':visible') && rcube_event.get_target(evt) != rcube_find_object('messagemenulink')) 
     260  else if (this.messagemenu && this.messagemenu.is(':visible') && target != rcube_find_object('messagemenulink')) 
    194261    this.show_messagemenu(false); 
    195   else if (this.searchmenu && this.searchmenu.is(':visible') && rcube_event.get_target(evt) != rcube_find_object('searchmod')) { 
     262  else if (this.listmenu && this.listmenu.is(':visible') && target != rcube_find_object('listmenulink')) { 
     263    var menu = rcube_find_object('listmenu'); 
     264    while (target.parentNode) { 
     265      if (target.parentNode == menu) 
     266        return; 
     267      target = target.parentNode; 
     268    } 
     269    this.show_listmenu(false); 
     270  } 
     271  else if (this.searchmenu && this.searchmenu.is(':visible') && target != rcube_find_object('searchmod')) { 
    196272    var menu = rcube_find_object('searchmenu'); 
    197     var target = rcube_event.get_target(evt); 
    198273    while (target.parentNode) { 
    199274      if (target.parentNode == menu) 
     
    214289    if (this.messagemenu && this.messagemenu.is(':visible')) 
    215290      this.show_messagemenu(false); 
     291    if (this.listmenu && this.listmenu.is(':visible')) 
     292      this.show_listmenu(false); 
    216293  } 
    217294} 
     
    226303  rcube_event.add_listener({ object:rcmail_ui, method:'body_mouseup', event:'mouseup' }); 
    227304  rcube_event.add_listener({ object:rcmail_ui, method:'body_keypress', event:'keypress' }); 
    228 } 
     305  rcmail.addEventListener('menu-open', 'open_listmenu', rcmail_ui); 
     306  rcmail.addEventListener('menu-save', 'save_listmenu', rcmail_ui); 
     307} 
  • trunk/roundcubemail/skins/default/ie6hacks.css

    r3054 r3367  
    1515} 
    1616 
    17 #messagemenu li a 
     17#messagemenu li a, 
     18#messagelist tr td div.expanded, 
     19#messagelist tr td div.collapsed 
    1820{ 
    1921  background-image: url('images/messageactions.gif'); 
     
    4850} 
    4951 
    50 ul.toolbarmenu li 
    51 { 
    52   width: auto; 
    53   border: 1px solid #f6f6f6; 
    54 } 
    55  
    5652ul.toolbarmenu li a 
    5753{ 
    5854  clear: left; 
    5955} 
     56 
     57ul.toolbarmenu li.separator_below 
     58{ 
     59  padding-bottom: 8px; 
     60} 
  • trunk/roundcubemail/skins/default/iehacks.css

    r3095 r3367  
    2525} 
    2626 
    27 #markmessagemenu, 
    28 #searchmenu, 
    29 #messagemenu 
    30 { 
    31   -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=90)"; 
    32   filter: alpha(opacity=90); 
    33  
     27.popupmenu 
     28{ 
     29        background-color: #ffffff; 
    3430} 
    3531 
     
    161157} 
    162158 
     159#messagelist tbody tr.unroot td.subject 
     160{ 
     161  text-decoration: underline; 
     162} 
     163 
    163164#messageframe 
    164165{ 
     
    244245} 
    245246 
     247ul.toolbarmenu 
     248{ 
     249  margin: 0 0 -4px 0; 
     250} 
     251 
    246252ul.toolbarmenu li 
    247253{ 
     
    264270  height: 19px; 
    265271} 
     272 
     273#listmenu fieldset 
     274{ 
     275  margin: 0 4px; 
     276  padding: 0.8em; 
     277} 
  • trunk/roundcubemail/skins/default/includes/messagemenu.html

    r2642 r3367  
    1 <div id="messagemenu"> 
     1<div id="messagemenu" class="popupmenu"> 
    22  <ul class="toolbarmenu"> 
    33    <li><roundcube:button class="printlink" command="print" label="printmessage" classAct="printlink active" /></li> 
  • trunk/roundcubemail/skins/default/mail.css

    r3173 r3367  
    165165} 
    166166 
    167 #markmessagemenu, 
    168 #searchmenu, 
    169 #messagemenu 
     167.popupmenu 
    170168{ 
    171169  position: absolute; 
     
    174172  width: auto; 
    175173  display: none; 
    176   background-color: #F9F9F9; 
    177   border: 1px solid #CCC; 
    178   padding: 1px; 
    179   opacity: 0.9; 
     174  background-color: #fff; 
     175  background-color: rgba(255, 255, 255, 0.95); 
     176  border: 1px solid #999; 
     177  padding: 4px; 
    180178  z-index: 240; 
     179  border-radius: 3px; 
     180  -moz-border-radius: 3px; 
     181  -webkit-border-radius: 3px; 
     182  -moz-box-shadow: 1px 1px 12px #999; 
     183  -webkit-box-shadow: #999 1px 1px 12px; 
    181184} 
    182185 
    183186#searchmenu 
    184187{ 
    185   width: 172px; 
     188  width: 160px; 
     189} 
     190 
     191#searchmenu ul.toolbarmenu 
     192{ 
     193  margin: 0; 
     194} 
     195 
     196#searchmenu ul.toolbarmenu li 
     197{ 
     198  margin: 1px 4px 1px; 
    186199} 
    187200 
     
    195208#messagemenu li a 
    196209{ 
    197   background: url('images/messageactions.png') no-repeat 1px 0; 
    198   background-position: 0px 20px; 
     210  background: url('images/messageactions.png') no-repeat 7px 0; 
     211  background-position: 7px 20px; 
    199212} 
    200213 
    201214#messagemenu li a.printlink 
    202215{ 
    203   background-position: 1px 1px; 
     216  background-position: 7px 1px; 
    204217} 
    205218 
    206219#messagemenu li a.downloadlink 
    207220{ 
    208   background-position: 1px -17px; 
     221  background-position: 7px -17px; 
    209222} 
    210223 
    211224#messagemenu li a.sourcelink 
    212225{ 
    213   background-position: 1px -35px; 
     226  background-position: 7px -35px; 
    214227} 
    215228 
    216229#messagemenu li a.openlink 
    217230{ 
    218   background-position: 1px -53px; 
     231  background-position: 7px -53px; 
    219232} 
    220233 
    221234#messagemenu li a.editlink 
    222235{ 
    223   background-position: 1px -71px; 
     236  background-position: 7px -71px; 
    224237} 
    225238 
    226239#markmessagemenu a.readlink 
    227240{ 
    228   background: url('images/icons/dot.png') no-repeat 2px; 
     241  background: url('images/icons/dot.png') no-repeat 7px 2px; 
    229242} 
    230243 
    231244#markmessagemenu a.unreadlink 
    232245{ 
    233   background: url('images/icons/unread.png') no-repeat 2px; 
     246  background: url('images/icons/unread.png') no-repeat 7px 2px; 
    234247} 
    235248 
    236249#markmessagemenu a.flaggedlink 
    237250{ 
    238   background: url('images/icons/flagged.png') no-repeat 2px; 
     251  background: url('images/icons/flagged.png') no-repeat 7px 2px; 
    239252} 
    240253 
    241254#markmessagemenu a.unflaggedlink 
    242255{ 
    243   background: url('images/icons/unflagged.png') no-repeat 2px; 
     256  background: url('images/icons/unflagged.png') no-repeat 7px 2px; 
    244257} 
    245258 
     
    614627} 
    615628 
     629#listcontrols a.expand-all { 
     630  background-position: -90px 0; 
     631} 
     632 
     633#listcontrols a.expand-allsel { 
     634  background-position: -90px -15px; 
     635} 
     636 
     637#listcontrols a.collapse-all { 
     638  background-position: -105px 0; 
     639} 
     640 
     641#listcontrols a.collapse-allsel { 
     642  background-position: -105px -15px; 
     643} 
     644 
     645#listcontrols a.expand-unread { 
     646  background-position: -120px 0; 
     647} 
     648 
     649#listcontrols a.expand-unreadsel { 
     650  background-position: -120px -15px; 
     651} 
     652 
    616653#countcontrols 
    617654{ 
     
    721758} 
    722759 
     760#messagelist thead tr td.subject 
     761{ 
     762  padding-left: 22px; 
     763} 
     764 
    723765#messagelist thead tr td.icon, 
    724 #messagelist thead tr td.flag 
     766#messagelist thead tr td.flag, 
     767#messagelist thead tr td.threads 
    725768{ 
    726769  width: 22px; 
     
    729772} 
    730773 
    731 #messagelist tbody tr td.icon, 
     774#messagelist thead tr td.threads 
     775{ 
     776  width: 18px; 
     777} 
     778 
    732779#messagelist tbody tr td.flag 
    733780{ 
     
    737784} 
    738785 
     786#messagelist tr td span.branch 
     787{ 
     788  display: inline-block; 
     789  width: 15px; 
     790  height: 15px; 
     791} 
     792 
     793#messagelist tr td.subject img.msgicon 
     794{ 
     795  vertical-align: middle; 
     796} 
     797 
     798#messagelist tbody td img.msgicon 
     799{ 
     800  position: relative; 
     801  top: 0px; 
     802  margin-right: 5px; 
     803} 
     804 
     805#messagelist tr td div.collapsed, 
     806#messagelist tr td div.expanded, 
     807#messagelist tr td img.flagicon, 
     808#messagelist tr td img.msgicon 
     809{ 
     810  cursor: pointer; 
     811} 
     812 
     813#messagelist tr td div.collapsed 
     814{ 
     815  display: block; 
     816  background: url('images/messageactions.png') center -91px no-repeat; 
     817} 
     818 
     819#messagelist tr td div.expanded 
     820{ 
     821  display: block; 
     822  background: url('images/messageactions.png') center -109px no-repeat; 
     823} 
     824 
    739825#messagelist tbody tr td.flag img:hover, 
    740826#messagelist thead tr td.flag img 
     
    748834  vertical-align: middle; 
    749835  width: 99%; 
     836} 
     837 
     838/* thread parent message with unread children */ 
     839#messagelist tbody tr.unroot td.subject a 
     840{ 
     841  text-decoration: underline; 
    750842} 
    751843 
     
    12681360  font-weight: bold; 
    12691361} 
     1362 
     1363#listmenu 
     1364{ 
     1365  padding: 6px; 
     1366} 
     1367 
     1368#listmenu legend 
     1369{ 
     1370  color: #999999; 
     1371} 
     1372 
     1373#listmenu fieldset 
     1374{ 
     1375  border: 1px solid #999999; 
     1376  margin: 0 5px; 
     1377  float: left; 
     1378} 
     1379 
     1380#listmenu div 
     1381{ 
     1382  padding: 8px 0 3px 0; 
     1383  text-align: center; 
     1384  clear: both; 
     1385} 
  • trunk/roundcubemail/skins/default/templates/mail.html

    r3196 r3367  
    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=""  
     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="expand-unread" type="link" title="expand-unread" class="buttonPas expand-unread" classAct="button expand-unread" classSel="button expand-unreadsel" content=" " /> 
     90      <roundcube:button command="collapse-all" type="link" title="collapse-all" class="buttonPas collapse-all" classAct="button collapse-all" classSel="button collapse-allsel" content=" " /> 
    8591      <roundcube:container name="listcontrols" id="listcontrols" /> 
    8692  <roundcube:if condition="env:quota" /> 
     
    112118<roundcube:button name="messagemenulink" id="messagemenulink" type="link" class="button messagemenu" title="messageactions" onclick="rcmail_ui.show_messagemenu();return false" content=" " /> 
    113119 
    114 <div id="markmessagemenu"> 
     120<div id="markmessagemenu" class="popupmenu"> 
    115121  <ul class="toolbarmenu"> 
    116122    <li><roundcube:button command="mark" prop="read" label="markread" classAct="readlink active" class="readlink" /></li> 
     
    126132</div> 
    127133 
    128 <div id="searchmenu"> 
     134<div id="searchmenu" class="popupmenu"> 
    129135  <ul class="toolbarmenu"> 
    130136    <li><input type="checkbox" name="s_mods[]" value="subject" id="s_mod_subject" onclick="rcmail_ui.set_searchmod(this)" /><label for="s_mod_subject"><roundcube:label name="subject" /></label></li> 
     
    147153</div> 
    148154 
     155<div id="listmenu" class="popupmenu"> 
     156<fieldset class="thinbordered"><legend><roundcube:label name="listmode" /></legend> 
     157  <ul class="toolbarmenu"> 
     158    <li><input type="radio" name="view" value="list" id="view_default" /><label for="view_default"><roundcube:label name="list" /></label></li> 
     159    <roundcube:if condition="env:threads" /> 
     160      <li><input type="radio" name="view" value="thread" id="view_thread" /><label for="view_thread"><roundcube:label name="threads" /></label></li> 
     161    <roundcube:else /> 
     162      <li><input type="radio" name="view" value="thread" id="view_thread" disabled="disabled" /><label for="view_thread" class="disabled"><roundcube:label name="threads" /></label></li> 
     163    <roundcube:endif /> 
     164  </ul> 
     165</fieldset> 
     166<fieldset class="thinbordered"><legend><roundcube:label name="listcolumns" /></legend> 
     167  <ul class="toolbarmenu"> 
     168    <li><input type="checkbox" name="list_col[]" value="flag" id="cols_flag" /><label for="cols_flag"><roundcube:label name="flag" /></label></li> 
     169    <li><input type="checkbox" name="list_col[]" value="subject" id="cols_subject" checked="checked" disabled="disabled" /><label for="cols_subject" class="disabled"><roundcube:label name="subject" /></label></li> 
     170    <li><input type="checkbox" name="list_col[]" value="from" id="cols_fromto" /><label for="cols_fromto"><roundcube:label name="fromto" /></label></li> 
     171    <li><input type="checkbox" name="list_col[]" value="replyto" id="cols_replyto" /><label for="cols_replyto"><roundcube:label name="replyto" /></label></li> 
     172    <li><input type="checkbox" name="list_col[]" value="cc" id="cols_cc" /><label for="cols_cc"><roundcube:label name="cc" /></label></li> 
     173    <li><input type="checkbox" name="list_col[]" value="date" id="cols_date" /><label for="cols_date"><roundcube:label name="date" /></label></li> 
     174    <li><input type="checkbox" name="list_col[]" value="size" id="cols_size" /><label for="cols_size"><roundcube:label name="size" /></label></li> 
     175    <li><input type="checkbox" name="list_col[]" value="attachment" id="cols_attachment" /><label for="cols_attachment"><roundcube:label name="attachment" /></label></li> 
     176  </ul> 
     177</fieldset> 
     178<fieldset class="thinbordered"><legend><roundcube:label name="listsorting" /></legend> 
     179  <ul class="toolbarmenu"> 
     180    <li><input type="radio" name="sort_col" value="" id="sort_default" /><label for="sort_default"><roundcube:label name="nonesort" /></label></li> 
     181    <li><input type="radio" name="sort_col" value="arrival" id="sort_arrival" /><label for="sort_arrival"><roundcube:label name="arrival" /></label></li> 
     182    <li><input type="radio" name="sort_col" value="date" id="sort_date" /><label for="sort_date"><roundcube:label name="sentdate" /></label></li> 
     183    <li><input type="radio" name="sort_col" value="subject" id="sort_subject" /><label for="sort_subject"><roundcube:label name="subject" /></label></li> 
     184    <li><input type="radio" name="sort_col" value="from" id="sort_fromto" /><label for="sort_fromto"><roundcube:label name="fromto" /></label></li> 
     185    <li><input type="radio" name="sort_col" value="to" id="sort_replyto" /><label for="sort_replyto"><roundcube:label name="replyto" /></label></li> 
     186    <li><input type="radio" name="sort_col" value="cc" id="sort_cc" /><label for="sort_cc"><roundcube:label name="cc" /></label></li> 
     187    <li><input type="radio" name="sort_col" value="size" id="sort_size" /><label for="sort_size"><roundcube:label name="size" /></label></li> 
     188  </ul> 
     189</fieldset> 
     190<fieldset><legend><roundcube:label name="listorder" /></legend>  
     191          <ul class="toolbarmenu">  
     192            <li><input type="radio" name="sort_ord" value="ASC" id="sort_asc" /><label for="sort_asc"><roundcube:label name="asc" /></label></li>  
     193            <li><input type="radio" name="sort_ord" value="DESC" id="sort_desc" /><label for="sort_desc"><roundcube:label name="desc" /></label></li>  
     194          </ul>  
     195</fieldset> 
     196<div> 
     197  <roundcube:button command="menu-open" id="listmenucancel" type="input" class="button" label="cancel" /> 
     198  <roundcube:button command="menu-save" id="listmenusave" type="input" class="button mainaction" label="save" /> 
     199</div> 
     200</div> 
     201 
    149202</body> 
    150203</html> 
Note: See TracChangeset for help on using the changeset viewer.