From ea0866a1adc9239b8b115ab2490e1dd88f3c64ec Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Wed, 07 May 2014 14:04:13 -0400
Subject: [PATCH] Improve keyboard navigation on compose screen: define tabindex groups + enable keyboard controls of contacts list widget

---
 program/js/app.js |   48 +++++++++++++++++++++++++++++++++---------------
 1 files changed, 33 insertions(+), 15 deletions(-)

diff --git a/program/js/app.js b/program/js/app.js
index b2c9209..4c9462a 100644
--- a/program/js/app.js
+++ b/program/js/app.js
@@ -197,7 +197,7 @@
 
     // enable general commands
     this.enable_command('close', 'logout', 'mail', 'addressbook', 'settings', 'save-pref',
-      'compose', 'undo', 'about', 'switch-task', 'menu-open', 'menu-save', true);
+      'compose', 'undo', 'about', 'switch-task', 'menu-open', 'menu-close', 'menu-save', true);
 
     // set active task button
     this.set_button(this.task, 'sel');
@@ -300,10 +300,12 @@
             $('a.insertresponse', this.gui_objects.responseslist)
               .attr('unselectable', 'on')
               .mousedown(function(e){ return rcube_event.cancel(e); })
-              .mouseup(function(e){
-                ref.command('insert-response', $(this).attr('rel'));
-                $(document.body).trigger('mouseup');  // hides the menu
-                return rcube_event.cancel(e);
+              .bind('mouseup keypress', function(e){
+                if (e.type == 'mouseup' || rcube_event.get_keycode(e) == 13) {
+                  ref.command('insert-response', $(this).attr('rel'));
+                  $(document.body).trigger('mouseup');  // hides the menu
+                  return rcube_event.cancel(e);
+                }
               });
 
             // avoid textarea loosing focus when hitting the save-response button/link
@@ -337,11 +339,12 @@
         // init address book widget
         if (this.gui_objects.contactslist) {
           this.contact_list = new rcube_list_widget(this.gui_objects.contactslist,
-            { multiselect:true, draggable:false, keyboard:false });
+            { multiselect:true, draggable:false, keyboard:true });
           this.contact_list
             .addEventListener('initrow', function(o) { ref.triggerEvent('insertrow', { cid:o.uid, row:o }); })
             .addEventListener('select', function(o) { ref.compose_recipient_select(o); })
             .addEventListener('dblclick', function(o) { ref.compose_add_recipient('to'); })
+            .addEventListener('keypress', function(o) { if (o.key_pressed == o.ENTER_KEY) ref.compose_add_recipient('to'); })
             .init();
         }
 
@@ -711,7 +714,8 @@
         }
 
       case 'menu-save':
-        this.triggerEvent(command, {props:props});
+      case 'menu-close':
+        this.triggerEvent(command, {props:props, originalEvent:event});
         return false;
 
       case 'open':
@@ -2316,6 +2320,7 @@
       url._page = page;
 
     this.http_request('list', url, lock);
+    this.update_state({ _mbox: mbox, _page: (page && page > 1 ? page : null) });
   };
 
   // removes messages that doesn't exists from list selection array
@@ -3592,15 +3597,18 @@
       $('<a>').addClass('insertresponse active')
         .attr('href', '#')
         .attr('rel', key)
+        .attr('tabindex', '0')
         .html(this.quote_html(response.name))
         .appendTo(li)
         .mousedown(function(e){
           return rcube_event.cancel(e);
         })
-        .mouseup(function(e){
-          ref.command('insert-response', key);
-          $(document.body).trigger('mouseup');  // hides the menu
-          return rcube_event.cancel(e);
+        .bind('mouseup keypress', function(e){
+          if (e.type == 'mouseup' || rcube_event.get_keycode(e) == 13) {
+            ref.command('insert-response', $(this).attr('rel'));
+            $(document.body).trigger('mouseup');  // hides the menu
+            return rcube_event.cancel(e);
+          }
         });
     }
   };
@@ -6192,7 +6200,7 @@
   // set button to a specific state
   this.set_button = function(command, state)
   {
-    var n, button, obj, a_buttons = this.buttons[command],
+    var n, button, obj, $obj, a_buttons = this.buttons[command],
       len = a_buttons ? a_buttons.length : 0;
 
     for (n=0; n<len; n++) {
@@ -6232,8 +6240,9 @@
         $(obj).button('option', 'disabled', state == 'pas');
       }
       else {
-        $(obj)
-          .attr('tabindex', state == 'pas' || state == 'sel' ? '-1' : '0')
+        $obj = $(obj);
+        $obj
+          .attr('tabindex', state == 'pas' || state == 'sel' ? '-1' : ($obj.attr('data-tabindex') || '0'))
           .attr('aria-disabled', state == 'pas' || state == 'sel' ? 'true' : 'false');
       }
     }
@@ -6359,7 +6368,8 @@
       this.messages[key].labels = [{'id': id, 'msg': msg}];
     }
     else {
-      obj.click(function() { return ref.hide_message(obj); });
+      obj.click(function() { return ref.hide_message(obj); })
+        .attr('role', 'alert');
     }
 
     this.triggerEvent('message', { message:msg, type:type, timeout:timeout, object:obj });
@@ -6939,6 +6949,13 @@
     this.start_keepalive();
   };
 
+  // update browser location to remember current view
+  this.update_state = function(query)
+  {
+    if (window.history.replaceState)
+      window.history.replaceState({}, document.title, rcmail.url('', query));
+  };
+
   // send a http request to the server
   this.http_request = function(action, query, lock)
   {
@@ -7136,6 +7153,7 @@
             this.enable_command('search-create', this.env.source == '');
             this.enable_command('search-delete', this.env.search_id);
             this.update_group_commands();
+            this.contact_list.focus();
             this.triggerEvent('listupdate', { folder:this.env.source, rowcount:this.contact_list.rowcount });
           }
         }

--
Gitblit v1.9.1