kms 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (196) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/kms/application.js +1 -0
  3. data/app/assets/javascripts/kms/application/controllers/assets_controller.coffee.erb +14 -4
  4. data/app/assets/javascripts/kms/application/controllers/pages_controller.coffee.erb +12 -2
  5. data/app/assets/javascripts/kms/application/controllers/snippets_controller.coffee.erb +13 -3
  6. data/app/assets/javascripts/kms/application/controllers/templates_controller.coffee.erb +13 -3
  7. data/app/assets/javascripts/kms/application/controllers/users_controller.coffee +5 -5
  8. data/app/assets/javascripts/kms/application/module.coffee +6 -2
  9. data/app/assets/javascripts/kms/application/routes.coffee.erb +10 -0
  10. data/app/assets/javascripts/templates/assets/edit.html.slim +2 -1
  11. data/app/assets/javascripts/templates/assets/form.html.slim +1 -1
  12. data/app/assets/javascripts/templates/pages/edit.html.slim +1 -0
  13. data/app/assets/javascripts/templates/shared/hotkey_notification.html.slim +6 -0
  14. data/app/assets/javascripts/templates/snippets/edit.html.slim +1 -0
  15. data/app/assets/javascripts/templates/templates/edit.html.slim +1 -0
  16. data/app/assets/javascripts/templates/users/edit.html.slim +5 -0
  17. data/app/assets/javascripts/templates/users/form.html.slim +3 -2
  18. data/app/assets/javascripts/templates/users/index.html.slim +2 -1
  19. data/app/assets/stylesheets/kms/custom.css.scss +10 -0
  20. data/app/controllers/kms/assets_controller.rb +6 -3
  21. data/app/controllers/kms/users_controller.rb +14 -0
  22. data/app/services/kms/resource_service.rb +3 -1
  23. data/app/views/layouts/kms/kms.html.erb +1 -1
  24. data/config/initializers/devise.rb +9 -0
  25. data/config/locales/en.yml +12 -0
  26. data/config/locales/ru.yml +12 -0
  27. data/config/routes.rb +1 -1
  28. data/lib/kms/engine.rb +1 -1
  29. data/lib/kms/version.rb +1 -1
  30. data/spec/controllers/kms/assets_controller_spec.rb +28 -10
  31. data/spec/controllers/kms/users_controller_spec.rb +23 -0
  32. data/spec/internal/config/routes.rb +1 -1
  33. data/spec/internal/log/test.log +0 -105823
  34. data/vendor/assets/bower.json +5 -4
  35. data/vendor/assets/bower_components/angular-cookies/angular-cookies.js +22 -18
  36. data/vendor/assets/bower_components/angular-cookies/angular-cookies.min.js +4 -4
  37. data/vendor/assets/bower_components/angular-cookies/angular-cookies.min.js.map +2 -2
  38. data/vendor/assets/bower_components/angular-cookies/bower.json +2 -2
  39. data/vendor/assets/bower_components/angular-cookies/package.json +1 -1
  40. data/vendor/assets/bower_components/angular-hotkeys/Gruntfile.js +118 -0
  41. data/vendor/assets/bower_components/angular-hotkeys/LICENSE +20 -0
  42. data/vendor/assets/bower_components/angular-hotkeys/README.md +248 -0
  43. data/vendor/assets/bower_components/angular-hotkeys/bower.json +19 -0
  44. data/vendor/assets/bower_components/angular-hotkeys/build/hotkeys.css +110 -0
  45. data/vendor/assets/bower_components/angular-hotkeys/build/hotkeys.js +1661 -0
  46. data/vendor/assets/bower_components/angular-hotkeys/build/hotkeys.min.css +1 -0
  47. data/vendor/assets/bower_components/angular-hotkeys/build/hotkeys.min.js +7 -0
  48. data/vendor/assets/bower_components/angular-hotkeys/package.json +45 -0
  49. data/vendor/assets/bower_components/angular-hotkeys/src/hotkeys.css +104 -0
  50. data/vendor/assets/bower_components/angular-hotkeys/src/hotkeys.js +633 -0
  51. data/vendor/assets/bower_components/angular-loading-bar/CHANGELOG.md +33 -0
  52. data/vendor/assets/bower_components/angular-loading-bar/CONTRIBUTING.md +17 -0
  53. data/vendor/assets/bower_components/angular-loading-bar/Gruntfile.js +9 -1
  54. data/vendor/assets/bower_components/angular-loading-bar/ISSUE_TEMPLATE.md +14 -0
  55. data/vendor/assets/bower_components/angular-loading-bar/PULL_REQUEST_TEMPLATE.md +13 -0
  56. data/vendor/assets/bower_components/angular-loading-bar/README.md +30 -3
  57. data/vendor/assets/bower_components/angular-loading-bar/bower.json +11 -6
  58. data/vendor/assets/bower_components/angular-loading-bar/build/loading-bar.css +5 -5
  59. data/vendor/assets/bower_components/angular-loading-bar/build/loading-bar.js +39 -12
  60. data/vendor/assets/bower_components/angular-loading-bar/build/loading-bar.min.css +1 -8
  61. data/vendor/assets/bower_components/angular-loading-bar/build/loading-bar.min.js +3 -3
  62. data/vendor/assets/bower_components/angular-loading-bar/index.js +2 -0
  63. data/vendor/assets/bower_components/angular-loading-bar/package.json +12 -15
  64. data/vendor/assets/bower_components/angular-loading-bar/src/loading-bar.css +3 -3
  65. data/vendor/assets/bower_components/angular-loading-bar/src/loading-bar.js +37 -10
  66. data/vendor/assets/bower_components/angular-sanitize/angular-sanitize.js +504 -386
  67. data/vendor/assets/bower_components/angular-sanitize/angular-sanitize.min.js +13 -12
  68. data/vendor/assets/bower_components/angular-sanitize/angular-sanitize.min.js.map +3 -3
  69. data/vendor/assets/bower_components/angular-sanitize/bower.json +2 -2
  70. data/vendor/assets/bower_components/angular-sanitize/package.json +1 -1
  71. data/vendor/assets/bower_components/angular-ui-router/CHANGELOG.md +1410 -0
  72. data/vendor/assets/bower_components/angular-ui-router/CONTRIBUTING.md +64 -16
  73. data/vendor/assets/bower_components/angular-ui-router/DOCS.md +48 -0
  74. data/vendor/assets/bower_components/angular-ui-router/ISSUE_TEMPLATE.md +53 -0
  75. data/vendor/assets/bower_components/angular-ui-router/LICENSE +1 -1
  76. data/vendor/assets/bower_components/angular-ui-router/README.md +24 -211
  77. data/vendor/assets/bower_components/angular-ui-router/artifacts.json +8 -0
  78. data/vendor/assets/bower_components/angular-ui-router/bower.json +1 -23
  79. data/vendor/assets/bower_components/angular-ui-router/karma.conf.js +105 -0
  80. data/vendor/assets/bower_components/angular-ui-router/release/angular-ui-router.js +9744 -3901
  81. data/vendor/assets/bower_components/angular-ui-router/release/angular-ui-router.js.map +192 -0
  82. data/vendor/assets/bower_components/angular-ui-router/release/angular-ui-router.min.js +9 -4
  83. data/vendor/assets/bower_components/angular-ui-router/release/angular-ui-router.min.js.map +1679 -0
  84. data/vendor/assets/bower_components/angular-ui-router/release/resolveService.js +83 -0
  85. data/vendor/assets/bower_components/angular-ui-router/release/resolveService.js.map +19 -0
  86. data/vendor/assets/bower_components/angular-ui-router/release/resolveService.min.js +8 -0
  87. data/vendor/assets/bower_components/angular-ui-router/release/resolveService.min.js.map +47 -0
  88. data/vendor/assets/bower_components/angular-ui-router/release/stateEvents.js +294 -0
  89. data/vendor/assets/bower_components/angular-ui-router/release/stateEvents.js.map +17 -0
  90. data/vendor/assets/bower_components/angular-ui-router/release/stateEvents.min.js +8 -0
  91. data/vendor/assets/bower_components/angular-ui-router/release/stateEvents.min.js.map +102 -0
  92. data/vendor/assets/bower_components/angular-ui-router/release/ui-router-angularjs.js +2014 -0
  93. data/vendor/assets/bower_components/angular-ui-router/release/ui-router-angularjs.js.map +70 -0
  94. data/vendor/assets/bower_components/angular-ui-router/release/ui-router-angularjs.min.js +9 -0
  95. data/vendor/assets/bower_components/angular-ui-router/release/ui-router-angularjs.min.js.map +541 -0
  96. data/vendor/assets/bower_components/angular-ui-router/rollup.config.js +116 -0
  97. data/vendor/assets/bower_components/angular-ui-router/tslint.json +60 -0
  98. data/vendor/assets/bower_components/angular-ui-router/yarn.lock +4146 -0
  99. data/vendor/assets/bower_components/angular-ui-tree/yarn.lock +4945 -0
  100. data/vendor/assets/bower_components/angular/angular.js +4019 -2449
  101. data/vendor/assets/bower_components/angular/angular.min.js +331 -319
  102. data/vendor/assets/bower_components/angular/angular.min.js.gzip +0 -0
  103. data/vendor/assets/bower_components/angular/angular.min.js.map +3 -3
  104. data/vendor/assets/bower_components/angular/bower.json +1 -1
  105. data/vendor/assets/bower_components/angular/package.json +1 -1
  106. data/vendor/assets/bower_components/angularjs-dropdown-multiselect/LICENSE +21 -0
  107. data/vendor/assets/bower_components/angularjs-dropdown-multiselect/README.md +14 -14
  108. data/vendor/assets/bower_components/angularjs-dropdown-multiselect/bower.json +25 -12
  109. data/vendor/assets/bower_components/angularjs-dropdown-multiselect/development_index.html +59 -52
  110. data/vendor/assets/bower_components/angularjs-dropdown-multiselect/dist/angularjs-dropdown-multiselect.min.js +1 -1
  111. data/vendor/assets/bower_components/angularjs-dropdown-multiselect/index.html +73 -0
  112. data/vendor/assets/bower_components/angularjs-dropdown-multiselect/package.json +19 -7
  113. data/vendor/assets/bower_components/angularjs-dropdown-multiselect/pages/javascripts/pages/home/ExampleCtrl.js +126 -3
  114. data/vendor/assets/bower_components/angularjs-dropdown-multiselect/pages/javascripts/pages/home/home.html +1262 -852
  115. data/vendor/assets/bower_components/angularjs-dropdown-multiselect/pages/stylesheets/stylesheet.css +10 -5
  116. data/vendor/assets/bower_components/angularjs-dropdown-multiselect/src/angularjs-dropdown-multiselect.js +612 -287
  117. metadata +66 -169
  118. data/spec/internal/config/database.yml +0 -7
  119. data/spec/internal/public/uploads/kms/asset/file/1/avatar.jpg +0 -0
  120. data/spec/internal/public/uploads/kms/asset/file/2/avatar.jpg +0 -0
  121. data/spec/internal/public/uploads/kms/asset/file/2/style.css +0 -1
  122. data/spec/internal/public/uploads/kms/asset/file/3/style.css +0 -1
  123. data/spec/internal/public/uploads/kms/asset/file/4/style.css +0 -1
  124. data/spec/internal/public/uploads/tmp/1500976987-41025-0002-0883/style.css +0 -1
  125. data/spec/internal/public/uploads/tmp/1500977082-41195-0002-6495/style.css +0 -1
  126. data/spec/internal/public/uploads/tmp/1500977109-41364-0002-4518/style.css +0 -1
  127. data/spec/internal/public/uploads/tmp/1500977152-41405-0002-2345/style.css +0 -1
  128. data/spec/internal/public/uploads/tmp/1500977327-41694-0002-5448/style.css +0 -1
  129. data/spec/internal/public/uploads/tmp/1500977376-41732-0002-7916/style.css +0 -1
  130. data/spec/internal/public/uploads/tmp/1500977392-41759-0002-7593/style.css +0 -1
  131. data/spec/internal/public/uploads/tmp/1500977410-42259-0002-7527/style.css +0 -1
  132. data/spec/internal/public/uploads/tmp/1500977429-42306-0002-5937/style.css +0 -1
  133. data/spec/internal/public/uploads/tmp/1500977437-42324-0002-5880/style.css +0 -1
  134. data/spec/internal/public/uploads/tmp/1500983228-53594-0002-4559/style.css +0 -1
  135. data/spec/internal/public/uploads/tmp/1500983284-53632-0002-6590/style.css +0 -1
  136. data/spec/internal/public/uploads/tmp/1500983360-53784-0002-7289/style.css +0 -1
  137. data/spec/internal/public/uploads/tmp/1500983469-54321-0002-0386/avatar.jpg +0 -0
  138. data/spec/internal/public/uploads/tmp/1500983469-54321-0004-5691/style.css +0 -1
  139. data/spec/internal/public/uploads/tmp/1500983511-54352-0002-5720/avatar.jpg +0 -0
  140. data/spec/internal/public/uploads/tmp/1500983511-54352-0004-1399/style.css +0 -1
  141. data/spec/internal/public/uploads/tmp/1500983610-54507-0002-4280/avatar.jpg +0 -0
  142. data/spec/internal/public/uploads/tmp/1500983610-54507-0004-9758/style.css +0 -1
  143. data/spec/internal/public/uploads/tmp/1500984466-57012-0002-4146/avatar.jpg +0 -0
  144. data/spec/internal/public/uploads/tmp/1500984466-57012-0004-5895/style.css +0 -1
  145. data/spec/internal/public/uploads/tmp/1500984509-57158-0002-9657/avatar.jpg +0 -0
  146. data/spec/internal/public/uploads/tmp/1500984509-57158-0004-5003/style.css +0 -1
  147. data/spec/internal/public/uploads/tmp/1500984616-57697-0002-7201/avatar.jpg +0 -0
  148. data/spec/internal/public/uploads/tmp/1500984616-57697-0004-6255/style.css +0 -1
  149. data/spec/internal/public/uploads/tmp/1500985257-58947-0002-3629/avatar.jpg +0 -0
  150. data/spec/internal/public/uploads/tmp/1500985257-58947-0004-5338/style.css +0 -1
  151. data/spec/internal/public/uploads/tmp/1500985407-58947-0006-5929/style.css +0 -1
  152. data/spec/internal/public/uploads/tmp/1500985473-59264-0002-0397/avatar.jpg +0 -0
  153. data/spec/internal/public/uploads/tmp/1500985473-59264-0004-6493/style.css +0 -1
  154. data/spec/internal/public/uploads/tmp/1500985475-59264-0007-8674/style.css +0 -1
  155. data/spec/internal/public/uploads/tmp/1500985538-59468-0002-9206/avatar.jpg +0 -0
  156. data/spec/internal/public/uploads/tmp/1500985538-59468-0004-2586/style.css +0 -1
  157. data/spec/internal/public/uploads/tmp/1500985538-59468-0007-6200/style.css +0 -1
  158. data/spec/internal/public/uploads/tmp/1500988358-65877-0002-4528/avatar.jpg +0 -0
  159. data/spec/internal/public/uploads/tmp/1500988358-65877-0004-5904/style.css +0 -1
  160. data/spec/internal/public/uploads/tmp/1500988358-65877-0007-7320/style.css +0 -1
  161. data/spec/internal/public/uploads/tmp/1500988407-65916-0002-3138/avatar.jpg +0 -0
  162. data/spec/internal/public/uploads/tmp/1500988407-65916-0004-5400/style.css +0 -1
  163. data/spec/internal/public/uploads/tmp/1500988407-65916-0007-1655/style.css +0 -1
  164. data/spec/internal/public/uploads/tmp/1500988421-65950-0002-9415/avatar.jpg +0 -0
  165. data/spec/internal/public/uploads/tmp/1500988421-65950-0004-7130/style.css +0 -1
  166. data/spec/internal/public/uploads/tmp/1500988421-65950-0007-9886/style.css +0 -1
  167. data/spec/internal/public/uploads/tmp/1500988435-65981-0002-3228/avatar.jpg +0 -0
  168. data/spec/internal/public/uploads/tmp/1500988435-65981-0004-3682/style.css +0 -1
  169. data/spec/internal/public/uploads/tmp/1500988435-65981-0007-1582/style.css +0 -1
  170. data/spec/internal/public/uploads/tmp/1500988475-66122-0002-9516/avatar.jpg +0 -0
  171. data/spec/internal/public/uploads/tmp/1500988475-66122-0004-5634/style.css +0 -1
  172. data/spec/internal/public/uploads/tmp/1500988530-66122-0007-2272/style.css +0 -1
  173. data/spec/internal/public/uploads/tmp/1500988554-66315-0002-6262/avatar.jpg +0 -0
  174. data/spec/internal/public/uploads/tmp/1500988554-66315-0004-6099/style.css +0 -1
  175. data/spec/internal/public/uploads/tmp/1500988554-66315-0007-1632/style.css +0 -1
  176. data/spec/internal/public/uploads/tmp/1500991751-73722-0002-9937/avatar.jpg +0 -0
  177. data/spec/internal/public/uploads/tmp/1500991751-73722-0004-8034/style.css +0 -1
  178. data/spec/internal/public/uploads/tmp/1500991751-73722-0007-7763/style.css +0 -1
  179. data/spec/internal/public/uploads/tmp/1501233238-34385-0002-3210/avatar.jpg +0 -0
  180. data/spec/internal/public/uploads/tmp/1501233238-34385-0004-5881/style.css +0 -1
  181. data/spec/internal/public/uploads/tmp/1501233238-34385-0007-6280/style.css +0 -1
  182. data/spec/internal/tmp/cache/assets/test/sprockets/v3.0/1XyAFYlYI0pK7WAgjR4PgXV6BgU6huJSviWmHetdCRs.cache +0 -1
  183. data/vendor/assets/bower_components/angular-ui-router/api/angular-ui-router.d.ts +0 -126
  184. data/vendor/assets/bower_components/angular-ui-router/src/common.js +0 -292
  185. data/vendor/assets/bower_components/angular-ui-router/src/resolve.js +0 -252
  186. data/vendor/assets/bower_components/angular-ui-router/src/state.js +0 -1373
  187. data/vendor/assets/bower_components/angular-ui-router/src/stateDirectives.js +0 -268
  188. data/vendor/assets/bower_components/angular-ui-router/src/stateFilters.js +0 -39
  189. data/vendor/assets/bower_components/angular-ui-router/src/templateFactory.js +0 -110
  190. data/vendor/assets/bower_components/angular-ui-router/src/urlMatcherFactory.js +0 -1036
  191. data/vendor/assets/bower_components/angular-ui-router/src/urlRouter.js +0 -413
  192. data/vendor/assets/bower_components/angular-ui-router/src/view.js +0 -71
  193. data/vendor/assets/bower_components/angular-ui-router/src/viewDirective.js +0 -302
  194. data/vendor/assets/bower_components/angular-ui-router/src/viewScroll.js +0 -52
  195. data/vendor/assets/bower_components/angularjs-dropdown-multiselect/pages/index.html +0 -67
  196. data/vendor/assets/bower_components/bootstrap/Gemfile.lock +0 -43
@@ -0,0 +1 @@
1
+ .cfp-hotkeys,.cfp-hotkeys-container{width:100%;height:100%}.cfp-hotkeys-container{display:table!important;position:fixed;top:0;left:0;color:#333;font-size:1em;background-color:rgba(255,255,255,.9)}.cfp-content,.cfp-hotkeys{display:table-cell;vertical-align:middle}.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 table{margin:auto;color:#333}.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}}
@@ -0,0 +1,7 @@
1
+ /*!
2
+ * angular-hotkeys v1.7.0
3
+ * https://chieffancypants.github.io/angular-hotkeys
4
+ * Copyright (c) 2016 Wes Cruver
5
+ * License: MIT
6
+ */
7
+ !function(){"use strict";angular.module("cfp.hotkeys",[]).provider("hotkeys",["$injector",function(a){this.includeCheatSheet=!0,this.useNgRoute=a.has("ngViewDirective"),this.templateTitle="Keyboard Shortcuts:",this.templateHeader=null,this.templateFooter=null,this.template='<div class="cfp-hotkeys-container fade" ng-class="{in: helpVisible}" style="display: none;"><div class="cfp-hotkeys"><h4 class="cfp-hotkeys-title" ng-if="!header">{{ title }}</h4><div ng-bind-html="header" ng-if="header"></div><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 ng-bind-html="footer" ng-if="footer"></div><div class="cfp-hotkeys-close" ng-click="toggleCheatSheet()">&#215;</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(){q=!1}function g(){q=!0}function h(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]&&(d.navigator&&d.navigator.platform.indexOf("Mac")>=0?a[c]="command":a[c]="ctrl"),a[c]=b[a[c]]||a[c];return a.join(" + ")}function i(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,this._formated=null}function j(){for(var a=r.hotkeys.length;a--;){var b=r.hotkeys[a];b&&!b.persistent&&m(b)}}function k(){r.helpVisible=!r.helpVisible,r.helpVisible?(w=n("esc"),m("esc"),l("esc",w.description,k,null,["INPUT","SELECT","TEXTAREA"])):(m("esc"),w!==!1&&l(w))}function l(a,b,c,d,e,f){var g,h=["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),m(a),b instanceof Function?(d=c,c=b,b="$$undefined$$"):angular.isUndefined(b)&&(b="$$undefined$$"),void 0===f&&(f=!0),"function"==typeof c){g=c,e instanceof Array||(e=[]);for(var k,l=0;l<e.length;l++)e[l]=e[l].toUpperCase(),k=h.indexOf(e[l]),-1!==k&&h.splice(k,1);c=function(a){var b=!0;if(a){var c=a.target||a.srcElement,d=c.nodeName.toUpperCase();if((" "+c.className+" ").indexOf(" mousetrap ")>-1)b=!0;else for(var e=0;e<h.length;e++)if(h[e]===d){b=!1;break}}b&&p(g.apply(this,arguments))}}"string"==typeof d?Mousetrap.bind(a,p(c),d):Mousetrap.bind(a,p(c));var n=new i(a,b,c,d,e,f);return r.hotkeys.push(n),n}function m(a){var b=a instanceof i?a.combo:a;if(Mousetrap.unbind(b),angular.isArray(b)){for(var c=!0,d=b.length;d--;)c=m(b[d])&&c;return c}var e=r.hotkeys.indexOf(n(b));return e>-1?(r.hotkeys[e].combo.length>1?r.hotkeys[e].combo.splice(r.hotkeys[e].combo.indexOf(b),1):(angular.forEach(s,function(a){var b=a.indexOf(r.hotkeys[e]);-1!==b&&a.splice(b,1)}),r.hotkeys.splice(e,1)),!0):!1}function n(a){if(!a)return r.hotkeys;for(var b,c=0;c<r.hotkeys.length;c++)if(b=r.hotkeys[c],b.combo.indexOf(a)>-1)return b;return!1}function o(a){return a.$id in s||(s[a.$id]=[],a.$on("$destroy",function(){for(var b=s[a.$id].length;b--;)m(s[a.$id].pop())})),{add:function(b){var c;return c=arguments.length>1?l.apply(this,arguments):l(b),s[a.$id].push(c),this}}}function p(a){return function(c,d){if(a instanceof Array){var e=a[0],f=a[1];a=function(a){f.scope.$eval(e)}}b.$apply(function(){a(c,n(d))})}}var q=!0;Mousetrap.prototype.stopCallback=function(a,b){return q?(" "+b.className+" ").indexOf(" mousetrap ")>-1?!1:b.contentEditable&&"true"==b.contentEditable:!0},i.prototype.format=function(){if(null===this._formated){for(var a=this.combo[0],b=a.split(/[\s]/),c=0;c<b.length;c++)b[c]=h(b[c]);this._formated=b}return this._formated};var r=b.$new();r.hotkeys=[],r.helpVisible=!1,r.title=this.templateTitle,r.header=this.templateHeader,r.footer=this.templateFooter,r.toggleCheatSheet=k;var s={};if(this.useNgRoute&&b.$on("$routeChangeSuccess",function(a,b){j(),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,l.apply(this,a)})}),this.includeCheatSheet){var t=e[0],u=a[0],v=angular.element(this.template);l(this.cheatSheetHotkey,this.cheatSheetDescription,k),(u===t||u===t.documentElement)&&(u=t.body),angular.element(u).append(c(v)(r))}var w=!1,x={add:l,del:m,get:n,bindTo:o,template:this.template,toggleCheatSheet:k,includeCheatSheet:this.includeCheatSheet,cheatSheetHotkey:this.cheatSheetHotkey,cheatSheetDescription:this.cheatSheetDescription,useNgRoute:this.useNgRoute,purgeHotkeys:j,templateTitle:this.templateTitle,pause:f,unpause:g};return x}]}]).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){e="string"==typeof d.hotkeyAllowIn?d.hotkeyAllowIn.split(/[\s,]+/):[],f.push(c),a.add({combo:c,description:d.hotkeyDescription,callback:b,action:d.hotkeyAction,allowIn:e})}),c.bind("$destroy",function(){angular.forEach(f,a.del)})}}}]).run(["hotkeys",function(a){}])}(),function(a,b,c){function d(a,b,c){return a.addEventListener?void a.addEventListener(b,c,!1):void a.attachEvent("on"+b,c)}function e(a){if("keypress"==a.type){var b=String.fromCharCode(a.which);return a.shiftKey||(b=b.toLowerCase()),b}return r[a.which]?r[a.which]:s[a.which]?s[a.which]:String.fromCharCode(a.which).toLowerCase()}function f(a,b){return a.sort().join(",")===b.sort().join(",")}function g(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 h(a){return a.preventDefault?void a.preventDefault():void(a.returnValue=!1)}function i(a){return a.stopPropagation?void a.stopPropagation():void(a.cancelBubble=!0)}function j(a){return"shift"==a||"ctrl"==a||"alt"==a||"meta"==a}function k(){if(!q){q={};for(var a in r)a>95&&112>a||r.hasOwnProperty(a)&&(q[r[a]]=a)}return q}function l(a,b,c){return c||(c=k()[a]?"keydown":"keypress"),"keypress"==c&&b.length&&(c="keydown"),c}function m(a){return"+"===a?["+"]:(a=a.replace(/\+{2}/g,"+plus"),a.split("+"))}function n(a,b){var c,d,e,f=[];for(c=m(a),e=0;e<c.length;++e)d=c[e],u[d]&&(d=u[d]),b&&"keypress"!=b&&t[d]&&(d=t[d],f.push("shift")),j(d)&&f.push(d);return b=l(d,f,b),{key:d,modifiers:f,action:b}}function o(a,c){return a===b?!1:a===c?!0:o(a.parentNode,c)}function p(a){function c(a){a=a||{};var b,c=!1;for(b in u)a[b]?c=!0:u[b]=0;c||(x=!1)}function k(a,b,c,d,e,g){var h,i,k=[],l=c.type;if(!s._callbacks[a])return[];for("keyup"==l&&j(a)&&(b=[a]),h=0;h<s._callbacks[a].length;++h)if(i=s._callbacks[a][h],(d||!i.seq||u[i.seq]==i.level)&&l==i.action&&("keypress"==l&&!c.metaKey&&!c.ctrlKey||f(b,i.modifiers))){var m=!d&&i.combo==e,n=d&&i.seq==d&&i.level==g;(m||n)&&s._callbacks[a].splice(h,1),k.push(i)}return k}function l(a,b,c,d){s.stopCallback(b,b.target||b.srcElement,c,d)||a(b,c)===!1&&(h(b),i(b))}function m(a){"number"!=typeof a.which&&(a.which=a.keyCode);var b=e(a);if(b)return"keyup"==a.type&&v===b?void(v=!1):void s.handleKey(b,g(a),a)}function o(){clearTimeout(t),t=setTimeout(c,1e3)}function q(a,b,d,f){function g(b){return function(){x=b,++u[a],o()}}function h(b){l(d,b,a),"keyup"!==f&&(v=e(b)),setTimeout(c,10)}u[a]=0;for(var i=0;i<b.length;++i){var j=i+1===b.length,k=j?h:g(f||n(b[i+1]).action);r(b[i],k,f,a,i)}}function r(a,b,c,d,e){s._directMap[a+":"+c]=b,a=a.replace(/\s+/g," ");var f,g=a.split(" ");return g.length>1?void q(a,g,b,c):(f=n(a,c),s._callbacks[f.key]=s._callbacks[f.key]||[],k(f.key,f.modifiers,{type:f.action},d,a,e),void s._callbacks[f.key][d?"unshift":"push"]({callback:b,modifiers:f.modifiers,action:f.action,seq:d,level:e,combo:a}))}var s=this;if(a=a||b,!(s instanceof p))return new p(a);s.target=a,s._callbacks={},s._directMap={};var t,u={},v=!1,w=!1,x=!1;s._handleKey=function(a,b,d){var e,f=k(a,b,d),g={},h=0,i=!1;for(e=0;e<f.length;++e)f[e].seq&&(h=Math.max(h,f[e].level));for(e=0;e<f.length;++e)if(f[e].seq){if(f[e].level!=h)continue;i=!0,g[f[e].seq]=1,l(f[e].callback,d,f[e].combo,f[e].seq)}else i||l(f[e].callback,d,f[e].combo);var m="keypress"==d.type&&w;d.type!=x||j(a)||m||c(g),w=i&&"keydown"==d.type},s._bindMultiple=function(a,b,c){for(var d=0;d<a.length;++d)r(a[d],b,c)},d(a,"keypress",m),d(a,"keydown",m),d(a,"keyup",m)}for(var q,r={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"},s={106:"*",107:"+",109:"-",110:".",111:"/",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},t={"~":"`","!":"1","@":"2","#":"3",$:"4","%":"5","^":"6","&":"7","*":"8","(":"9",")":"0",_:"-","+":"=",":":";",'"':"'","<":",",">":".","?":"/","|":"\\"},u={option:"alt",command:"meta","return":"enter",escape:"esc",plus:"+",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"},v=1;20>v;++v)r[111+v]="f"+v;for(v=0;9>=v;++v)r[v+96]=v;p.prototype.bind=function(a,b,c){var d=this;return a=a instanceof Array?a:[a],d._bindMultiple.call(d,a,b,c),d},p.prototype.unbind=function(a,b){var c=this;return c.bind.call(c,a,function(){},b)},p.prototype.trigger=function(a,b){var c=this;return c._directMap[a+":"+b]&&c._directMap[a+":"+b]({},a),c},p.prototype.reset=function(){var a=this;return a._callbacks={},a._directMap={},a},p.prototype.stopCallback=function(a,b){var c=this;return(" "+b.className+" ").indexOf(" mousetrap ")>-1?!1:o(b,c.target)?!1:"INPUT"==b.tagName||"SELECT"==b.tagName||"TEXTAREA"==b.tagName||b.isContentEditable},p.prototype.handleKey=function(){var a=this;return a._handleKey.apply(a,arguments)},p.init=function(){var a=p(b);for(var c in a)"_"!==c.charAt(0)&&(p[c]=function(b){return function(){return a[b].apply(a,arguments)}}(c))},p.init(),a.Mousetrap=p,"undefined"!=typeof module&&module.exports&&(module.exports=p),"function"==typeof define&&define.amd&&define(function(){return p})}(window,document);
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "angular-hotkeys",
3
+ "author": "Wes Cruver",
4
+ "version": "1.7.0",
5
+ "license": "MIT",
6
+ "description": "Automatic keyboard shortcuts for your Angular Apps",
7
+ "homepage": "https://chieffancypants.github.io/angular-hotkeys",
8
+ "main": "build/hotkeys.js",
9
+ "keywords": [
10
+ "angular",
11
+ "angularjs",
12
+ "keyboard",
13
+ "shortcut",
14
+ "hotkeys"
15
+ ],
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git://github.com/chieffancypants/angular-hotkeys.git"
19
+ },
20
+ "bugs": {
21
+ "url": "https://github.com/chieffancypants/angular-hotkeys/issues"
22
+ },
23
+ "scripts": {
24
+ "test": "node_modules/karma/bin/karma start test/karma.conf.js"
25
+ },
26
+ "devDependencies": {
27
+ "grunt": "~0.4.1",
28
+ "grunt-contrib-concat": "^0.5.1",
29
+ "grunt-contrib-cssmin": "^0.12.3",
30
+ "grunt-contrib-jshint": "~0.6.4",
31
+ "grunt-contrib-uglify": "^0.9.1",
32
+ "grunt-contrib-watch": "^0.6.1",
33
+ "grunt-karma": "^0.11.0",
34
+ "grunt-ng-annotate": "^0.3.0",
35
+ "karma": "~0.12.0",
36
+ "karma-chrome-launcher": "~0.1.0",
37
+ "karma-coffee-preprocessor": "~0.1.0",
38
+ "karma-coverage": "~0.1.0",
39
+ "karma-firefox-launcher": "~0.1.0",
40
+ "karma-html2js-preprocessor": "~0.1.0",
41
+ "karma-jasmine": "~0.1.3",
42
+ "karma-phantomjs-launcher": "^0.2.0",
43
+ "karma-script-launcher": "~0.1.0"
44
+ }
45
+ }
@@ -0,0 +1,104 @@
1
+ .cfp-hotkeys-container {
2
+ display: table !important;
3
+ position: fixed;
4
+ width: 100%;
5
+ height: 100%;
6
+ top: 0;
7
+ left: 0;
8
+ color: #333;
9
+ font-size: 1em;
10
+ background-color: rgba(255,255,255,0.9);
11
+ }
12
+
13
+ .cfp-hotkeys-container.fade {
14
+ z-index: -1024;
15
+ visibility: hidden;
16
+ opacity: 0;
17
+ -webkit-transition: opacity 0.15s linear;
18
+ -moz-transition: opacity 0.15s linear;
19
+ -o-transition: opacity 0.15s linear;
20
+ transition: opacity 0.15s linear;
21
+ }
22
+
23
+ .cfp-hotkeys-container.fade.in {
24
+ z-index: 10002;
25
+ visibility: visible;
26
+ opacity: 1;
27
+ }
28
+
29
+ .cfp-hotkeys-title {
30
+ font-weight: bold;
31
+ text-align: center;
32
+ font-size: 1.2em;
33
+ }
34
+
35
+ .cfp-hotkeys {
36
+ width: 100%;
37
+ height: 100%;
38
+ display: table-cell;
39
+ vertical-align: middle;
40
+ }
41
+
42
+ .cfp-hotkeys table {
43
+ margin: auto;
44
+ color: #333;
45
+ }
46
+
47
+ .cfp-content {
48
+ display: table-cell;
49
+ vertical-align: middle;
50
+ }
51
+
52
+ .cfp-hotkeys-keys {
53
+ padding: 5px;
54
+ text-align: right;
55
+ }
56
+
57
+ .cfp-hotkeys-key {
58
+ display: inline-block;
59
+ color: #fff;
60
+ background-color: #333;
61
+ border: 1px solid #333;
62
+ border-radius: 5px;
63
+ text-align: center;
64
+ margin-right: 5px;
65
+ box-shadow: inset 0 1px 0 #666, 0 1px 0 #bbb;
66
+ padding: 5px 9px;
67
+ font-size: 1em;
68
+ }
69
+
70
+ .cfp-hotkeys-text {
71
+ padding-left: 10px;
72
+ font-size: 1em;
73
+ }
74
+
75
+ .cfp-hotkeys-close {
76
+ position: fixed;
77
+ top: 20px;
78
+ right: 20px;
79
+ font-size: 2em;
80
+ font-weight: bold;
81
+ padding: 5px 10px;
82
+ border: 1px solid #ddd;
83
+ border-radius: 5px;
84
+ min-height: 45px;
85
+ min-width: 45px;
86
+ text-align: center;
87
+ }
88
+
89
+ .cfp-hotkeys-close:hover {
90
+ background-color: #fff;
91
+ cursor: pointer;
92
+ }
93
+
94
+ @media all and (max-width: 500px) {
95
+ .cfp-hotkeys {
96
+ font-size: 0.8em;
97
+ }
98
+ }
99
+
100
+ @media all and (min-width: 750px) {
101
+ .cfp-hotkeys {
102
+ font-size: 1.2em;
103
+ }
104
+ }
@@ -0,0 +1,633 @@
1
+ /*
2
+ * angular-hotkeys
3
+ *
4
+ * Automatic keyboard shortcuts for your angular apps
5
+ *
6
+ * (c) 2016 Wes Cruver
7
+ * License: MIT
8
+ */
9
+
10
+ (function() {
11
+
12
+ 'use strict';
13
+
14
+ angular.module('cfp.hotkeys', []).provider('hotkeys', function($injector) {
15
+
16
+ /**
17
+ * Configurable setting to disable the cheatsheet entirely
18
+ * @type {Boolean}
19
+ */
20
+ this.includeCheatSheet = true;
21
+
22
+ /**
23
+ * Configurable setting to disable ngRoute hooks
24
+ * @type {Boolean}
25
+ */
26
+ this.useNgRoute = $injector.has('ngViewDirective');
27
+
28
+ /**
29
+ * Configurable setting for the cheat sheet title
30
+ * @type {String}
31
+ */
32
+
33
+ this.templateTitle = 'Keyboard Shortcuts:';
34
+
35
+ /**
36
+ * Configurable settings for the cheat sheet header and footer. Both are HTML, and the header
37
+ * overrides the normal title if specified.
38
+ * @type {String}
39
+ */
40
+ this.templateHeader = null;
41
+ this.templateFooter = null;
42
+
43
+ /**
44
+ * Cheat sheet template in the event you want to totally customize it.
45
+ * @type {String}
46
+ */
47
+ this.template = '<div class="cfp-hotkeys-container fade" ng-class="{in: helpVisible}" style="display: none;"><div class="cfp-hotkeys">' +
48
+ '<h4 class="cfp-hotkeys-title" ng-if="!header">{{ title }}</h4>' +
49
+ '<div ng-bind-html="header" ng-if="header"></div>' +
50
+ '<table><tbody>' +
51
+ '<tr ng-repeat="hotkey in hotkeys | filter:{ description: \'!$$undefined$$\' }">' +
52
+ '<td class="cfp-hotkeys-keys">' +
53
+ '<span ng-repeat="key in hotkey.format() track by $index" class="cfp-hotkeys-key">{{ key }}</span>' +
54
+ '</td>' +
55
+ '<td class="cfp-hotkeys-text">{{ hotkey.description }}</td>' +
56
+ '</tr>' +
57
+ '</tbody></table>' +
58
+ '<div ng-bind-html="footer" ng-if="footer"></div>' +
59
+ '<div class="cfp-hotkeys-close" ng-click="toggleCheatSheet()">&#215;</div>' +
60
+ '</div></div>';
61
+
62
+ /**
63
+ * Configurable setting for the cheat sheet hotkey
64
+ * @type {String}
65
+ */
66
+ this.cheatSheetHotkey = '?';
67
+
68
+ /**
69
+ * Configurable setting for the cheat sheet description
70
+ * @type {String}
71
+ */
72
+ this.cheatSheetDescription = 'Show / hide this help menu';
73
+
74
+ this.$get = function ($rootElement, $rootScope, $compile, $window, $document) {
75
+
76
+ var mouseTrapEnabled = true;
77
+
78
+ function pause() {
79
+ mouseTrapEnabled = false;
80
+ }
81
+
82
+ function unpause() {
83
+ mouseTrapEnabled = true;
84
+ }
85
+
86
+ // monkeypatch Mousetrap's stopCallback() function
87
+ // this version doesn't return true when the element is an INPUT, SELECT, or TEXTAREA
88
+ // (instead we will perform this check per-key in the _add() method)
89
+ Mousetrap.prototype.stopCallback = function(event, element) {
90
+ if (!mouseTrapEnabled) {
91
+ return true;
92
+ }
93
+
94
+ // if the element has the class "mousetrap" then no need to stop
95
+ if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) {
96
+ return false;
97
+ }
98
+
99
+ return (element.contentEditable && element.contentEditable == 'true');
100
+ };
101
+
102
+ /**
103
+ * Convert strings like cmd into symbols like ⌘
104
+ * @param {String} combo Key combination, e.g. 'mod+f'
105
+ * @return {String} The key combination with symbols
106
+ */
107
+ function symbolize (combo) {
108
+ var map = {
109
+ command : '\u2318', // ⌘
110
+ shift : '\u21E7', // ⇧
111
+ left : '\u2190', // ←
112
+ right : '\u2192', // →
113
+ up : '\u2191', // ↑
114
+ down : '\u2193', // ↓
115
+ 'return' : '\u23CE', // ⏎
116
+ backspace : '\u232B' // ⌫
117
+ };
118
+ combo = combo.split('+');
119
+
120
+ for (var i = 0; i < combo.length; i++) {
121
+ // try to resolve command / ctrl based on OS:
122
+ if (combo[i] === 'mod') {
123
+ if ($window.navigator && $window.navigator.platform.indexOf('Mac') >=0 ) {
124
+ combo[i] = 'command';
125
+ } else {
126
+ combo[i] = 'ctrl';
127
+ }
128
+ }
129
+
130
+ combo[i] = map[combo[i]] || combo[i];
131
+ }
132
+
133
+ return combo.join(' + ');
134
+ }
135
+
136
+ /**
137
+ * Hotkey object used internally for consistency
138
+ *
139
+ * @param {array} combo The keycombo. it's an array to support multiple combos
140
+ * @param {String} description Description for the keycombo
141
+ * @param {Function} callback function to execute when keycombo pressed
142
+ * @param {string} action the type of event to listen for (for mousetrap)
143
+ * @param {array} allowIn an array of tag names to allow this combo in ('INPUT', 'SELECT', and/or 'TEXTAREA')
144
+ * @param {Boolean} persistent Whether the hotkey persists navigation events
145
+ */
146
+ function Hotkey (combo, description, callback, action, allowIn, persistent) {
147
+ // TODO: Check that the values are sane because we could
148
+ // be trying to instantiate a new Hotkey with outside dev's
149
+ // supplied values
150
+
151
+ this.combo = combo instanceof Array ? combo : [combo];
152
+ this.description = description;
153
+ this.callback = callback;
154
+ this.action = action;
155
+ this.allowIn = allowIn;
156
+ this.persistent = persistent;
157
+ this._formated = null;
158
+ }
159
+
160
+ /**
161
+ * Helper method to format (symbolize) the key combo for display
162
+ *
163
+ * @return {[Array]} An array of the key combination sequence
164
+ * for example: "command+g c i" becomes ["⌘ + g", "c", "i"]
165
+ *
166
+ */
167
+ Hotkey.prototype.format = function() {
168
+ if (this._formated === null) {
169
+ // Don't show all the possible key combos, just the first one. Not sure
170
+ // of usecase here, so open a ticket if my assumptions are wrong
171
+ var combo = this.combo[0];
172
+
173
+ var sequence = combo.split(/[\s]/);
174
+ for (var i = 0; i < sequence.length; i++) {
175
+ sequence[i] = symbolize(sequence[i]);
176
+ }
177
+ this._formated = sequence;
178
+ }
179
+
180
+ return this._formated;
181
+ };
182
+
183
+ /**
184
+ * A new scope used internally for the cheatsheet
185
+ * @type {$rootScope.Scope}
186
+ */
187
+ var scope = $rootScope.$new();
188
+
189
+ /**
190
+ * Holds an array of Hotkey objects currently bound
191
+ * @type {Array}
192
+ */
193
+ scope.hotkeys = [];
194
+
195
+ /**
196
+ * Contains the state of the help's visibility
197
+ * @type {Boolean}
198
+ */
199
+ scope.helpVisible = false;
200
+
201
+ /**
202
+ * Holds the title string for the help menu
203
+ * @type {String}
204
+ */
205
+ scope.title = this.templateTitle;
206
+
207
+ /**
208
+ * Holds the header HTML for the help menu
209
+ * @type {String}
210
+ */
211
+ scope.header = this.templateHeader;
212
+
213
+ /**
214
+ * Holds the footer HTML for the help menu
215
+ * @type {String}
216
+ */
217
+ scope.footer = this.templateFooter;
218
+
219
+ /**
220
+ * Expose toggleCheatSheet to hotkeys scope so we can call it using
221
+ * ng-click from the template
222
+ * @type {function}
223
+ */
224
+ scope.toggleCheatSheet = toggleCheatSheet;
225
+
226
+
227
+ /**
228
+ * Holds references to the different scopes that have bound hotkeys
229
+ * attached. This is useful to catch when the scopes are `$destroy`d and
230
+ * then automatically unbind the hotkey.
231
+ *
232
+ * @type {Object}
233
+ */
234
+ var boundScopes = {};
235
+
236
+ if (this.useNgRoute) {
237
+ $rootScope.$on('$routeChangeSuccess', function (event, route) {
238
+ purgeHotkeys();
239
+
240
+ if (route && route.hotkeys) {
241
+ angular.forEach(route.hotkeys, function (hotkey) {
242
+ // a string was given, which implies this is a function that is to be
243
+ // $eval()'d within that controller's scope
244
+ // TODO: hotkey here is super confusing. sometimes a function (that gets turned into an array), sometimes a string
245
+ var callback = hotkey[2];
246
+ if (typeof(callback) === 'string' || callback instanceof String) {
247
+ hotkey[2] = [callback, route];
248
+ }
249
+
250
+ // todo: perform check to make sure not already defined:
251
+ // this came from a route, so it's likely not meant to be persistent
252
+ hotkey[5] = false;
253
+ _add.apply(this, hotkey);
254
+ });
255
+ }
256
+ });
257
+ }
258
+
259
+
260
+
261
+ // Auto-create a help menu:
262
+ if (this.includeCheatSheet) {
263
+ var document = $document[0];
264
+ var element = $rootElement[0];
265
+ var helpMenu = angular.element(this.template);
266
+ _add(this.cheatSheetHotkey, this.cheatSheetDescription, toggleCheatSheet);
267
+
268
+ // If $rootElement is document or documentElement, then body must be used
269
+ if (element === document || element === document.documentElement) {
270
+ element = document.body;
271
+ }
272
+
273
+ angular.element(element).append($compile(helpMenu)(scope));
274
+ }
275
+
276
+
277
+ /**
278
+ * Purges all non-persistent hotkeys (such as those defined in routes)
279
+ *
280
+ * Without this, the same hotkey would get recreated everytime
281
+ * the route is accessed.
282
+ */
283
+ function purgeHotkeys() {
284
+ var i = scope.hotkeys.length;
285
+ while (i--) {
286
+ var hotkey = scope.hotkeys[i];
287
+ if (hotkey && !hotkey.persistent) {
288
+ _del(hotkey);
289
+ }
290
+ }
291
+ }
292
+
293
+ /**
294
+ * Toggles the help menu element's visiblity
295
+ */
296
+ var previousEsc = false;
297
+
298
+ function toggleCheatSheet() {
299
+ scope.helpVisible = !scope.helpVisible;
300
+
301
+ // Bind to esc to remove the cheat sheet. Ideally, this would be done
302
+ // as a directive in the template, but that would create a nasty
303
+ // circular dependency issue that I don't feel like sorting out.
304
+ if (scope.helpVisible) {
305
+ previousEsc = _get('esc');
306
+ _del('esc');
307
+
308
+ // Here's an odd way to do this: we're going to use the original
309
+ // description of the hotkey on the cheat sheet so that it shows up.
310
+ // without it, no entry for esc will ever show up (#22)
311
+ _add('esc', previousEsc.description, toggleCheatSheet, null, ['INPUT', 'SELECT', 'TEXTAREA']);
312
+ } else {
313
+ _del('esc');
314
+
315
+ // restore the previously bound ESC key
316
+ if (previousEsc !== false) {
317
+ _add(previousEsc);
318
+ }
319
+ }
320
+ }
321
+
322
+ /**
323
+ * Creates a new Hotkey and creates the Mousetrap binding
324
+ *
325
+ * @param {string} combo mousetrap key binding
326
+ * @param {string} description description for the help menu
327
+ * @param {Function} callback method to call when key is pressed
328
+ * @param {string} action the type of event to listen for (for mousetrap)
329
+ * @param {array} allowIn an array of tag names to allow this combo in ('INPUT', 'SELECT', and/or 'TEXTAREA')
330
+ * @param {boolean} persistent if true, the binding is preserved upon route changes
331
+ */
332
+ function _add (combo, description, callback, action, allowIn, persistent) {
333
+
334
+ // used to save original callback for "allowIn" wrapping:
335
+ var _callback;
336
+
337
+ // these elements are prevented by the default Mousetrap.stopCallback():
338
+ var preventIn = ['INPUT', 'SELECT', 'TEXTAREA'];
339
+
340
+ // Determine if object format was given:
341
+ var objType = Object.prototype.toString.call(combo);
342
+
343
+ if (objType === '[object Object]') {
344
+ description = combo.description;
345
+ callback = combo.callback;
346
+ action = combo.action;
347
+ persistent = combo.persistent;
348
+ allowIn = combo.allowIn;
349
+ combo = combo.combo;
350
+ }
351
+
352
+ // no duplicates please
353
+ _del(combo);
354
+
355
+ // description is optional:
356
+ if (description instanceof Function) {
357
+ action = callback;
358
+ callback = description;
359
+ description = '$$undefined$$';
360
+ } else if (angular.isUndefined(description)) {
361
+ description = '$$undefined$$';
362
+ }
363
+
364
+ // any items added through the public API are for controllers
365
+ // that persist through navigation, and thus undefined should mean
366
+ // true in this case.
367
+ if (persistent === undefined) {
368
+ persistent = true;
369
+ }
370
+ // if callback is defined, then wrap it in a function
371
+ // that checks if the event originated from a form element.
372
+ // the function blocks the callback from executing unless the element is specified
373
+ // in allowIn (emulates Mousetrap.stopCallback() on a per-key level)
374
+ if (typeof callback === 'function') {
375
+
376
+ // save the original callback
377
+ _callback = callback;
378
+
379
+ // make sure allowIn is an array
380
+ if (!(allowIn instanceof Array)) {
381
+ allowIn = [];
382
+ }
383
+
384
+ // remove anything from preventIn that's present in allowIn
385
+ var index;
386
+ for (var i=0; i < allowIn.length; i++) {
387
+ allowIn[i] = allowIn[i].toUpperCase();
388
+ index = preventIn.indexOf(allowIn[i]);
389
+ if (index !== -1) {
390
+ preventIn.splice(index, 1);
391
+ }
392
+ }
393
+
394
+ // create the new wrapper callback
395
+ callback = function(event) {
396
+ var shouldExecute = true;
397
+
398
+ // if the callback is executed directly `hotkey.get('w').callback()`
399
+ // there will be no event, so just execute the callback.
400
+ if (event) {
401
+ var target = event.target || event.srcElement; // srcElement is IE only
402
+ var nodeName = target.nodeName.toUpperCase();
403
+
404
+ // check if the input has a mousetrap class, and skip checking preventIn if so
405
+ if ((' ' + target.className + ' ').indexOf(' mousetrap ') > -1) {
406
+ shouldExecute = true;
407
+ } else {
408
+ // don't execute callback if the event was fired from inside an element listed in preventIn
409
+ for (var i=0; i<preventIn.length; i++) {
410
+ if (preventIn[i] === nodeName) {
411
+ shouldExecute = false;
412
+ break;
413
+ }
414
+ }
415
+ }
416
+ }
417
+
418
+ if (shouldExecute) {
419
+ wrapApply(_callback.apply(this, arguments));
420
+ }
421
+ };
422
+ }
423
+
424
+ if (typeof(action) === 'string') {
425
+ Mousetrap.bind(combo, wrapApply(callback), action);
426
+ } else {
427
+ Mousetrap.bind(combo, wrapApply(callback));
428
+ }
429
+
430
+ var hotkey = new Hotkey(combo, description, callback, action, allowIn, persistent);
431
+ scope.hotkeys.push(hotkey);
432
+ return hotkey;
433
+ }
434
+
435
+ /**
436
+ * delete and unbind a Hotkey
437
+ *
438
+ * @param {mixed} hotkey Either the bound key or an instance of Hotkey
439
+ * @return {boolean} true if successful
440
+ */
441
+ function _del (hotkey) {
442
+ var combo = (hotkey instanceof Hotkey) ? hotkey.combo : hotkey;
443
+
444
+ Mousetrap.unbind(combo);
445
+
446
+ if (angular.isArray(combo)) {
447
+ var retStatus = true;
448
+ var i = combo.length;
449
+ while (i--) {
450
+ retStatus = _del(combo[i]) && retStatus;
451
+ }
452
+ return retStatus;
453
+ } else {
454
+ var index = scope.hotkeys.indexOf(_get(combo));
455
+
456
+ if (index > -1) {
457
+ // if the combo has other combos bound, don't unbind the whole thing, just the one combo:
458
+ if (scope.hotkeys[index].combo.length > 1) {
459
+ scope.hotkeys[index].combo.splice(scope.hotkeys[index].combo.indexOf(combo), 1);
460
+ } else {
461
+
462
+ // remove hotkey from bound scopes
463
+ angular.forEach(boundScopes, function (boundScope) {
464
+ var scopeIndex = boundScope.indexOf(scope.hotkeys[index]);
465
+ if (scopeIndex !== -1) {
466
+ boundScope.splice(scopeIndex, 1);
467
+ }
468
+ });
469
+
470
+ scope.hotkeys.splice(index, 1);
471
+ }
472
+ return true;
473
+ }
474
+ }
475
+
476
+ return false;
477
+
478
+ }
479
+
480
+ /**
481
+ * Get a Hotkey object by key binding
482
+ *
483
+ * @param {[string]} [combo] the key the Hotkey is bound to. Returns all key bindings if no key is passed
484
+ * @return {Hotkey} The Hotkey object
485
+ */
486
+ function _get (combo) {
487
+
488
+ if (!combo) {
489
+ return scope.hotkeys;
490
+ }
491
+
492
+ var hotkey;
493
+
494
+ for (var i = 0; i < scope.hotkeys.length; i++) {
495
+ hotkey = scope.hotkeys[i];
496
+
497
+ if (hotkey.combo.indexOf(combo) > -1) {
498
+ return hotkey;
499
+ }
500
+ }
501
+
502
+ return false;
503
+ }
504
+
505
+ /**
506
+ * Binds the hotkey to a particular scope. Useful if the scope is
507
+ * destroyed, we can automatically destroy the hotkey binding.
508
+ *
509
+ * @param {Object} scope The scope to bind to
510
+ */
511
+ function bindTo (scope) {
512
+ // Only initialize once to allow multiple calls for same scope.
513
+ if (!(scope.$id in boundScopes)) {
514
+
515
+ // Add the scope to the list of bound scopes
516
+ boundScopes[scope.$id] = [];
517
+
518
+ scope.$on('$destroy', function () {
519
+ var i = boundScopes[scope.$id].length;
520
+ while (i--) {
521
+ _del(boundScopes[scope.$id].pop());
522
+ }
523
+ });
524
+ }
525
+ // return an object with an add function so we can keep track of the
526
+ // hotkeys and their scope that we added via this chaining method
527
+ return {
528
+ add: function (args) {
529
+ var hotkey;
530
+
531
+ if (arguments.length > 1) {
532
+ hotkey = _add.apply(this, arguments);
533
+ } else {
534
+ hotkey = _add(args);
535
+ }
536
+
537
+ boundScopes[scope.$id].push(hotkey);
538
+ return this;
539
+ }
540
+ };
541
+ }
542
+
543
+ /**
544
+ * All callbacks sent to Mousetrap are wrapped using this function
545
+ * so that we can force a $scope.$apply()
546
+ *
547
+ * @param {Function} callback [description]
548
+ * @return {[type]} [description]
549
+ */
550
+ function wrapApply (callback) {
551
+ // return mousetrap a function to call
552
+ return function (event, combo) {
553
+
554
+ // if this is an array, it means we provided a route object
555
+ // because the scope wasn't available yet, so rewrap the callback
556
+ // now that the scope is available:
557
+ if (callback instanceof Array) {
558
+ var funcString = callback[0];
559
+ var route = callback[1];
560
+ callback = function (event) {
561
+ route.scope.$eval(funcString);
562
+ };
563
+ }
564
+
565
+ // this takes place outside angular, so we'll have to call
566
+ // $apply() to make sure angular's digest happens
567
+ $rootScope.$apply(function() {
568
+ // call the original hotkey callback with the keyboard event
569
+ callback(event, _get(combo));
570
+ });
571
+ };
572
+ }
573
+
574
+ var publicApi = {
575
+ add : _add,
576
+ del : _del,
577
+ get : _get,
578
+ bindTo : bindTo,
579
+ template : this.template,
580
+ toggleCheatSheet : toggleCheatSheet,
581
+ includeCheatSheet : this.includeCheatSheet,
582
+ cheatSheetHotkey : this.cheatSheetHotkey,
583
+ cheatSheetDescription : this.cheatSheetDescription,
584
+ useNgRoute : this.useNgRoute,
585
+ purgeHotkeys : purgeHotkeys,
586
+ templateTitle : this.templateTitle,
587
+ pause : pause,
588
+ unpause : unpause
589
+ };
590
+
591
+ return publicApi;
592
+
593
+ };
594
+
595
+
596
+ })
597
+
598
+ .directive('hotkey', function (hotkeys) {
599
+ return {
600
+ restrict: 'A',
601
+ link: function (scope, el, attrs) {
602
+ var keys = [],
603
+ allowIn;
604
+
605
+ angular.forEach(scope.$eval(attrs.hotkey), function (func, hotkey) {
606
+ // split and trim the hotkeys string into array
607
+ allowIn = typeof attrs.hotkeyAllowIn === "string" ? attrs.hotkeyAllowIn.split(/[\s,]+/) : [];
608
+
609
+ keys.push(hotkey);
610
+
611
+ hotkeys.add({
612
+ combo: hotkey,
613
+ description: attrs.hotkeyDescription,
614
+ callback: func,
615
+ action: attrs.hotkeyAction,
616
+ allowIn: allowIn
617
+ });
618
+ });
619
+
620
+ // remove the hotkey if the directive is destroyed:
621
+ el.bind('$destroy', function() {
622
+ angular.forEach(keys, hotkeys.del);
623
+ });
624
+ }
625
+ };
626
+ })
627
+
628
+ .run(function(hotkeys) {
629
+ // force hotkeys to run by injecting it. Without this, hotkeys only runs
630
+ // when a controller or something else asks for it via DI.
631
+ });
632
+
633
+ })();