repla 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ }) ();