From 9313f27a89d54081ad2efa82c555d8d1c6ed2d99 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C3=A9gis=20Hanol?= <regis@hanol.fr>
Date: Wed, 22 Jan 2014 10:17:37 +0100
Subject: [PATCH] update mousetrap to latest

---
 vendor/assets/javascripts/mousetrap.js | 483 +++++++++++++++----------
 1 file changed, 286 insertions(+), 197 deletions(-)

diff --git a/vendor/assets/javascripts/mousetrap.js b/vendor/assets/javascripts/mousetrap.js
index 5de5abfaf..66f2d5b9c 100644
--- a/vendor/assets/javascripts/mousetrap.js
+++ b/vendor/assets/javascripts/mousetrap.js
@@ -1,5 +1,6 @@
+/*global define:false */
 /**
- * Copyright 2012 Craig Campbell
+ * Copyright 2013 Craig Campbell
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,10 +17,10 @@
  * Mousetrap is a simple keyboard shortcut library for Javascript with
  * no external dependencies
  *
- * @version 1.2.2
+ * @version 1.4.6
  * @url craig.is/killing/mice
  */
-(function() {
+(function(window, document, undefined) {
 
     /**
      * mapping of special keycodes to their corresponding keys
@@ -124,7 +125,8 @@
             'option': 'alt',
             'command': 'meta',
             'return': 'enter',
-            'escape': 'esc'
+            'escape': 'esc',
+            'mod': /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? 'meta' : 'ctrl'
         },
 
         /**
@@ -148,7 +150,7 @@
          *
          * @type {Object}
          */
-        _direct_map = {},
+        _directMap = {},
 
         /**
          * keeps track of what level each sequence is at since multiple
@@ -156,21 +158,28 @@
          *
          * @type {Object}
          */
-        _sequence_levels = {},
+        _sequenceLevels = {},
 
         /**
          * variable to store the setTimeout call
          *
          * @type {null|number}
          */
-        _reset_timer,
+        _resetTimer,
 
         /**
          * temporary state where we will ignore the next keyup
          *
          * @type {boolean|string}
          */
-        _ignore_next_keyup = false,
+        _ignoreNextKeyup = false,
+
+        /**
+         * temporary state where we will ignore the next keypress
+         *
+         * @type {boolean}
+         */
+        _ignoreNextKeypress = false,
 
         /**
          * are we currently inside of a sequence?
@@ -178,7 +187,7 @@
          *
          * @type {boolean|string}
          */
-        _sequence_type = false;
+        _nextExpectedAction = false;
 
     /**
      * loop through the f keys, f1 to f19 and add them to the map
@@ -222,7 +231,22 @@
 
         // for keypress events we should return the character as is
         if (e.type == 'keypress') {
-            return String.fromCharCode(e.which);
+            var character = String.fromCharCode(e.which);
+
+            // if the shift key is not pressed then it is safe to assume
+            // that we want the character to be lowercase.  this means if
+            // you accidentally have caps lock on then your key bindings
+            // will continue to work
+            //
+            // the only side effect that might not be desired is if you
+            // bind something like 'A' cause you want to trigger an
+            // event when capital A is pressed caps lock will no longer
+            // trigger the event.  shift+a will though.
+            if (!e.shiftKey) {
+                character = character.toLowerCase();
+            }
+
+            return character;
         }
 
         // for non keypress events the special maps are needed
@@ -235,6 +259,10 @@
         }
 
         // if it is not in the special map
+
+        // with keydown and keyup events the character seems to always
+        // come in as an uppercase character whether you are pressing shift
+        // or not.  we should make sure it is always lowercase for comparisons
         return String.fromCharCode(e.which).toLowerCase();
     }
 
@@ -252,25 +280,25 @@
     /**
      * resets all sequence counters except for the ones passed in
      *
-     * @param {Object} do_not_reset
+     * @param {Object} doNotReset
      * @returns void
      */
-    function _resetSequences(do_not_reset, max_level) {
-        do_not_reset = do_not_reset || {};
+    function _resetSequences(doNotReset) {
+        doNotReset = doNotReset || {};
 
-        var active_sequences = false,
+        var activeSequences = false,
             key;
 
-        for (key in _sequence_levels) {
-            if (do_not_reset[key] && _sequence_levels[key] > max_level) {
-                active_sequences = true;
+        for (key in _sequenceLevels) {
+            if (doNotReset[key]) {
+                activeSequences = true;
                 continue;
             }
-            _sequence_levels[key] = 0;
+            _sequenceLevels[key] = 0;
         }
 
-        if (!active_sequences) {
-            _sequence_type = false;
+        if (!activeSequences) {
+            _nextExpectedAction = false;
         }
     }
 
@@ -281,11 +309,12 @@
      * @param {string} character
      * @param {Array} modifiers
      * @param {Event|Object} e
-     * @param {boolean=} remove - should we remove any matches
+     * @param {string=} sequenceName - name of the sequence we are looking for
      * @param {string=} combination
+     * @param {number=} level
      * @returns {Array}
      */
-    function _getMatches(character, modifiers, e, remove, combination) {
+    function _getMatches(character, modifiers, e, sequenceName, combination, level) {
         var i,
             callback,
             matches = [],
@@ -306,9 +335,9 @@
         for (i = 0; i < _callbacks[character].length; ++i) {
             callback = _callbacks[character][i];
 
-            // if this is a sequence but it is not at the right level
-            // then move onto the next match
-            if (callback.seq && _sequence_levels[callback.seq] != callback.level) {
+            // if a sequence name is not specified, but this is a sequence at
+            // the wrong level then move onto the next match
+            if (!sequenceName && callback.seq && _sequenceLevels[callback.seq] != callback.level) {
                 continue;
             }
 
@@ -327,9 +356,14 @@
             // firefox will fire a keypress if meta or control is down
             if ((action == 'keypress' && !e.metaKey && !e.ctrlKey) || _modifiersMatch(modifiers, callback.modifiers)) {
 
-                // remove is used so if you change your mind and call bind a
-                // second time with a new function the first one is overwritten
-                if (remove && callback.combo == combination) {
+                // when you bind a combination or sequence a second time it
+                // should overwrite the first one.  if a sequenceName or
+                // combination is specified in this call it does just that
+                //
+                // @todo make deleting its own method?
+                var deleteCombo = !sequenceName && callback.combo == combination;
+                var deleteSequence = sequenceName && callback.seq == sequenceName && callback.level == level;
+                if (deleteCombo || deleteSequence) {
                     _callbacks[character].splice(i, 1);
                 }
 
@@ -368,6 +402,36 @@
         return modifiers;
     }
 
+    /**
+     * prevents default for this event
+     *
+     * @param {Event} e
+     * @returns void
+     */
+    function _preventDefault(e) {
+        if (e.preventDefault) {
+            e.preventDefault();
+            return;
+        }
+
+        e.returnValue = false;
+    }
+
+    /**
+     * stops propogation for this event
+     *
+     * @param {Event} e
+     * @returns void
+     */
+    function _stopPropagation(e) {
+        if (e.stopPropagation) {
+            e.stopPropagation();
+            return;
+        }
+
+        e.cancelBubble = true;
+    }
+
     /**
      * actually calls the callback function
      *
@@ -378,24 +442,16 @@
      * @param {Event} e
      * @returns void
      */
-    function _fireCallback(callback, e, combo) {
+    function _fireCallback(callback, e, combo, sequence) {
 
         // if this event should not happen stop here
-        if (Mousetrap.stopCallback(e, e.target || e.srcElement, combo)) {
+        if (Mousetrap.stopCallback(e, e.target || e.srcElement, combo, sequence)) {
             return;
         }
 
         if (callback(e, combo) === false) {
-            if (e.preventDefault) {
-                e.preventDefault();
-            }
-
-            if (e.stopPropagation) {
-                e.stopPropagation();
-            }
-
-            e.returnValue = false;
-            e.cancelBubble = true;
+            _preventDefault(e);
+            _stopPropagation(e);
         }
     }
 
@@ -403,15 +459,23 @@
      * handles a character key event
      *
      * @param {string} character
+     * @param {Array} modifiers
      * @param {Event} e
      * @returns void
      */
-    function _handleCharacter(character, e) {
-        var callbacks = _getMatches(character, _eventModifiers(e), e),
+    function _handleKey(character, modifiers, e) {
+        var callbacks = _getMatches(character, modifiers, e),
             i,
-            do_not_reset = {},
-            max_level = 0,
-            processed_sequence_callback = false;
+            doNotReset = {},
+            maxLevel = 0,
+            processedSequenceCallback = false;
+
+        // Calculate the maxLevel for sequences so we can only execute the longest callback sequence
+        for (i = 0; i < callbacks.length; ++i) {
+            if (callbacks[i].seq) {
+                maxLevel = Math.max(maxLevel, callbacks[i].level);
+            }
+        }
 
         // loop through matching callbacks for this key event
         for (i = 0; i < callbacks.length; ++i) {
@@ -422,31 +486,61 @@
             // callback for matching g cause otherwise you can only ever
             // match the first one
             if (callbacks[i].seq) {
-                processed_sequence_callback = true;
 
-                // as we loop through keep track of the max
-                // any sequence at a lower level will be discarded
-                max_level = Math.max(max_level, callbacks[i].level);
+                // only fire callbacks for the maxLevel to prevent
+                // subsequences from also firing
+                //
+                // for example 'a option b' should not cause 'option b' to fire
+                // even though 'option b' is part of the other sequence
+                //
+                // any sequences that do not match here will be discarded
+                // below by the _resetSequences call
+                if (callbacks[i].level != maxLevel) {
+                    continue;
+                }
+
+                processedSequenceCallback = true;
 
                 // keep a list of which sequences were matches for later
-                do_not_reset[callbacks[i].seq] = 1;
-                _fireCallback(callbacks[i].callback, e, callbacks[i].combo);
+                doNotReset[callbacks[i].seq] = 1;
+                _fireCallback(callbacks[i].callback, e, callbacks[i].combo, callbacks[i].seq);
                 continue;
             }
 
             // if there were no sequence matches but we are still here
             // that means this is a regular match so we should fire that
-            if (!processed_sequence_callback && !_sequence_type) {
+            if (!processedSequenceCallback) {
                 _fireCallback(callbacks[i].callback, e, callbacks[i].combo);
             }
         }
 
-        // if you are inside of a sequence and the key you are pressing
-        // is not a modifier key then we should reset all sequences
-        // that were not matched by this key event
-        if (e.type == _sequence_type && !_isModifier(character)) {
-            _resetSequences(do_not_reset, max_level);
+        // if the key you pressed matches the type of sequence without
+        // being a modifier (ie "keyup" or "keypress") then we should
+        // reset all sequences that were not matched by this event
+        //
+        // this is so, for example, if you have the sequence "h a t" and you
+        // type "h e a r t" it does not match.  in this case the "e" will
+        // cause the sequence to reset
+        //
+        // modifier keys are ignored because you can have a sequence
+        // that contains modifiers such as "enter ctrl+space" and in most
+        // cases the modifier key will be pressed before the next key
+        //
+        // also if you have a sequence such as "ctrl+b a" then pressing the
+        // "b" key will trigger a "keypress" and a "keydown"
+        //
+        // the "keydown" is expected when there is a modifier, but the
+        // "keypress" ends up matching the _nextExpectedAction since it occurs
+        // after and that causes the sequence to reset
+        //
+        // we ignore keypresses in a sequence that directly follow a keydown
+        // for the same character
+        var ignoreThisKeypress = e.type == 'keypress' && _ignoreNextKeypress;
+        if (e.type == _nextExpectedAction && !_isModifier(character) && !ignoreThisKeypress) {
+            _resetSequences(doNotReset);
         }
+
+        _ignoreNextKeypress = processedSequenceCallback && e.type == 'keydown';
     }
 
     /**
@@ -455,7 +549,7 @@
      * @param {Event} e
      * @returns void
      */
-    function _handleKey(e) {
+    function _handleKeyEvent(e) {
 
         // normalize e.which for key events
         // @see http://stackoverflow.com/questions/4285627/javascript-keycode-vs-charcode-utter-confusion
@@ -470,12 +564,13 @@
             return;
         }
 
-        if (e.type == 'keyup' && _ignore_next_keyup == character) {
-            _ignore_next_keyup = false;
+        // need to use === for the character check because the character can be 0
+        if (e.type == 'keyup' && _ignoreNextKeyup === character) {
+            _ignoreNextKeyup = false;
             return;
         }
 
-        _handleCharacter(character, e);
+        Mousetrap.handleKey(character, _eventModifiers(e), e);
     }
 
     /**
@@ -497,8 +592,8 @@
      * @returns void
      */
     function _resetSequenceTimer() {
-        clearTimeout(_reset_timer);
-        _reset_timer = setTimeout(_resetSequences, 1000);
+        clearTimeout(_resetTimer);
+        _resetTimer = setTimeout(_resetSequences, 1000);
     }
 
     /**
@@ -563,89 +658,91 @@
 
         // start off by adding a sequence level record for this combination
         // and setting the level to 0
-        _sequence_levels[combo] = 0;
-
-        // if there is no action pick the best one for the first key
-        // in the sequence
-        if (!action) {
-            action = _pickBestAction(keys[0], []);
-        }
+        _sequenceLevels[combo] = 0;
 
         /**
          * callback to increase the sequence level for this sequence and reset
          * all other sequences that were active
          *
+         * @param {string} nextAction
+         * @returns {Function}
+         */
+        function _increaseSequence(nextAction) {
+            return function() {
+                _nextExpectedAction = nextAction;
+                ++_sequenceLevels[combo];
+                _resetSequenceTimer();
+            };
+        }
+
+        /**
+         * wraps the specified callback inside of another function in order
+         * to reset all sequence counters as soon as this sequence is done
+         *
          * @param {Event} e
          * @returns void
          */
-        var _increaseSequence = function(e) {
-                _sequence_type = action;
-                ++_sequence_levels[combo];
-                _resetSequenceTimer();
-            },
+        function _callbackAndReset(e) {
+            _fireCallback(callback, e, combo);
 
-            /**
-             * wraps the specified callback inside of another function in order
-             * to reset all sequence counters as soon as this sequence is done
-             *
-             * @param {Event} e
-             * @returns void
-             */
-            _callbackAndReset = function(e) {
-                _fireCallback(callback, e, combo);
+            // we should ignore the next key up if the action is key down
+            // or keypress.  this is so if you finish a sequence and
+            // release the key the final key will not trigger a keyup
+            if (action !== 'keyup') {
+                _ignoreNextKeyup = _characterFromEvent(e);
+            }
 
-                // we should ignore the next key up if the action is key down
-                // or keypress.  this is so if you finish a sequence and
-                // release the key the final key will not trigger a keyup
-                if (action !== 'keyup') {
-                    _ignore_next_keyup = _characterFromEvent(e);
-                }
-
-                // weird race condition if a sequence ends with the key
-                // another sequence begins with
-                setTimeout(_resetSequences, 10);
-            },
-            i;
+            // weird race condition if a sequence ends with the key
+            // another sequence begins with
+            setTimeout(_resetSequences, 10);
+        }
 
         // loop through keys one at a time and bind the appropriate callback
         // function.  for any key leading up to the final one it should
         // increase the sequence. after the final, it should reset all sequences
-        for (i = 0; i < keys.length; ++i) {
-            _bindSingle(keys[i], i < keys.length - 1 ? _increaseSequence : _callbackAndReset, action, combo, i);
+        //
+        // if an action is specified in the original bind call then that will
+        // be used throughout.  otherwise we will pass the action that the
+        // next key in the sequence should match.  this allows a sequence
+        // to mix and match keypress and keydown events depending on which
+        // ones are better suited to the key provided
+        for (var i = 0; i < keys.length; ++i) {
+            var isFinal = i + 1 === keys.length;
+            var wrappedCallback = isFinal ? _callbackAndReset : _increaseSequence(action || _getKeyInfo(keys[i + 1]).action);
+            _bindSingle(keys[i], wrappedCallback, action, combo, i);
         }
     }
 
     /**
-     * binds a single keyboard combination
+     * Converts from a string key combination to an array
      *
-     * @param {string} combination
-     * @param {Function} callback
-     * @param {string=} action
-     * @param {string=} sequence_name - name of sequence if part of sequence
-     * @param {number=} level - what part of the sequence the command is
-     * @returns void
+     * @param  {string} combination like "command+shift+l"
+     * @return {Array}
      */
-    function _bindSingle(combination, callback, action, sequence_name, level) {
-
-        // make sure multiple spaces in a row become a single space
-        combination = combination.replace(/\s+/g, ' ');
-
-        var sequence = combination.split(' '),
-            i,
-            key,
-            keys,
-            modifiers = [];
-
-        // if this pattern is a sequence of keys then run through this method
-        // to reprocess each pattern one key at a time
-        if (sequence.length > 1) {
-            _bindSequence(combination, sequence, callback, action);
-            return;
+    function _keysFromString(combination) {
+        if (combination === '+') {
+            return ['+'];
         }
 
+        return combination.split('+');
+    }
+
+    /**
+     * Gets info for a specific key combination
+     *
+     * @param  {string} combination key combination ("command+s" or "a" or "*")
+     * @param  {string=} action
+     * @returns {Object}
+     */
+    function _getKeyInfo(combination, action) {
+        var keys,
+            key,
+            i,
+            modifiers = [];
+
         // take the keys from this pattern and figure out what the actual
         // pattern is all about
-        keys = combination === '+' ? ['+'] : combination.split('+');
+        keys = _keysFromString(combination);
 
         for (i = 0; i < keys.length; ++i) {
             key = keys[i];
@@ -673,14 +770,49 @@
         // we will try to pick the best event for it
         action = _pickBestAction(key, modifiers, action);
 
-        // make sure to initialize array if this is the first time
-        // a callback is added for this key
-        if (!_callbacks[key]) {
-            _callbacks[key] = [];
+        return {
+            key: key,
+            modifiers: modifiers,
+            action: action
+        };
+    }
+
+    /**
+     * binds a single keyboard combination
+     *
+     * @param {string} combination
+     * @param {Function} callback
+     * @param {string=} action
+     * @param {string=} sequenceName - name of sequence if part of sequence
+     * @param {number=} level - what part of the sequence the command is
+     * @returns void
+     */
+    function _bindSingle(combination, callback, action, sequenceName, level) {
+
+        // store a direct mapped reference for use with Mousetrap.trigger
+        _directMap[combination + ':' + action] = callback;
+
+        // make sure multiple spaces in a row become a single space
+        combination = combination.replace(/\s+/g, ' ');
+
+        var sequence = combination.split(' '),
+            info;
+
+        // if this pattern is a sequence of keys then run through this method
+        // to reprocess each pattern one key at a time
+        if (sequence.length > 1) {
+            _bindSequence(combination, sequence, callback, action);
+            return;
         }
 
+        info = _getKeyInfo(combination, action);
+
+        // make sure to initialize array if this is the first time
+        // a callback is added for this key
+        _callbacks[info.key] = _callbacks[info.key] || [];
+
         // remove an existing match if there is one
-        _getMatches(key, modifiers, {type: action}, !sequence_name, combination);
+        _getMatches(info.key, info.modifiers, {type: info.action}, sequenceName, combination, level);
 
         // add this call back to the array
         // if it is a sequence put it at the beginning
@@ -688,11 +820,11 @@
         //
         // this is important because the way these are processed expects
         // the sequence ones to come first
-        _callbacks[key][sequence_name ? 'unshift' : 'push']({
+        _callbacks[info.key][sequenceName ? 'unshift' : 'push']({
             callback: callback,
-            modifiers: modifiers,
-            action: action,
-            seq: sequence_name,
+            modifiers: info.modifiers,
+            action: info.action,
+            seq: sequenceName,
             level: level,
             combo: combination
         });
@@ -713,9 +845,9 @@
     }
 
     // start!
-    _addEvent(document, 'keypress', _handleKey);
-    _addEvent(document, 'keydown', _handleKey);
-    _addEvent(document, 'keyup', _handleKey);
+    _addEvent(document, 'keypress', _handleKeyEvent);
+    _addEvent(document, 'keydown', _handleKeyEvent);
+    _addEvent(document, 'keyup', _handleKeyEvent);
 
     var Mousetrap = {
 
@@ -734,8 +866,8 @@
          * @returns void
          */
         bind: function(keys, callback, action) {
-            _bindMultiple(keys instanceof Array ? keys : [keys], callback, action);
-            _direct_map[keys + ':' + action] = callback;
+            keys = keys instanceof Array ? keys : [keys];
+            _bindMultiple(keys, callback, action);
             return this;
         },
 
@@ -744,24 +876,20 @@
          *
          * the unbinding sets the callback function of the specified key combo
          * to an empty function and deletes the corresponding key in the
-         * _direct_map dict.
-         *
-         * the keycombo+action has to be exactly the same as
-         * it was defined in the bind method
+         * _directMap dict.
          *
          * TODO: actually remove this from the _callbacks dictionary instead
          * of binding an empty function
          *
+         * the keycombo+action has to be exactly the same as
+         * it was defined in the bind method
+         *
          * @param {string|Array} keys
          * @param {string} action
          * @returns void
          */
         unbind: function(keys, action) {
-            if (_direct_map[keys + ':' + action]) {
-                delete _direct_map[keys + ':' + action];
-                this.bind(keys, function() {}, action);
-            }
-            return this;
+            return Mousetrap.bind(keys, function() {}, action);
         },
 
         /**
@@ -772,7 +900,9 @@
          * @returns void
          */
         trigger: function(keys, action) {
-            _direct_map[keys + ':' + action]();
+            if (_directMap[keys + ':' + action]) {
+                _directMap[keys + ':' + action]({}, keys);
+            }
             return this;
         },
 
@@ -785,7 +915,7 @@
          */
         reset: function() {
             _callbacks = {};
-            _direct_map = {};
+            _directMap = {};
             return this;
         },
 
@@ -796,7 +926,7 @@
         * @param {Element} element
         * @return {boolean}
         */
-        stopCallback: function(e, element, combo) {
+        stopCallback: function(e, element) {
 
             // if the element has the class "mousetrap" then no need to stop
             if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) {
@@ -804,8 +934,13 @@
             }
 
             // stop for input, select, and textarea
-            return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || (element.contentEditable && element.contentEditable == 'true');
-        }
+            return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || element.isContentEditable;
+        },
+
+        /**
+         * exposes _handleKey publicly so it can be overwritten by extensions
+         */
+        handleKey: _handleKey
     };
 
     // expose mousetrap to the global object
@@ -815,50 +950,4 @@
     if (typeof define === 'function' && define.amd) {
         define(Mousetrap);
     }
-}) ();
-
-
-/**
- * adds a bindGlobal method to Mousetrap that allows you to
- * bind specific keyboard shortcuts that will still work
- * inside a text input field
- *
- * usage:
- * Mousetrap.bindGlobal('ctrl+s', _saveChanges);
- */
-Mousetrap = (function(Mousetrap) {
-    var _global_callbacks = {},
-        _original_stop_callback = Mousetrap.stopCallback;
-
-    Mousetrap.stopCallback = function(e, element, combo) {
-        if (_global_callbacks[combo]) {
-            return false;
-        }
-
-        return _original_stop_callback(e, element, combo);
-    };
-
-    Mousetrap.bindGlobal = function(keys, callback, action) {
-        Mousetrap.bind(keys, callback, action);
-
-        if (keys instanceof Array) {
-            for (var i = 0; i < keys.length; i++) {
-                _global_callbacks[keys[i]] = true;
-            }
-            return;
-        }
-
-        _global_callbacks[keys] = true;
-    };
-
-    Mousetrap.unbindGlobal = function(keys) {
-        if (keys instanceof Array) {
-            for (var i = 0; i < keys.length; i++) {
-                _global_callbacks[keys[i]] = false;
-            }
-            return;
-        }
-    };
-
-    return Mousetrap;
-}) (Mousetrap);
+}) (window, document);