kms 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
})();
|