Changeset 0213f8d in github


Ignore:
Timestamp:
Jul 25, 2011 6:49:39 AM (22 months ago)
Author:
alecpl <alec@…>
Branches:
master, HEAD, courier-fix, dev-browser-capabilities, pdo, release-0.6, release-0.7, release-0.8
Children:
71e8cc3
Parents:
18371736
Message:
  • Added optional "multithreading" autocomplete feature
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • CHANGELOG

    r18371736 r0213f8d  
    22=========================== 
    33 
     4- Added optional "multithreading" autocomplete feature 
    45- Plugin API: Added 'config_get' hook 
    56- Fixed new_user_identity plugin to work with updated rcube_ldap class (#1487994) 
  • config/main.inc.php.dist

    rca8a178 r0213f8d  
    572572$rcmail_config['autocomplete_min_length'] = 1; 
    573573 
     574// Number of parallel autocomplete requests. 
     575// If there's more than one address book, n parallel (async) requests will be created, 
     576// where each request will search in one address book. By default (0), all address 
     577// books are searched in one request. 
     578$rcmail_config['autocomplete_threads'] = 0; 
     579 
     580// Max. numer of entries in autocomplete popup. Default: 15. 
     581$rcmail_config['autocomplete_max'] = 15; 
     582 
    574583// show address fields in this order 
    575584// available placeholders: {street}, {locality}, {zipcode}, {country}, {region} 
  • program/include/main.inc

    rd8aff9a r0213f8d  
    21232123    } 
    21242124} 
     2125 
     2126/** 
     2127 * Initializes client-side autocompletion 
     2128 */ 
     2129function rcube_autocomplete_init() 
     2130{ 
     2131    global $RCMAIL; 
     2132    static $init; 
     2133 
     2134    if ($init) 
     2135        return; 
     2136 
     2137    $init = 1; 
     2138 
     2139    if (($threads = (int)$RCMAIL->config->get('autocomplete_threads')) > 0) { 
     2140      $book_types = (array) $RCMAIL->config->get('autocomplete_addressbooks', 'sql'); 
     2141      if (count($book_types) > 1) { 
     2142        $RCMAIL->output->set_env('autocomplete_threads', $threads); 
     2143        $RCMAIL->output->set_env('autocomplete_sources', $book_types); 
     2144      } 
     2145    } 
     2146 
     2147    $RCMAIL->output->set_env('autocomplete_max', (int)$RCMAIL->config->get('autocomplete_max', 15)); 
     2148    $RCMAIL->output->set_env('autocomplete_min_length', $RCMAIL->config->get('autocomplete_min_length')); 
     2149    $RCMAIL->output->add_label('autocompletechars'); 
     2150} 
  • program/js/app.js

    r1b3ce75 r0213f8d  
    28722872      input_message = $("[name='_message']").get(0), 
    28732873      html_mode = $("input[name='_is_html']").val() == '1', 
    2874       ac_fields = ['cc', 'bcc', 'replyto', 'followupto']; 
     2874      ac_fields = ['cc', 'bcc', 'replyto', 'followupto'], 
     2875      ac_props; 
     2876 
     2877    // configure parallel autocompletion 
     2878    if (this.env.autocomplete_threads > 0) { 
     2879      ac_props = { 
     2880        threads: this.env.autocomplete_threads, 
     2881        sources: this.env.autocomplete_sources, 
     2882      }; 
     2883    } 
    28752884 
    28762885    // init live search events 
    2877     this.init_address_input_events(input_to); 
     2886    this.init_address_input_events(input_to, ac_props); 
    28782887    for (var i in ac_fields) { 
    2879       this.init_address_input_events($("[name='_"+ac_fields[i]+"']")); 
     2888      this.init_address_input_events($("[name='_"+ac_fields[i]+"']"), ac_props); 
    28802889    } 
    28812890 
     
    29052914  }; 
    29062915 
    2907   this.init_address_input_events = function(obj, action) 
    2908   { 
    2909     obj[bw.ie || bw.safari || bw.chrome ? 'keydown' : 'keypress'](function(e) { return ref.ksearch_keydown(e, this, action); }) 
     2916  this.init_address_input_events = function(obj, props) 
     2917  { 
     2918    obj[bw.ie || bw.safari || bw.chrome ? 'keydown' : 'keypress'](function(e) { return ref.ksearch_keydown(e, this, props); }) 
    29102919      .attr('autocomplete', 'off'); 
    29112920  }; 
     
    34423451 
    34433452  // handler for keyboard events on address-fields 
    3444   this.ksearch_keydown = function(e, obj, action) 
     3453  this.ksearch_keydown = function(e, obj, props) 
    34453454  { 
    34463455    if (this.ksearch_timer) 
     
    34723481          break; 
    34733482 
    3474      case 13:  // enter 
    3475         if (this.ksearch_selected === null || !this.ksearch_input || !this.ksearch_value) 
     3483      case 13:  // enter 
     3484        if (this.ksearch_selected === null || !this.ksearch_value) 
    34763485          break; 
    34773486 
     
    34933502 
    34943503    // start timer 
    3495     this.ksearch_timer = window.setTimeout(function(){ ref.ksearch_get_results(action); }, 200); 
     3504    this.ksearch_timer = window.setTimeout(function(){ ref.ksearch_get_results(props); }, 200); 
    34963505    this.ksearch_input = obj; 
    34973506 
     
    35233532      trigger = false, 
    35243533      insert = '', 
    3525  
    35263534      // replace search string with full address 
    35273535      pre = inp_value.substring(0, p), 
    35283536      end = inp_value.substring(p+this.ksearch_value.length, inp_value.length); 
     3537 
     3538    this.ksearch_destroy(); 
    35293539 
    35303540    // insert all members of a group 
     
    35613571 
    35623572  // address search processor 
    3563   this.ksearch_get_results = function(action) 
     3573  this.ksearch_get_results = function(props) 
    35643574  { 
    35653575    var inp_value = this.ksearch_input ? this.ksearch_input.value : null; 
     
    36063616      return; 
    36073617 
    3608     var lock = this.display_message(this.get_label('searching'), 'loading'); 
    3609     this.http_post(action ? action : 'mail/autocomplete', '_search='+urlencode(q), lock); 
    3610   }; 
    3611  
    3612   this.ksearch_query_results = function(results, search) 
     3618    this.ksearch_destroy(); 
     3619 
     3620    var i, lock, source, xhr, reqid = new Date().getTime(), 
     3621      threads = props && props.threads ? props.threads : 1, 
     3622      sources = props && props.sources ? props.sources : [], 
     3623      action = props && props.action ? props.action : 'mail/autocomplete'; 
     3624 
     3625    this.ksearch_data = {id: reqid, sources: sources.slice(), action: action, locks: [], requests: []}; 
     3626 
     3627    for (i=0; i<threads; i++) { 
     3628      source = this.ksearch_data.sources.shift(); 
     3629      if (threads > 1 && source === null) 
     3630        break; 
     3631 
     3632      lock = this.display_message(this.get_label('searching'), 'loading'); 
     3633      xhr = this.http_post(action, '_search='+urlencode(q)+'&_id='+reqid 
     3634        + (source ? '&_source='+urlencode(source) : ''), lock); 
     3635 
     3636      this.ksearch_data.locks.push(lock); 
     3637      this.ksearch_data.requests.push(xhr); 
     3638    } 
     3639  }; 
     3640 
     3641  this.ksearch_query_results = function(results, search, reqid) 
    36133642  { 
    36143643    // ignore this outdated search response 
    3615     if (this.ksearch_value && search != this.ksearch_value) 
     3644    if (this.ksearch_input && this.ksearch_value && search != this.ksearch_value) 
    36163645      return; 
    36173646 
    3618     this.env.contacts = results ? results : []; 
    3619     this.ksearch_display_results(this.env.contacts); 
    3620   }; 
    3621  
    3622   this.ksearch_display_results = function (a_results) 
    3623   { 
    36243647    // display search results 
    3625     if (a_results.length && this.ksearch_input && this.ksearch_value) { 
    3626       var p, ul, li, text, s_val = this.ksearch_value; 
    3627  
    3628       // create results pane if not present 
    3629       if (!this.ksearch_pane) { 
    3630         ul = $('<ul>'); 
    3631         this.ksearch_pane = $('<div>').attr('id', 'rcmKSearchpane').css({ position:'absolute', 'z-index':30000 }).append(ul).appendTo(document.body); 
    3632         this.ksearch_pane.__ul = ul[0]; 
    3633       } 
    3634  
    3635       // remove all search results 
    3636       ul = this.ksearch_pane.__ul; 
     3648    var p, ul, li, text, init, s_val = this.ksearch_value, 
     3649      maxlen = this.env.autocomplete_max ? this.env.autocomplete_max : 15; 
     3650 
     3651    // create results pane if not present 
     3652    if (!this.ksearch_pane) { 
     3653      ul = $('<ul>'); 
     3654      this.ksearch_pane = $('<div>').attr('id', 'rcmKSearchpane') 
     3655        .css({ position:'absolute', 'z-index':30000 }).append(ul).appendTo(document.body); 
     3656      this.ksearch_pane.__ul = ul[0]; 
     3657    } 
     3658 
     3659    ul = this.ksearch_pane.__ul; 
     3660 
     3661    // remove all search results or add to existing list if parallel search 
     3662    if (reqid && this.ksearch_pane.data('reqid') == reqid) { 
     3663      maxlen -= ul.childNodes.length; 
     3664    } 
     3665    else { 
     3666      this.ksearch_pane.data('reqid', reqid); 
     3667      init = 1; 
     3668      // reset content 
    36373669      ul.innerHTML = ''; 
    3638  
    3639       // add each result line to list 
    3640       for (i=0; i < a_results.length; i++) { 
    3641         text = typeof a_results[i] === 'object' ? a_results[i].name : a_results[i]; 
     3670      this.env.contacts = []; 
     3671      // move the results pane right under the input box 
     3672      var pos = $(this.ksearch_input).offset(); 
     3673      this.ksearch_pane.css({ left:pos.left+'px', top:(pos.top + this.ksearch_input.offsetHeight)+'px', display: 'none'}); 
     3674    } 
     3675 
     3676    // add each result line to list 
     3677    if (results && results.length) { 
     3678      for (i=0; i < results.length && maxlen > 0; i++) { 
     3679        text = typeof results[i] === 'object' ? results[i].name : results[i]; 
    36423680        li = document.createElement('LI'); 
    36433681        li.innerHTML = text.replace(new RegExp('('+RegExp.escape(s_val)+')', 'ig'), '##$1%%').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/##([^%]+)%%/g, '<b>$1</b>'); 
     
    36463684        li._rcm_id = i; 
    36473685        ul.appendChild(li); 
    3648       } 
    3649  
     3686        maxlen -= 1; 
     3687      } 
     3688    } 
     3689 
     3690    if (ul.childNodes.length) { 
     3691      this.ksearch_pane.show(); 
    36503692      // select the first 
    3651       $(ul.firstChild).attr('id', 'rcmksearchSelected').addClass('selected'); 
    3652       this.ksearch_selected = 0; 
    3653  
    3654       // move the results pane right under the input box and make it visible 
    3655       var pos = $(this.ksearch_input).offset(); 
    3656       this.ksearch_pane.css({ left:pos.left+'px', top:(pos.top + this.ksearch_input.offsetHeight)+'px' }).show(); 
    3657     } 
    3658     // hide results pane 
    3659     else 
    3660       this.ksearch_hide(); 
     3693      if (!this.env.contacts.length) { 
     3694        $('li:first', ul).attr('id', 'rcmksearchSelected').addClass('selected'); 
     3695        this.ksearch_selected = 0; 
     3696      } 
     3697    } 
     3698 
     3699    if (results && results.length) 
     3700      this.env.contacts = this.env.contacts.concat(results); 
     3701 
     3702    // run next parallel search 
     3703    if (maxlen > 0 && this.ksearch_data.id == reqid && this.ksearch_data.sources.length) { 
     3704      var lock, xhr, props = this.ksearch_data, source = props.sources.shift(); 
     3705      if (source) { 
     3706        lock = this.display_message(this.get_label('searching'), 'loading'); 
     3707        xhr = this.http_post(props.action, '_search='+urlencode(s_val)+'&_id='+reqid 
     3708          +'&_source='+urlencode(source), lock); 
     3709 
     3710        this.ksearch_data.locks.push(lock); 
     3711        this.ksearch_data.requests.push(xhr); 
     3712      } 
     3713    } 
    36613714  }; 
    36623715 
     
    36753728      clearTimeout(this.ksearch_timer); 
    36763729 
    3677     this.ksearch_value = ''; 
    36783730    this.ksearch_input = null; 
    36793731    this.ksearch_hide(); 
    36803732  }; 
    36813733 
    3682  
    36833734  this.ksearch_hide = function() 
    36843735  { 
    36853736    this.ksearch_selected = null; 
     3737    this.ksearch_value = ''; 
    36863738 
    36873739    if (this.ksearch_pane) 
     
    36893741   }; 
    36903742 
     3743  // Aborts pending autocomplete requests 
     3744  this.ksearch_destroy = function() 
     3745  { 
     3746    var i, len, ac = this.ksearch_data; 
     3747 
     3748    if (!ac) 
     3749      return; 
     3750 
     3751    for (i=0, len=ac.locks.length; i<len; i++) { 
     3752      this.hide_message(ac.locks[i]); // hide loading message 
     3753      ac.requests[i].abort(); // abort ajax request 
     3754    } 
     3755 
     3756    this.ksearch_data = null; 
     3757  } 
    36913758 
    36923759  /*********************************************************/ 
     
    45344601      var basename = this.env.mailbox.replace(reg, ''), 
    45354602        newname = this.env.dstfolder==this.env.delimiter ? basename : this.env.dstfolder+this.env.delimiter+basename; 
    4536          
     4603 
    45374604      if (newname != this.env.mailbox) { 
    45384605        this.http_post('rename-folder', '_folder_oldname='+urlencode(this.env.mailbox)+'&_folder_newname='+urlencode(newname), this.set_busy(true, 'foldermoving')); 
     
    54565523  /*********        remote request methods        *********/ 
    54575524  /********************************************************/ 
    5458    
     5525 
    54595526  // compose a valid url with the given parameters 
    54605527  this.url = function(action, query) 
     
    55355602    // send request 
    55365603    console.log('HTTP GET: ' + url); 
    5537     $.ajax({ 
     5604 
     5605    return $.ajax({ 
    55385606      type: 'GET', url: url, data: { _unlock:(lock?lock:0) }, dataType: 'json', 
    55395607      success: function(data){ ref.http_response(data); }, 
     
    55665634    // send request 
    55675635    console.log('HTTP POST: ' + url); 
    5568     $.ajax({ 
     5636 
     5637    return $.ajax({ 
    55695638      type: 'POST', url: url, data: postdata, dataType: 'json', 
    55705639      success: function(data){ ref.http_response(data); }, 
  • program/steps/mail/autocomplete.inc

    r03eb13f r0213f8d  
    2020*/ 
    2121 
    22 $MAXNUM = 15; 
    23 $book_types = (array) $RCMAIL->config->get('autocomplete_addressbooks', 'sql'); 
    24  
    2522if ($RCMAIL->action == 'group-expand') { 
    2623  $abook = $RCMAIL->get_address_book(get_input_value('_source', RCUBE_INPUT_GPC)); 
     
    3734    $OUTPUT->command('replace_group_recipients', $gid, join(', ', $members)); 
    3835  } 
     36 
     37  $OUTPUT->send(); 
    3938} 
    40 else if ($book_types && ($search = get_input_value('_search', RCUBE_INPUT_GPC, true))) { 
     39 
     40 
     41$MAXNUM = (int)$RCMAIL->config->get('autocomplete_max', 15); 
     42$search = get_input_value('_search', RCUBE_INPUT_GPC, true); 
     43$source = get_input_value('_source', RCUBE_INPUT_GPC); 
     44$sid    = get_input_value('_id', RCUBE_INPUT_GPC); 
     45 
     46if (strlen($source)) 
     47  $book_types = array($source); 
     48else 
     49  $book_types = (array) $RCMAIL->config->get('autocomplete_addressbooks', 'sql'); 
     50 
     51if (!empty($book_types) && strlen($search)) { 
    4152  $contacts = array(); 
    4253  $books_num = count($book_types); 
     
    8899} 
    89100 
    90 $OUTPUT->command('ksearch_query_results', $contacts, $search); 
     101$OUTPUT->command('ksearch_query_results', $contacts, $search, $sid); 
    91102$OUTPUT->send(); 
    92103 
  • program/steps/mail/compose.inc

    rf52c4f44 r0213f8d  
    112112    'nobodywarning', 'notsentwarning', 'notuploadedwarning', 'savingmessage', 'sendingmessage',  
    113113    'messagesaved', 'converting', 'editorwarning', 'searching', 'uploading', 'uploadingmany', 
    114     'fileuploaderror', 'autocompletechars'); 
     114    'fileuploaderror'); 
    115115 
    116116$OUTPUT->set_env('compose_id', $COMPOSE_ID); 
     
    125125$OUTPUT->set_env('sig_above', $CONFIG['sig_above']); 
    126126$OUTPUT->set_env('top_posting', $CONFIG['top_posting']); 
    127 $OUTPUT->set_env('autocomplete_min_length', $CONFIG['autocomplete_min_length']); 
    128127 
    129128// get reference message and set compose mode 
     
    467466    $out = $input->show($MESSAGE->compose[$param]); 
    468467  } 
    469    
     468 
    470469  if ($form_start) 
    471470    $out = $form_start.$out; 
     471 
     472  // configure autocompletion 
     473  rcube_autocomplete_init(); 
    472474 
    473475  return $out; 
Note: See TracChangeset for help on using the changeset viewer.