angular-hotkeys-rails 0.0.1
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 +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: []
|