angular-hotkeys-rails 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +64 -0
- data/lib/angular/hotkeys/rails.rb +11 -0
- data/lib/angular/hotkeys/rails/version.rb +7 -0
- data/vendor/assets/javascripts/angular/hotkeys.js +552 -0
- data/vendor/assets/stylesheets/angular/hotkeys.css +104 -0
- metadata +107 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 00d0bb733f8c9b0d5b90e2a30f01240339be635d
|
4
|
+
data.tar.gz: 0a7c9916b1e0636494dabea7cb482aedfcd74809
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ff74e2fa1a3da3f9dadcc2169cb9448714ba22385e38cc62cbe99a556cc445806424617dcd52e28b5b595e1cb3c9d46046feaf110040969d52681ab7a71cc722
|
7
|
+
data.tar.gz: 86be4b2c282823ec9447aa7897da3afffb285a9dc60a9b2ddd9cc190c2c416ffa2de313184f57e094b7cbdb1c39def6846bea34c1113143e5b9bd5d6b252c4f2
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Anton Shemerey
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# Angular::Hotkeys::Rails
|
2
|
+
================
|
3
|
+
|
4
|
+
Configuration-centric keyboard shortcuts for your Rails + Angular apps, with
|
5
|
+
powerful [Mousetrap](https://github.com/ccampbell/mousetrap) under the hood.
|
6
|
+
|
7
|
+
This gem is just a simple wrapper around this two awesome libraries
|
8
|
+
|
9
|
+
* [Mousetrap](https://github.com/ccampbell/mousetrap)
|
10
|
+
* [angular-hotkeys](https://github.com/chieffancypants/angular-hotkeys)
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
Add this line to your application's Gemfile:
|
15
|
+
|
16
|
+
gem 'angular-hotkeys-rails'
|
17
|
+
|
18
|
+
And then execute:
|
19
|
+
|
20
|
+
$ bundle
|
21
|
+
|
22
|
+
Now you are able to add this awesome feature into your product by adding following lines:
|
23
|
+
|
24
|
+
```coffeescript
|
25
|
+
//= require angular/hotkeys # ---> application.js
|
26
|
+
```
|
27
|
+
|
28
|
+
```css
|
29
|
+
*= require angular/hotkeys # ---> application.css
|
30
|
+
```
|
31
|
+
|
32
|
+
### Usage:
|
33
|
+
|
34
|
+
You can either define hotkeys in your Controller, or in your Route
|
35
|
+
configuration (or both). To start, though, require the lib as a dependency for
|
36
|
+
your angular app:
|
37
|
+
|
38
|
+
```js
|
39
|
+
angular.module('myApp', ['ngRoute', 'cfp.hotkeys']);
|
40
|
+
```
|
41
|
+
|
42
|
+
Now you could easily check it out by pressing `Cmd` + `?`
|
43
|
+
|
44
|
+
Behind the scenes, there is [Mousetrap](https://github.com/ccampbell/mousetrap) library to manage the key
|
45
|
+
bindings. Check out the docs there for more information on what kind of key
|
46
|
+
combinations can be used.
|
47
|
+
|
48
|
+
You could find more examples on the following link [Examples](https://github.com/chieffancypants/angular-hotkeys#usage)
|
49
|
+
|
50
|
+
## Dependency
|
51
|
+
|
52
|
+
Please take into account this library take care only about
|
53
|
+
[Mousetrap](https://github.com/ccampbell/mousetrap) dependency, under the hood it depends on
|
54
|
+
[mousetrap-rails](https://github.com/kugaevsky/mousetrap-rails). That basically means you still have to add `Angular JS` manually.
|
55
|
+
|
56
|
+
You could consider to add [angularjs-rails](https://github.com/hiravgandhi/angularjs-rails) it works perfectly fine.
|
57
|
+
|
58
|
+
## Contributing
|
59
|
+
|
60
|
+
1. Fork it ( https://github.com/shemerey/angular-hotkeys-rails/fork )
|
61
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
62
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
63
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
64
|
+
5. Create a new Pull Request
|
@@ -0,0 +1,552 @@
|
|
1
|
+
/*
|
2
|
+
* angular-hotkeys
|
3
|
+
*
|
4
|
+
* Automatic keyboard shortcuts for your angular apps
|
5
|
+
*
|
6
|
+
* (c) 2014 Wes Cruver
|
7
|
+
* License: MIT
|
8
|
+
*/
|
9
|
+
//= require mousetrap
|
10
|
+
|
11
|
+
(function() {
|
12
|
+
|
13
|
+
'use strict';
|
14
|
+
|
15
|
+
angular.module('cfp.hotkeys', []).provider('hotkeys', function() {
|
16
|
+
|
17
|
+
/**
|
18
|
+
* Configurable setting to disable the cheatsheet entirely
|
19
|
+
* @type {Boolean}
|
20
|
+
*/
|
21
|
+
this.includeCheatSheet = true;
|
22
|
+
|
23
|
+
/**
|
24
|
+
* Cheat sheet template in the event you want to totally customize it.
|
25
|
+
* @type {String}
|
26
|
+
*/
|
27
|
+
this.template = '<div class="cfp-hotkeys-container fade" ng-class="{in: helpVisible}" style="display: none;"><div class="cfp-hotkeys">' +
|
28
|
+
'<h4 class="cfp-hotkeys-title">{{ title }}</h4>' +
|
29
|
+
'<table><tbody>' +
|
30
|
+
'<tr ng-repeat="hotkey in hotkeys | filter:{ description: \'!$$undefined$$\' }">' +
|
31
|
+
'<td class="cfp-hotkeys-keys">' +
|
32
|
+
'<span ng-repeat="key in hotkey.format() track by $index" class="cfp-hotkeys-key">{{ key }}</span>' +
|
33
|
+
'</td>' +
|
34
|
+
'<td class="cfp-hotkeys-text">{{ hotkey.description }}</td>' +
|
35
|
+
'</tr>' +
|
36
|
+
'</tbody></table>' +
|
37
|
+
'<div class="cfp-hotkeys-close" ng-click="toggleCheatSheet()">×</div>' +
|
38
|
+
'</div></div>';
|
39
|
+
|
40
|
+
/**
|
41
|
+
* Configurable setting for the cheat sheet hotkey
|
42
|
+
* @type {String}
|
43
|
+
*/
|
44
|
+
this.cheatSheetHotkey = '?';
|
45
|
+
|
46
|
+
/**
|
47
|
+
* Configurable setting for the cheat sheet description
|
48
|
+
* @type {String}
|
49
|
+
*/
|
50
|
+
this.cheatSheetDescription = 'Show / hide this help menu';
|
51
|
+
|
52
|
+
this.$get = ['$rootElement', '$rootScope', '$compile', '$window', '$document', function ($rootElement, $rootScope, $compile, $window, $document) {
|
53
|
+
|
54
|
+
// monkeypatch Mousetrap's stopCallback() function
|
55
|
+
// this version doesn't return true when the element is an INPUT, SELECT, or TEXTAREA
|
56
|
+
// (instead we will perform this check per-key in the _add() method)
|
57
|
+
Mousetrap.stopCallback = function(event, element) {
|
58
|
+
// if the element has the class "mousetrap" then no need to stop
|
59
|
+
if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) {
|
60
|
+
return false;
|
61
|
+
}
|
62
|
+
|
63
|
+
return (element.contentEditable && element.contentEditable == 'true');
|
64
|
+
};
|
65
|
+
|
66
|
+
/**
|
67
|
+
* Convert strings like cmd into symbols like ⌘
|
68
|
+
* @param {String} combo Key combination, e.g. 'mod+f'
|
69
|
+
* @return {String} The key combination with symbols
|
70
|
+
*/
|
71
|
+
function symbolize (combo) {
|
72
|
+
var map = {
|
73
|
+
command : '⌘',
|
74
|
+
shift : '⇧',
|
75
|
+
left : '←',
|
76
|
+
right : '→',
|
77
|
+
up : '↑',
|
78
|
+
down : '↓',
|
79
|
+
'return' : '↩',
|
80
|
+
backspace : '⌫'
|
81
|
+
};
|
82
|
+
combo = combo.split('+');
|
83
|
+
|
84
|
+
for (var i = 0; i < combo.length; i++) {
|
85
|
+
// try to resolve command / ctrl based on OS:
|
86
|
+
if (combo[i] === 'mod') {
|
87
|
+
if ($window.navigator && $window.navigator.platform.indexOf('Mac') >=0 ) {
|
88
|
+
combo[i] = 'command';
|
89
|
+
} else {
|
90
|
+
combo[i] = 'ctrl';
|
91
|
+
}
|
92
|
+
}
|
93
|
+
|
94
|
+
combo[i] = map[combo[i]] || combo[i];
|
95
|
+
}
|
96
|
+
|
97
|
+
return combo.join(' + ');
|
98
|
+
}
|
99
|
+
|
100
|
+
/**
|
101
|
+
* Hotkey object used internally for consistency
|
102
|
+
*
|
103
|
+
* @param {array} combo The keycombo. it's an array to support multiple combos
|
104
|
+
* @param {String} description Description for the keycombo
|
105
|
+
* @param {Function} callback function to execute when keycombo pressed
|
106
|
+
* @param {string} action the type of event to listen for (for mousetrap)
|
107
|
+
* @param {array} allowIn an array of tag names to allow this combo in ('INPUT', 'SELECT', and/or 'TEXTAREA')
|
108
|
+
* @param {Boolean} persistent Whether the hotkey persists navigation events
|
109
|
+
*/
|
110
|
+
function Hotkey (combo, description, callback, action, allowIn, persistent) {
|
111
|
+
// TODO: Check that the values are sane because we could
|
112
|
+
// be trying to instantiate a new Hotkey with outside dev's
|
113
|
+
// supplied values
|
114
|
+
|
115
|
+
this.combo = combo instanceof Array ? combo : [combo];
|
116
|
+
this.description = description;
|
117
|
+
this.callback = callback;
|
118
|
+
this.action = action;
|
119
|
+
this.allowIn = allowIn;
|
120
|
+
this.persistent = persistent;
|
121
|
+
}
|
122
|
+
|
123
|
+
/**
|
124
|
+
* Helper method to format (symbolize) the key combo for display
|
125
|
+
*
|
126
|
+
* @return {[Array]} An array of the key combination sequence
|
127
|
+
* for example: "command+g c i" becomes ["⌘ + g", "c", "i"]
|
128
|
+
*
|
129
|
+
* TODO: this gets called a lot. We should cache the result
|
130
|
+
*/
|
131
|
+
Hotkey.prototype.format = function() {
|
132
|
+
|
133
|
+
// Don't show all the possible key combos, just the first one. Not sure
|
134
|
+
// of usecase here, so open a ticket if my assumptions are wrong
|
135
|
+
var combo = this.combo[0];
|
136
|
+
|
137
|
+
var sequence = combo.split(/[\s]/);
|
138
|
+
for (var i = 0; i < sequence.length; i++) {
|
139
|
+
sequence[i] = symbolize(sequence[i]);
|
140
|
+
}
|
141
|
+
|
142
|
+
return sequence;
|
143
|
+
};
|
144
|
+
|
145
|
+
/**
|
146
|
+
* A new scope used internally for the cheatsheet
|
147
|
+
* @type {$rootScope.Scope}
|
148
|
+
*/
|
149
|
+
var scope = $rootScope.$new();
|
150
|
+
|
151
|
+
/**
|
152
|
+
* Holds an array of Hotkey objects currently bound
|
153
|
+
* @type {Array}
|
154
|
+
*/
|
155
|
+
scope.hotkeys = [];
|
156
|
+
|
157
|
+
/**
|
158
|
+
* Contains the state of the help's visibility
|
159
|
+
* @type {Boolean}
|
160
|
+
*/
|
161
|
+
scope.helpVisible = false;
|
162
|
+
|
163
|
+
/**
|
164
|
+
* Holds the title string for the help menu
|
165
|
+
* @type {String}
|
166
|
+
*/
|
167
|
+
scope.title = 'Keyboard Shortcuts:';
|
168
|
+
|
169
|
+
/**
|
170
|
+
* Expose toggleCheatSheet to hotkeys scope so we can call it using
|
171
|
+
* ng-click from the template
|
172
|
+
* @type {function}
|
173
|
+
*/
|
174
|
+
scope.toggleCheatSheet = toggleCheatSheet;
|
175
|
+
|
176
|
+
|
177
|
+
/**
|
178
|
+
* Holds references to the different scopes that have bound hotkeys
|
179
|
+
* attached. This is useful to catch when the scopes are `$destroy`d and
|
180
|
+
* then automatically unbind the hotkey.
|
181
|
+
*
|
182
|
+
* @type {Array}
|
183
|
+
*/
|
184
|
+
var boundScopes = [];
|
185
|
+
|
186
|
+
|
187
|
+
$rootScope.$on('$routeChangeSuccess', function (event, route) {
|
188
|
+
purgeHotkeys();
|
189
|
+
|
190
|
+
if (route.hotkeys) {
|
191
|
+
angular.forEach(route.hotkeys, function (hotkey) {
|
192
|
+
// a string was given, which implies this is a function that is to be
|
193
|
+
// $eval()'d within that controller's scope
|
194
|
+
// TODO: hotkey here is super confusing. sometimes a function (that gets turned into an array), sometimes a string
|
195
|
+
var callback = hotkey[2];
|
196
|
+
if (typeof(callback) === 'string' || callback instanceof String) {
|
197
|
+
hotkey[2] = [callback, route];
|
198
|
+
}
|
199
|
+
|
200
|
+
// todo: perform check to make sure not already defined:
|
201
|
+
// this came from a route, so it's likely not meant to be persistent
|
202
|
+
hotkey[5] = false;
|
203
|
+
_add.apply(this, hotkey);
|
204
|
+
});
|
205
|
+
}
|
206
|
+
});
|
207
|
+
|
208
|
+
|
209
|
+
// Auto-create a help menu:
|
210
|
+
if (this.includeCheatSheet) {
|
211
|
+
var document = $document[0];
|
212
|
+
var element = $rootElement[0];
|
213
|
+
var helpMenu = angular.element(this.template);
|
214
|
+
_add(this.cheatSheetHotkey, this.cheatSheetDescription, toggleCheatSheet);
|
215
|
+
|
216
|
+
// If $rootElement is document or documentElement, then body must be used
|
217
|
+
if (element === document || element === document.documentElement) {
|
218
|
+
element = document.body;
|
219
|
+
}
|
220
|
+
|
221
|
+
angular.element(element).append($compile(helpMenu)(scope));
|
222
|
+
}
|
223
|
+
|
224
|
+
|
225
|
+
/**
|
226
|
+
* Purges all non-persistent hotkeys (such as those defined in routes)
|
227
|
+
*
|
228
|
+
* Without this, the same hotkey would get recreated everytime
|
229
|
+
* the route is accessed.
|
230
|
+
*/
|
231
|
+
function purgeHotkeys() {
|
232
|
+
var i = scope.hotkeys.length;
|
233
|
+
while (i--) {
|
234
|
+
var hotkey = scope.hotkeys[i];
|
235
|
+
if (hotkey && !hotkey.persistent) {
|
236
|
+
_del(hotkey);
|
237
|
+
}
|
238
|
+
}
|
239
|
+
}
|
240
|
+
|
241
|
+
/**
|
242
|
+
* Toggles the help menu element's visiblity
|
243
|
+
*/
|
244
|
+
var previousEsc = false;
|
245
|
+
|
246
|
+
function toggleCheatSheet() {
|
247
|
+
scope.helpVisible = !scope.helpVisible;
|
248
|
+
|
249
|
+
// Bind to esc to remove the cheat sheet. Ideally, this would be done
|
250
|
+
// as a directive in the template, but that would create a nasty
|
251
|
+
// circular dependency issue that I don't feel like sorting out.
|
252
|
+
if (scope.helpVisible) {
|
253
|
+
previousEsc = _get('esc');
|
254
|
+
_del('esc');
|
255
|
+
|
256
|
+
// Here's an odd way to do this: we're going to use the original
|
257
|
+
// description of the hotkey on the cheat sheet so that it shows up.
|
258
|
+
// without it, no entry for esc will ever show up (#22)
|
259
|
+
_add('esc', previousEsc.description, toggleCheatSheet);
|
260
|
+
} else {
|
261
|
+
_del('esc');
|
262
|
+
|
263
|
+
// restore the previously bound ESC key
|
264
|
+
if (previousEsc !== false) {
|
265
|
+
_add(previousEsc);
|
266
|
+
}
|
267
|
+
}
|
268
|
+
}
|
269
|
+
|
270
|
+
/**
|
271
|
+
* Creates a new Hotkey and creates the Mousetrap binding
|
272
|
+
*
|
273
|
+
* @param {string} combo mousetrap key binding
|
274
|
+
* @param {string} description description for the help menu
|
275
|
+
* @param {Function} callback method to call when key is pressed
|
276
|
+
* @param {string} action the type of event to listen for (for mousetrap)
|
277
|
+
* @param {array} allowIn an array of tag names to allow this combo in ('INPUT', 'SELECT', and/or 'TEXTAREA')
|
278
|
+
* @param {boolean} persistent if true, the binding is preserved upon route changes
|
279
|
+
*/
|
280
|
+
function _add (combo, description, callback, action, allowIn, persistent) {
|
281
|
+
|
282
|
+
// used to save original callback for "allowIn" wrapping:
|
283
|
+
var _callback;
|
284
|
+
|
285
|
+
// these elements are prevented by the default Mousetrap.stopCallback():
|
286
|
+
var preventIn = ['INPUT', 'SELECT', 'TEXTAREA'];
|
287
|
+
|
288
|
+
// Determine if object format was given:
|
289
|
+
var objType = Object.prototype.toString.call(combo);
|
290
|
+
|
291
|
+
if (objType === '[object Object]') {
|
292
|
+
description = combo.description;
|
293
|
+
callback = combo.callback;
|
294
|
+
action = combo.action;
|
295
|
+
persistent = combo.persistent;
|
296
|
+
allowIn = combo.allowIn;
|
297
|
+
combo = combo.combo;
|
298
|
+
}
|
299
|
+
|
300
|
+
// description is optional:
|
301
|
+
if (description instanceof Function) {
|
302
|
+
action = callback;
|
303
|
+
callback = description;
|
304
|
+
description = '$$undefined$$';
|
305
|
+
} else if (angular.isUndefined(description)) {
|
306
|
+
description = '$$undefined$$';
|
307
|
+
}
|
308
|
+
|
309
|
+
// any items added through the public API are for controllers
|
310
|
+
// that persist through navigation, and thus undefined should mean
|
311
|
+
// true in this case.
|
312
|
+
if (persistent === undefined) {
|
313
|
+
persistent = true;
|
314
|
+
}
|
315
|
+
|
316
|
+
// if callback is defined, then wrap it in a function
|
317
|
+
// that checks if the event originated from a form element.
|
318
|
+
// the function blocks the callback from executing unless the element is specified
|
319
|
+
// in allowIn (emulates Mousetrap.stopCallback() on a per-key level)
|
320
|
+
if (typeof callback === 'function') {
|
321
|
+
|
322
|
+
// save the original callback
|
323
|
+
_callback = callback;
|
324
|
+
|
325
|
+
// make sure allowIn is an array
|
326
|
+
if (!(allowIn instanceof Array)) {
|
327
|
+
allowIn = [];
|
328
|
+
}
|
329
|
+
|
330
|
+
// remove anything from preventIn that's present in allowIn
|
331
|
+
var index;
|
332
|
+
for (var i=0; i < allowIn.length; i++) {
|
333
|
+
allowIn[i] = allowIn[i].toUpperCase();
|
334
|
+
index = preventIn.indexOf(allowIn[i]);
|
335
|
+
if (index !== -1) {
|
336
|
+
preventIn.splice(index, 1);
|
337
|
+
}
|
338
|
+
}
|
339
|
+
|
340
|
+
// create the new wrapper callback
|
341
|
+
callback = function(event) {
|
342
|
+
var shouldExecute = true;
|
343
|
+
var target = event.target || event.srcElement; // srcElement is IE only
|
344
|
+
var nodeName = target.nodeName.toUpperCase();
|
345
|
+
|
346
|
+
// check if the input has a mousetrap class, and skip checking preventIn if so
|
347
|
+
if ((' ' + target.className + ' ').indexOf(' mousetrap ') > -1) {
|
348
|
+
shouldExecute = true;
|
349
|
+
} else {
|
350
|
+
// don't execute callback if the event was fired from inside an element listed in preventIn
|
351
|
+
for (var i=0; i<preventIn.length; i++) {
|
352
|
+
if (preventIn[i] === nodeName) {
|
353
|
+
shouldExecute = false;
|
354
|
+
break;
|
355
|
+
}
|
356
|
+
}
|
357
|
+
}
|
358
|
+
|
359
|
+
if (shouldExecute) {
|
360
|
+
wrapApply(_callback.apply(this, arguments));
|
361
|
+
}
|
362
|
+
};
|
363
|
+
}
|
364
|
+
|
365
|
+
if (typeof(action) === 'string') {
|
366
|
+
Mousetrap.bind(combo, wrapApply(callback), action);
|
367
|
+
} else {
|
368
|
+
Mousetrap.bind(combo, wrapApply(callback));
|
369
|
+
}
|
370
|
+
|
371
|
+
var hotkey = new Hotkey(combo, description, callback, action, allowIn, persistent);
|
372
|
+
scope.hotkeys.push(hotkey);
|
373
|
+
return hotkey;
|
374
|
+
}
|
375
|
+
|
376
|
+
/**
|
377
|
+
* delete and unbind a Hotkey
|
378
|
+
*
|
379
|
+
* @param {mixed} hotkey Either the bound key or an instance of Hotkey
|
380
|
+
* @return {boolean} true if successful
|
381
|
+
*/
|
382
|
+
function _del (hotkey) {
|
383
|
+
var combo = (hotkey instanceof Hotkey) ? hotkey.combo : hotkey;
|
384
|
+
|
385
|
+
Mousetrap.unbind(combo);
|
386
|
+
|
387
|
+
if (combo instanceof Array) {
|
388
|
+
var retStatus = true;
|
389
|
+
for (var i = 0; i < combo.length; i++) {
|
390
|
+
retStatus = _del(combo[i]) && retStatus;
|
391
|
+
}
|
392
|
+
return retStatus;
|
393
|
+
} else {
|
394
|
+
var index = scope.hotkeys.indexOf(_get(combo));
|
395
|
+
|
396
|
+
if (index > -1) {
|
397
|
+
// if the combo has other combos bound, don't unbind the whole thing, just the one combo:
|
398
|
+
if (scope.hotkeys[index].combo.length > 1) {
|
399
|
+
scope.hotkeys[index].combo.splice(scope.hotkeys[index].combo.indexOf(combo), 1);
|
400
|
+
} else {
|
401
|
+
scope.hotkeys.splice(index, 1);
|
402
|
+
}
|
403
|
+
return true;
|
404
|
+
}
|
405
|
+
}
|
406
|
+
|
407
|
+
return false;
|
408
|
+
|
409
|
+
}
|
410
|
+
|
411
|
+
/**
|
412
|
+
* Get a Hotkey object by key binding
|
413
|
+
*
|
414
|
+
* @param {[string]} combo the key the Hotkey is bound to
|
415
|
+
* @return {Hotkey} The Hotkey object
|
416
|
+
*/
|
417
|
+
function _get (combo) {
|
418
|
+
|
419
|
+
var hotkey;
|
420
|
+
|
421
|
+
for (var i = 0; i < scope.hotkeys.length; i++) {
|
422
|
+
hotkey = scope.hotkeys[i];
|
423
|
+
|
424
|
+
if (hotkey.combo.indexOf(combo) > -1) {
|
425
|
+
return hotkey;
|
426
|
+
}
|
427
|
+
}
|
428
|
+
|
429
|
+
return false;
|
430
|
+
}
|
431
|
+
|
432
|
+
/**
|
433
|
+
* Binds the hotkey to a particular scope. Useful if the scope is
|
434
|
+
* destroyed, we can automatically destroy the hotkey binding.
|
435
|
+
*
|
436
|
+
* @param {Object} scope The scope to bind to
|
437
|
+
*/
|
438
|
+
function bindTo (scope) {
|
439
|
+
// Add the scope to the list of bound scopes
|
440
|
+
boundScopes[scope.$id] = [];
|
441
|
+
|
442
|
+
scope.$on('$destroy', function () {
|
443
|
+
var i = boundScopes[scope.$id].length;
|
444
|
+
while (i--) {
|
445
|
+
_del(boundScopes[scope.$id][i]);
|
446
|
+
delete boundScopes[scope.$id][i];
|
447
|
+
}
|
448
|
+
});
|
449
|
+
|
450
|
+
// return an object with an add function so we can keep track of the
|
451
|
+
// hotkeys and their scope that we added via this chaining method
|
452
|
+
return {
|
453
|
+
add: function (args) {
|
454
|
+
var hotkey;
|
455
|
+
|
456
|
+
if (arguments.length > 1) {
|
457
|
+
hotkey = _add.apply(this, arguments);
|
458
|
+
} else {
|
459
|
+
hotkey = _add(args);
|
460
|
+
}
|
461
|
+
|
462
|
+
boundScopes[scope.$id].push(hotkey);
|
463
|
+
return this;
|
464
|
+
}
|
465
|
+
};
|
466
|
+
}
|
467
|
+
|
468
|
+
/**
|
469
|
+
* All callbacks sent to Mousetrap are wrapped using this function
|
470
|
+
* so that we can force a $scope.$apply()
|
471
|
+
*
|
472
|
+
* @param {Function} callback [description]
|
473
|
+
* @return {[type]} [description]
|
474
|
+
*/
|
475
|
+
function wrapApply (callback) {
|
476
|
+
// return mousetrap a function to call
|
477
|
+
return function (event, combo) {
|
478
|
+
|
479
|
+
// if this is an array, it means we provided a route object
|
480
|
+
// because the scope wasn't available yet, so rewrap the callback
|
481
|
+
// now that the scope is available:
|
482
|
+
if (callback instanceof Array) {
|
483
|
+
var funcString = callback[0];
|
484
|
+
var route = callback[1];
|
485
|
+
callback = function (event) {
|
486
|
+
route.scope.$eval(funcString);
|
487
|
+
};
|
488
|
+
}
|
489
|
+
|
490
|
+
// this takes place outside angular, so we'll have to call
|
491
|
+
// $apply() to make sure angular's digest happens
|
492
|
+
$rootScope.$apply(function() {
|
493
|
+
// call the original hotkey callback with the keyboard event
|
494
|
+
callback(event, _get(combo));
|
495
|
+
});
|
496
|
+
};
|
497
|
+
}
|
498
|
+
|
499
|
+
|
500
|
+
var publicApi = {
|
501
|
+
add : _add,
|
502
|
+
del : _del,
|
503
|
+
get : _get,
|
504
|
+
bindTo : bindTo,
|
505
|
+
template : this.template,
|
506
|
+
toggleCheatSheet : toggleCheatSheet,
|
507
|
+
includeCheatSheat : this.includeCheatSheat,
|
508
|
+
cheatSheetHotkey : this.cheatSheetHotkey,
|
509
|
+
cheatSheetDescription : this.cheatSheetDescription,
|
510
|
+
purgeHotkeys : purgeHotkeys
|
511
|
+
};
|
512
|
+
|
513
|
+
return publicApi;
|
514
|
+
|
515
|
+
}];
|
516
|
+
})
|
517
|
+
|
518
|
+
.directive('hotkey', function (hotkeys) {
|
519
|
+
return {
|
520
|
+
restrict: 'A',
|
521
|
+
link: function (scope, el, attrs) {
|
522
|
+
var key, allowIn;
|
523
|
+
|
524
|
+
angular.forEach(scope.$eval(attrs.hotkey), function (func, hotkey) {
|
525
|
+
// split and trim the hotkeys string into array
|
526
|
+
allowIn = attrs.hotkeyAllowIn.split(/[\s,]+/);
|
527
|
+
|
528
|
+
key = hotkey;
|
529
|
+
|
530
|
+
hotkeys.add({
|
531
|
+
combo: hotkey,
|
532
|
+
description: attrs.hotkeyDescription,
|
533
|
+
callback: func,
|
534
|
+
action: attrs.hotkeyAction,
|
535
|
+
allowIn: allowIn
|
536
|
+
});
|
537
|
+
});
|
538
|
+
|
539
|
+
// remove the hotkey if the directive is destroyed:
|
540
|
+
el.bind('$destroy', function() {
|
541
|
+
hotkeys.del(key);
|
542
|
+
});
|
543
|
+
}
|
544
|
+
};
|
545
|
+
})
|
546
|
+
|
547
|
+
.run(function(hotkeys) {
|
548
|
+
// force hotkeys to run by injecting it. Without this, hotkeys only runs
|
549
|
+
// when a controller or something else asks for it via DI.
|
550
|
+
});
|
551
|
+
|
552
|
+
})();
|
@@ -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: 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
|
+
}
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: angular-hotkeys-rails
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Anton Shemerey
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-07-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: railties
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: mousetrap-rails
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.6'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.6'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: Asset Pipline wrapper around "keyboard shortcuts for your Angular apps."
|
70
|
+
angular-hotkeys
|
71
|
+
email:
|
72
|
+
- shemerey@gmail.com
|
73
|
+
executables: []
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- LICENSE
|
78
|
+
- README.md
|
79
|
+
- lib/angular/hotkeys/rails.rb
|
80
|
+
- lib/angular/hotkeys/rails/version.rb
|
81
|
+
- vendor/assets/javascripts/angular/hotkeys.js
|
82
|
+
- vendor/assets/stylesheets/angular/hotkeys.css
|
83
|
+
homepage: https://github.com/shemerey/angular-hotkeys-rails
|
84
|
+
licenses:
|
85
|
+
- MIT
|
86
|
+
metadata: {}
|
87
|
+
post_install_message:
|
88
|
+
rdoc_options: []
|
89
|
+
require_paths:
|
90
|
+
- lib
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - '>='
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - '>='
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
requirements: []
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 2.2.2
|
104
|
+
signing_key:
|
105
|
+
specification_version: 4
|
106
|
+
summary: angular-hotkeys Asset Pipline wrapper
|
107
|
+
test_files: []
|