parade 0.8.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 (106) hide show
  1. data/LICENSE +21 -0
  2. data/README.md +542 -0
  3. data/Rakefile +15 -0
  4. data/bin/parade +138 -0
  5. data/lib/parade.rb +43 -0
  6. data/lib/parade/commands/commands.rb +84 -0
  7. data/lib/parade/commands/generate_outline.rb +34 -0
  8. data/lib/parade/commands/generate_presentation.rb +34 -0
  9. data/lib/parade/commands/generate_rackup.rb +32 -0
  10. data/lib/parade/commands/html_output.rb +47 -0
  11. data/lib/parade/commands/render_from_template.rb +50 -0
  12. data/lib/parade/commands/static_html.rb +38 -0
  13. data/lib/parade/commands/static_pdf.rb +39 -0
  14. data/lib/parade/commands/unknown.rb +23 -0
  15. data/lib/parade/features/live_ruby.rb +18 -0
  16. data/lib/parade/features/pdf_presentation.rb +24 -0
  17. data/lib/parade/features/preshow.rb +11 -0
  18. data/lib/parade/helpers/encode_image.rb +24 -0
  19. data/lib/parade/helpers/template_generator.rb +130 -0
  20. data/lib/parade/metadata.rb +73 -0
  21. data/lib/parade/metadata/assignment.rb +38 -0
  22. data/lib/parade/metadata/css_classes.rb +22 -0
  23. data/lib/parade/metadata/html_id.rb +35 -0
  24. data/lib/parade/metadata/template.rb +31 -0
  25. data/lib/parade/parsers/dsl.rb +138 -0
  26. data/lib/parade/parsers/dsl_file_parser.rb +17 -0
  27. data/lib/parade/parsers/json_file_parser.rb +67 -0
  28. data/lib/parade/parsers/markdown_image_paths.rb +44 -0
  29. data/lib/parade/parsers/markdown_slide_splitter.rb +63 -0
  30. data/lib/parade/parsers/presentation_directory_parser.rb +36 -0
  31. data/lib/parade/parsers/presentation_file_parser.rb +27 -0
  32. data/lib/parade/parsers/presentation_filepath_parser.rb +35 -0
  33. data/lib/parade/parsers/slides_file_content_parser.rb +27 -0
  34. data/lib/parade/renderers/columns_renderer.rb +68 -0
  35. data/lib/parade/renderers/command_line_renderer.rb +142 -0
  36. data/lib/parade/renderers/html_with_pygments.rb +42 -0
  37. data/lib/parade/renderers/inline_images.rb +31 -0
  38. data/lib/parade/renderers/special_paragraph_renderer.rb +23 -0
  39. data/lib/parade/renderers/update_image_paths.rb +75 -0
  40. data/lib/parade/section.rb +183 -0
  41. data/lib/parade/server.rb +139 -0
  42. data/lib/parade/slide.rb +128 -0
  43. data/lib/parade/version.rb +3 -0
  44. data/lib/public/css/960.css +653 -0
  45. data/lib/public/css/fg.menu.css +114 -0
  46. data/lib/public/css/ghf_marked.css +180 -0
  47. data/lib/public/css/jquery-terminal.css +73 -0
  48. data/lib/public/css/onepage.css +62 -0
  49. data/lib/public/css/parade.css +450 -0
  50. data/lib/public/css/pdf.css +13 -0
  51. data/lib/public/css/reset.css +53 -0
  52. data/lib/public/css/spinner_bar.gif +0 -0
  53. data/lib/public/css/theme/images/ui-bg_diagonals-small_100_f0efea_40x40.png +0 -0
  54. data/lib/public/css/theme/images/ui-bg_flat_35_f0f0f0_40x100.png +0 -0
  55. data/lib/public/css/theme/images/ui-bg_glass_55_fcf0ba_1x400.png +0 -0
  56. data/lib/public/css/theme/images/ui-bg_glow-ball_25_2e2e28_600x600.png +0 -0
  57. data/lib/public/css/theme/images/ui-bg_highlight-soft_100_f0efea_1x100.png +0 -0
  58. data/lib/public/css/theme/images/ui-bg_highlight-soft_25_327E04_1x100.png +0 -0
  59. data/lib/public/css/theme/images/ui-bg_highlight-soft_25_5A9D1A_1x100.png +0 -0
  60. data/lib/public/css/theme/images/ui-bg_highlight-soft_95_ffedad_1x100.png +0 -0
  61. data/lib/public/css/theme/images/ui-bg_inset-soft_22_3b3b35_1x100.png +0 -0
  62. data/lib/public/css/theme/images/ui-icons_808080_256x240.png +0 -0
  63. data/lib/public/css/theme/images/ui-icons_8DC262_256x240.png +0 -0
  64. data/lib/public/css/theme/images/ui-icons_cd0a0a_256x240.png +0 -0
  65. data/lib/public/css/theme/images/ui-icons_e7e6e4_256x240.png +0 -0
  66. data/lib/public/css/theme/images/ui-icons_eeeeee_256x240.png +0 -0
  67. data/lib/public/css/theme/images/ui-icons_ffffff_256x240.png +0 -0
  68. data/lib/public/css/theme/ui.accordion.css +9 -0
  69. data/lib/public/css/theme/ui.all.css +2 -0
  70. data/lib/public/css/theme/ui.base.css +9 -0
  71. data/lib/public/css/theme/ui.core.css +37 -0
  72. data/lib/public/css/theme/ui.datepicker.css +62 -0
  73. data/lib/public/css/theme/ui.dialog.css +13 -0
  74. data/lib/public/css/theme/ui.progressbar.css +4 -0
  75. data/lib/public/css/theme/ui.resizable.css +13 -0
  76. data/lib/public/css/theme/ui.slider.css +17 -0
  77. data/lib/public/css/theme/ui.tabs.css +9 -0
  78. data/lib/public/css/theme/ui.theme.css +245 -0
  79. data/lib/public/favicon.ico +0 -0
  80. data/lib/public/js/coffee-script.js +8 -0
  81. data/lib/public/js/fg.menu.js +645 -0
  82. data/lib/public/js/jTypeWriter.js +26 -0
  83. data/lib/public/js/jquery-1.4.2.js +6240 -0
  84. data/lib/public/js/jquery-print.js +109 -0
  85. data/lib/public/js/jquery-pubsub.js +27 -0
  86. data/lib/public/js/jquery-terminal.js +2712 -0
  87. data/lib/public/js/jquery.batchImageLoad.js +56 -0
  88. data/lib/public/js/jquery.cycle.all.js +1284 -0
  89. data/lib/public/js/keyboard.js +733 -0
  90. data/lib/public/js/parade-code-execution.js +122 -0
  91. data/lib/public/js/parade-command-input.js +16 -0
  92. data/lib/public/js/parade-command-visor.js +92 -0
  93. data/lib/public/js/parade-keyboard-input.js +54 -0
  94. data/lib/public/js/parade.js +675 -0
  95. data/lib/public/js/spine.js +904 -0
  96. data/lib/templates/config.ru.erb +4 -0
  97. data/lib/templates/showoff.erb +27 -0
  98. data/lib/templates/slides.md.erb +25 -0
  99. data/lib/views/header.erb +73 -0
  100. data/lib/views/index.erb +53 -0
  101. data/lib/views/inline_css.erb +3 -0
  102. data/lib/views/inline_js.erb +3 -0
  103. data/lib/views/onepage.erb +17 -0
  104. data/lib/views/pdf.erb +17 -0
  105. data/lib/views/slide.erb +5 -0
  106. metadata +317 -0
@@ -0,0 +1,733 @@
1
+ /**
2
+ * Title: KeyboardJS
3
+ * Version: v0.3.0
4
+ * Description: KeyboardJS is a flexible and easy to use keyboard binding library.
5
+ * Author: Robert Hurst.
6
+ *
7
+ * Copyright 2011, Robert William Hurst
8
+ * Licenced under the BSD License.
9
+ * See https://raw.github.com/RobertWHurst/KeyboardJS/master/license.txt
10
+ */
11
+ (function(context, factory) {
12
+ var namespaces = [], previousValues = {}, library;
13
+ if(typeof define === 'function' && define.amd) {
14
+ define(function() { return factory('amd'); });
15
+ } else {
16
+ library = factory('global');
17
+ library.noConflict = function( ) {
18
+ var args, nI;
19
+ newNamespaces = Array.prototype.slice.apply(arguments);
20
+ for(nI = 0; nI < namespaces.length; nI += 1) {
21
+ if(typeof previousValues[namespaces[nI]] === 'undefined') {
22
+ delete context[namespaces[nI]];
23
+ } else {
24
+ context[namespaces[nI]] = previousValues[namespaces[nI]];
25
+ }
26
+ }
27
+ previousValues = {};
28
+ for(nI = 0; nI < newNamespaces.length; nI += 1) {
29
+ if(typeof newNamespaces[nI] !== 'string') { throw new Error('Cannot replace namespaces. All new namespaces must be strings.'); }
30
+ previousValues[newNamespaces[nI]] = context[newNamespaces[nI]];
31
+ context[newNamespaces[nI]] = library;
32
+ }
33
+ namespaces = newNamespaces;
34
+ return namespaces;
35
+ };
36
+ library.noConflict('KeyboardJS', 'k');
37
+ }
38
+ })(this, function(env) {
39
+ var KeyboardJS = {}, locales, locale, map, macros, activeKeys = [], bindings = [], activeBindings = [], activeMacros = [];
40
+
41
+ //INDEXOF POLLYFILL
42
+ [].indexOf||(Array.prototype.indexOf=function(a,b,c){for(c=this.length,b=(c+~~b)%c;b<c&&(!(b in this)||this[b]!==a);b++);return b^c?b:-1;});
43
+
44
+ //BUNDLED LOCALES
45
+ locales = {
46
+ 'us': {
47
+ "map": {
48
+
49
+ //general
50
+ "3": ["cancel"],
51
+ "8": ["backspace"],
52
+ "9": ["tab"],
53
+ "12": ["clear"],
54
+ "13": ["enter"],
55
+ "16": ["shift"],
56
+ "17": ["ctrl"],
57
+ "18": ["alt", "menu"],
58
+ "19": ["pause", "break"],
59
+ "20": ["capslock"],
60
+ "27": ["escape", "esc"],
61
+ "32": ["space", "spacebar"],
62
+ "33": ["pageup"],
63
+ "34": ["pagedown"],
64
+ "35": ["end"],
65
+ "36": ["home"],
66
+ "37": ["left"],
67
+ "38": ["up"],
68
+ "39": ["right"],
69
+ "40": ["down"],
70
+ "41": ["select"],
71
+ "42": ["printscreen"],
72
+ "43": ["execute"],
73
+ "44": ["snapshot"],
74
+ "45": ["insert", "ins"],
75
+ "46": ["delete", "del"],
76
+ "47": ["help"],
77
+ "91": ["command", "windows", "win", "super", "leftcommand", "leftwindows", "leftwin", "leftsuper"],
78
+ "92": ["command", "windows", "win", "super", "rightcommand", "rightwindows", "rightwin", "rightsuper"],
79
+ "145": ["scrolllock", "scroll"],
80
+ "186": ["semicolon", ";"],
81
+ "187": ["equal", "equalsign", "="],
82
+ "188": ["comma", ","],
83
+ "189": ["dash", "-"],
84
+ "190": ["period", "."],
85
+ "191": ["slash", "forwardslash", "/"],
86
+ "192": ["graveaccent", "`"],
87
+ "219": ["openbracket", "["],
88
+ "220": ["backslash", "\\"],
89
+ "221": ["closebracket", "]"],
90
+ "222": ["apostrophe", "'"],
91
+
92
+ //a-z
93
+ "65": ["a"],
94
+ "66": ["b"],
95
+ "67": ["c"],
96
+ "68": ["d"],
97
+ "69": ["e"],
98
+ "70": ["f"],
99
+ "71": ["g"],
100
+ "72": ["h"],
101
+ "73": ["i"],
102
+ "74": ["j"],
103
+ "75": ["k"],
104
+ "76": ["l"],
105
+ "77": ["m"],
106
+ "78": ["n"],
107
+ "79": ["o"],
108
+ "80": ["p"],
109
+ "81": ["q"],
110
+ "82": ["r"],
111
+ "83": ["s"],
112
+ "84": ["t"],
113
+ "85": ["u"],
114
+ "86": ["v"],
115
+ "87": ["w"],
116
+ "88": ["x"],
117
+ "89": ["y"],
118
+ "90": ["z"],
119
+
120
+ //0-9
121
+ "48": ["zero", "0"],
122
+ "49": ["one", "1"],
123
+ "50": ["two", "2"],
124
+ "51": ["three", "3"],
125
+ "52": ["four", "4"],
126
+ "53": ["five", "5"],
127
+ "54": ["six", "6"],
128
+ "55": ["seven", "7"],
129
+ "56": ["eight", "8"],
130
+ "57": ["nine", "9"],
131
+
132
+ //numpad
133
+ "96": ["numzero", "num0"],
134
+ "97": ["numone", "num1"],
135
+ "98": ["numtwo", "num2"],
136
+ "99": ["numthree", "num3"],
137
+ "100": ["numfour", "num4"],
138
+ "101": ["numfive", "num5"],
139
+ "102": ["numsix", "num6"],
140
+ "103": ["numseven", "num7"],
141
+ "104": ["numeight", "num8"],
142
+ "105": ["numnine", "num9"],
143
+ "106": ["nummultiply", "num*"],
144
+ "107": ["numadd", "num+"],
145
+ "108": ["numenter"],
146
+ "109": ["numsubtract", "num-"],
147
+ "110": ["numdecimal", "num."],
148
+ "111": ["numdevide", "num/"],
149
+ "144": ["numlock", "num"],
150
+
151
+ //function keys
152
+ "112": ["f1"],
153
+ "113": ["f2"],
154
+ "114": ["f3"],
155
+ "115": ["f4"],
156
+ "116": ["f5"],
157
+ "117": ["f6"],
158
+ "118": ["f7"],
159
+ "119": ["f8"],
160
+ "120": ["f9"],
161
+ "121": ["f10"],
162
+ "122": ["f11"],
163
+ "123": ["f12"]
164
+ },
165
+ "macros": [
166
+
167
+ //secondary key symbols
168
+ [[[["shift", "graveaccent"]]], ["tilde", "~"]],
169
+ [[[["shift", "one"]]], ["exclamation", "exclamationpoint", "!"]],
170
+ [[[["shift", "two"]]], ["at", "@"]],
171
+ [[[["shift", "three"]]], ["number", "#"]],
172
+ [[[["shift", "four"]]], ["dollar", "dollars", "dollarsign", "$"]],
173
+ [[[["shift", "five"]]], ["percent", "%"]],
174
+ [[[["shift", "six"]]], ["caret", "^"]],
175
+ [[[["shift", "seven"]]], ["ampersand", "and", "&"]],
176
+ [[[["shift", "eight"]]], ["asterisk", "*"]],
177
+ [[[["shift", "nine"]]], ["openparen", "("]],
178
+ [[[["shift", "zero"]]], ["closeparen", ")"]],
179
+ [[[["shift", "dash"]]], ["underscore", "_"]],
180
+ [[[["shift", "equal"]]], ["plus", "+"]],
181
+ [[[["shift", "openbracket"]]], ["opencurlybrace", "opencurlybracket", "{"]],
182
+ [[[["shift", "closebracket"]]], ["closecurlybrace", "closecurlybracket", "}"]],
183
+ [[[["shift", "backslash"]]], ["verticalbar", "|"]],
184
+ [[[["shift", "semicolon"]]], ["colon", ":"]],
185
+ [[[["shift", "apostrophe"]]], ["quotationmark", "\""]],
186
+ [[[["shift", "comma"]]], ["openanglebracket", "<"]],
187
+ [[[["shift", "period"]]], ["closeanglebracket", ">"]],
188
+ [[[["shift", "forwardslash"]]], ["questionmark", "?"]],
189
+
190
+ //capital A-Z
191
+ [[[["shift", "a"]]], ["A"]],
192
+ [[[["shift", "b"]]], ["B"]],
193
+ [[[["shift", "c"]]], ["C"]],
194
+ [[[["shift", "d"]]], ["D"]],
195
+ [[[["shift", "e"]]], ["E"]],
196
+ [[[["shift", "f"]]], ["F"]],
197
+ [[[["shift", "g"]]], ["G"]],
198
+ [[[["shift", "h"]]], ["H"]],
199
+ [[[["shift", "i"]]], ["I"]],
200
+ [[[["shift", "j"]]], ["J"]],
201
+ [[[["shift", "k"]]], ["K"]],
202
+ [[[["shift", "l"]]], ["L"]],
203
+ [[[["shift", "m"]]], ["M"]],
204
+ [[[["shift", "n"]]], ["N"]],
205
+ [[[["shift", "o"]]], ["O"]],
206
+ [[[["shift", "p"]]], ["P"]],
207
+ [[[["shift", "q"]]], ["Q"]],
208
+ [[[["shift", "r"]]], ["R"]],
209
+ [[[["shift", "s"]]], ["S"]],
210
+ [[[["shift", "t"]]], ["T"]],
211
+ [[[["shift", "u"]]], ["U"]],
212
+ [[[["shift", "v"]]], ["V"]],
213
+ [[[["shift", "w"]]], ["W"]],
214
+ [[[["shift", "x"]]], ["X"]],
215
+ [[[["shift", "y"]]], ["Y"]],
216
+ [[[["shift", "z"]]], ["Z"]]
217
+ ]
218
+ }
219
+
220
+ //If you create a new locale please submit it as a pull request or post it in the issue tracker at
221
+ // http://github.com/RobertWhurst/KeyboardJS/issues/
222
+ };
223
+ locale = 'us';
224
+ map = locales[locale].map;
225
+ macros = locales[locale].macros;
226
+ if(document.addEventListener) {
227
+ document.addEventListener('keydown', keydown, false);
228
+ document.addEventListener('keyup', keyup, false);
229
+ document.addEventListener('blur', blur, false);
230
+ } else if(document.attachEvent) {
231
+ document.attachEvent('onkeydown', keydown);
232
+ document.attachEvent('onkeyup', keyup);
233
+ document.attachEvent('onblur', blur);
234
+ } else {
235
+ throw new Error('Cannot bind to keydown event. Both addEventListener and attachEvent are unsupported by your browser.');
236
+ }
237
+
238
+ KeyboardJS.enable = function() {
239
+ KeyboardJS.enabled = true;
240
+ };
241
+
242
+ KeyboardJS.disable = function() {
243
+ KeyboardJS.enabled = false;
244
+ };
245
+
246
+ KeyboardJS.toggle = function() {
247
+ KeyboardJS.enabled = !KeyboardJS.enabled;
248
+ };
249
+
250
+ KeyboardJS.enabled = true;
251
+ KeyboardJS.activeKeys = getActiveKeys;
252
+ KeyboardJS.on = createBinding;
253
+ KeyboardJS.clear = removeBindingByKeyCombo;
254
+ KeyboardJS.clear.key = removeBindingByKeyName;
255
+ KeyboardJS.locale = getSetLocale;
256
+ KeyboardJS.locale.register = registerLocale;
257
+ KeyboardJS.macro = createMacro;
258
+ KeyboardJS.macro.remove = removeMacro;
259
+ KeyboardJS.getKey = getKeyName;
260
+ KeyboardJS.combo = {};
261
+ KeyboardJS.combo.parse = parseKeyCombo;
262
+ KeyboardJS.combo.stringify = stringifyKeyCombo;
263
+
264
+ window.map = map;
265
+ window.macros = macros;
266
+
267
+ return KeyboardJS;
268
+
269
+ function keydown(event) {
270
+ if (!KeyboardJS.enabled) { return; }
271
+
272
+ var keyNames, kI;
273
+ keyNames = getKeyName(event.keyCode);
274
+ if(keyNames.length < 1) { return; }
275
+ for(kI = 0; kI < keyNames.length; kI += 1) {
276
+ addActiveKey(keyNames[kI]);
277
+ }
278
+ executeMacros();
279
+ executeBindings(event);
280
+ }
281
+ function keyup(event) {
282
+ if (!KeyboardJS.enabled) { return; }
283
+
284
+ var keyNames, kI;
285
+ keyNames = getKeyName(event.keyCode);
286
+ if(keyNames.length < 1) { return; }
287
+ for(kI = 0; kI < keyNames.length; kI += 1) {
288
+ removeActiveKey(keyNames[kI]);
289
+ }
290
+ pruneMacros();
291
+ pruneBindings(event);
292
+ }
293
+ function blur(event) {
294
+ if (!KeyboardJS.enabled) { return; }
295
+
296
+ activeKeys = [];
297
+ pruneMacros();
298
+ pruneBindings(event);
299
+ }
300
+ function getKeyName(keyCode) {
301
+ return map[keyCode] || [];
302
+ }
303
+ function createMacro(combo, injectedKeys) {
304
+ if(typeof combo !== 'string' && (typeof combo !== 'object' || typeof combo.push !== 'function')) { throw new Error("Cannot create macro. The combo must be a string or array."); }
305
+ if(typeof injectedKeys !== 'object' || typeof injectedKeys.push !== 'function') { throw new Error("Cannot create macro. The injectedKeys must be an array."); }
306
+ marcos.push([combo, injectKeys]);
307
+ }
308
+ function removeMacro(combo) {
309
+ var macro;
310
+ if(typeof combo !== 'string' && (typeof combo !== 'object' || typeof combo.push !== 'function')) { throw new Error("Cannot remove macro. The combo must be a string or array."); }
311
+ for(mI = 0; mI < macros.length; mI += 1) {
312
+ macro = macros[mI];
313
+ if(compareCombos(combo, macro[0])) {
314
+ removeActiveKey(macro[1]);
315
+ macros.splice(mI, 1);
316
+ break;
317
+ }
318
+ }
319
+ }
320
+ function executeMacros() {
321
+ var mI, combo, kI;
322
+ for(mI = 0; mI < macros.length; mI += 1) {
323
+ combo = parseKeyCombo(macros[mI][0]);
324
+ if(activeMacros.indexOf(macros[mI]) === -1 && isSatifiedCombo(combo)) {
325
+ activeMacros.push(macros[mI]);
326
+ for(kI = 0; kI < macros[mI][1].length; kI += 1) {
327
+ addActiveKey(macros[mI][1][kI]);
328
+ }
329
+ }
330
+ }
331
+ }
332
+ function pruneMacros() {
333
+ var mI, combo, kI;
334
+ for(mI = 0; mI < activeMacros.length; mI += 1) {
335
+ combo = parseKeyCombo(activeMacros[mI][0]);
336
+ if(isSatifiedCombo(combo) === false) {
337
+ for(kI = 0; kI < activeMacros[mI][1].length; kI += 1) {
338
+ removeActiveKey(activeMacros[mI][1][kI]);
339
+ }
340
+ activeMacros.splice(mI, 1);
341
+ mI -= 1;
342
+ }
343
+ }
344
+ }
345
+ function createBinding(keyCombo, keyDownCallback, keyUpCallback) {
346
+ var binding, subBindings = [], bindingApi = {}, kI, subCombo;
347
+ if(typeof keyCombo === 'string') {
348
+ keyCombo = parseKeyCombo(keyCombo);
349
+ }
350
+ for(kI = 0; kI < keyCombo.length; kI += 1) {
351
+ binding = {};
352
+
353
+ subCombo = stringifyKeyCombo([keyCombo[kI]]);
354
+
355
+ if(typeof subCombo === 'function') {
356
+ subCombo = false;
357
+ keyUpCallback = keyDownCallback;
358
+ keyDownCallback = subCombo;
359
+ }
360
+
361
+ if(subCombo !== false && typeof subCombo !== 'string') { throw new Error('Failed to bind key combo. The key combo must be string.'); }
362
+ binding.keyCombo = subCombo;
363
+ binding.keyDownCallback = [];
364
+ binding.keyUpCallback = [];
365
+ if(keyDownCallback) { binding.keyDownCallback.push(keyDownCallback); }
366
+ if(keyUpCallback) { binding.keyUpCallback.push(keyUpCallback); }
367
+ bindings.push(binding);
368
+ subBindings.push(binding);
369
+ }
370
+
371
+ return {
372
+ "clear": clear,
373
+ "on": on
374
+ };
375
+
376
+ function clear() {
377
+ var bI;
378
+ for(bI = 0; bI < subBindings.length; bI += 1) {
379
+ bindings.splice(bindings.indexOf(subBindings[bI]), 1);
380
+ }
381
+ }
382
+
383
+ function on(eventName ) {
384
+ var callbacks, cI, bI;
385
+
386
+ if(typeof eventName !== 'string') { throw new Error('Cannot bind callback. The event name must be a string.'); }
387
+ if(eventName !== 'keyup' && eventName !== 'keydown') { throw new Error('Cannot bind callback. The event name must be a "keyup" or "keydown".'); }
388
+ callbacks = Array.prototype.slice.apply(arguments, [1]);
389
+ for(cI = 0; cI < callbacks.length; cI += 1) {
390
+ if(typeof callbacks[cI] === 'function') {
391
+ if(eventName === 'keyup') {
392
+ for(bI = 0; bI < subBindings.length; bI += 1) {
393
+ subBindings[bI].keyUpCallback.push(callbacks[cI]);
394
+ }
395
+ } else {
396
+ for(bI = 0; bI < subBindings.length; bI += 1) {
397
+ subBindings[bI].keyDownCallback.push(callbacks[cI]);
398
+ }
399
+ }
400
+ }
401
+ }
402
+
403
+ return { "clear": clear };
404
+
405
+ function clear() {
406
+ var cI, bI;
407
+ for(cI = 0; cI < callbacks.length; cI += 1) {
408
+ if(typeof callbacks[cI] === 'function') {
409
+ if(eventName === 'keyup') {
410
+ for(bI = 0; bI < subBindings.length; bI += 1) {
411
+ subBindings[bI].keyUpCallback.splice(subBindings[bI].keyUpCallback.indexOf(callbacks[cI]), 1);
412
+ }
413
+ } else {
414
+ for(bI = 0; bI < subBindings.length; bI += 1) {
415
+ subBindings[bI].keyDownCallback.splice(subBindings[bI].keyDownCallback.indexOf(callbacks[cI]), 1);
416
+ }
417
+ }
418
+ }
419
+ }
420
+ }
421
+ }
422
+ }
423
+ function removeBindingByKeyCombo(keyCombo) {
424
+ var bI, binding, keyName;
425
+ for(bI = 0; bI < bindings.length; bI += 1) {
426
+ binding = bindings[bi];
427
+ if(compareCombos(keyCombo, binding.keyCombo)) {
428
+ bindings.splice(bI, 1); bI -= 1;
429
+ }
430
+ }
431
+ }
432
+ function removeBindingByKeyName(keyName) {
433
+ var bI, cI, binding;
434
+ for(bI = 0; bI < bindings.length; bI += 1) {
435
+ binding = bindings[bi];
436
+ for(cI = 0; cI < binding.keyCombo.length; cI += 1) {
437
+ if(binding.keyCombo[kI].indexOf(keyName) > -1) {
438
+ bindings.splice(bI, 1); bI -= 1;
439
+ break;
440
+ }
441
+ }
442
+ }
443
+ }
444
+
445
+ /**
446
+ * Executes bindings that are active. Only allows the keys to be used once as to prevent binding overlap.
447
+ * @param {KeyboardEvent} event The keyboard event.
448
+ */
449
+ function executeBindings(event) {
450
+ var bI, sBI, binding, bidningKeys, remainingKeys, cI, killEventBubble, kI, bindingKeysSatified,
451
+ index, sortedBindings = [], bindingWeight;
452
+
453
+ remainingKeys = [].concat(activeKeys);
454
+ for(bI = 0; bI < bindings.length; bI += 1) {
455
+ bindingWeight = extractComboKeys(bindings[bI].keyCombo).length;
456
+ if(!sortedBindings[bindingWeight]) { sortedBindings[bindingWeight] = []; }
457
+ sortedBindings[bindingWeight].push(bindings[bI]);
458
+ }
459
+ for(sBI = sortedBindings.length - 1; sBI >= 0; sBI -= 1) {
460
+ if(!sortedBindings[sBI]) { continue; }
461
+ for(bI = 0; bI < sortedBindings[sBI].length; bI += 1) {
462
+ binding = sortedBindings[sBI][bI];
463
+ bindingKeys = extractComboKeys(binding.keyCombo);
464
+ bindingKeysSatified = true;
465
+ for(kI = 0; kI < bindingKeys.length; kI += 1) {
466
+ if(remainingKeys.indexOf(bindingKeys[kI]) === -1) {
467
+ bindingKeysSatified = false;
468
+ break;
469
+ }
470
+ }
471
+ if(bindingKeysSatified && isSatifiedCombo(binding.keyCombo)) {
472
+ activeBindings.push(binding);
473
+ for(kI = 0; kI < bindingKeys.length; kI += 1) {
474
+ index = remainingKeys.indexOf(bindingKeys[kI]);
475
+ if(index > -1) {
476
+ remainingKeys.splice(index, 1);
477
+ kI -= 1;
478
+ }
479
+ }
480
+ for(cI = 0; cI < binding.keyDownCallback.length; cI += 1) {
481
+ if (binding.keyDownCallback[cI](event) === false) {
482
+ killEventBubble = true;
483
+ }
484
+ }
485
+ if(killEventBubble === true) {
486
+ event.preventDefault();
487
+ event.stopPropagation();
488
+ }
489
+ }
490
+ }
491
+ }
492
+ }
493
+
494
+ /**
495
+ * Removes bindings that are no longer satisfied by the active keys. Also fires the keyup callbacks.
496
+ * @param {KeyboardEvent} event [description]
497
+ */
498
+ function pruneBindings(event) {
499
+ var bI, cI, binding, killEventBubble;
500
+ for(bI = 0; bI < activeBindings.length; bI += 1) {
501
+ binding = activeBindings[bI];
502
+ if(isSatifiedCombo(binding.keyCombo) === false) {
503
+ for(cI = 0; cI < binding.keyUpCallback.length; cI += 1) {
504
+ if (binding.keyUpCallback[cI](event) === false) {
505
+ killEventBubble = true;
506
+ }
507
+ }
508
+ if(killEventBubble === true) {
509
+ event.preventDefault();
510
+ event.stopPropagation();
511
+ }
512
+ activeBindings.splice(bI, 1);
513
+ bI -= 1;
514
+ }
515
+ }
516
+ }
517
+
518
+ /**
519
+ * Compares two key combos returning true when they are functionally equivalent.
520
+ * @param {String|Array} keyComboArrayA keyCombo A key combo string or array.
521
+ * @param {String|Array} keyComboArrayB keyCombo A key combo string or array.
522
+ * @return {Boolean}
523
+ */
524
+ function compareCombos(keyComboArrayA, keyComboArrayB) {
525
+ var cI, sI, kI;
526
+ keyComboArrayA = parseKeyCombo(keyComboArrayA);
527
+ keyComboArrayB = parseKeyCombo(keyComboArrayB);
528
+ if(keyComboArrayA.length !== keyComboArrayB.length) { return false; }
529
+ for(cI = 0; cI < keyComboArrayA.length; cI += 1) {
530
+ if(keyComboArrayA[aI].length !== keyComboArrayB[aI].length) { return false; }
531
+ for(sI = 0; sI < keyComboArrayA[aI].length; sI += 1) {
532
+ if(keyComboArrayA[aI][sI].length !== keyComboArrayB[aI][sI].length) { return false; }
533
+ for(kI = 0; kI < keyComboArrayA[aI][sI].length; kI += 1) {
534
+ if(keyComboArrayB[aI][sI].indexOf(keyComboArrayA[aI][sI][kI]) === -1) { return false; }
535
+ }
536
+ }
537
+ }
538
+ return true;
539
+ }
540
+
541
+ /**
542
+ * Checks to see if a key combo string or key array is satisfied by the currently active keys. It does not
543
+ * take into account spent keys.
544
+ * @param {String|Array} keyCombo A key combo string or array.
545
+ * @return {Boolean}
546
+ */
547
+ function isSatifiedCombo(keyCombo) {
548
+ var cI, sI, stage, kI, stageOffset = 0, index;
549
+ keyCombo = parseKeyCombo(keyCombo);
550
+ for(cI = 0; cI < keyCombo.length; cI += 1) {
551
+ for(sI = 0; sI < keyCombo[cI].length; sI += 1) {
552
+ stage = [].concat(keyCombo[cI][sI]);
553
+ for(kI = stageOffset; kI < activeKeys.length; kI += 1) {
554
+ index = stage.indexOf(activeKeys[kI]);
555
+ if(index > -1) {
556
+ stage.splice(index, 1);
557
+ stageOffset = kI;
558
+ }
559
+ }
560
+ if(stage.length !== 0) { return false; }
561
+ }
562
+ }
563
+ return true;
564
+ }
565
+
566
+ /**
567
+ * Accepts a key combo array or string and returns a flat array containing all keys referenced by
568
+ * the key combo.
569
+ * @param {String|Array} keyCombo A key combo string or array.
570
+ * @return {Array}
571
+ */
572
+ function extractComboKeys(keyCombo) {
573
+ var cI, sI, kI, keys = [];
574
+ keyCombo = parseKeyCombo(keyCombo);
575
+ for(cI = 0; cI < keyCombo.length; cI += 1) {
576
+ for(sI = 0; sI < keyCombo[cI].length; sI += 1) {
577
+ keys = keys.concat(keyCombo[cI][sI]);
578
+ }
579
+ }
580
+ return keys;
581
+ }
582
+
583
+ /**
584
+ * Converts a key combo string into a 3 dimensional array.
585
+ * Level 1 represents each combo as combo strings can contain more than one.
586
+ * Level 2 represents each stage. A stages are sub combos that must be satisfied in the order they are defined.
587
+ * Level 3 represents each key that must be pressed to satisfy a stage.
588
+ * @param {String} keyCombo A key combo string.
589
+ * @return {Array}
590
+ */
591
+ function parseKeyCombo(keyCombo) {
592
+ var s = keyCombo, i = 0, op = 0, ws = false, nc = false, combos = [], combo = [], stage = [], key = '';
593
+
594
+ if(typeof keyCombo === 'object' && typeof keyCombo.push === 'function') { return keyCombo; }
595
+ if(typeof keyCombo !== 'string') { throw new Error('Cannot parse "keyCombo" because its type is "' + (typeof keyCombo) + '". It must be a "string".'); }
596
+
597
+ //remove leading whitespace
598
+ while(s.charAt(i) === ' ') { i += 1; }
599
+ while(true) {
600
+ if(s.charAt(i) === ' ') {
601
+ //white space & next combo op
602
+ while(s.charAt(i) === ' ') { i += 1; }
603
+ ws = true;
604
+ } else if(s.charAt(i) === ',') {
605
+ if(op || nc) { throw new Error('Failed to parse key combo. Unexpected , at character index ' + i + '.'); }
606
+ nc = true;
607
+ i += 1;
608
+ } else if(s.charAt(i) === '+') {
609
+ //next key
610
+ if(key.length) { stage.push(key); key = ''; }
611
+ if(op || nc) { throw new Error('Failed to parse key combo. Unexpected + at character index ' + i + '.'); }
612
+ op = true;
613
+ i += 1;
614
+ } else if(s.charAt(i) === '>') {
615
+ //next stage op
616
+ if(key.length) { stage.push(key); key = ''; }
617
+ if(stage.length) { combo.push(stage); stage = []; }
618
+ if(op || nc) { throw new Error('Failed to parse key combo. Unexpected > at character index ' + i + '.'); }
619
+ op = true;
620
+ i += 1;
621
+ } else if(i < s.length - 1 && s.charAt(i) === '!' && (s.charAt(i + 1) === '>' || s.charAt(i + 1) === ',' || s.charAt(i + 1) === '+')) {
622
+ key += s.charAt(i + 1);
623
+ op = false;
624
+ ws = false;
625
+ nc = false;
626
+ i += 2;
627
+ } else if(i < s.length && s.charAt(i) !== '+' && s.charAt(i) !== '>' && s.charAt(i) !== ',' && s.charAt(i) !== ' ') {
628
+ //end combo
629
+ if(op === false && ws === true || nc === true) {
630
+ if(key.length) { stage.push(key); key = ''; }
631
+ if(stage.length) { combo.push(stage); stage = []; }
632
+ if(combo.length) { combos.push(combo); combo = []; }
633
+ }
634
+ op = false;
635
+ ws = false;
636
+ nc = false;
637
+ //key
638
+ while(i < s.length && s.charAt(i) !== '+' && s.charAt(i) !== '>' && s.charAt(i) !== ',' && s.charAt(i) !== ' ') {
639
+ key += s.charAt(i);
640
+ i += 1;
641
+ }
642
+ } else {
643
+ //unknown char
644
+ i += 1;
645
+ continue;
646
+ }
647
+ //end of combos string
648
+ if(i >= s.length) {
649
+ if(key.length) { stage.push(key); key = ''; }
650
+ if(stage.length) { combo.push(stage); stage = []; }
651
+ if(combo.length) { combos.push(combo); combo = []; }
652
+ break;
653
+ }
654
+ }
655
+ return combos;
656
+ }
657
+
658
+ /**
659
+ * Stringifys a key combo.
660
+ * @param {Array|String} keyComboArray A key combo array. If a key combo string is given it will be returned.
661
+ * @return {String}
662
+ */
663
+ function stringifyKeyCombo(keyComboArray) {
664
+ var cI, ccI, output = [];
665
+ if(typeof keyComboArray === 'string') { return keyComboArray; }
666
+ if(typeof keyComboArray !== 'object' || typeof keyComboArray.push !== 'function') { throw new Error('Cannot stringify key combo.'); }
667
+ for(cI = 0; cI < keyComboArray.length; cI += 1) {
668
+ output[cI] = [];
669
+ for(ccI = 0; ccI < keyComboArray[cI].length; ccI += 1) {
670
+ output[cI][ccI] = keyComboArray[cI][ccI].join(' + ');
671
+ }
672
+ output[cI] = output[cI].join(' > ');
673
+ }
674
+ return output.join(' ');
675
+ }
676
+
677
+ /**
678
+ * Returns the a copy of the active keys array.
679
+ * @return {Array}
680
+ */
681
+ function getActiveKeys() {
682
+ return [].concat(activeKeys);
683
+ }
684
+
685
+ /**
686
+ * Adds a key to the active keys array, but only if it has not already been added.
687
+ * @param {String} keyName The key name string.
688
+ */
689
+ function addActiveKey(keyName) {
690
+ if(keyName.match(/\s/)) { throw new Error('Cannot add key name ' + keyName + ' to active keys because it contains whitespace.'); }
691
+ if(activeKeys.indexOf(keyName) > -1) { return; }
692
+ activeKeys.push(keyName);
693
+ }
694
+
695
+ /**
696
+ * Removes a key from the active keys array.
697
+ * @param {String} keyName The key name string.
698
+ */
699
+ function removeActiveKey(keyName) {
700
+ if(keyName === 'super') { activeKeys = []; } //remove all key on release of super.
701
+ activeKeys.splice(activeKeys.indexOf(keyName), 1);
702
+ }
703
+
704
+ /**
705
+ * Registers a new locale. This is useful if you would like to add support for a new keyboard layout. It could also be useful for
706
+ * alternative key names. For example if you program games you could create a locale for your key mappings. Instead of key 65 mapped
707
+ * to 'a' you could map it to 'jump'.
708
+ * @param {String} localeName The name of the new locale.
709
+ * @param {Object} localeMap The locale map.
710
+ */
711
+ function registerLocale(localeName, localeMap) {
712
+ if(typeof localeName !== 'string') { throw new Error('Cannot register new locale. The locale name must be a string.'); }
713
+ if(typeof localeMap !== 'object') { throw new Error('Cannot register ' + localeName + ' locale. The locale map must be an object.'); }
714
+ if(typeof localeMap.keys !== 'object' || typeof localeMap.keys.push !== 'function' || typeof localeMap.map !== 'object' || typeof localeMap.map !== 'function') { throw new Error('Cannot register ' + localeName + ' locale. The locale map is invalid.'); }
715
+ locales[localeName] = localeMap;
716
+ }
717
+
718
+ /**
719
+ * Swaps the current locale.
720
+ * @param {String} localeName The locale to activate.
721
+ * @return {Object}
722
+ */
723
+ function getSetLocale(localeName) {
724
+ if(!localeName) {
725
+ if(typeof localeName !== 'string') { throw new Error('Cannot set locale. The locale name must be a string.'); }
726
+ if(!locales[localeName]) { throw new Error('Cannot set locale to ' + localeName + ' because it does not exist. If you would like to submit a ' + localeName + ' locale map for KeyboardJS please submit it at https://github.com/RobertWHurst/KeyboardJS/issues.'); }
727
+ locale = localeName;
728
+ map = locales[locale].map;
729
+ macros = locales[locale].macros;
730
+ }
731
+ return locale;
732
+ }
733
+ });