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.
- checksums.yaml +4 -4
- data/app/assets/javascripts/kms/application.js +1 -0
- data/app/assets/javascripts/kms/application/controllers/assets_controller.coffee.erb +14 -4
- data/app/assets/javascripts/kms/application/controllers/pages_controller.coffee.erb +12 -2
- data/app/assets/javascripts/kms/application/controllers/snippets_controller.coffee.erb +13 -3
- data/app/assets/javascripts/kms/application/controllers/templates_controller.coffee.erb +13 -3
- data/app/assets/javascripts/kms/application/controllers/users_controller.coffee +5 -5
- data/app/assets/javascripts/kms/application/module.coffee +6 -2
- data/app/assets/javascripts/kms/application/routes.coffee.erb +10 -0
- data/app/assets/javascripts/templates/assets/edit.html.slim +2 -1
- data/app/assets/javascripts/templates/assets/form.html.slim +1 -1
- data/app/assets/javascripts/templates/pages/edit.html.slim +1 -0
- data/app/assets/javascripts/templates/shared/hotkey_notification.html.slim +6 -0
- data/app/assets/javascripts/templates/snippets/edit.html.slim +1 -0
- data/app/assets/javascripts/templates/templates/edit.html.slim +1 -0
- data/app/assets/javascripts/templates/users/edit.html.slim +5 -0
- data/app/assets/javascripts/templates/users/form.html.slim +3 -2
- data/app/assets/javascripts/templates/users/index.html.slim +2 -1
- data/app/assets/stylesheets/kms/custom.css.scss +10 -0
- data/app/controllers/kms/assets_controller.rb +6 -3
- data/app/controllers/kms/users_controller.rb +14 -0
- data/app/services/kms/resource_service.rb +3 -1
- data/app/views/layouts/kms/kms.html.erb +1 -1
- data/config/initializers/devise.rb +9 -0
- data/config/locales/en.yml +12 -0
- data/config/locales/ru.yml +12 -0
- data/config/routes.rb +1 -1
- data/lib/kms/engine.rb +1 -1
- data/lib/kms/version.rb +1 -1
- data/spec/controllers/kms/assets_controller_spec.rb +28 -10
- data/spec/controllers/kms/users_controller_spec.rb +23 -0
- data/spec/internal/config/routes.rb +1 -1
- data/spec/internal/log/test.log +0 -105823
- data/vendor/assets/bower.json +5 -4
- data/vendor/assets/bower_components/angular-cookies/angular-cookies.js +22 -18
- data/vendor/assets/bower_components/angular-cookies/angular-cookies.min.js +4 -4
- data/vendor/assets/bower_components/angular-cookies/angular-cookies.min.js.map +2 -2
- data/vendor/assets/bower_components/angular-cookies/bower.json +2 -2
- data/vendor/assets/bower_components/angular-cookies/package.json +1 -1
- data/vendor/assets/bower_components/angular-hotkeys/Gruntfile.js +118 -0
- data/vendor/assets/bower_components/angular-hotkeys/LICENSE +20 -0
- data/vendor/assets/bower_components/angular-hotkeys/README.md +248 -0
- data/vendor/assets/bower_components/angular-hotkeys/bower.json +19 -0
- data/vendor/assets/bower_components/angular-hotkeys/build/hotkeys.css +110 -0
- data/vendor/assets/bower_components/angular-hotkeys/build/hotkeys.js +1661 -0
- data/vendor/assets/bower_components/angular-hotkeys/build/hotkeys.min.css +1 -0
- data/vendor/assets/bower_components/angular-hotkeys/build/hotkeys.min.js +7 -0
- data/vendor/assets/bower_components/angular-hotkeys/package.json +45 -0
- data/vendor/assets/bower_components/angular-hotkeys/src/hotkeys.css +104 -0
- data/vendor/assets/bower_components/angular-hotkeys/src/hotkeys.js +633 -0
- data/vendor/assets/bower_components/angular-loading-bar/CHANGELOG.md +33 -0
- data/vendor/assets/bower_components/angular-loading-bar/CONTRIBUTING.md +17 -0
- data/vendor/assets/bower_components/angular-loading-bar/Gruntfile.js +9 -1
- data/vendor/assets/bower_components/angular-loading-bar/ISSUE_TEMPLATE.md +14 -0
- data/vendor/assets/bower_components/angular-loading-bar/PULL_REQUEST_TEMPLATE.md +13 -0
- data/vendor/assets/bower_components/angular-loading-bar/README.md +30 -3
- data/vendor/assets/bower_components/angular-loading-bar/bower.json +11 -6
- data/vendor/assets/bower_components/angular-loading-bar/build/loading-bar.css +5 -5
- data/vendor/assets/bower_components/angular-loading-bar/build/loading-bar.js +39 -12
- data/vendor/assets/bower_components/angular-loading-bar/build/loading-bar.min.css +1 -8
- data/vendor/assets/bower_components/angular-loading-bar/build/loading-bar.min.js +3 -3
- data/vendor/assets/bower_components/angular-loading-bar/index.js +2 -0
- data/vendor/assets/bower_components/angular-loading-bar/package.json +12 -15
- data/vendor/assets/bower_components/angular-loading-bar/src/loading-bar.css +3 -3
- data/vendor/assets/bower_components/angular-loading-bar/src/loading-bar.js +37 -10
- data/vendor/assets/bower_components/angular-sanitize/angular-sanitize.js +504 -386
- data/vendor/assets/bower_components/angular-sanitize/angular-sanitize.min.js +13 -12
- data/vendor/assets/bower_components/angular-sanitize/angular-sanitize.min.js.map +3 -3
- data/vendor/assets/bower_components/angular-sanitize/bower.json +2 -2
- data/vendor/assets/bower_components/angular-sanitize/package.json +1 -1
- data/vendor/assets/bower_components/angular-ui-router/CHANGELOG.md +1410 -0
- data/vendor/assets/bower_components/angular-ui-router/CONTRIBUTING.md +64 -16
- data/vendor/assets/bower_components/angular-ui-router/DOCS.md +48 -0
- data/vendor/assets/bower_components/angular-ui-router/ISSUE_TEMPLATE.md +53 -0
- data/vendor/assets/bower_components/angular-ui-router/LICENSE +1 -1
- data/vendor/assets/bower_components/angular-ui-router/README.md +24 -211
- data/vendor/assets/bower_components/angular-ui-router/artifacts.json +8 -0
- data/vendor/assets/bower_components/angular-ui-router/bower.json +1 -23
- data/vendor/assets/bower_components/angular-ui-router/karma.conf.js +105 -0
- data/vendor/assets/bower_components/angular-ui-router/release/angular-ui-router.js +9744 -3901
- data/vendor/assets/bower_components/angular-ui-router/release/angular-ui-router.js.map +192 -0
- data/vendor/assets/bower_components/angular-ui-router/release/angular-ui-router.min.js +9 -4
- data/vendor/assets/bower_components/angular-ui-router/release/angular-ui-router.min.js.map +1679 -0
- data/vendor/assets/bower_components/angular-ui-router/release/resolveService.js +83 -0
- data/vendor/assets/bower_components/angular-ui-router/release/resolveService.js.map +19 -0
- data/vendor/assets/bower_components/angular-ui-router/release/resolveService.min.js +8 -0
- data/vendor/assets/bower_components/angular-ui-router/release/resolveService.min.js.map +47 -0
- data/vendor/assets/bower_components/angular-ui-router/release/stateEvents.js +294 -0
- data/vendor/assets/bower_components/angular-ui-router/release/stateEvents.js.map +17 -0
- data/vendor/assets/bower_components/angular-ui-router/release/stateEvents.min.js +8 -0
- data/vendor/assets/bower_components/angular-ui-router/release/stateEvents.min.js.map +102 -0
- data/vendor/assets/bower_components/angular-ui-router/release/ui-router-angularjs.js +2014 -0
- data/vendor/assets/bower_components/angular-ui-router/release/ui-router-angularjs.js.map +70 -0
- data/vendor/assets/bower_components/angular-ui-router/release/ui-router-angularjs.min.js +9 -0
- data/vendor/assets/bower_components/angular-ui-router/release/ui-router-angularjs.min.js.map +541 -0
- data/vendor/assets/bower_components/angular-ui-router/rollup.config.js +116 -0
- data/vendor/assets/bower_components/angular-ui-router/tslint.json +60 -0
- data/vendor/assets/bower_components/angular-ui-router/yarn.lock +4146 -0
- data/vendor/assets/bower_components/angular-ui-tree/yarn.lock +4945 -0
- data/vendor/assets/bower_components/angular/angular.js +4019 -2449
- data/vendor/assets/bower_components/angular/angular.min.js +331 -319
- data/vendor/assets/bower_components/angular/angular.min.js.gzip +0 -0
- data/vendor/assets/bower_components/angular/angular.min.js.map +3 -3
- data/vendor/assets/bower_components/angular/bower.json +1 -1
- data/vendor/assets/bower_components/angular/package.json +1 -1
- data/vendor/assets/bower_components/angularjs-dropdown-multiselect/LICENSE +21 -0
- data/vendor/assets/bower_components/angularjs-dropdown-multiselect/README.md +14 -14
- data/vendor/assets/bower_components/angularjs-dropdown-multiselect/bower.json +25 -12
- data/vendor/assets/bower_components/angularjs-dropdown-multiselect/development_index.html +59 -52
- data/vendor/assets/bower_components/angularjs-dropdown-multiselect/dist/angularjs-dropdown-multiselect.min.js +1 -1
- data/vendor/assets/bower_components/angularjs-dropdown-multiselect/index.html +73 -0
- data/vendor/assets/bower_components/angularjs-dropdown-multiselect/package.json +19 -7
- data/vendor/assets/bower_components/angularjs-dropdown-multiselect/pages/javascripts/pages/home/ExampleCtrl.js +126 -3
- data/vendor/assets/bower_components/angularjs-dropdown-multiselect/pages/javascripts/pages/home/home.html +1262 -852
- data/vendor/assets/bower_components/angularjs-dropdown-multiselect/pages/stylesheets/stylesheet.css +10 -5
- data/vendor/assets/bower_components/angularjs-dropdown-multiselect/src/angularjs-dropdown-multiselect.js +612 -287
- metadata +66 -169
- data/spec/internal/config/database.yml +0 -7
- data/spec/internal/public/uploads/kms/asset/file/1/avatar.jpg +0 -0
- data/spec/internal/public/uploads/kms/asset/file/2/avatar.jpg +0 -0
- data/spec/internal/public/uploads/kms/asset/file/2/style.css +0 -1
- data/spec/internal/public/uploads/kms/asset/file/3/style.css +0 -1
- data/spec/internal/public/uploads/kms/asset/file/4/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500976987-41025-0002-0883/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500977082-41195-0002-6495/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500977109-41364-0002-4518/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500977152-41405-0002-2345/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500977327-41694-0002-5448/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500977376-41732-0002-7916/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500977392-41759-0002-7593/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500977410-42259-0002-7527/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500977429-42306-0002-5937/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500977437-42324-0002-5880/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500983228-53594-0002-4559/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500983284-53632-0002-6590/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500983360-53784-0002-7289/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500983469-54321-0002-0386/avatar.jpg +0 -0
- data/spec/internal/public/uploads/tmp/1500983469-54321-0004-5691/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500983511-54352-0002-5720/avatar.jpg +0 -0
- data/spec/internal/public/uploads/tmp/1500983511-54352-0004-1399/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500983610-54507-0002-4280/avatar.jpg +0 -0
- data/spec/internal/public/uploads/tmp/1500983610-54507-0004-9758/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500984466-57012-0002-4146/avatar.jpg +0 -0
- data/spec/internal/public/uploads/tmp/1500984466-57012-0004-5895/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500984509-57158-0002-9657/avatar.jpg +0 -0
- data/spec/internal/public/uploads/tmp/1500984509-57158-0004-5003/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500984616-57697-0002-7201/avatar.jpg +0 -0
- data/spec/internal/public/uploads/tmp/1500984616-57697-0004-6255/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500985257-58947-0002-3629/avatar.jpg +0 -0
- data/spec/internal/public/uploads/tmp/1500985257-58947-0004-5338/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500985407-58947-0006-5929/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500985473-59264-0002-0397/avatar.jpg +0 -0
- data/spec/internal/public/uploads/tmp/1500985473-59264-0004-6493/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500985475-59264-0007-8674/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500985538-59468-0002-9206/avatar.jpg +0 -0
- data/spec/internal/public/uploads/tmp/1500985538-59468-0004-2586/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500985538-59468-0007-6200/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500988358-65877-0002-4528/avatar.jpg +0 -0
- data/spec/internal/public/uploads/tmp/1500988358-65877-0004-5904/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500988358-65877-0007-7320/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500988407-65916-0002-3138/avatar.jpg +0 -0
- data/spec/internal/public/uploads/tmp/1500988407-65916-0004-5400/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500988407-65916-0007-1655/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500988421-65950-0002-9415/avatar.jpg +0 -0
- data/spec/internal/public/uploads/tmp/1500988421-65950-0004-7130/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500988421-65950-0007-9886/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500988435-65981-0002-3228/avatar.jpg +0 -0
- data/spec/internal/public/uploads/tmp/1500988435-65981-0004-3682/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500988435-65981-0007-1582/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500988475-66122-0002-9516/avatar.jpg +0 -0
- data/spec/internal/public/uploads/tmp/1500988475-66122-0004-5634/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500988530-66122-0007-2272/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500988554-66315-0002-6262/avatar.jpg +0 -0
- data/spec/internal/public/uploads/tmp/1500988554-66315-0004-6099/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500988554-66315-0007-1632/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500991751-73722-0002-9937/avatar.jpg +0 -0
- data/spec/internal/public/uploads/tmp/1500991751-73722-0004-8034/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500991751-73722-0007-7763/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1501233238-34385-0002-3210/avatar.jpg +0 -0
- data/spec/internal/public/uploads/tmp/1501233238-34385-0004-5881/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1501233238-34385-0007-6280/style.css +0 -1
- data/spec/internal/tmp/cache/assets/test/sprockets/v3.0/1XyAFYlYI0pK7WAgjR4PgXV6BgU6huJSviWmHetdCRs.cache +0 -1
- data/vendor/assets/bower_components/angular-ui-router/api/angular-ui-router.d.ts +0 -126
- data/vendor/assets/bower_components/angular-ui-router/src/common.js +0 -292
- data/vendor/assets/bower_components/angular-ui-router/src/resolve.js +0 -252
- data/vendor/assets/bower_components/angular-ui-router/src/state.js +0 -1373
- data/vendor/assets/bower_components/angular-ui-router/src/stateDirectives.js +0 -268
- data/vendor/assets/bower_components/angular-ui-router/src/stateFilters.js +0 -39
- data/vendor/assets/bower_components/angular-ui-router/src/templateFactory.js +0 -110
- data/vendor/assets/bower_components/angular-ui-router/src/urlMatcherFactory.js +0 -1036
- data/vendor/assets/bower_components/angular-ui-router/src/urlRouter.js +0 -413
- data/vendor/assets/bower_components/angular-ui-router/src/view.js +0 -71
- data/vendor/assets/bower_components/angular-ui-router/src/viewDirective.js +0 -302
- data/vendor/assets/bower_components/angular-ui-router/src/viewScroll.js +0 -52
- data/vendor/assets/bower_components/angularjs-dropdown-multiselect/pages/index.html +0 -67
- 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()">×</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()">×</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
|
+
})();
|