diff --git a/public/js/keyboard.js b/public/js/keyboard.js deleted file mode 100644 index 5a518d7d..00000000 --- a/public/js/keyboard.js +++ /dev/null @@ -1,961 +0,0 @@ -/** - * Title: KeyboardJS - * Version: v0.4.1 - * Description: KeyboardJS is a flexible and easy to use keyboard binding - * library. - * Author: Robert Hurst. - * - * Copyright 2011, Robert William Hurst - * Licenced under the BSD License. - * See https://raw.github.com/RobertWHurst/KeyboardJS/master/license.txt - */ -(function(context, factory) { - - //INDEXOF POLLYFILL - [].indexOf||(Array.prototype.indexOf=function(a,b,c){for(c=this.length,b=(c+~~b)%c;b"]], - ['shift + /', ["questionmark", "?"]] - ] - }; - //a-z and A-Z - for (aI = 65; aI <= 90; aI += 1) { - usLocale.map[aI] = String.fromCharCode(aI + 32); - usLocale.macros.push(['shift + ' + String.fromCharCode(aI + 32) + ', capslock + ' + String.fromCharCode(aI + 32), [String.fromCharCode(aI)]]); - } - registerLocale('us', usLocale); - getSetLocale('us'); - - - ////////// - // INIT // - ////////// - - //enable the library - enable(); - - - ///////// - // API // - ///////// - - //assemble the library and return it - KeyboardJS.enable = enable; - KeyboardJS.disable = disable; - KeyboardJS.activeKeys = getActiveKeys; - KeyboardJS.releaseKey = removeActiveKey; - KeyboardJS.pressKey = addActiveKey; - KeyboardJS.on = createBinding; - KeyboardJS.clear = removeBindingByKeyCombo; - KeyboardJS.clear.key = removeBindingByKeyName; - KeyboardJS.locale = getSetLocale; - KeyboardJS.locale.register = registerLocale; - KeyboardJS.macro = createMacro; - KeyboardJS.macro.remove = removeMacro; - KeyboardJS.key = {}; - KeyboardJS.key.name = getKeyName; - KeyboardJS.key.code = getKeyCode; - KeyboardJS.combo = {}; - KeyboardJS.combo.active = isSatisfiedCombo; - KeyboardJS.combo.parse = parseKeyCombo; - KeyboardJS.combo.stringify = stringifyKeyCombo; - return KeyboardJS; - - - ////////////////////// - // INSTANCE METHODS // - ////////////////////// - - /** - * Enables KeyboardJS - */ - function enable() { - if(targetWindow.addEventListener) { - targetWindow.document.addEventListener('keydown', keydown, false); - targetWindow.document.addEventListener('keyup', keyup, false); - targetWindow.addEventListener('blur', reset, false); - targetWindow.addEventListener('webkitfullscreenchange', reset, false); - targetWindow.addEventListener('mozfullscreenchange', reset, false); - } else if(targetWindow.attachEvent) { - targetWindow.document.attachEvent('onkeydown', keydown); - targetWindow.document.attachEvent('onkeyup', keyup); - targetWindow.attachEvent('onblur', reset); - } - } - - /** - * Exits all active bindings and disables KeyboardJS - */ - function disable() { - reset(); - if(targetWindow.removeEventListener) { - targetWindow.document.removeEventListener('keydown', keydown, false); - targetWindow.document.removeEventListener('keyup', keyup, false); - targetWindow.removeEventListener('blur', reset, false); - targetWindow.removeEventListener('webkitfullscreenchange', reset, false); - targetWindow.removeEventListener('mozfullscreenchange', reset, false); - } else if(targetWindow.detachEvent) { - targetWindow.document.detachEvent('onkeydown', keydown); - targetWindow.document.detachEvent('onkeyup', keyup); - targetWindow.detachEvent('onblur', reset); - } - } - - - //////////////////// - // EVENT HANDLERS // - //////////////////// - - /** - * Exits all active bindings. Optionally passes an event to all binding - * handlers. - * @param {KeyboardEvent} event [Optional] - */ - function reset(event) { - activeKeys = []; - pruneMacros(); - pruneBindings(event); - } - - /** - * Key down event handler. - * @param {KeyboardEvent} event - */ - function keydown(event) { - var keyNames, keyName, kI; - keyNames = getKeyName(event.keyCode); - if(keyNames.length < 1) { return; } - event.isRepeat = false; - for(kI = 0; kI < keyNames.length; kI += 1) { - keyName = keyNames[kI]; - if (getActiveKeys().indexOf(keyName) != -1) - event.isRepeat = true; - addActiveKey(keyName); - } - executeMacros(); - executeBindings(event); - } - - /** - * Key up event handler. - * @param {KeyboardEvent} event - */ - function keyup(event) { - var keyNames, kI; - keyNames = getKeyName(event.keyCode); - if(keyNames.length < 1) { return; } - for(kI = 0; kI < keyNames.length; kI += 1) { - removeActiveKey(keyNames[kI]); - } - pruneMacros(); - pruneBindings(event); - } - - /** - * Accepts a key code and returns the key names defined by the current - * locale. - * @param {Number} keyCode - * @return {Array} keyNames An array of key names defined for the key - * code as defined by the current locale. - */ - function getKeyName(keyCode) { - return map[keyCode] || []; - } - - /** - * Accepts a key name and returns the key code defined by the current - * locale. - * @param {Number} keyName - * @return {Number|false} - */ - function getKeyCode(keyName) { - var keyCode; - for(keyCode in map) { - if(!map.hasOwnProperty(keyCode)) { continue; } - if(map[keyCode].indexOf(keyName) > -1) { return keyCode; } - } - return false; - } - - - //////////// - // MACROS // - //////////// - - /** - * Accepts a key combo and an array of key names to inject once the key - * combo is satisfied. - * @param {String} combo - * @param {Array} injectedKeys - */ - function createMacro(combo, injectedKeys) { - 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."); - } - if(typeof injectedKeys !== 'object' || typeof injectedKeys.push !== 'function') { - throw new Error("Cannot create macro. The injectedKeys must be an array."); - } - macros.push([combo, injectedKeys]); - } - - /** - * Accepts a key combo and clears any and all macros bound to that key - * combo. - * @param {String} combo - */ - function removeMacro(combo) { - var macro; - 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."); } - for(mI = 0; mI < macros.length; mI += 1) { - macro = macros[mI]; - if(compareCombos(combo, macro[0])) { - removeActiveKey(macro[1]); - macros.splice(mI, 1); - break; - } - } - } - - /** - * Executes macros against the active keys. Each macro's key combo is - * checked and if found to be satisfied, the macro's key names are injected - * into active keys. - */ - function executeMacros() { - var mI, combo, kI; - for(mI = 0; mI < macros.length; mI += 1) { - combo = parseKeyCombo(macros[mI][0]); - if(activeMacros.indexOf(macros[mI]) === -1 && isSatisfiedCombo(combo)) { - activeMacros.push(macros[mI]); - for(kI = 0; kI < macros[mI][1].length; kI += 1) { - addActiveKey(macros[mI][1][kI]); - } - } - } - } - - /** - * Prunes active macros. Checks each active macro's key combo and if found - * to no longer to be satisfied, each of the macro's key names are removed - * from active keys. - */ - function pruneMacros() { - var mI, combo, kI; - for(mI = 0; mI < activeMacros.length; mI += 1) { - combo = parseKeyCombo(activeMacros[mI][0]); - if(isSatisfiedCombo(combo) === false) { - for(kI = 0; kI < activeMacros[mI][1].length; kI += 1) { - removeActiveKey(activeMacros[mI][1][kI]); - } - activeMacros.splice(mI, 1); - mI -= 1; - } - } - } - - - ////////////// - // BINDINGS // - ////////////// - - /** - * Creates a binding object, and, if provided, binds a key down hander and - * a key up handler. Returns a binding object that emits keyup and - * keydown events. - * @param {String} keyCombo - * @param {Function} keyDownCallback [Optional] - * @param {Function} keyUpCallback [Optional] - * @return {Object} binding - */ - function createBinding(keyCombo, keyDownCallback, keyUpCallback) { - var api = {}, binding, subBindings = [], bindingApi = {}, kI, - subCombo; - - //break the combo down into a combo array - if(typeof keyCombo === 'string') { - keyCombo = parseKeyCombo(keyCombo); - } - - //bind each sub combo contained within the combo string - for(kI = 0; kI < keyCombo.length; kI += 1) { - binding = {}; - - //stringify the combo again - subCombo = stringifyKeyCombo([keyCombo[kI]]); - - //validate the sub combo - if(typeof subCombo !== 'string') { throw new Error('Failed to bind key combo. The key combo must be string.'); } - - //create the binding - binding.keyCombo = subCombo; - binding.keyDownCallback = []; - binding.keyUpCallback = []; - - //inject the key down and key up callbacks if given - if(keyDownCallback) { binding.keyDownCallback.push(keyDownCallback); } - if(keyUpCallback) { binding.keyUpCallback.push(keyUpCallback); } - - //stash the new binding - bindings.push(binding); - subBindings.push(binding); - } - - //build the binding api - api.clear = clear; - api.on = on; - return api; - - /** - * Clears the binding - */ - function clear() { - var bI; - for(bI = 0; bI < subBindings.length; bI += 1) { - bindings.splice(bindings.indexOf(subBindings[bI]), 1); - } - } - - /** - * Accepts an event name. and any number of callbacks. When the event is - * emitted, all callbacks are executed. Available events are key up and - * key down. - * @param {String} eventName - * @return {Object} subBinding - */ - function on(eventName ) { - var api = {}, callbacks, cI, bI; - - //validate event name - if(typeof eventName !== 'string') { throw new Error('Cannot bind callback. The event name must be a string.'); } - if(eventName !== 'keyup' && eventName !== 'keydown') { throw new Error('Cannot bind callback. The event name must be a "keyup" or "keydown".'); } - - //gather the callbacks - callbacks = Array.prototype.slice.apply(arguments, [1]); - - //stash each the new binding - for(cI = 0; cI < callbacks.length; cI += 1) { - if(typeof callbacks[cI] === 'function') { - if(eventName === 'keyup') { - for(bI = 0; bI < subBindings.length; bI += 1) { - subBindings[bI].keyUpCallback.push(callbacks[cI]); - } - } else if(eventName === 'keydown') { - for(bI = 0; bI < subBindings.length; bI += 1) { - subBindings[bI].keyDownCallback.push(callbacks[cI]); - } - } - } - } - - //construct and return the sub binding api - api.clear = clear; - return api; - - /** - * Clears the binding - */ - function clear() { - var cI, bI; - for(cI = 0; cI < callbacks.length; cI += 1) { - if(typeof callbacks[cI] === 'function') { - if(eventName === 'keyup') { - for(bI = 0; bI < subBindings.length; bI += 1) { - subBindings[bI].keyUpCallback.splice(subBindings[bI].keyUpCallback.indexOf(callbacks[cI]), 1); - } - } else { - for(bI = 0; bI < subBindings.length; bI += 1) { - subBindings[bI].keyDownCallback.splice(subBindings[bI].keyDownCallback.indexOf(callbacks[cI]), 1); - } - } - } - } - } - } - } - - /** - * Clears all binding attached to a given key combo. Key name order does not - * matter as long as the key combos equate. - * @param {String} keyCombo - */ - function removeBindingByKeyCombo(keyCombo) { - var bI, binding, keyName; - for(bI = 0; bI < bindings.length; bI += 1) { - binding = bindings[bI]; - if(compareCombos(keyCombo, binding.keyCombo)) { - bindings.splice(bI, 1); bI -= 1; - } - } - } - - /** - * Clears all binding attached to key combos containing a given key name. - * @param {String} keyName - */ - function removeBindingByKeyName(keyName) { - var bI, kI, binding; - if(keyName) { - for(bI = 0; bI < bindings.length; bI += 1) { - binding = bindings[bI]; - for(kI = 0; kI < binding.keyCombo.length; kI += 1) { - if(binding.keyCombo[kI].indexOf(keyName) > -1) { - bindings.splice(bI, 1); bI -= 1; - break; - } - } - } - } else { - bindings = []; - } - } - - /** - * Executes bindings that are active. Only allows the keys to be used once - * as to prevent binding overlap. - * @param {KeyboardEvent} event The keyboard event. - */ - function executeBindings(event) { - var bI, sBI, binding, bindingKeys, remainingKeys, cI, killEventBubble, kI, bindingKeysSatisfied, - index, sortedBindings = [], bindingWeight; - - remainingKeys = [].concat(activeKeys); - for(bI = 0; bI < bindings.length; bI += 1) { - bindingWeight = extractComboKeys(bindings[bI].keyCombo).length; - if(!sortedBindings[bindingWeight]) { sortedBindings[bindingWeight] = []; } - sortedBindings[bindingWeight].push(bindings[bI]); - } - for(sBI = sortedBindings.length - 1; sBI >= 0; sBI -= 1) { - if(!sortedBindings[sBI]) { continue; } - for(bI = 0; bI < sortedBindings[sBI].length; bI += 1) { - binding = sortedBindings[sBI][bI]; - bindingKeys = extractComboKeys(binding.keyCombo); - bindingKeysSatisfied = true; - for(kI = 0; kI < bindingKeys.length; kI += 1) { - if(remainingKeys.indexOf(bindingKeys[kI]) === -1) { - bindingKeysSatisfied = false; - break; - } - } - if(bindingKeysSatisfied && isSatisfiedCombo(binding.keyCombo)) { - activeBindings.push(binding); - for(kI = 0; kI < bindingKeys.length; kI += 1) { - index = remainingKeys.indexOf(bindingKeys[kI]); - if(index > -1) { - remainingKeys.splice(index, 1); - kI -= 1; - } - } - for(cI = 0; cI < binding.keyDownCallback.length; cI += 1) { - if (binding.keyDownCallback[cI](event, getActiveKeys(), binding.keyCombo) === false) { - killEventBubble = true; - } - } - if(killEventBubble === true) { - event.preventDefault(); - event.stopPropagation(); - } - } - } - } - } - - /** - * Removes bindings that are no longer satisfied by the active keys. Also - * fires the key up callbacks. - * @param {KeyboardEvent} event - */ - function pruneBindings(event) { - var bI, cI, binding, killEventBubble; - for(bI = 0; bI < activeBindings.length; bI += 1) { - binding = activeBindings[bI]; - if(isSatisfiedCombo(binding.keyCombo) === false) { - for(cI = 0; cI < binding.keyUpCallback.length; cI += 1) { - if (binding.keyUpCallback[cI](event, getActiveKeys(), binding.keyCombo) === false) { - killEventBubble = true; - } - } - if(killEventBubble === true) { - event.preventDefault(); - event.stopPropagation(); - } - activeBindings.splice(bI, 1); - bI -= 1; - } - } - } - - - /////////////////// - // COMBO STRINGS // - /////////////////// - - /** - * Compares two key combos returning true when they are functionally - * equivalent. - * @param {String} keyComboArrayA keyCombo A key combo string or array. - * @param {String} keyComboArrayB keyCombo A key combo string or array. - * @return {Boolean} - */ - function compareCombos(keyComboArrayA, keyComboArrayB) { - var cI, sI, kI; - keyComboArrayA = parseKeyCombo(keyComboArrayA); - keyComboArrayB = parseKeyCombo(keyComboArrayB); - if(keyComboArrayA.length !== keyComboArrayB.length) { return false; } - for(cI = 0; cI < keyComboArrayA.length; cI += 1) { - if(keyComboArrayA[cI].length !== keyComboArrayB[cI].length) { return false; } - for(sI = 0; sI < keyComboArrayA[cI].length; sI += 1) { - if(keyComboArrayA[cI][sI].length !== keyComboArrayB[cI][sI].length) { return false; } - for(kI = 0; kI < keyComboArrayA[cI][sI].length; kI += 1) { - if(keyComboArrayB[cI][sI].indexOf(keyComboArrayA[cI][sI][kI]) === -1) { return false; } - } - } - } - return true; - } - - /** - * Checks to see if a key combo string or key array is satisfied by the - * currently active keys. It does not take into account spent keys. - * @param {String} keyCombo A key combo string or array. - * @return {Boolean} - */ - function isSatisfiedCombo(keyCombo) { - var cI, sI, stage, kI, stageOffset = 0, index, comboMatches; - keyCombo = parseKeyCombo(keyCombo); - for(cI = 0; cI < keyCombo.length; cI += 1) { - comboMatches = true; - stageOffset = 0; - for(sI = 0; sI < keyCombo[cI].length; sI += 1) { - stage = [].concat(keyCombo[cI][sI]); - for(kI = stageOffset; kI < activeKeys.length; kI += 1) { - index = stage.indexOf(activeKeys[kI]); - if(index > -1) { - stage.splice(index, 1); - stageOffset = kI; - } - } - if(stage.length !== 0) { comboMatches = false; break; } - } - if(comboMatches) { return true; } - } - return false; - } - - /** - * Accepts a key combo array or string and returns a flat array containing all keys referenced by - * the key combo. - * @param {String} keyCombo A key combo string or array. - * @return {Array} - */ - function extractComboKeys(keyCombo) { - var cI, sI, kI, keys = []; - keyCombo = parseKeyCombo(keyCombo); - for(cI = 0; cI < keyCombo.length; cI += 1) { - for(sI = 0; sI < keyCombo[cI].length; sI += 1) { - keys = keys.concat(keyCombo[cI][sI]); - } - } - return keys; - } - - /** - * Parses a key combo string into a 3 dimensional array. - * - Level 1 - sub combos. - * - Level 2 - combo stages. A stage is a set of key name pairs that must - * be satisfied in the order they are defined. - * - Level 3 - each key name to the stage. - * @param {String|Array} keyCombo A key combo string. - * @return {Array} - */ - function parseKeyCombo(keyCombo) { - var s = keyCombo, i = 0, op = 0, ws = false, nc = false, combos = [], combo = [], stage = [], key = ''; - - if(typeof keyCombo === 'object' && typeof keyCombo.push === 'function') { return keyCombo; } - if(typeof keyCombo !== 'string') { throw new Error('Cannot parse "keyCombo" because its type is "' + (typeof keyCombo) + '". It must be a "string".'); } - - //remove leading whitespace - while(s.charAt(i) === ' ') { i += 1; } - while(true) { - if(s.charAt(i) === ' ') { - //white space & next combo op - while(s.charAt(i) === ' ') { i += 1; } - ws = true; - } else if(s.charAt(i) === ',') { - if(op || nc) { throw new Error('Failed to parse key combo. Unexpected , at character index ' + i + '.'); } - nc = true; - i += 1; - } else if(s.charAt(i) === '+') { - //next key - if(key.length) { stage.push(key); key = ''; } - if(op || nc) { throw new Error('Failed to parse key combo. Unexpected + at character index ' + i + '.'); } - op = true; - i += 1; - } else if(s.charAt(i) === '>') { - //next stage op - if(key.length) { stage.push(key); key = ''; } - if(stage.length) { combo.push(stage); stage = []; } - if(op || nc) { throw new Error('Failed to parse key combo. Unexpected > at character index ' + i + '.'); } - op = true; - i += 1; - } else if(i < s.length - 1 && s.charAt(i) === '!' && (s.charAt(i + 1) === '>' || s.charAt(i + 1) === ',' || s.charAt(i + 1) === '+')) { - key += s.charAt(i + 1); - op = false; - ws = false; - nc = false; - i += 2; - } else if(i < s.length && s.charAt(i) !== '+' && s.charAt(i) !== '>' && s.charAt(i) !== ',' && s.charAt(i) !== ' ') { - //end combo - if(op === false && ws === true || nc === true) { - if(key.length) { stage.push(key); key = ''; } - if(stage.length) { combo.push(stage); stage = []; } - if(combo.length) { combos.push(combo); combo = []; } - } - op = false; - ws = false; - nc = false; - //key - while(i < s.length && s.charAt(i) !== '+' && s.charAt(i) !== '>' && s.charAt(i) !== ',' && s.charAt(i) !== ' ') { - key += s.charAt(i); - i += 1; - } - } else { - //unknown char - i += 1; - continue; - } - //end of combos string - if(i >= s.length) { - if(key.length) { stage.push(key); key = ''; } - if(stage.length) { combo.push(stage); stage = []; } - if(combo.length) { combos.push(combo); combo = []; } - break; - } - } - return combos; - } - - /** - * Stringifys a key combo. - * @param {Array|String} keyComboArray A key combo array. If a key - * combo string is given it will be returned. - * @return {String} - */ - function stringifyKeyCombo(keyComboArray) { - var cI, ccI, output = []; - if(typeof keyComboArray === 'string') { return keyComboArray; } - if(typeof keyComboArray !== 'object' || typeof keyComboArray.push !== 'function') { throw new Error('Cannot stringify key combo.'); } - for(cI = 0; cI < keyComboArray.length; cI += 1) { - output[cI] = []; - for(ccI = 0; ccI < keyComboArray[cI].length; ccI += 1) { - output[cI][ccI] = keyComboArray[cI][ccI].join(' + '); - } - output[cI] = output[cI].join(' > '); - } - return output.join(' '); - } - - - ///////////////// - // ACTIVE KEYS // - ///////////////// - - /** - * Returns the a copy of the active keys array. - * @return {Array} - */ - function getActiveKeys() { - return [].concat(activeKeys); - } - - /** - * Adds a key to the active keys array, but only if it has not already been - * added. - * @param {String} keyName The key name string. - */ - function addActiveKey(keyName) { - if(keyName.match(/\s/)) { throw new Error('Cannot add key name ' + keyName + ' to active keys because it contains whitespace.'); } - if(activeKeys.indexOf(keyName) > -1) { return; } - activeKeys.push(keyName); - } - - /** - * Removes a key from the active keys array. - * @param {String} keyNames The key name string. - */ - function removeActiveKey(keyName) { - var keyCode = getKeyCode(keyName); - if(keyCode === '91' || keyCode === '92') { activeKeys = []; } //remove all key on release of super. - else { activeKeys.splice(activeKeys.indexOf(keyName), 1); } - } - - - ///////////// - // LOCALES // - ///////////// - - /** - * 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 - * alternative key names. For example if you program games you could create a locale for your key mappings. Instead of key 65 mapped - * to 'a' you could map it to 'jump'. - * @param {String} localeName The name of the new locale. - * @param {Object} localeMap The locale map. - */ - function registerLocale(localeName, localeMap) { - - //validate arguments - if(typeof localeName !== 'string') { throw new Error('Cannot register new locale. The locale name must be a string.'); } - if(typeof localeMap !== 'object') { throw new Error('Cannot register ' + localeName + ' locale. The locale map must be an object.'); } - if(typeof localeMap.map !== 'object') { throw new Error('Cannot register ' + localeName + ' locale. The locale map is invalid.'); } - - //stash the locale - if(!localeMap.macros) { localeMap.macros = []; } - locales[localeName] = localeMap; - } - - /** - * Swaps the current locale. - * @param {String} localeName The locale to activate. - * @return {Object} - */ - function getSetLocale(localeName) { - - //if a new locale is given then set it - if(localeName) { - if(typeof localeName !== 'string') { throw new Error('Cannot set locale. The locale name must be a string.'); } - 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.'); } - - //set the current map and macros - map = locales[localeName].map; - macros = locales[localeName].macros; - - //set the current locale - locale = localeName; - } - - //return the current locale - return locale; - } -}); diff --git a/views/layout.erb b/views/layout.erb index 78c2ebd7..3478c3f7 100644 --- a/views/layout.erb +++ b/views/layout.erb @@ -47,7 +47,6 @@ -