kms 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ })();