repla 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/lib/applescript/{load_html.scpt → load.scpt} +0 -0
  3. data/lib/applescript/{load_html_with_base_url.scpt → load_with_root_access_directory.scpt} +0 -0
  4. data/lib/repla/dependencies/html/index.html +25 -0
  5. data/lib/repla/dependencies/lib/view.rb +5 -5
  6. data/lib/repla/dependencies.rb +1 -2
  7. data/lib/repla/lib/constants.rb +0 -2
  8. data/lib/repla/lib/module.rb +1 -24
  9. data/lib/repla/lib/view/javascript.rb +7 -1
  10. data/lib/repla/lib/view/resources.rb +0 -38
  11. data/lib/repla/lib/view.rb +0 -1
  12. data/lib/repla/lib/window.rb +10 -15
  13. data/lib/repla/logger/test/Rakefile +7 -9
  14. data/lib/repla/logger/test/lib/test_setup.rb +10 -0
  15. data/lib/repla/logger/test/lib/test_view_helper.rb +1 -1
  16. data/lib/repla/logger/test/run_tests.sh +3 -1
  17. data/lib/repla/logger/test/tc_logger.rb +18 -21
  18. data/lib/repla/logger.rb +1 -2
  19. data/lib/repla/repl/html/index.html +22 -0
  20. data/lib/repla/repl/lib/view.rb +5 -50
  21. data/lib/repla/repl.rb +1 -2
  22. data/lib/repla/resources/css/raster.css +131 -0
  23. data/lib/repla/resources/js/bullets/Gruntfile.js +30 -0
  24. data/lib/repla/resources/js/bullets/README.md +3 -0
  25. data/lib/repla/resources/js/bullets/bullets.js +82 -0
  26. data/lib/repla/resources/js/bullets/example/css/bullets.css +6 -0
  27. data/lib/repla/resources/js/bullets/example/css/raster.css +131 -0
  28. data/lib/repla/resources/js/bullets/example/css/style.css +7 -0
  29. data/lib/repla/resources/js/bullets/example/index.html +137 -0
  30. data/lib/repla/resources/js/bullets/example/jade/content.jade +10 -0
  31. data/lib/repla/resources/js/bullets/example/jade/css.jade +3 -0
  32. data/lib/repla/resources/js/bullets/example/jade/index.jade +10 -0
  33. data/lib/repla/resources/js/bullets/example/jade/javascript.jade +5 -0
  34. data/lib/repla/resources/js/bullets/example/js/handlebars.js +2278 -0
  35. data/lib/repla/resources/js/bullets/example/js/jquery.js +9789 -0
  36. data/lib/repla/resources/js/bullets/example/js/mousetrap.js +910 -0
  37. data/lib/repla/resources/js/bullets/example/js/mousetrap_config.js +14 -0
  38. data/lib/repla/resources/js/bullets/example/js/zepto.js +1565 -0
  39. data/lib/repla/resources/js/bullets/package.json +28 -0
  40. data/lib/repla/resources/js/bullets/test/lib/css/mocha.css +270 -0
  41. data/lib/repla/resources/js/bullets/test/lib/index.html +153 -0
  42. data/lib/repla/resources/js/bullets/test/lib/jade/index.jade +13 -0
  43. data/lib/repla/resources/js/bullets/test/lib/jade/mocha.html +15 -0
  44. data/lib/repla/resources/js/bullets/test/lib/js/chai.js +4613 -0
  45. data/lib/repla/resources/js/bullets/test/lib/js/mocha.js +5726 -0
  46. data/lib/repla/resources/js/bullets/test/lib/js/test_helper.js +26 -0
  47. data/lib/repla/resources/js/bullets/test/run_tests.rb +7 -0
  48. data/lib/repla/resources/js/bullets/test/test.js +131 -0
  49. data/lib/repla/resources/js/handlebars.js +2278 -0
  50. data/lib/repla/resources/js/mousetrap.js +910 -0
  51. data/lib/repla/resources/js/zepto.js +1565 -0
  52. data/lib/repla/test/applescript/cancel_dialog.applescript +2 -0
  53. data/lib/repla/test/applescript/confirm_dialog.applescript +2 -0
  54. data/lib/repla/test/applescript/is_running.applescript +5 -0
  55. data/lib/repla/test/applescript/quit.applescript +3 -0
  56. data/lib/repla/test/applescript/set_window_bounds.applescript +23 -0
  57. data/lib/repla/test/applescript/switch_windows.applescript +2 -0
  58. data/lib/repla/test/applescript/window_bounds.applescript +20 -0
  59. data/lib/repla/test/applescript/window_id.applescript +7 -0
  60. data/lib/repla/test/bundles/Cat.wcplugin/Contents/Info.plist +14 -0
  61. data/lib/repla/test/bundles/Cat.wcplugin/Contents/Resources/cat.sh +3 -0
  62. data/lib/repla/test/bundles/HelloWorld.wcplugin/Contents/Info.plist +14 -0
  63. data/lib/repla/test/bundles/HelloWorld.wcplugin/Contents/Resources/hello_world.rb +3 -0
  64. data/lib/repla/test/bundles/Invalid.wcplugin/Contents/Info.plist +16 -0
  65. data/lib/repla/test/bundles/Print.wcplugin/Contents/Info.plist +16 -0
  66. data/lib/repla/test/bundles/Print.wcplugin/Contents/Resources/html/css/style.css +7 -0
  67. data/lib/repla/test/bundles/Print.wcplugin/Contents/Resources/html/index.html +24 -0
  68. data/lib/repla/test/bundles/Print.wcplugin/Contents/Resources/html/js/wcprint.js +8 -0
  69. data/lib/repla/test/bundles/Print.wcplugin/Contents/Resources/lib/controller.rb +18 -0
  70. data/lib/repla/test/bundles/Print.wcplugin/Contents/Resources/lib/view.rb +13 -0
  71. data/lib/repla/test/bundles/Print.wcplugin/Contents/Resources/print.rb +10 -0
  72. data/lib/repla/test/bundles/TestEnvironment.wcplugin/Contents/Info.plist +14 -0
  73. data/lib/repla/test/bundles/TestEnvironment.wcplugin/Contents/Resources/constants.rb +1 -0
  74. data/lib/repla/test/bundles/TestEnvironment.wcplugin/Contents/Resources/test_environment.rb +35 -0
  75. data/lib/repla/test/bundles/TestLog.wcplugin/Contents/Info.plist +16 -0
  76. data/lib/repla/test/bundles/TestLog.wcplugin/Contents/Resources/test_log.rb +13 -0
  77. data/lib/repla/test/html/index.html +36 -0
  78. data/lib/repla/test/html/indexjquery.html +37 -0
  79. data/lib/repla/test/html/js/zepto.js +1565 -0
  80. data/lib/repla/test/js/firstcode.js +3 -0
  81. data/lib/repla/test/js/lastcode.js +3 -0
  82. data/lib/repla/test/js/nodom.js +5 -0
  83. data/lib/repla/test/js/text.js +1 -0
  84. data/lib/repla/test/js/textjquery.js +1 -0
  85. data/lib/repla/test/js/title.js +1 -0
  86. data/lib/repla/test/lib/helper.rb +121 -0
  87. data/lib/repla/test.rb +51 -0
  88. data/lib/repla.rb +6 -6
  89. metadata +78 -14
  90. data/lib/repla/dependencies/views/view.html.erb +0 -25
  91. data/lib/repla/extension_constants.rb +0 -1
  92. data/lib/repla/lib/view/erb.rb +0 -17
  93. data/lib/repla/logger/test/lib/test_constants.rb +0 -8
  94. data/lib/repla/repl/view/view.html.erb +0 -12
  95. /data/lib/repla/dependencies/{css → html/css}/style.css +0 -0
  96. /data/lib/repla/dependencies/{js → html/js}/wcdependencies.js +0 -0
  97. /data/lib/repla/repl/{css → html/css}/style.css +0 -0
  98. /data/lib/repla/repl/{js → html/js}/wcrepl.js +0 -0
@@ -0,0 +1,910 @@
1
+ /*global define:false */
2
+ /**
3
+ * Copyright 2013 Craig Campbell
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ *
17
+ * Mousetrap is a simple keyboard shortcut library for Javascript with
18
+ * no external dependencies
19
+ *
20
+ * @version 1.4.1
21
+ * @url craig.is/killing/mice
22
+ */
23
+ (function() {
24
+
25
+ /**
26
+ * mapping of special keycodes to their corresponding keys
27
+ *
28
+ * everything in this dictionary cannot use keypress events
29
+ * so it has to be here to map to the correct keycodes for
30
+ * keyup/keydown events
31
+ *
32
+ * @type {Object}
33
+ */
34
+ var _MAP = {
35
+ 8: 'backspace',
36
+ 9: 'tab',
37
+ 13: 'enter',
38
+ 16: 'shift',
39
+ 17: 'ctrl',
40
+ 18: 'alt',
41
+ 20: 'capslock',
42
+ 27: 'esc',
43
+ 32: 'space',
44
+ 33: 'pageup',
45
+ 34: 'pagedown',
46
+ 35: 'end',
47
+ 36: 'home',
48
+ 37: 'left',
49
+ 38: 'up',
50
+ 39: 'right',
51
+ 40: 'down',
52
+ 45: 'ins',
53
+ 46: 'del',
54
+ 91: 'meta',
55
+ 93: 'meta',
56
+ 224: 'meta'
57
+ },
58
+
59
+ /**
60
+ * mapping for special characters so they can support
61
+ *
62
+ * this dictionary is only used incase you want to bind a
63
+ * keyup or keydown event to one of these keys
64
+ *
65
+ * @type {Object}
66
+ */
67
+ _KEYCODE_MAP = {
68
+ 106: '*',
69
+ 107: '+',
70
+ 109: '-',
71
+ 110: '.',
72
+ 111 : '/',
73
+ 186: ';',
74
+ 187: '=',
75
+ 188: ',',
76
+ 189: '-',
77
+ 190: '.',
78
+ 191: '/',
79
+ 192: '`',
80
+ 219: '[',
81
+ 220: '\\',
82
+ 221: ']',
83
+ 222: '\''
84
+ },
85
+
86
+ /**
87
+ * this is a mapping of keys that require shift on a US keypad
88
+ * back to the non shift equivelents
89
+ *
90
+ * this is so you can use keyup events with these keys
91
+ *
92
+ * note that this will only work reliably on US keyboards
93
+ *
94
+ * @type {Object}
95
+ */
96
+ _SHIFT_MAP = {
97
+ '~': '`',
98
+ '!': '1',
99
+ '@': '2',
100
+ '#': '3',
101
+ '$': '4',
102
+ '%': '5',
103
+ '^': '6',
104
+ '&': '7',
105
+ '*': '8',
106
+ '(': '9',
107
+ ')': '0',
108
+ '_': '-',
109
+ '+': '=',
110
+ ':': ';',
111
+ '\"': '\'',
112
+ '<': ',',
113
+ '>': '.',
114
+ '?': '/',
115
+ '|': '\\'
116
+ },
117
+
118
+ /**
119
+ * this is a list of special strings you can use to map
120
+ * to modifier keys when you specify your keyboard shortcuts
121
+ *
122
+ * @type {Object}
123
+ */
124
+ _SPECIAL_ALIASES = {
125
+ 'option': 'alt',
126
+ 'command': 'meta',
127
+ 'return': 'enter',
128
+ 'escape': 'esc',
129
+ 'mod': /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? 'meta' : 'ctrl'
130
+ },
131
+
132
+ /**
133
+ * variable to store the flipped version of _MAP from above
134
+ * needed to check if we should use keypress or not when no action
135
+ * is specified
136
+ *
137
+ * @type {Object|undefined}
138
+ */
139
+ _REVERSE_MAP,
140
+
141
+ /**
142
+ * a list of all the callbacks setup via Mousetrap.bind()
143
+ *
144
+ * @type {Object}
145
+ */
146
+ _callbacks = {},
147
+
148
+ /**
149
+ * direct map of string combinations to callbacks used for trigger()
150
+ *
151
+ * @type {Object}
152
+ */
153
+ _directMap = {},
154
+
155
+ /**
156
+ * keeps track of what level each sequence is at since multiple
157
+ * sequences can start out with the same sequence
158
+ *
159
+ * @type {Object}
160
+ */
161
+ _sequenceLevels = {},
162
+
163
+ /**
164
+ * variable to store the setTimeout call
165
+ *
166
+ * @type {null|number}
167
+ */
168
+ _resetTimer,
169
+
170
+ /**
171
+ * temporary state where we will ignore the next keyup
172
+ *
173
+ * @type {boolean|string}
174
+ */
175
+ _ignoreNextKeyup = false,
176
+
177
+ /**
178
+ * are we currently inside of a sequence?
179
+ * type of action ("keyup" or "keydown" or "keypress") or false
180
+ *
181
+ * @type {boolean|string}
182
+ */
183
+ _nextExpectedAction = false;
184
+
185
+ /**
186
+ * loop through the f keys, f1 to f19 and add them to the map
187
+ * programatically
188
+ */
189
+ for (var i = 1; i < 20; ++i) {
190
+ _MAP[111 + i] = 'f' + i;
191
+ }
192
+
193
+ /**
194
+ * loop through to map numbers on the numeric keypad
195
+ */
196
+ for (i = 0; i <= 9; ++i) {
197
+ _MAP[i + 96] = i;
198
+ }
199
+
200
+ /**
201
+ * cross browser add event method
202
+ *
203
+ * @param {Element|HTMLDocument} object
204
+ * @param {string} type
205
+ * @param {Function} callback
206
+ * @returns void
207
+ */
208
+ function _addEvent(object, type, callback) {
209
+ if (object.addEventListener) {
210
+ object.addEventListener(type, callback, false);
211
+ return;
212
+ }
213
+
214
+ object.attachEvent('on' + type, callback);
215
+ }
216
+
217
+ /**
218
+ * takes the event and returns the key character
219
+ *
220
+ * @param {Event} e
221
+ * @return {string}
222
+ */
223
+ function _characterFromEvent(e) {
224
+
225
+ // for keypress events we should return the character as is
226
+ if (e.type == 'keypress') {
227
+ var character = String.fromCharCode(e.which);
228
+
229
+ // if the shift key is not pressed then it is safe to assume
230
+ // that we want the character to be lowercase. this means if
231
+ // you accidentally have caps lock on then your key bindings
232
+ // will continue to work
233
+ //
234
+ // the only side effect that might not be desired is if you
235
+ // bind something like 'A' cause you want to trigger an
236
+ // event when capital A is pressed caps lock will no longer
237
+ // trigger the event. shift+a will though.
238
+ if (!e.shiftKey) {
239
+ character = character.toLowerCase();
240
+ }
241
+
242
+ return character;
243
+ }
244
+
245
+ // for non keypress events the special maps are needed
246
+ if (_MAP[e.which]) {
247
+ return _MAP[e.which];
248
+ }
249
+
250
+ if (_KEYCODE_MAP[e.which]) {
251
+ return _KEYCODE_MAP[e.which];
252
+ }
253
+
254
+ // if it is not in the special map
255
+
256
+ // with keydown and keyup events the character seems to always
257
+ // come in as an uppercase character whether you are pressing shift
258
+ // or not. we should make sure it is always lowercase for comparisons
259
+ return String.fromCharCode(e.which).toLowerCase();
260
+ }
261
+
262
+ /**
263
+ * checks if two arrays are equal
264
+ *
265
+ * @param {Array} modifiers1
266
+ * @param {Array} modifiers2
267
+ * @returns {boolean}
268
+ */
269
+ function _modifiersMatch(modifiers1, modifiers2) {
270
+ return modifiers1.sort().join(',') === modifiers2.sort().join(',');
271
+ }
272
+
273
+ /**
274
+ * resets all sequence counters except for the ones passed in
275
+ *
276
+ * @param {Object} doNotReset
277
+ * @returns void
278
+ */
279
+ function _resetSequences(doNotReset) {
280
+ doNotReset = doNotReset || {};
281
+
282
+ var activeSequences = false,
283
+ key;
284
+
285
+ for (key in _sequenceLevels) {
286
+ if (doNotReset[key]) {
287
+ activeSequences = true;
288
+ continue;
289
+ }
290
+ _sequenceLevels[key] = 0;
291
+ }
292
+
293
+ if (!activeSequences) {
294
+ _nextExpectedAction = false;
295
+ }
296
+ }
297
+
298
+ /**
299
+ * finds all callbacks that match based on the keycode, modifiers,
300
+ * and action
301
+ *
302
+ * @param {string} character
303
+ * @param {Array} modifiers
304
+ * @param {Event|Object} e
305
+ * @param {string=} sequenceName - name of the sequence we are looking for
306
+ * @param {string=} combination
307
+ * @param {number=} level
308
+ * @returns {Array}
309
+ */
310
+ function _getMatches(character, modifiers, e, sequenceName, combination, level) {
311
+ var i,
312
+ callback,
313
+ matches = [],
314
+ action = e.type;
315
+
316
+ // if there are no events related to this keycode
317
+ if (!_callbacks[character]) {
318
+ return [];
319
+ }
320
+
321
+ // if a modifier key is coming up on its own we should allow it
322
+ if (action == 'keyup' && _isModifier(character)) {
323
+ modifiers = [character];
324
+ }
325
+
326
+ // loop through all callbacks for the key that was pressed
327
+ // and see if any of them match
328
+ for (i = 0; i < _callbacks[character].length; ++i) {
329
+ callback = _callbacks[character][i];
330
+
331
+ // if a sequence name is not specified, but this is a sequence at
332
+ // the wrong level then move onto the next match
333
+ if (!sequenceName && callback.seq && _sequenceLevels[callback.seq] != callback.level) {
334
+ continue;
335
+ }
336
+
337
+ // if the action we are looking for doesn't match the action we got
338
+ // then we should keep going
339
+ if (action != callback.action) {
340
+ continue;
341
+ }
342
+
343
+ // if this is a keypress event and the meta key and control key
344
+ // are not pressed that means that we need to only look at the
345
+ // character, otherwise check the modifiers as well
346
+ //
347
+ // chrome will not fire a keypress if meta or control is down
348
+ // safari will fire a keypress if meta or meta+shift is down
349
+ // firefox will fire a keypress if meta or control is down
350
+ if ((action == 'keypress' && !e.metaKey && !e.ctrlKey) || _modifiersMatch(modifiers, callback.modifiers)) {
351
+
352
+ // when you bind a combination or sequence a second time it
353
+ // should overwrite the first one. if a sequenceName or
354
+ // combination is specified in this call it does just that
355
+ //
356
+ // @todo make deleting its own method?
357
+ var deleteCombo = !sequenceName && callback.combo == combination;
358
+ var deleteSequence = sequenceName && callback.seq == sequenceName && callback.level == level;
359
+ if (deleteCombo || deleteSequence) {
360
+ _callbacks[character].splice(i, 1);
361
+ }
362
+
363
+ matches.push(callback);
364
+ }
365
+ }
366
+
367
+ return matches;
368
+ }
369
+
370
+ /**
371
+ * takes a key event and figures out what the modifiers are
372
+ *
373
+ * @param {Event} e
374
+ * @returns {Array}
375
+ */
376
+ function _eventModifiers(e) {
377
+ var modifiers = [];
378
+
379
+ if (e.shiftKey) {
380
+ modifiers.push('shift');
381
+ }
382
+
383
+ if (e.altKey) {
384
+ modifiers.push('alt');
385
+ }
386
+
387
+ if (e.ctrlKey) {
388
+ modifiers.push('ctrl');
389
+ }
390
+
391
+ if (e.metaKey) {
392
+ modifiers.push('meta');
393
+ }
394
+
395
+ return modifiers;
396
+ }
397
+
398
+ /**
399
+ * actually calls the callback function
400
+ *
401
+ * if your callback function returns false this will use the jquery
402
+ * convention - prevent default and stop propogation on the event
403
+ *
404
+ * @param {Function} callback
405
+ * @param {Event} e
406
+ * @returns void
407
+ */
408
+ function _fireCallback(callback, e, combo) {
409
+
410
+ // if this event should not happen stop here
411
+ if (Mousetrap.stopCallback(e, e.target || e.srcElement, combo)) {
412
+ return;
413
+ }
414
+
415
+ if (callback(e, combo) === false) {
416
+ if (e.preventDefault) {
417
+ e.preventDefault();
418
+ }
419
+
420
+ if (e.stopPropagation) {
421
+ e.stopPropagation();
422
+ }
423
+
424
+ e.returnValue = false;
425
+ e.cancelBubble = true;
426
+ }
427
+ }
428
+
429
+ /**
430
+ * handles a character key event
431
+ *
432
+ * @param {string} character
433
+ * @param {Array} modifiers
434
+ * @param {Event} e
435
+ * @returns void
436
+ */
437
+ function _handleKey(character, modifiers, e) {
438
+ var callbacks = _getMatches(character, modifiers, e),
439
+ i,
440
+ doNotReset = {},
441
+ maxLevel = 0,
442
+ processedSequenceCallback = false;
443
+
444
+ // Calculate the maxLevel for sequences so we can only execute the longest callback sequence
445
+ for (i = 0; i < callbacks.length; ++i) {
446
+ if (callbacks[i].seq) {
447
+ maxLevel = Math.max(maxLevel, callbacks[i].level);
448
+ }
449
+ }
450
+
451
+ // loop through matching callbacks for this key event
452
+ for (i = 0; i < callbacks.length; ++i) {
453
+
454
+ // fire for all sequence callbacks
455
+ // this is because if for example you have multiple sequences
456
+ // bound such as "g i" and "g t" they both need to fire the
457
+ // callback for matching g cause otherwise you can only ever
458
+ // match the first one
459
+ if (callbacks[i].seq) {
460
+
461
+ // only fire callbacks for the maxLevel to prevent
462
+ // subsequences from also firing
463
+ //
464
+ // for example 'a option b' should not cause 'option b' to fire
465
+ // even though 'option b' is part of the other sequence
466
+ //
467
+ // any sequences that do not match here will be discarded
468
+ // below by the _resetSequences call
469
+ if (callbacks[i].level != maxLevel) {
470
+ continue;
471
+ }
472
+
473
+ processedSequenceCallback = true;
474
+
475
+ // keep a list of which sequences were matches for later
476
+ doNotReset[callbacks[i].seq] = 1;
477
+ _fireCallback(callbacks[i].callback, e, callbacks[i].combo);
478
+ continue;
479
+ }
480
+
481
+ // if there were no sequence matches but we are still here
482
+ // that means this is a regular match so we should fire that
483
+ if (!processedSequenceCallback) {
484
+ _fireCallback(callbacks[i].callback, e, callbacks[i].combo);
485
+ }
486
+ }
487
+
488
+ // if the key you pressed matches the type of sequence without
489
+ // being a modifier (ie "keyup" or "keypress") then we should
490
+ // reset all sequences that were not matched by this event
491
+ //
492
+ // this is so, for example, if you have the sequence "h a t" and you
493
+ // type "h e a r t" it does not match. in this case the "e" will
494
+ // cause the sequence to reset
495
+ //
496
+ // modifier keys are ignored because you can have a sequence
497
+ // that contains modifiers such as "enter ctrl+space" and in most
498
+ // cases the modifier key will be pressed before the next key
499
+ if (e.type == _nextExpectedAction && !_isModifier(character)) {
500
+ _resetSequences(doNotReset);
501
+ }
502
+ }
503
+
504
+ /**
505
+ * handles a keydown event
506
+ *
507
+ * @param {Event} e
508
+ * @returns void
509
+ */
510
+ function _handleKeyEvent(e) {
511
+
512
+ // normalize e.which for key events
513
+ // @see http://stackoverflow.com/questions/4285627/javascript-keycode-vs-charcode-utter-confusion
514
+ if (typeof e.which !== 'number') {
515
+ e.which = e.keyCode;
516
+ }
517
+
518
+ var character = _characterFromEvent(e);
519
+
520
+ // no character found then stop
521
+ if (!character) {
522
+ return;
523
+ }
524
+
525
+ if (e.type == 'keyup' && _ignoreNextKeyup == character) {
526
+ _ignoreNextKeyup = false;
527
+ return;
528
+ }
529
+
530
+ Mousetrap.handleKey(character, _eventModifiers(e), e);
531
+ }
532
+
533
+ /**
534
+ * determines if the keycode specified is a modifier key or not
535
+ *
536
+ * @param {string} key
537
+ * @returns {boolean}
538
+ */
539
+ function _isModifier(key) {
540
+ return key == 'shift' || key == 'ctrl' || key == 'alt' || key == 'meta';
541
+ }
542
+
543
+ /**
544
+ * called to set a 1 second timeout on the specified sequence
545
+ *
546
+ * this is so after each key press in the sequence you have 1 second
547
+ * to press the next key before you have to start over
548
+ *
549
+ * @returns void
550
+ */
551
+ function _resetSequenceTimer() {
552
+ clearTimeout(_resetTimer);
553
+ _resetTimer = setTimeout(_resetSequences, 1000);
554
+ }
555
+
556
+ /**
557
+ * reverses the map lookup so that we can look for specific keys
558
+ * to see what can and can't use keypress
559
+ *
560
+ * @return {Object}
561
+ */
562
+ function _getReverseMap() {
563
+ if (!_REVERSE_MAP) {
564
+ _REVERSE_MAP = {};
565
+ for (var key in _MAP) {
566
+
567
+ // pull out the numeric keypad from here cause keypress should
568
+ // be able to detect the keys from the character
569
+ if (key > 95 && key < 112) {
570
+ continue;
571
+ }
572
+
573
+ if (_MAP.hasOwnProperty(key)) {
574
+ _REVERSE_MAP[_MAP[key]] = key;
575
+ }
576
+ }
577
+ }
578
+ return _REVERSE_MAP;
579
+ }
580
+
581
+ /**
582
+ * picks the best action based on the key combination
583
+ *
584
+ * @param {string} key - character for key
585
+ * @param {Array} modifiers
586
+ * @param {string=} action passed in
587
+ */
588
+ function _pickBestAction(key, modifiers, action) {
589
+
590
+ // if no action was picked in we should try to pick the one
591
+ // that we think would work best for this key
592
+ if (!action) {
593
+ action = _getReverseMap()[key] ? 'keydown' : 'keypress';
594
+ }
595
+
596
+ // modifier keys don't work as expected with keypress,
597
+ // switch to keydown
598
+ if (action == 'keypress' && modifiers.length) {
599
+ action = 'keydown';
600
+ }
601
+
602
+ return action;
603
+ }
604
+
605
+ /**
606
+ * binds a key sequence to an event
607
+ *
608
+ * @param {string} combo - combo specified in bind call
609
+ * @param {Array} keys
610
+ * @param {Function} callback
611
+ * @param {string=} action
612
+ * @returns void
613
+ */
614
+ function _bindSequence(combo, keys, callback, action) {
615
+
616
+ // start off by adding a sequence level record for this combination
617
+ // and setting the level to 0
618
+ _sequenceLevels[combo] = 0;
619
+
620
+ /**
621
+ * callback to increase the sequence level for this sequence and reset
622
+ * all other sequences that were active
623
+ *
624
+ * @param {string} nextAction
625
+ * @returns {Function}
626
+ */
627
+ function _increaseSequence(nextAction) {
628
+ return function() {
629
+ _nextExpectedAction = nextAction;
630
+ ++_sequenceLevels[combo];
631
+ _resetSequenceTimer();
632
+ };
633
+ }
634
+
635
+ /**
636
+ * wraps the specified callback inside of another function in order
637
+ * to reset all sequence counters as soon as this sequence is done
638
+ *
639
+ * @param {Event} e
640
+ * @returns void
641
+ */
642
+ function _callbackAndReset(e) {
643
+ _fireCallback(callback, e, combo);
644
+
645
+ // we should ignore the next key up if the action is key down
646
+ // or keypress. this is so if you finish a sequence and
647
+ // release the key the final key will not trigger a keyup
648
+ if (action !== 'keyup') {
649
+ _ignoreNextKeyup = _characterFromEvent(e);
650
+ }
651
+
652
+ // weird race condition if a sequence ends with the key
653
+ // another sequence begins with
654
+ setTimeout(_resetSequences, 10);
655
+ }
656
+
657
+ // loop through keys one at a time and bind the appropriate callback
658
+ // function. for any key leading up to the final one it should
659
+ // increase the sequence. after the final, it should reset all sequences
660
+ //
661
+ // if an action is specified in the original bind call then that will
662
+ // be used throughout. otherwise we will pass the action that the
663
+ // next key in the sequence should match. this allows a sequence
664
+ // to mix and match keypress and keydown events depending on which
665
+ // ones are better suited to the key provided
666
+ for (var i = 0; i < keys.length; ++i) {
667
+ var isFinal = i + 1 === keys.length;
668
+ var wrappedCallback = isFinal ? _callbackAndReset : _increaseSequence(action || _getKeyInfo(keys[i + 1]).action);
669
+ _bindSingle(keys[i], wrappedCallback, action, combo, i);
670
+ }
671
+ }
672
+
673
+ /**
674
+ * Converts from a string key combination to an array
675
+ *
676
+ * @param {string} combination like "command+shift+l"
677
+ * @return {Array}
678
+ */
679
+ function _keysFromString(combination) {
680
+ if (combination === '+') {
681
+ return ['+'];
682
+ }
683
+
684
+ return combination.split('+');
685
+ }
686
+
687
+ /**
688
+ * Gets info for a specific key combination
689
+ *
690
+ * @param {string} combination key combination ("command+s" or "a" or "*")
691
+ * @param {string=} action
692
+ * @returns {Object}
693
+ */
694
+ function _getKeyInfo(combination, action) {
695
+ var keys,
696
+ key,
697
+ i,
698
+ modifiers = [];
699
+
700
+ // take the keys from this pattern and figure out what the actual
701
+ // pattern is all about
702
+ keys = _keysFromString(combination);
703
+
704
+ for (i = 0; i < keys.length; ++i) {
705
+ key = keys[i];
706
+
707
+ // normalize key names
708
+ if (_SPECIAL_ALIASES[key]) {
709
+ key = _SPECIAL_ALIASES[key];
710
+ }
711
+
712
+ // if this is not a keypress event then we should
713
+ // be smart about using shift keys
714
+ // this will only work for US keyboards however
715
+ if (action && action != 'keypress' && _SHIFT_MAP[key]) {
716
+ key = _SHIFT_MAP[key];
717
+ modifiers.push('shift');
718
+ }
719
+
720
+ // if this key is a modifier then add it to the list of modifiers
721
+ if (_isModifier(key)) {
722
+ modifiers.push(key);
723
+ }
724
+ }
725
+
726
+ // depending on what the key combination is
727
+ // we will try to pick the best event for it
728
+ action = _pickBestAction(key, modifiers, action);
729
+
730
+ return {
731
+ key: key,
732
+ modifiers: modifiers,
733
+ action: action
734
+ };
735
+ }
736
+
737
+ /**
738
+ * binds a single keyboard combination
739
+ *
740
+ * @param {string} combination
741
+ * @param {Function} callback
742
+ * @param {string=} action
743
+ * @param {string=} sequenceName - name of sequence if part of sequence
744
+ * @param {number=} level - what part of the sequence the command is
745
+ * @returns void
746
+ */
747
+ function _bindSingle(combination, callback, action, sequenceName, level) {
748
+
749
+ // store a direct mapped reference for use with Mousetrap.trigger
750
+ _directMap[combination + ':' + action] = callback;
751
+
752
+ // make sure multiple spaces in a row become a single space
753
+ combination = combination.replace(/\s+/g, ' ');
754
+
755
+ var sequence = combination.split(' '),
756
+ info;
757
+
758
+ // if this pattern is a sequence of keys then run through this method
759
+ // to reprocess each pattern one key at a time
760
+ if (sequence.length > 1) {
761
+ _bindSequence(combination, sequence, callback, action);
762
+ return;
763
+ }
764
+
765
+ info = _getKeyInfo(combination, action);
766
+
767
+ // make sure to initialize array if this is the first time
768
+ // a callback is added for this key
769
+ _callbacks[info.key] = _callbacks[info.key] || [];
770
+
771
+ // remove an existing match if there is one
772
+ _getMatches(info.key, info.modifiers, {type: info.action}, sequenceName, combination, level);
773
+
774
+ // add this call back to the array
775
+ // if it is a sequence put it at the beginning
776
+ // if not put it at the end
777
+ //
778
+ // this is important because the way these are processed expects
779
+ // the sequence ones to come first
780
+ _callbacks[info.key][sequenceName ? 'unshift' : 'push']({
781
+ callback: callback,
782
+ modifiers: info.modifiers,
783
+ action: info.action,
784
+ seq: sequenceName,
785
+ level: level,
786
+ combo: combination
787
+ });
788
+ }
789
+
790
+ /**
791
+ * binds multiple combinations to the same callback
792
+ *
793
+ * @param {Array} combinations
794
+ * @param {Function} callback
795
+ * @param {string|undefined} action
796
+ * @returns void
797
+ */
798
+ function _bindMultiple(combinations, callback, action) {
799
+ for (var i = 0; i < combinations.length; ++i) {
800
+ _bindSingle(combinations[i], callback, action);
801
+ }
802
+ }
803
+
804
+ // start!
805
+ _addEvent(document, 'keypress', _handleKeyEvent);
806
+ _addEvent(document, 'keydown', _handleKeyEvent);
807
+ _addEvent(document, 'keyup', _handleKeyEvent);
808
+
809
+ var Mousetrap = {
810
+
811
+ /**
812
+ * binds an event to mousetrap
813
+ *
814
+ * can be a single key, a combination of keys separated with +,
815
+ * an array of keys, or a sequence of keys separated by spaces
816
+ *
817
+ * be sure to list the modifier keys first to make sure that the
818
+ * correct key ends up getting bound (the last key in the pattern)
819
+ *
820
+ * @param {string|Array} keys
821
+ * @param {Function} callback
822
+ * @param {string=} action - 'keypress', 'keydown', or 'keyup'
823
+ * @returns void
824
+ */
825
+ bind: function(keys, callback, action) {
826
+ keys = keys instanceof Array ? keys : [keys];
827
+ _bindMultiple(keys, callback, action);
828
+ return this;
829
+ },
830
+
831
+ /**
832
+ * unbinds an event to mousetrap
833
+ *
834
+ * the unbinding sets the callback function of the specified key combo
835
+ * to an empty function and deletes the corresponding key in the
836
+ * _directMap dict.
837
+ *
838
+ * TODO: actually remove this from the _callbacks dictionary instead
839
+ * of binding an empty function
840
+ *
841
+ * the keycombo+action has to be exactly the same as
842
+ * it was defined in the bind method
843
+ *
844
+ * @param {string|Array} keys
845
+ * @param {string} action
846
+ * @returns void
847
+ */
848
+ unbind: function(keys, action) {
849
+ return Mousetrap.bind(keys, function() {}, action);
850
+ },
851
+
852
+ /**
853
+ * triggers an event that has already been bound
854
+ *
855
+ * @param {string} keys
856
+ * @param {string=} action
857
+ * @returns void
858
+ */
859
+ trigger: function(keys, action) {
860
+ if (_directMap[keys + ':' + action]) {
861
+ _directMap[keys + ':' + action]({}, keys);
862
+ }
863
+ return this;
864
+ },
865
+
866
+ /**
867
+ * resets the library back to its initial state. this is useful
868
+ * if you want to clear out the current keyboard shortcuts and bind
869
+ * new ones - for example if you switch to another page
870
+ *
871
+ * @returns void
872
+ */
873
+ reset: function() {
874
+ _callbacks = {};
875
+ _directMap = {};
876
+ return this;
877
+ },
878
+
879
+ /**
880
+ * should we stop this event before firing off callbacks
881
+ *
882
+ * @param {Event} e
883
+ * @param {Element} element
884
+ * @return {boolean}
885
+ */
886
+ stopCallback: function(e, element) {
887
+
888
+ // if the element has the class "mousetrap" then no need to stop
889
+ if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) {
890
+ return false;
891
+ }
892
+
893
+ // stop for input, select, and textarea
894
+ return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || (element.contentEditable && element.contentEditable == 'true');
895
+ },
896
+
897
+ /**
898
+ * exposes _handleKey publicly so it can be overwritten by extensions
899
+ */
900
+ handleKey: _handleKey
901
+ };
902
+
903
+ // expose mousetrap to the global object
904
+ window.Mousetrap = Mousetrap;
905
+
906
+ // expose mousetrap as an AMD module
907
+ if (typeof define === 'function' && define.amd) {
908
+ define(Mousetrap);
909
+ }
910
+ }) ();