From af468a8c6aa107d29ec669a24e910451998b3291 Mon Sep 17 00:00:00 2001
From: Joseph Schorr <joseph.schorr@coreos.com>
Date: Wed, 8 Apr 2015 14:39:52 -0400
Subject: [PATCH] - UI feedback fixes: text cutoffs, misformatting fixes, etc -
 Add hotkey support for searching and creating repositories - Make search
 animations significantly faster

---
 .../css/directives/ui/build-mini-status.css   | 17 ++++++-
 static/css/directives/ui/header-bar.css       |  4 +-
 static/css/pages/repo-view.css                |  4 ++
 static/directives/build-mini-status.html      | 15 ++----
 static/directives/new-header-bar.html         |  2 +-
 .../repo-view/repo-panel-builds.html          | 13 +++--
 .../directives/repo-view/repo-panel-tags.html | 11 +++--
 .../triggered-build-description.html          | 12 ++---
 static/js/app.js                              |  2 +-
 static/js/directives/ui/header-bar.js         | 49 +++++++++++++------
 static/lib/LICENSES                           |  1 +
 static/lib/hotkeys.min.css                    |  8 +++
 static/lib/hotkeys.min.js                     |  7 +++
 13 files changed, 99 insertions(+), 46 deletions(-)
 create mode 100644 static/lib/hotkeys.min.css
 create mode 100644 static/lib/hotkeys.min.js

diff --git a/static/css/directives/ui/build-mini-status.css b/static/css/directives/ui/build-mini-status.css
index 0913e9b53..4f39f171d 100644
--- a/static/css/directives/ui/build-mini-status.css
+++ b/static/css/directives/ui/build-mini-status.css
@@ -10,6 +10,11 @@
   text-decoration: none !important;
 }
 
+.build-mini-status a {
+  text-decoration: none !important;
+  color: black;
+}
+
 .build-mini-status .timing {
   display: inline-block;
   margin-left: 30px;
@@ -29,5 +34,15 @@
   bottom: 4px;
   line-height: 33px;
   overflow: hidden;
+}
+
+.build-mini-status .build-description .tbd-content {
+  position: absolute;
+  top: 0px;
+  bottom: 0px;
+  left: 0px;
+  right: 0px;
+  overflow: hidden;
   text-overflow: ellipsis;
-}
\ No newline at end of file
+  white-space: nowrap;
+}
diff --git a/static/css/directives/ui/header-bar.css b/static/css/directives/ui/header-bar.css
index c9d320221..5629c1c69 100644
--- a/static/css/directives/ui/header-bar.css
+++ b/static/css/directives/ui/header-bar.css
@@ -42,7 +42,7 @@ nav.navbar-default .navbar-nav>li>a.active {
   top: -50px;
   z-index: 4;
   height: 83px;
-  transition: top 0.7s cubic-bezier(.23,.88,.72,.98);
+  transition: top 0.3s cubic-bezier(.23,.88,.72,.98);
   background: white;
   box-shadow: 0px 1px 16px #444;
   padding: 10px;
@@ -89,7 +89,7 @@ nav.navbar-default .navbar-nav>li>a.active {
   right: 0px;
   top: -130px;
   z-index: 3;
-  transition: top 0.7s cubic-bezier(.23,.88,.72,.98), height 0.5s ease-in-out;
+  transition: top 0.4s cubic-bezier(.23,.88,.72,.98), height 0.25s ease-in-out;
 
   background: white;
   box-shadow: 0px 1px 16px #444;
diff --git a/static/css/pages/repo-view.css b/static/css/pages/repo-view.css
index 9051be756..dd0c3d298 100644
--- a/static/css/pages/repo-view.css
+++ b/static/css/pages/repo-view.css
@@ -44,3 +44,7 @@
 .repository-view .heading-controls .btn .fa {
   margin-right: 6px;
 }
+
+.repository-view .tag-span {
+  white-space: nowrap;
+}
\ No newline at end of file
diff --git a/static/directives/build-mini-status.html b/static/directives/build-mini-status.html
index 590a857b5..bba073329 100644
--- a/static/directives/build-mini-status.html
+++ b/static/directives/build-mini-status.html
@@ -1,6 +1,6 @@
 <span class="build-mini-status-element">
-  <a href="/repository/{{ build.repository.namespace }}/{{ build.repository.name }}/build/{{ build.id }}"
-     ng-if="is_admin">
+  <span class="anchor" href="/repository/{{ build.repository.namespace }}/{{ build.repository.name }}/build/{{ build.id }}"
+     is-text-only="!isAdmin">
     <div>
      <span class="build-state-icon" build="build"></span>
      <span class="timing">
@@ -9,14 +9,5 @@
 
      <div class="build-description triggered-build-description" build="build"></div>
     </div>
-  </a>
-
-  <div ng-if="!is_admin">
-   <span class="build-state-icon" build="build"></span>
-   <span class="timing">
-     <i class="fa fa-clock-o"></i><span am-time-ago="build.started || 0"></span>
-   </span>
-
-   <div class="build-description triggered-build-description" build="build"></div>
-  </div>
+  </span>
 </span>
diff --git a/static/directives/new-header-bar.html b/static/directives/new-header-bar.html
index 68dfa504a..9938674bf 100644
--- a/static/directives/new-header-bar.html
+++ b/static/directives/new-header-bar.html
@@ -39,7 +39,7 @@
         <li>
           <span class="navbar-left user-tools">
             <i class="fa fa-search fa-lg user-tool" ng-click="toggleSearch()"
-               data-placement="bottom" data-title="Search" bs-tooltip></i>
+               data-placement="bottom" data-title="Search - Keyboard Shortcut: /" bs-tooltip></i>
           </span>
         </li>
         <li>
diff --git a/static/directives/repo-view/repo-panel-builds.html b/static/directives/repo-view/repo-panel-builds.html
index 31c0c10e7..cd3949024 100644
--- a/static/directives/repo-view/repo-panel-builds.html
+++ b/static/directives/repo-view/repo-panel-builds.html
@@ -37,16 +37,19 @@
         <table class="co-table" ng-if="fullBuilds.length">
            <thead>
             <td class="status-col"></td>
-            <td ng-class="tablePredicateClass('id', options.predicate, options.reverse)">
+            <td ng-class="tablePredicateClass('id', options.predicate, options.reverse)"
+                style="min-width: 85px;">
               <a href="javascript:void(0)" ng-click="orderBy('id')">Build ID</a>
             </td>
-            <td ng-class="tablePredicateClass('commit_sha', options.predicate, options.reverse)">
+            <td ng-class="tablePredicateClass('commit_sha', options.predicate, options.reverse)"
+                style="min-width: 115px">
               <a href="javascript:void(0)" ng-click="orderBy('commit_sha')">Triggered By</a>
             </td>
-            <td ng-class="tablePredicateClass('started_datetime', options.predicate, options.reverse)">
+            <td ng-class="tablePredicateClass('started_datetime', options.predicate, options.reverse)" style="min-width: 120px;">
               <a href="javascript:void(0)" ng-click="orderBy('started_datetime')">Date Started</a>
             </td>
-            <td ng-class="tablePredicateClass('tags', options.predicate, options.reverse)">
+            <td ng-class="tablePredicateClass('tags', options.predicate, options.reverse)"
+                style="min-width: 66px;">
               <a href="javascript:void(0)" ng-click="orderBy('tags')">Tags</a>
             </td>
             <td class="options-col"></td>
@@ -63,7 +66,7 @@
              <td>{{ build.started | amCalendar }}</td>
              <td>
                 <span class="building-tag" ng-repeat="tag in build.building_tags">
-                  <i class="fa fa-tag"></i>{{ tag }}
+                  <span class="tag-span"><i class="fa fa-tag"></i>{{ tag }}</span>
                 </span>
              </td>
            </tr>
diff --git a/static/directives/repo-view/repo-panel-tags.html b/static/directives/repo-view/repo-panel-tags.html
index 867009d4a..402312c69 100644
--- a/static/directives/repo-view/repo-panel-tags.html
+++ b/static/directives/repo-view/repo-panel-tags.html
@@ -41,14 +41,17 @@
       <td ng-class="tablePredicateClass('name', options.predicate, options.reverse)">
         <a href="javascript:void(0)" ng-click="orderBy('name')">Tag</a>
       </td>
-      <td ng-class="tablePredicateClass('last_modified_datetime', options.predicate, options.reverse)">
+      <td ng-class="tablePredicateClass('last_modified_datetime', options.predicate, options.reverse)"
+          style="min-width: 120px;">
         <a href="javascript:void(0)" ng-click="orderBy('last_modified_datetime')">Last Modified</a>
       </td>
-      <td ng-class="tablePredicateClass('size', options.predicate, options.reverse)">
+      <td ng-class="tablePredicateClass('size', options.predicate, options.reverse)"
+          style="min-width: 62px;">
         <a href="javascript:void(0)" ng-click="orderBy('size')">Size</a>
       </td>
       <td ng-class="tablePredicateClass('image_id', options.predicate, options.reverse)"
-          colspan="{{ imageTracks.length + 1 }}">
+          colspan="{{ imageTracks.length + 1 }}"
+          style="min-width: 120px;">
         <a href="javascript:void(0)" ng-click="orderBy('image_id')">Image</a>
       </td>
       <td class="options-col"></td>
@@ -59,7 +62,7 @@
          ng-repeat="tag in tags"
          ng-class="checkedTags.isChecked(tag, checkedTags.checked) ? 'checked' : ''">
       <td><span class="cor-checkable-item" controller="checkedTags" item="tag"></span></td>
-      <td><i class="fa fa-tag"></i> {{ tag.name }}</td>
+      <td><span class="tag-span"><i class="fa fa-tag"></i> {{ tag.name }}</span></td>
       <td>
         <span am-time-ago="tag.last_modified" ng-if="tag.last_modified"></span>
         <span ng-if="!tag.last_modified">Unknown</span>
diff --git a/static/directives/triggered-build-description.html b/static/directives/triggered-build-description.html
index 09f494417..5a1e346e1 100644
--- a/static/directives/triggered-build-description.html
+++ b/static/directives/triggered-build-description.html
@@ -1,10 +1,10 @@
 <div class="triggered-build-description-element">
 
-<span class="manual" ng-if="!build.trigger && !build.job_config.manual_user">
+<span class="tbd-content" class="manual" ng-if="!build.trigger && !build.job_config.manual_user">
   (Manually Triggered Build)
 </span>
 
-<span ng-if="!build.trigger && build.job_config.manual_user">
+<span class="tbd-content" ng-if="!build.trigger && build.job_config.manual_user">
   <i class="fa fa-user"></i> {{ build.job_config.manual_user }}
 </span>
 
@@ -12,7 +12,7 @@
   <!-- GitHub -->
   <span ng-switch-when="github">
     <!-- Full Commit Information -->
-    <span ng-if="build.job_config.trigger_metadata.commit_info">
+    <span class="tbd-content" ng-if="build.job_config.trigger_metadata.commit_info">
       <div class="commit-message">
         <a ng-href="{{ getGitHubRepoURL(build) }}/commit/{{ build.job_config.trigger_metadata.commit_sha }}"
            target="_blank">
@@ -42,7 +42,7 @@
      </span>
 
     <!-- Just commit SHA -->
-    <span ng-if="build.job_config.trigger_metadata && !build.job_config.trigger_metadata.commit_info">
+    <span class="tbd-content" ng-if="build.job_config.trigger_metadata && !build.job_config.trigger_metadata.commit_info">
       Triggered by commit
       <span class="source-commit-link"
             commit-sha="build.job_config.trigger_metadata.commit_sha"
@@ -50,9 +50,9 @@
     </span>
 
     <!-- No information -->
-    <span ng-if="!build.job_config.trigger_metadata">
+    <span class="tbd-content" ng-if="!build.job_config.trigger_metadata">
       Triggered by commit to
-      <i class="fa fa-github fa-lg" data-title="GitHub" bs-tooltip="tooltip.title"></i>
+      <i class="fa fa-github fa-lg" data-title="GitHub" data-container="body" bs-tooltip></i>
       <a ng-href="{{ getGitHubRepoURL(build) }}" target="_new">
         {{ build.trigger.config.build_source }}
       </a>
diff --git a/static/js/app.js b/static/js/app.js
index 8dd7a84d9..92df787a5 100644
--- a/static/js/app.js
+++ b/static/js/app.js
@@ -35,7 +35,7 @@ quayPages.constant('pages', {
   }
 });
 
-quayDependencies = ['ngRoute', 'chieffancypants.loadingBar', 'angular-tour', 'restangular', 'angularMoment',
+quayDependencies = ['ngRoute', 'chieffancypants.loadingBar', 'cfp.hotkeys', 'angular-tour', 'restangular', 'angularMoment',
                     'mgcrea.ngStrap', 'ngCookies', 'ngSanitize', 'angular-md5', 'pasvaz.bindonce', 'ansiToHtml',
                     'core-ui', 'core-config-setup', 'quayPages'];
 
diff --git a/static/js/directives/ui/header-bar.js b/static/js/directives/ui/header-bar.js
index 67f170833..9bfc8bb4f 100644
--- a/static/js/directives/ui/header-bar.js
+++ b/static/js/directives/ui/header-bar.js
@@ -12,7 +12,28 @@ angular.module('quay').directive('headerBar', function () {
     restrict: 'C',
     scope: {
     },
-    controller: function($rootScope, $scope, $element, $location, $timeout, UserService, PlanService, ApiService, NotificationService, Config, CreateService) {
+    controller: function($rootScope, $scope, $element, $location, $timeout, hotkeys, UserService, PlanService, ApiService, NotificationService, Config, CreateService) {
+      // Register hotkeys:
+      hotkeys.add({
+        combo: '/',
+        description: 'Show search',
+        callback: function(e) {
+          e.preventDefault();
+          e.stopPropagation();
+          $scope.toggleSearch();
+        }
+      });
+
+      hotkeys.add({
+        combo: 'alt+c',
+        description: 'Create new repository',
+        callback: function(e) {
+          e.preventDefault();
+          e.stopPropagation();
+          $location.url('/new');
+        }
+      });
+
       $scope.notificationService = NotificationService;
       $scope.searchVisible = false;
       $scope.currentSearchQuery = null;
@@ -94,6 +115,7 @@ angular.module('quay').directive('headerBar', function () {
             conductSearch($scope.currentSearchQuery);
           }
         } else {
+          $('#search-box-input').blur()
           $scope.searchResultState = null;
         }
       };
@@ -112,35 +134,34 @@ angular.module('quay').directive('headerBar', function () {
           return;
         }
 
-        if (!$scope.searchResultState) { return; }
+        var state = $scope.searchResultState;
+        if (!state || !state['results']) { return; }
 
         if (e.keyCode == 40) {
-          $scope.searchResultState['current']++;
+          state['current']++;
           e.preventDefault();
         } else if (e.keyCode == 38) {
-          $scope.searchResultState['current']--;
+          state['current']--;
           e.preventDefault();
         } else if (e.keyCode == 13) {
-          var current = $scope.searchResultState['current'];
-          if (current >= 0 &&
-              current < $scope.searchResultState['results'].length) {
-            $scope.showResult($scope.searchResultState['results'][current]);
+          var current = state['current'];
+          if (current >= 0 && current < state['results'].length) {
+            $scope.showResult(state['results'][current]);
           }
           e.preventDefault();
         }
 
-        if (!$scope.searchResultState) { return; }
-
-        if ($scope.searchResultState['current'] < -1) {
-          $scope.searchResultState['current'] = $scope.searchResultState['results'].length - 1;
-        } else if ($scope.searchResultState['current'] >= $scope.searchResultState['results'].length) {
-          $scope.searchResultState['current'] = 0;
+        if (state['current'] < -1) {
+          state['current'] = state['results'].length - 1;
+        } else if (state['current'] >= state['results'].length) {
+          state['current'] = 0;
         }
       };
 
       $scope.showResult = function(result) {
         $scope.toggleSearch();
         $timeout(function() {
+          $scope.currentSearchQuery = '';
           $location.url(result['href'])
         }, 500);
       };
diff --git a/static/lib/LICENSES b/static/lib/LICENSES
index 39ac45d5a..35b9bb015 100644
--- a/static/lib/LICENSES
+++ b/static/lib/LICENSES
@@ -20,6 +20,7 @@ zlib - MIT (https://github.com/imaya/zlib.js)
 pagedown - Permissive
 jquery.overscroll - MIT (https://github.com/azoff/overscroll/blob/master/mit.license)
 URI.js - MIT (https://github.com/medialize/URI.js)
+angular-hotkeys - MIT (https://github.com/chieffancypants/angular-hotkeys/blob/master/LICENSE)
 
 Issues:
 >>>>> jquery.spotlight - GPLv3 (https://github.com/jameshalsall/jQuery-Spotlight)
\ No newline at end of file
diff --git a/static/lib/hotkeys.min.css b/static/lib/hotkeys.min.css
new file mode 100644
index 000000000..c7f8d24fb
--- /dev/null
+++ b/static/lib/hotkeys.min.css
@@ -0,0 +1,8 @@
+/*! 
+ * angular-hotkeys v1.4.5
+ * https://chieffancypants.github.io/angular-hotkeys
+ * Copyright (c) 2014 Wes Cruver
+ * License: MIT
+ */
+
+.cfp-hotkeys-container{display:table!important;position:fixed;width:100%;height:100%;top:0;left:0;color:#333;font-size:1em;background-color:rgba(255,255,255,.9)}.cfp-hotkeys-container.fade{z-index:-1024;visibility:hidden;opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.cfp-hotkeys-container.fade.in{z-index:10002;visibility:visible;opacity:1}.cfp-hotkeys-title{font-weight:700;text-align:center;font-size:1.2em}.cfp-hotkeys{width:100%;height:100%;display:table-cell;vertical-align:middle}.cfp-hotkeys table{margin:auto;color:#333}.cfp-content{display:table-cell;vertical-align:middle}.cfp-hotkeys-keys{padding:5px;text-align:right}.cfp-hotkeys-key{display:inline-block;color:#fff;background-color:#333;border:1px solid #333;border-radius:5px;text-align:center;margin-right:5px;box-shadow:inset 0 1px 0 #666,0 1px 0 #bbb;padding:5px 9px;font-size:1em}.cfp-hotkeys-text{padding-left:10px;font-size:1em}.cfp-hotkeys-close{position:fixed;top:20px;right:20px;font-size:2em;font-weight:700;padding:5px 10px;border:1px solid #ddd;border-radius:5px;min-height:45px;min-width:45px;text-align:center}.cfp-hotkeys-close:hover{background-color:#fff;cursor:pointer}@media all and (max-width:500px){.cfp-hotkeys{font-size:.8em}}@media all and (min-width:750px){.cfp-hotkeys{font-size:1.2em}}
\ No newline at end of file
diff --git a/static/lib/hotkeys.min.js b/static/lib/hotkeys.min.js
new file mode 100644
index 000000000..2c658c1d8
--- /dev/null
+++ b/static/lib/hotkeys.min.js
@@ -0,0 +1,7 @@
+/*! 
+ * angular-hotkeys v1.4.5
+ * https://chieffancypants.github.io/angular-hotkeys
+ * Copyright (c) 2014 Wes Cruver
+ * License: MIT
+ */
+!function(){"use strict";angular.module("cfp.hotkeys",[]).provider("hotkeys",function(){this.includeCheatSheet=!0,this.templateTitle="Keyboard Shortcuts:",this.template='<div class="cfp-hotkeys-container fade" ng-class="{in: helpVisible}" style="display: none;"><div class="cfp-hotkeys"><h4 class="cfp-hotkeys-title">{{ title }}</h4><table><tbody><tr ng-repeat="hotkey in hotkeys | filter:{ description: \'!$$undefined$$\' }"><td class="cfp-hotkeys-keys"><span ng-repeat="key in hotkey.format() track by $index" class="cfp-hotkeys-key">{{ key }}</span></td><td class="cfp-hotkeys-text">{{ hotkey.description }}</td></tr></tbody></table><div class="cfp-hotkeys-close" ng-click="toggleCheatSheet()">×</div></div></div>',this.cheatSheetHotkey="?",this.cheatSheetDescription="Show / hide this help menu",this.$get=["$rootElement","$rootScope","$compile","$window","$document",function(a,b,c,d,e){function f(a){var b={command:"⌘",shift:"⇧",left:"←",right:"→",up:"↑",down:"↓","return":"↩",backspace:"⌫"};a=a.split("+");for(var c=0;c<a.length;c++)"mod"===a[c]&&(a[c]=d.navigator&&d.navigator.platform.indexOf("Mac")>=0?"command":"ctrl"),a[c]=b[a[c]]||a[c];return a.join(" + ")}function g(a,b,c,d,e,f){this.combo=a instanceof Array?a:[a],this.description=b,this.callback=c,this.action=d,this.allowIn=e,this.persistent=f}function h(){for(var a=o.hotkeys.length;a--;){var b=o.hotkeys[a];b&&!b.persistent&&k(b)}}function i(){o.helpVisible=!o.helpVisible,o.helpVisible?(t=l("esc"),k("esc"),j("esc",t.description,i)):(k("esc"),t!==!1&&j(t))}function j(a,b,c,d,e,f){var h,i=["INPUT","SELECT","TEXTAREA"],j=Object.prototype.toString.call(a);if("[object Object]"===j&&(b=a.description,c=a.callback,d=a.action,f=a.persistent,e=a.allowIn,a=a.combo),b instanceof Function?(d=c,c=b,b="$$undefined$$"):angular.isUndefined(b)&&(b="$$undefined$$"),void 0===f&&(f=!0),"function"==typeof c){h=c,e instanceof Array||(e=[]);for(var k,l=0;l<e.length;l++)e[l]=e[l].toUpperCase(),k=i.indexOf(e[l]),-1!==k&&i.splice(k,1);c=function(a){var b=!0,c=a.target||a.srcElement,d=c.nodeName.toUpperCase();if((" "+c.className+" ").indexOf(" mousetrap ")>-1)b=!0;else for(var e=0;e<i.length;e++)if(i[e]===d){b=!1;break}b&&n(h.apply(this,arguments))}}"string"==typeof d?Mousetrap.bind(a,n(c),d):Mousetrap.bind(a,n(c));var m=new g(a,b,c,d,e,f);return o.hotkeys.push(m),m}function k(a){var b=a instanceof g?a.combo:a;if(Mousetrap.unbind(b),angular.isArray(b)){for(var c=!0,d=b.length;d--;)c=k(b[d])&&c;return c}var e=o.hotkeys.indexOf(l(b));return e>-1?(o.hotkeys[e].combo.length>1?o.hotkeys[e].combo.splice(o.hotkeys[e].combo.indexOf(b),1):o.hotkeys.splice(e,1),!0):!1}function l(a){for(var b,c=0;c<o.hotkeys.length;c++)if(b=o.hotkeys[c],b.combo.indexOf(a)>-1)return b;return!1}function m(a){return a.$id in p||(p[a.$id]=[],a.$on("$destroy",function(){for(var b=p[a.$id].length;b--;)k(p[a.$id][b]),delete p[a.$id][b]})),{add:function(b){var c;return c=arguments.length>1?j.apply(this,arguments):j(b),p[a.$id].push(c),this}}}function n(a){return function(c,d){if(a instanceof Array){var e=a[0],f=a[1];a=function(){f.scope.$eval(e)}}b.$apply(function(){a(c,l(d))})}}Mousetrap.stopCallback=function(a,b){return(" "+b.className+" ").indexOf(" mousetrap ")>-1?!1:b.contentEditable&&"true"==b.contentEditable},g.prototype.format=function(){for(var a=this.combo[0],b=a.split(/[\s]/),c=0;c<b.length;c++)b[c]=f(b[c]);return b};var o=b.$new();o.hotkeys=[],o.helpVisible=!1,o.title=this.templateTitle,o.toggleCheatSheet=i;var p=[];if(b.$on("$routeChangeSuccess",function(a,b){h(),b&&b.hotkeys&&angular.forEach(b.hotkeys,function(a){var c=a[2];("string"==typeof c||c instanceof String)&&(a[2]=[c,b]),a[5]=!1,j.apply(this,a)})}),this.includeCheatSheet){var q=e[0],r=a[0],s=angular.element(this.template);j(this.cheatSheetHotkey,this.cheatSheetDescription,i),(r===q||r===q.documentElement)&&(r=q.body),angular.element(r).append(c(s)(o))}var t=!1,u={add:j,del:k,get:l,bindTo:m,template:this.template,toggleCheatSheet:i,includeCheatSheet:this.includeCheatSheet,cheatSheetHotkey:this.cheatSheetHotkey,cheatSheetDescription:this.cheatSheetDescription,purgeHotkeys:h,templateTitle:this.templateTitle};return u}]}).directive("hotkey",["hotkeys",function(a){return{restrict:"A",link:function(b,c,d){var e,f;angular.forEach(b.$eval(d.hotkey),function(b,c){f="string"==typeof d.hotkeyAllowIn?d.hotkeyAllowIn.split(/[\s,]+/):[],e=c,a.add({combo:c,description:d.hotkeyDescription,callback:b,action:d.hotkeyAction,allowIn:f})}),c.bind("$destroy",function(){a.del(e)})}}}]).run(["hotkeys",function(){}])}(),function(a,b){function c(a,b,c){return a.addEventListener?void a.addEventListener(b,c,!1):void a.attachEvent("on"+b,c)}function d(a){if("keypress"==a.type){var b=String.fromCharCode(a.which);return a.shiftKey||(b=b.toLowerCase()),b}return y[a.which]?y[a.which]:z[a.which]?z[a.which]:String.fromCharCode(a.which).toLowerCase()}function e(a,b){return a.sort().join(",")===b.sort().join(",")}function f(a){a=a||{};var b,c=!1;for(b in E)a[b]?c=!0:E[b]=0;c||(H=!1)}function g(a,b,c,d,f,g){var h,i,j=[],k=c.type;if(!C[a])return[];for("keyup"==k&&n(a)&&(b=[a]),h=0;h<C[a].length;++h)if(i=C[a][h],(d||!i.seq||E[i.seq]==i.level)&&k==i.action&&("keypress"==k&&!c.metaKey&&!c.ctrlKey||e(b,i.modifiers))){var l=!d&&i.combo==f,m=d&&i.seq==d&&i.level==g;(l||m)&&C[a].splice(h,1),j.push(i)}return j}function h(a){var b=[];return a.shiftKey&&b.push("shift"),a.altKey&&b.push("alt"),a.ctrlKey&&b.push("ctrl"),a.metaKey&&b.push("meta"),b}function i(a){return a.preventDefault?void a.preventDefault():void(a.returnValue=!1)}function j(a){return a.stopPropagation?void a.stopPropagation():void(a.cancelBubble=!0)}function k(a,b,c,d){J.stopCallback(b,b.target||b.srcElement,c,d)||a(b,c)===!1&&(i(b),j(b))}function l(a,b,c){var d,e=g(a,b,c),h={},i=0,j=!1;for(d=0;d<e.length;++d)e[d].seq&&(i=Math.max(i,e[d].level));for(d=0;d<e.length;++d)if(e[d].seq){if(e[d].level!=i)continue;j=!0,h[e[d].seq]=1,k(e[d].callback,c,e[d].combo,e[d].seq)}else j||k(e[d].callback,c,e[d].combo);var l="keypress"==c.type&&G;c.type!=H||n(a)||l||f(h),G=j&&"keydown"==c.type}function m(a){"number"!=typeof a.which&&(a.which=a.keyCode);var b=d(a);if(b)return"keyup"==a.type&&F===b?void(F=!1):void J.handleKey(b,h(a),a)}function n(a){return"shift"==a||"ctrl"==a||"alt"==a||"meta"==a}function o(){clearTimeout(x),x=setTimeout(f,1e3)}function p(){if(!w){w={};for(var a in y)a>95&&112>a||y.hasOwnProperty(a)&&(w[y[a]]=a)}return w}function q(a,b,c){return c||(c=p()[a]?"keydown":"keypress"),"keypress"==c&&b.length&&(c="keydown"),c}function r(a,b,c,e){function g(b){return function(){H=b,++E[a],o()}}function h(b){k(c,b,a),"keyup"!==e&&(F=d(b)),setTimeout(f,10)}E[a]=0;for(var i=0;i<b.length;++i){var j=i+1===b.length,l=j?h:g(e||t(b[i+1]).action);u(b[i],l,e,a,i)}}function s(a){return"+"===a?["+"]:a.split("+")}function t(a,b){var c,d,e,f=[];for(c=s(a),e=0;e<c.length;++e)d=c[e],B[d]&&(d=B[d]),b&&"keypress"!=b&&A[d]&&(d=A[d],f.push("shift")),n(d)&&f.push(d);return b=q(d,f,b),{key:d,modifiers:f,action:b}}function u(a,b,c,d,e){D[a+":"+c]=b,a=a.replace(/\s+/g," ");var f,h=a.split(" ");return h.length>1?void r(a,h,b,c):(f=t(a,c),C[f.key]=C[f.key]||[],g(f.key,f.modifiers,{type:f.action},d,a,e),void C[f.key][d?"unshift":"push"]({callback:b,modifiers:f.modifiers,action:f.action,seq:d,level:e,combo:a}))}function v(a,b,c){for(var d=0;d<a.length;++d)u(a[d],b,c)}for(var w,x,y={8:"backspace",9:"tab",13:"enter",16:"shift",17:"ctrl",18:"alt",20:"capslock",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"ins",46:"del",91:"meta",93:"meta",224:"meta"},z={106:"*",107:"+",109:"-",110:".",111:"/",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},A={"~":"`","!":"1","@":"2","#":"3",$:"4","%":"5","^":"6","&":"7","*":"8","(":"9",")":"0",_:"-","+":"=",":":";",'"':"'","<":",",">":".","?":"/","|":"\\"},B={option:"alt",command:"meta","return":"enter",escape:"esc",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"},C={},D={},E={},F=!1,G=!1,H=!1,I=1;20>I;++I)y[111+I]="f"+I;for(I=0;9>=I;++I)y[I+96]=I;c(b,"keypress",m),c(b,"keydown",m),c(b,"keyup",m);var J={bind:function(a,b,c){return a=a instanceof Array?a:[a],v(a,b,c),this},unbind:function(a,b){return J.bind(a,function(){},b)},trigger:function(a,b){return D[a+":"+b]&&D[a+":"+b]({},a),this},reset:function(){return C={},D={},this},stopCallback:function(a,b){return(" "+b.className+" ").indexOf(" mousetrap ")>-1?!1:"INPUT"==b.tagName||"SELECT"==b.tagName||"TEXTAREA"==b.tagName||b.isContentEditable},handleKey:l};a.Mousetrap=J,"function"==typeof define&&define.amd&&define(J)}(window,document);
\ No newline at end of file