/* jshint ignore:start */ /** * @license * Fuse - Lightweight fuzzy-search * * Copyright (c) 2012 Kirollos Risk . * All Rights Reserved. Apache Software License 2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ (function(global) { /** * Adapted from "Diff, Match and Patch", by Google * * http://code.google.com/p/google-diff-match-patch/ * * Modified by: Kirollos Risk * ----------------------------------------------- * Details: the algorithm and structure was modified to allow the creation of * instances with a method which does the actual * bitap search. The (the string that is searched for) is only defined * once per instance and thus it eliminates redundant re-creation when searching * over a list of strings. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. */ var BitapSearcher = function(pattern, options) { options = options || {}; this.options = options; this.options.location = options.location || BitapSearcher.defaultOptions.location; this.options.distance = 'distance' in options ? options.distance : BitapSearcher.defaultOptions.distance; this.options.threshold = 'threshold' in options ? options.threshold : BitapSearcher.defaultOptions.threshold; this.options.maxPatternLength = options.maxPatternLength || BitapSearcher.defaultOptions.maxPatternLength; this.pattern = options.caseSensitive ? pattern : pattern.toLowerCase(); this.patternLen = pattern.length; if (this.patternLen > this.options.maxPatternLength) { throw new Error('Pattern length is too long'); } this.matchmask = 1 << (this.patternLen - 1); this.patternAlphabet = this._calculatePatternAlphabet(); }; BitapSearcher.defaultOptions = { // Approximately where in the text is the pattern expected to be found? location: 0, // Determines how close the match must be to the fuzzy location (specified above). // An exact letter match which is 'distance' characters away from the fuzzy location // would score as a complete mismatch. A distance of '0' requires the match be at // the exact location specified, a threshold of '1000' would require a perfect match // to be within 800 characters of the fuzzy location to be found using a 0.8 threshold. distance: 100, // At what point does the match algorithm give up. A threshold of '0.0' requires a perfect match // (of both letters and location), a threshold of '1.0' would match anything. threshold: 0.6, // Machine word size maxPatternLength: 32 }; /** * Initialize the alphabet for the Bitap algorithm. * @return {Object} Hash of character locations. * @private */ BitapSearcher.prototype._calculatePatternAlphabet = function() { var mask = {}, i = 0; for (i = 0; i < this.patternLen; i++) { mask[this.pattern.charAt(i)] = 0; } for (i = 0; i < this.patternLen; i++) { mask[this.pattern.charAt(i)] |= 1 << (this.pattern.length - i - 1); } return mask; }; /** * Compute and return the score for a match with `e` errors and `x` location. * @param {number} errors Number of errors in match. * @param {number} location Location of match. * @return {number} Overall score for match (0.0 = good, 1.0 = bad). * @private */ BitapSearcher.prototype._bitapScore = function(errors, location) { var accuracy = errors / this.patternLen, proximity = Math.abs(this.options.location - location); if (!this.options.distance) { // Dodge divide by zero error. return proximity ? 1.0 : accuracy; } return accuracy + (proximity / this.options.distance); }; /** * Compute and return the result of the search * @param {String} text The text to search in * @return {Object} Literal containing: * {Boolean} isMatch Whether the text is a match or not * {Decimal} score Overall score for the match * @public */ BitapSearcher.prototype.search = function(text) { text = this.options.caseSensitive ? text : text.toLowerCase(); if (this.pattern === text) { // Exact match return { isMatch: true, score: 0 }; } var i, j, // Set starting location at beginning text and initialize the alphabet. textLen = text.length, LOCATION = this.options.location, // Highest score beyond which we give up. THRESHOLD = this.options.threshold, // Is there a nearby exact match? (speedup) bestLoc = text.indexOf(this.pattern, LOCATION), binMin, binMid, binMax = this.patternLen + textLen, start, finish, bitArr, lastBitArr, charMatch, score = 1, locations = []; if (bestLoc != -1) { THRESHOLD = Math.min(this._bitapScore(0, bestLoc), THRESHOLD); // What about in the other direction? (speedup) bestLoc = text.lastIndexOf(this.pattern, LOCATION + this.patternLen); if (bestLoc != -1) { THRESHOLD = Math.min(this._bitapScore(0, bestLoc), THRESHOLD); } } bestLoc = -1; for (i = 0; i < this.patternLen; i++) { // Scan for the best match; each iteration allows for one more error. // Run a binary search to determine how far from 'MATCH_LOCATION' we can stray at this // error level. binMin = 0; binMid = binMax; while (binMin < binMid) { if (this._bitapScore(i, LOCATION + binMid) <= THRESHOLD) { binMin = binMid; } else { binMax = binMid; } binMid = Math.floor((binMax - binMin) / 2 + binMin); } // Use the result from this iteration as the maximum for the next. binMax = binMid; start = Math.max(1, LOCATION - binMid + 1); finish = Math.min(LOCATION + binMid, textLen) + this.patternLen; // Initialize the bit array bitArr = Array(finish + 2); bitArr[finish + 1] = (1 << i) - 1; for (j = finish; j >= start; j--) { // The alphabet is a sparse hash, so the following line generates warnings. charMatch = this.patternAlphabet[text.charAt(j - 1)]; if (i === 0) { // First pass: exact match. bitArr[j] = ((bitArr[j + 1] << 1) | 1) & charMatch; } else { // Subsequent passes: fuzzy match. bitArr[j] = ((bitArr[j + 1] << 1) | 1) & charMatch | (((lastBitArr[j + 1] | lastBitArr[j]) << 1) | 1) | lastBitArr[j + 1]; } if (bitArr[j] & this.matchmask) { score = this._bitapScore(i, j - 1); // This match will almost certainly be better than any existing match. // But check anyway. if (score <= THRESHOLD) { // Told you so. THRESHOLD = score; bestLoc = j - 1; locations.push(bestLoc); if (bestLoc > LOCATION) { // When passing loc, don't exceed our current distance from loc. start = Math.max(1, 2 * LOCATION - bestLoc); } else { // Already passed loc, downhill from here on in. break; } } } } // No hope for a (better) match at greater error levels. if (this._bitapScore(i + 1, LOCATION) > THRESHOLD) { break; } lastBitArr = bitArr; } return { isMatch: bestLoc >= 0, score: score }; }; var deepValueHelper = function(obj, path, list) { var firstSegment, remaining, dotIndex; if (!path) { // If there's no path left, we've gotten to the object we care about. list.push(obj); } else { dotIndex = path.indexOf('.'); if (dotIndex !== -1) { firstSegment = path.slice(0, dotIndex); remaining = path.slice(dotIndex + 1); } else { firstSegment = path; } var value = obj[firstSegment]; if (value) { if (!remaining && (typeof value === 'string' || typeof value === 'number')) { list.push(value); } else if (Utils.isArray(value)) { // Search each item in the array. for (var i = 0, len = value.length; i < len; i++) { deepValueHelper(value[i], remaining, list); } } else if (remaining) { // An object. Recurse further. deepValueHelper(value, remaining, list); } } } return list; }; var Utils = { /** * Traverse an object * @param {Object} obj The object to traverse * @param {String} path A . separated path to a key in the object. Example 'Data.Object.Somevalue' * @return {Object} */ deepValue: function(obj, path) { return deepValueHelper(obj, path, []); }, isArray: function(obj) { return Object.prototype.toString.call(obj) === '[object Array]'; } }; /** * @param {Array} list * @param {Object} options * @public */ function Fuse(list, options) { this.list = list; this.options = options = options || {}; var i, len, key, keys; // Add boolean type options for (i = 0, keys = ['sort', 'includeScore', 'shouldSort'], len = keys.length; i < len; i++) { key = keys[i]; this.options[key] = key in options ? options[key] : Fuse.defaultOptions[key]; } // Add all other options for (i = 0, keys = ['searchFn', 'sortFn', 'keys', 'getFn'], len = keys.length; i < len; i++) { key = keys[i]; this.options[key] = options[key] || Fuse.defaultOptions[key]; } }; Fuse.defaultOptions = { id: null, caseSensitive: false, // Whether the score should be included in the result set. // When , each result in the list will be of the form: `{ item: ..., score: ... }` includeScore: false, // Whether to sort the result list, by score shouldSort: true, // The search function to use // Note that the default search function ([[Function]]) must conform to the following API: // // @param pattern The pattern string to search // @param options The search option // [[Function]].constructor = function(pattern, options) // // @param text: the string to search in for the pattern // @return Object in the form of: // - isMatch: boolean // - score: Int // [[Function]].prototype.search = function(text) searchFn: BitapSearcher, // Default sort function sortFn: function(a, b) { return a.score - b.score; }, // Default get function getFn: Utils.deepValue, keys: [] }; /** * Sets a new list for Fuse to match against. * @param {Array} list * @return {Array} The newly set list * @public */ Fuse.prototype.set = function(list) { this.list = list; return list; }; /** * Searches for all the items whose keys (fuzzy) match the pattern. * @param {String} pattern The pattern string to fuzzy search on. * @return {Array} A list of all serch matches. * @public */ Fuse.prototype.search = function(pattern) { var searcher = new(this.options.searchFn)(pattern, this.options), j, item, list = this.list, dataLen = list.length, options = this.options, searchKeys = this.options.keys, searchKeysLen = searchKeys.length, bitapResult, rawResults = [], resultMap = {}, existingResult, results = []; /** * Calls for bitap analysis. Builds the raw result list. * @param {String} text The pattern string to fuzzy search on. * @param {String|Number} entity If the is an Array, then entity will be an index, * otherwise it's the item object. * @param {Number} index * @private */ var analyzeText = function(text, entity, index) { // Check if the text can be searched if (text === undefined || text === null) { return; } if (typeof text === 'string') { // Get the result bitapResult = searcher.search(text); // If a match is found, add the item to , including its score if (bitapResult.isMatch) { // Check if the item already exists in our results existingResult = resultMap[index]; if (existingResult) { // Use the lowest score existingResult.score = Math.min(existingResult.score, bitapResult.score); } else { // Add it to the raw result list resultMap[index] = { item: entity, score: bitapResult.score }; rawResults.push(resultMap[index]); } } } else if (Utils.isArray(text)) { for (var i = 0; i < text.length; i++) { analyzeText(text[i], entity, index); } } }; // Check the first item in the list, if it's a string, then we assume // that every item in the list is also a string, and thus it's a flattened array. if (typeof list[0] === 'string') { // Iterate over every item for (var i = 0; i < dataLen; i++) { analyzeText(list[i], i, i); } } else { // Otherwise, the first item is an Object (hopefully), and thus the searching // is done on the values of the keys of each item. // Iterate over every item for (var i = 0; i < dataLen; i++) { item = list[i]; // Iterate over every key for (j = 0; j < searchKeysLen; j++) { analyzeText(options.getFn(item, searchKeys[j]), item, i); } } } if (options.shouldSort) { rawResults.sort(options.sortFn); } // Helper function, here for speed-up, which returns the // the raw item, including the score, or simply the item itself, depending // on the specified option var getItem = options.includeScore ? function(i) { return rawResults[i]; } : function(i) { return rawResults[i].item; }; // Helper function, here for speed-up, which replaces the item with its value, // if the options specifies it, var replaceValue = options.id ? function(i) { rawResults[i].item = options.getFn(rawResults[i].item, options.id)[0]; } : function() { return; // no-op }; // From the results, push into a new array only the item identifier (if specified) // of the entire item. This is because we don't want to return the , // since it contains other metadata; for (var i = 0, len = rawResults.length; i < len; i++) { // replace the item with its value, which can be its id if the options specifies it replaceValue(i); results.push(getItem(i)); } return results; }; // Export to Common JS Loader if (typeof exports === 'object') { // Node. Does not work with strict CommonJS, but // only CommonJS-like environments that support module.exports, // like Node. module.exports = Fuse; } else if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define(function() { return Fuse; }); } else { // Browser globals (root is window) global.Fuse = Fuse; } })(this); /* jshint ignore:end */ /* jshint ignore:start */ /** * Copyright 2015 Craig Campbell * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Mousetrap is a simple keyboard shortcut library for Javascript with * no external dependencies * * @version 1.5.2 * @url craig.is/killing/mice */ (function(window, document, undefined) { /** * mapping of special keycodes to their corresponding keys * * everything in this dictionary cannot use keypress events * so it has to be here to map to the correct keycodes for * keyup/keydown events * * @type {Object} */ var _MAP = { 8: 'backspace', 9: 'tab', 13: 'enter', 16: 'shift', 17: 'ctrl', 18: 'alt', 20: 'capslock', 27: 'esc', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down', 45: 'ins', 46: 'del', 91: 'meta', 93: 'meta', 224: 'meta' }; /** * mapping for special characters so they can support * * this dictionary is only used incase you want to bind a * keyup or keydown event to one of these keys * * @type {Object} */ var _KEYCODE_MAP = { 106: '*', 107: '+', 109: '-', 110: '.', 111 : '/', 186: ';', 187: '=', 188: ',', 189: '-', 190: '.', 191: '/', 192: '`', 219: '[', 220: '\\', 221: ']', 222: '\'' }; /** * this is a mapping of keys that require shift on a US keypad * back to the non shift equivelents * * this is so you can use keyup events with these keys * * note that this will only work reliably on US keyboards * * @type {Object} */ var _SHIFT_MAP = { '~': '`', '!': '1', '@': '2', '#': '3', '$': '4', '%': '5', '^': '6', '&': '7', '*': '8', '(': '9', ')': '0', '_': '-', '+': '=', ':': ';', '\"': '\'', '<': ',', '>': '.', '?': '/', '|': '\\' }; /** * this is a list of special strings you can use to map * to modifier keys when you specify your keyboard shortcuts * * @type {Object} */ var _SPECIAL_ALIASES = { 'option': 'alt', 'command': 'meta', 'return': 'enter', 'escape': 'esc', 'plus': '+', 'mod': /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? 'meta' : 'ctrl' }; /** * variable to store the flipped version of _MAP from above * needed to check if we should use keypress or not when no action * is specified * * @type {Object|undefined} */ var _REVERSE_MAP; /** * loop through the f keys, f1 to f19 and add them to the map * programatically */ for (var i = 1; i < 20; ++i) { _MAP[111 + i] = 'f' + i; } /** * loop through to map numbers on the numeric keypad */ for (i = 0; i <= 9; ++i) { _MAP[i + 96] = i; } /** * cross browser add event method * * @param {Element|HTMLDocument} object * @param {string} type * @param {Function} callback * @returns void */ function _addEvent(object, type, callback) { if (object.addEventListener) { object.addEventListener(type, callback, false); return; } object.attachEvent('on' + type, callback); } /** * takes the event and returns the key character * * @param {Event} e * @return {string} */ function _characterFromEvent(e) { // for keypress events we should return the character as is if (e.type == 'keypress') { var character = String.fromCharCode(e.which); // if the shift key is not pressed then it is safe to assume // that we want the character to be lowercase. this means if // you accidentally have caps lock on then your key bindings // will continue to work // // the only side effect that might not be desired is if you // bind something like 'A' cause you want to trigger an // event when capital A is pressed caps lock will no longer // trigger the event. shift+a will though. if (!e.shiftKey) { character = character.toLowerCase(); } return character; } // for non keypress events the special maps are needed if (_MAP[e.which]) { return _MAP[e.which]; } if (_KEYCODE_MAP[e.which]) { return _KEYCODE_MAP[e.which]; } // if it is not in the special map // with keydown and keyup events the character seems to always // come in as an uppercase character whether you are pressing shift // or not. we should make sure it is always lowercase for comparisons return String.fromCharCode(e.which).toLowerCase(); } /** * checks if two arrays are equal * * @param {Array} modifiers1 * @param {Array} modifiers2 * @returns {boolean} */ function _modifiersMatch(modifiers1, modifiers2) { return modifiers1.sort().join(',') === modifiers2.sort().join(','); } /** * takes a key event and figures out what the modifiers are * * @param {Event} e * @returns {Array} */ function _eventModifiers(e) { var modifiers = []; if (e.shiftKey) { modifiers.push('shift'); } if (e.altKey) { modifiers.push('alt'); } if (e.ctrlKey) { modifiers.push('ctrl'); } if (e.metaKey) { modifiers.push('meta'); } return modifiers; } /** * prevents default for this event * * @param {Event} e * @returns void */ function _preventDefault(e) { if (e.preventDefault) { e.preventDefault(); return; } e.returnValue = false; } /** * stops propogation for this event * * @param {Event} e * @returns void */ function _stopPropagation(e) { if (e.stopPropagation) { e.stopPropagation(); return; } e.cancelBubble = true; } /** * determines if the keycode specified is a modifier key or not * * @param {string} key * @returns {boolean} */ function _isModifier(key) { return key == 'shift' || key == 'ctrl' || key == 'alt' || key == 'meta'; } /** * reverses the map lookup so that we can look for specific keys * to see what can and can't use keypress * * @return {Object} */ function _getReverseMap() { if (!_REVERSE_MAP) { _REVERSE_MAP = {}; for (var key in _MAP) { // pull out the numeric keypad from here cause keypress should // be able to detect the keys from the character if (key > 95 && key < 112) { continue; } if (_MAP.hasOwnProperty(key)) { _REVERSE_MAP[_MAP[key]] = key; } } } return _REVERSE_MAP; } /** * picks the best action based on the key combination * * @param {string} key - character for key * @param {Array} modifiers * @param {string=} action passed in */ function _pickBestAction(key, modifiers, action) { // if no action was picked in we should try to pick the one // that we think would work best for this key if (!action) { action = _getReverseMap()[key] ? 'keydown' : 'keypress'; } // modifier keys don't work as expected with keypress, // switch to keydown if (action == 'keypress' && modifiers.length) { action = 'keydown'; } return action; } /** * Converts from a string key combination to an array * * @param {string} combination like "command+shift+l" * @return {Array} */ function _keysFromString(combination) { if (combination === '+') { return ['+']; } combination = combination.replace(/\+{2}/g, '+plus'); return combination.split('+'); } /** * Gets info for a specific key combination * * @param {string} combination key combination ("command+s" or "a" or "*") * @param {string=} action * @returns {Object} */ function _getKeyInfo(combination, action) { var keys; var key; var i; var modifiers = []; // take the keys from this pattern and figure out what the actual // pattern is all about keys = _keysFromString(combination); for (i = 0; i < keys.length; ++i) { key = keys[i]; // normalize key names if (_SPECIAL_ALIASES[key]) { key = _SPECIAL_ALIASES[key]; } // if this is not a keypress event then we should // be smart about using shift keys // this will only work for US keyboards however if (action && action != 'keypress' && _SHIFT_MAP[key]) { key = _SHIFT_MAP[key]; modifiers.push('shift'); } // if this key is a modifier then add it to the list of modifiers if (_isModifier(key)) { modifiers.push(key); } } // depending on what the key combination is // we will try to pick the best event for it action = _pickBestAction(key, modifiers, action); return { key: key, modifiers: modifiers, action: action }; } function _belongsTo(element, ancestor) { if (element === document) { return false; } if (element === ancestor) { return true; } return _belongsTo(element.parentNode, ancestor); } function Mousetrap(targetElement) { var self = this; targetElement = targetElement || document; if (!(self instanceof Mousetrap)) { return new Mousetrap(targetElement); } /** * element to attach key events to * * @type {Element} */ self.target = targetElement; /** * a list of all the callbacks setup via Mousetrap.bind() * * @type {Object} */ self._callbacks = {}; /** * direct map of string combinations to callbacks used for trigger() * * @type {Object} */ self._directMap = {}; /** * keeps track of what level each sequence is at since multiple * sequences can start out with the same sequence * * @type {Object} */ var _sequenceLevels = {}; /** * variable to store the setTimeout call * * @type {null|number} */ var _resetTimer; /** * temporary state where we will ignore the next keyup * * @type {boolean|string} */ var _ignoreNextKeyup = false; /** * temporary state where we will ignore the next keypress * * @type {boolean} */ var _ignoreNextKeypress = false; /** * are we currently inside of a sequence? * type of action ("keyup" or "keydown" or "keypress") or false * * @type {boolean|string} */ var _nextExpectedAction = false; /** * resets all sequence counters except for the ones passed in * * @param {Object} doNotReset * @returns void */ function _resetSequences(doNotReset) { doNotReset = doNotReset || {}; var activeSequences = false, key; for (key in _sequenceLevels) { if (doNotReset[key]) { activeSequences = true; continue; } _sequenceLevels[key] = 0; } if (!activeSequences) { _nextExpectedAction = false; } } /** * finds all callbacks that match based on the keycode, modifiers, * and action * * @param {string} character * @param {Array} modifiers * @param {Event|Object} e * @param {string=} sequenceName - name of the sequence we are looking for * @param {string=} combination * @param {number=} level * @returns {Array} */ function _getMatches(character, modifiers, e, sequenceName, combination, level) { var i; var callback; var matches = []; var action = e.type; // if there are no events related to this keycode if (!self._callbacks[character]) { return []; } // if a modifier key is coming up on its own we should allow it if (action == 'keyup' && _isModifier(character)) { modifiers = [character]; } // loop through all callbacks for the key that was pressed // and see if any of them match for (i = 0; i < self._callbacks[character].length; ++i) { callback = self._callbacks[character][i]; // if a sequence name is not specified, but this is a sequence at // the wrong level then move onto the next match if (!sequenceName && callback.seq && _sequenceLevels[callback.seq] != callback.level) { continue; } // if the action we are looking for doesn't match the action we got // then we should keep going if (action != callback.action) { continue; } // if this is a keypress event and the meta key and control key // are not pressed that means that we need to only look at the // character, otherwise check the modifiers as well // // chrome will not fire a keypress if meta or control is down // safari will fire a keypress if meta or meta+shift is down // firefox will fire a keypress if meta or control is down if ((action == 'keypress' && !e.metaKey && !e.ctrlKey) || _modifiersMatch(modifiers, callback.modifiers)) { // when you bind a combination or sequence a second time it // should overwrite the first one. if a sequenceName or // combination is specified in this call it does just that // // @todo make deleting its own method? var deleteCombo = !sequenceName && callback.combo == combination; var deleteSequence = sequenceName && callback.seq == sequenceName && callback.level == level; if (deleteCombo || deleteSequence) { self._callbacks[character].splice(i, 1); } matches.push(callback); } } return matches; } /** * actually calls the callback function * * if your callback function returns false this will use the jquery * convention - prevent default and stop propogation on the event * * @param {Function} callback * @param {Event} e * @returns void */ function _fireCallback(callback, e, combo, sequence) { // if this event should not happen stop here if (self.stopCallback(e, e.target || e.srcElement, combo, sequence)) { return; } if (callback(e, combo) === false) { _preventDefault(e); _stopPropagation(e); } } /** * handles a character key event * * @param {string} character * @param {Array} modifiers * @param {Event} e * @returns void */ self._handleKey = function(character, modifiers, e) { var callbacks = _getMatches(character, modifiers, e); var i; var doNotReset = {}; var maxLevel = 0; var processedSequenceCallback = false; // Calculate the maxLevel for sequences so we can only execute the longest callback sequence for (i = 0; i < callbacks.length; ++i) { if (callbacks[i].seq) { maxLevel = Math.max(maxLevel, callbacks[i].level); } } // loop through matching callbacks for this key event for (i = 0; i < callbacks.length; ++i) { // fire for all sequence callbacks // this is because if for example you have multiple sequences // bound such as "g i" and "g t" they both need to fire the // callback for matching g cause otherwise you can only ever // match the first one if (callbacks[i].seq) { // only fire callbacks for the maxLevel to prevent // subsequences from also firing // // for example 'a option b' should not cause 'option b' to fire // even though 'option b' is part of the other sequence // // any sequences that do not match here will be discarded // below by the _resetSequences call if (callbacks[i].level != maxLevel) { continue; } processedSequenceCallback = true; // keep a list of which sequences were matches for later doNotReset[callbacks[i].seq] = 1; _fireCallback(callbacks[i].callback, e, callbacks[i].combo, callbacks[i].seq); continue; } // if there were no sequence matches but we are still here // that means this is a regular match so we should fire that if (!processedSequenceCallback) { _fireCallback(callbacks[i].callback, e, callbacks[i].combo); } } // if the key you pressed matches the type of sequence without // being a modifier (ie "keyup" or "keypress") then we should // reset all sequences that were not matched by this event // // this is so, for example, if you have the sequence "h a t" and you // type "h e a r t" it does not match. in this case the "e" will // cause the sequence to reset // // modifier keys are ignored because you can have a sequence // that contains modifiers such as "enter ctrl+space" and in most // cases the modifier key will be pressed before the next key // // also if you have a sequence such as "ctrl+b a" then pressing the // "b" key will trigger a "keypress" and a "keydown" // // the "keydown" is expected when there is a modifier, but the // "keypress" ends up matching the _nextExpectedAction since it occurs // after and that causes the sequence to reset // // we ignore keypresses in a sequence that directly follow a keydown // for the same character var ignoreThisKeypress = e.type == 'keypress' && _ignoreNextKeypress; if (e.type == _nextExpectedAction && !_isModifier(character) && !ignoreThisKeypress) { _resetSequences(doNotReset); } _ignoreNextKeypress = processedSequenceCallback && e.type == 'keydown'; }; /** * handles a keydown event * * @param {Event} e * @returns void */ function _handleKeyEvent(e) { // normalize e.which for key events // @see http://stackoverflow.com/questions/4285627/javascript-keycode-vs-charcode-utter-confusion if (typeof e.which !== 'number') { e.which = e.keyCode; } var character = _characterFromEvent(e); // no character found then stop if (!character) { return; } // need to use === for the character check because the character can be 0 if (e.type == 'keyup' && _ignoreNextKeyup === character) { _ignoreNextKeyup = false; return; } self.handleKey(character, _eventModifiers(e), e); } /** * called to set a 1 second timeout on the specified sequence * * this is so after each key press in the sequence you have 1 second * to press the next key before you have to start over * * @returns void */ function _resetSequenceTimer() { clearTimeout(_resetTimer); _resetTimer = setTimeout(_resetSequences, 1000); } /** * binds a key sequence to an event * * @param {string} combo - combo specified in bind call * @param {Array} keys * @param {Function} callback * @param {string=} action * @returns void */ function _bindSequence(combo, keys, callback, action) { // start off by adding a sequence level record for this combination // and setting the level to 0 _sequenceLevels[combo] = 0; /** * callback to increase the sequence level for this sequence and reset * all other sequences that were active * * @param {string} nextAction * @returns {Function} */ function _increaseSequence(nextAction) { return function() { _nextExpectedAction = nextAction; ++_sequenceLevels[combo]; _resetSequenceTimer(); }; } /** * wraps the specified callback inside of another function in order * to reset all sequence counters as soon as this sequence is done * * @param {Event} e * @returns void */ function _callbackAndReset(e) { _fireCallback(callback, e, combo); // we should ignore the next key up if the action is key down // or keypress. this is so if you finish a sequence and // release the key the final key will not trigger a keyup if (action !== 'keyup') { _ignoreNextKeyup = _characterFromEvent(e); } // weird race condition if a sequence ends with the key // another sequence begins with setTimeout(_resetSequences, 10); } // loop through keys one at a time and bind the appropriate callback // function. for any key leading up to the final one it should // increase the sequence. after the final, it should reset all sequences // // if an action is specified in the original bind call then that will // be used throughout. otherwise we will pass the action that the // next key in the sequence should match. this allows a sequence // to mix and match keypress and keydown events depending on which // ones are better suited to the key provided for (var i = 0; i < keys.length; ++i) { var isFinal = i + 1 === keys.length; var wrappedCallback = isFinal ? _callbackAndReset : _increaseSequence(action || _getKeyInfo(keys[i + 1]).action); _bindSingle(keys[i], wrappedCallback, action, combo, i); } } /** * binds a single keyboard combination * * @param {string} combination * @param {Function} callback * @param {string=} action * @param {string=} sequenceName - name of sequence if part of sequence * @param {number=} level - what part of the sequence the command is * @returns void */ function _bindSingle(combination, callback, action, sequenceName, level) { // store a direct mapped reference for use with Mousetrap.trigger self._directMap[combination + ':' + action] = callback; // make sure multiple spaces in a row become a single space combination = combination.replace(/\s+/g, ' '); var sequence = combination.split(' '); var info; // if this pattern is a sequence of keys then run through this method // to reprocess each pattern one key at a time if (sequence.length > 1) { _bindSequence(combination, sequence, callback, action); return; } info = _getKeyInfo(combination, action); // make sure to initialize array if this is the first time // a callback is added for this key self._callbacks[info.key] = self._callbacks[info.key] || []; // remove an existing match if there is one _getMatches(info.key, info.modifiers, {type: info.action}, sequenceName, combination, level); // add this call back to the array // if it is a sequence put it at the beginning // if not put it at the end // // this is important because the way these are processed expects // the sequence ones to come first self._callbacks[info.key][sequenceName ? 'unshift' : 'push']({ callback: callback, modifiers: info.modifiers, action: info.action, seq: sequenceName, level: level, combo: combination }); } /** * binds multiple combinations to the same callback * * @param {Array} combinations * @param {Function} callback * @param {string|undefined} action * @returns void */ self._bindMultiple = function(combinations, callback, action) { for (var i = 0; i < combinations.length; ++i) { _bindSingle(combinations[i], callback, action); } }; // start! _addEvent(targetElement, 'keypress', _handleKeyEvent); _addEvent(targetElement, 'keydown', _handleKeyEvent); _addEvent(targetElement, 'keyup', _handleKeyEvent); } /** * binds an event to mousetrap * * can be a single key, a combination of keys separated with +, * an array of keys, or a sequence of keys separated by spaces * * be sure to list the modifier keys first to make sure that the * correct key ends up getting bound (the last key in the pattern) * * @param {string|Array} keys * @param {Function} callback * @param {string=} action - 'keypress', 'keydown', or 'keyup' * @returns void */ Mousetrap.prototype.bind = function(keys, callback, action) { var self = this; keys = keys instanceof Array ? keys : [keys]; self._bindMultiple.call(self, keys, callback, action); return self; }; /** * unbinds an event to mousetrap * * the unbinding sets the callback function of the specified key combo * to an empty function and deletes the corresponding key in the * _directMap dict. * * TODO: actually remove this from the _callbacks dictionary instead * of binding an empty function * * the keycombo+action has to be exactly the same as * it was defined in the bind method * * @param {string|Array} keys * @param {string} action * @returns void */ Mousetrap.prototype.unbind = function(keys, action) { var self = this; return self.bind.call(self, keys, function() {}, action); }; /** * triggers an event that has already been bound * * @param {string} keys * @param {string=} action * @returns void */ Mousetrap.prototype.trigger = function(keys, action) { var self = this; if (self._directMap[keys + ':' + action]) { self._directMap[keys + ':' + action]({}, keys); } return self; }; /** * resets the library back to its initial state. this is useful * if you want to clear out the current keyboard shortcuts and bind * new ones - for example if you switch to another page * * @returns void */ Mousetrap.prototype.reset = function() { var self = this; self._callbacks = {}; self._directMap = {}; return self; }; /** * should we stop this event before firing off callbacks * * @param {Event} e * @param {Element} element * @return {boolean} */ Mousetrap.prototype.stopCallback = function(e, element) { var self = this; // if the element has the class "mousetrap" then no need to stop if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) { return false; } if (_belongsTo(element, self.target)) { return false; } // stop for input, select, and textarea return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || element.isContentEditable; }; /** * exposes _handleKey publicly so it can be overwritten by extensions */ Mousetrap.prototype.handleKey = function() { var self = this; return self._handleKey.apply(self, arguments); }; /** * Init the global mousetrap functions * * This method is needed to allow the global mousetrap functions to work * now that mousetrap is a constructor function. */ Mousetrap.init = function() { var documentMousetrap = Mousetrap(document); for (var method in documentMousetrap) { if (method.charAt(0) !== '_') { Mousetrap[method] = (function(method) { return function() { return documentMousetrap[method].apply(documentMousetrap, arguments); }; } (method)); } } }; Mousetrap.init(); // expose mousetrap to the global object window.Mousetrap = Mousetrap; // expose as a common js module if (typeof module !== 'undefined' && module.exports) { module.exports = Mousetrap; } // expose mousetrap as an AMD module if (typeof define === 'function' && define.amd) { define(function() { return Mousetrap; }); } }) (window, document); /* jshint ignore:end */ /** * adds a bindGlobal method to Mousetrap that allows you to * bind specific keyboard shortcuts that will still work * inside a text input field * * usage: * Mousetrap.bindGlobal('ctrl+s', _saveChanges); */ /* global Mousetrap:true */ (function(Mousetrap) { var _globalCallbacks = {}; var _originalStopCallback = Mousetrap.prototype.stopCallback; Mousetrap.prototype.stopCallback = function(e, element, combo, sequence) { var self = this; if (self.paused) { return true; } if (_globalCallbacks[combo] || _globalCallbacks[sequence]) { return false; } return _originalStopCallback.call(self, e, element, combo); }; Mousetrap.prototype.bindGlobal = function(keys, callback, action) { var self = this; self.bind(keys, callback, action); if (keys instanceof Array) { for (var i = 0; i < keys.length; i++) { _globalCallbacks[keys[i]] = true; } return; } _globalCallbacks[keys] = true; }; Mousetrap.init(); }) (Mousetrap); /* jshint ignore:start */ /* Ractive.js v0.7.3 Sat Apr 25 2015 13:52:38 GMT-0400 (EDT) - commit da40f81c660ba2f09c45a09a9c20fdd34ee36d80 http://ractivejs.org http://twitter.com/RactiveJS Released under the MIT License. */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : global.Ractive = factory() }(this, function () { 'use strict'; var TEMPLATE_VERSION = 3; var defaultOptions = { // render placement: el: void 0, append: false, // template: template: { v: TEMPLATE_VERSION, t: [] }, // parse: // TODO static delimiters? preserveWhitespace: false, sanitize: false, stripComments: true, delimiters: ["{{", "}}"], tripleDelimiters: ["{{{", "}}}"], interpolate: false, // data & binding: data: {}, computed: {}, magic: false, modifyArrays: true, adapt: [], isolated: false, twoway: true, lazy: false, // transitions: noIntro: false, transitionsEnabled: true, complete: void 0, // css: css: null, noCssTransform: false }; var config_defaults = defaultOptions; // These are a subset of the easing equations found at // https://raw.github.com/danro/easing-js - license info // follows: // -------------------------------------------------- // easing.js v0.5.4 // Generic set of easing functions with AMD support // https://github.com/danro/easing-js // This code may be freely distributed under the MIT license // http://danro.mit-license.org/ // -------------------------------------------------- // All functions adapted from Thomas Fuchs & Jeremy Kahn // Easing Equations (c) 2003 Robert Penner, BSD license // https://raw.github.com/danro/easing-js/master/LICENSE // -------------------------------------------------- // In that library, the functions named easeIn, easeOut, and // easeInOut below are named easeInCubic, easeOutCubic, and // (you guessed it) easeInOutCubic. // // You can add additional easing functions to this list, and they // will be globally available. var static_easing = { linear: function (pos) { return pos; }, easeIn: function (pos) { return Math.pow(pos, 3); }, easeOut: function (pos) { return Math.pow(pos - 1, 3) + 1; }, easeInOut: function (pos) { if ((pos /= 0.5) < 1) { return 0.5 * Math.pow(pos, 3); } return 0.5 * (Math.pow(pos - 2, 3) + 2); } }; /*global console, navigator */ var isClient, isJsdom, hasConsole, environment__magic, namespaces, svg, vendors; isClient = typeof document === "object"; isJsdom = typeof navigator !== "undefined" && /jsDom/.test(navigator.appName); hasConsole = typeof console !== "undefined" && typeof console.warn === "function" && typeof console.warn.apply === "function"; try { Object.defineProperty({}, "test", { value: 0 }); environment__magic = true; } catch (e) { environment__magic = false; } namespaces = { html: "http://www.w3.org/1999/xhtml", mathml: "http://www.w3.org/1998/Math/MathML", svg: "http://www.w3.org/2000/svg", xlink: "http://www.w3.org/1999/xlink", xml: "http://www.w3.org/XML/1998/namespace", xmlns: "http://www.w3.org/2000/xmlns/" }; if (typeof document === "undefined") { svg = false; } else { svg = document && document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"); } vendors = ["o", "ms", "moz", "webkit"]; var createElement, matches, dom__div, methodNames, unprefixed, prefixed, dom__i, j, makeFunction; // Test for SVG support if (!svg) { createElement = function (type, ns) { if (ns && ns !== namespaces.html) { throw "This browser does not support namespaces other than http://www.w3.org/1999/xhtml. The most likely cause of this error is that you're trying to render SVG in an older browser. See http://docs.ractivejs.org/latest/svg-and-older-browsers for more information"; } return document.createElement(type); }; } else { createElement = function (type, ns) { if (!ns || ns === namespaces.html) { return document.createElement(type); } return document.createElementNS(ns, type); }; } function getElement(input) { var output; if (!input || typeof input === "boolean") { return; } if (typeof window === "undefined" || !document || !input) { return null; } // We already have a DOM node - no work to do. (Duck typing alert!) if (input.nodeType) { return input; } // Get node from string if (typeof input === "string") { // try ID first output = document.getElementById(input); // then as selector, if possible if (!output && document.querySelector) { output = document.querySelector(input); } // did it work? if (output && output.nodeType) { return output; } } // If we've been given a collection (jQuery, Zepto etc), extract the first item if (input[0] && input[0].nodeType) { return input[0]; } return null; } if (!isClient) { matches = null; } else { dom__div = createElement("div"); methodNames = ["matches", "matchesSelector"]; makeFunction = function (methodName) { return function (node, selector) { return node[methodName](selector); }; }; dom__i = methodNames.length; while (dom__i-- && !matches) { unprefixed = methodNames[dom__i]; if (dom__div[unprefixed]) { matches = makeFunction(unprefixed); } else { j = vendors.length; while (j--) { prefixed = vendors[dom__i] + unprefixed.substr(0, 1).toUpperCase() + unprefixed.substring(1); if (dom__div[prefixed]) { matches = makeFunction(prefixed); break; } } } } // IE8... if (!matches) { matches = function (node, selector) { var nodes, parentNode, i; parentNode = node.parentNode; if (!parentNode) { // empty dummy
dom__div.innerHTML = ""; parentNode = dom__div; node = node.cloneNode(); dom__div.appendChild(node); } nodes = parentNode.querySelectorAll(selector); i = nodes.length; while (i--) { if (nodes[i] === node) { return true; } } return false; }; } } function detachNode(node) { if (node && typeof node.parentNode !== "unknown" && node.parentNode) { node.parentNode.removeChild(node); } return node; } function safeToStringValue(value) { return value == null || !value.toString ? "" : value; } var legacy = null; var create, defineProperty, defineProperties; try { Object.defineProperty({}, "test", { value: 0 }); if (isClient) { Object.defineProperty(document.createElement("div"), "test", { value: 0 }); } defineProperty = Object.defineProperty; } catch (err) { // Object.defineProperty doesn't exist, or we're in IE8 where you can // only use it with DOM objects (what were you smoking, MSFT?) defineProperty = function (obj, prop, desc) { obj[prop] = desc.value; }; } try { try { Object.defineProperties({}, { test: { value: 0 } }); } catch (err) { // TODO how do we account for this? noMagic = true; throw err; } if (isClient) { Object.defineProperties(createElement("div"), { test: { value: 0 } }); } defineProperties = Object.defineProperties; } catch (err) { defineProperties = function (obj, props) { var prop; for (prop in props) { if (props.hasOwnProperty(prop)) { defineProperty(obj, prop, props[prop]); } } }; } try { Object.create(null); create = Object.create; } catch (err) { // sigh create = (function () { var F = function () {}; return function (proto, props) { var obj; if (proto === null) { return {}; } F.prototype = proto; obj = new F(); if (props) { Object.defineProperties(obj, props); } return obj; }; })(); } function utils_object__extend(target) { for (var _len = arguments.length, sources = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { sources[_key - 1] = arguments[_key]; } var prop, source; while (source = sources.shift()) { for (prop in source) { if (hasOwn.call(source, prop)) { target[prop] = source[prop]; } } } return target; } function fillGaps(target) { for (var _len = arguments.length, sources = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { sources[_key - 1] = arguments[_key]; } sources.forEach(function (s) { for (var key in s) { if (s.hasOwnProperty(key) && !(key in target)) { target[key] = s[key]; } } }); return target; } var hasOwn = Object.prototype.hasOwnProperty; // thanks, http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/ var is__toString = Object.prototype.toString, arrayLikePattern = /^\[object (?:Array|FileList)\]$/; function isArray(thing) { return is__toString.call(thing) === "[object Array]"; } function isArrayLike(obj) { return arrayLikePattern.test(is__toString.call(obj)); } function isEqual(a, b) { if (a === null && b === null) { return true; } if (typeof a === "object" || typeof b === "object") { return false; } return a === b; } function is__isNumeric(thing) { return !isNaN(parseFloat(thing)) && isFinite(thing); } function isObject(thing) { return thing && is__toString.call(thing) === "[object Object]"; } var noop = function () {}; /* global console */ var alreadyWarned = {}, log, printWarning, welcome; if (hasConsole) { (function () { var welcomeIntro = ["%cRactive.js %c0.7.3 %cin debug mode, %cmore...", "color: rgb(114, 157, 52); font-weight: normal;", "color: rgb(85, 85, 85); font-weight: normal;", "color: rgb(85, 85, 85); font-weight: normal;", "color: rgb(82, 140, 224); font-weight: normal; text-decoration: underline;"]; var welcomeMessage = "You're running Ractive 0.7.3 in debug mode - messages will be printed to the console to help you fix problems and optimise your application.\n\nTo disable debug mode, add this line at the start of your app:\n Ractive.DEBUG = false;\n\nTo disable debug mode when your app is minified, add this snippet:\n Ractive.DEBUG = /unminified/.test(function(){/*unminified*/});\n\nGet help and support:\n http://docs.ractivejs.org\n http://stackoverflow.com/questions/tagged/ractivejs\n http://groups.google.com/forum/#!forum/ractive-js\n http://twitter.com/ractivejs\n\nFound a bug? Raise an issue:\n https://github.com/ractivejs/ractive/issues\n\n"; welcome = function () { var hasGroup = !!console.groupCollapsed; console[hasGroup ? "groupCollapsed" : "log"].apply(console, welcomeIntro); console.log(welcomeMessage); if (hasGroup) { console.groupEnd(welcomeIntro); } welcome = noop; }; printWarning = function (message, args) { welcome(); // extract information about the instance this message pertains to, if applicable if (typeof args[args.length - 1] === "object") { var options = args.pop(); var ractive = options ? options.ractive : null; if (ractive) { // if this is an instance of a component that we know the name of, add // it to the message var _name = undefined; if (ractive.component && (_name = ractive.component.name)) { message = "<" + _name + "> " + message; } var node = undefined; if (node = options.node || ractive.fragment && ractive.fragment.rendered && ractive.find("*")) { args.push(node); } } } console.warn.apply(console, ["%cRactive.js: %c" + message, "color: rgb(114, 157, 52);", "color: rgb(85, 85, 85);"].concat(args)); }; log = function () { console.log.apply(console, arguments); }; })(); } else { printWarning = log = welcome = noop; } function format(message, args) { return message.replace(/%s/g, function () { return args.shift(); }); } function fatal(message) { for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { args[_key - 1] = arguments[_key]; } message = format(message, args); throw new Error(message); } function logIfDebug() { if (_Ractive.DEBUG) { log.apply(null, arguments); } } function warn(message) { for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { args[_key - 1] = arguments[_key]; } message = format(message, args); printWarning(message, args); } function warnOnce(message) { for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { args[_key - 1] = arguments[_key]; } message = format(message, args); if (alreadyWarned[message]) { return; } alreadyWarned[message] = true; printWarning(message, args); } function warnIfDebug() { if (_Ractive.DEBUG) { warn.apply(null, arguments); } } function warnOnceIfDebug() { if (_Ractive.DEBUG) { warnOnce.apply(null, arguments); } } // Error messages that are used (or could be) in multiple places var badArguments = "Bad arguments"; var noRegistryFunctionReturn = "A function was specified for \"%s\" %s, but no %s was returned"; var missingPlugin = function (name, type) { return "Missing \"" + name + "\" " + type + " plugin. You may need to download a plugin via http://docs.ractivejs.org/latest/plugins#" + type + "s"; }; function findInViewHierarchy(registryName, ractive, name) { var instance = findInstance(registryName, ractive, name); return instance ? instance[registryName][name] : null; } function findInstance(registryName, ractive, name) { while (ractive) { if (name in ractive[registryName]) { return ractive; } if (ractive.isolated) { return null; } ractive = ractive.parent; } } var interpolate = function (from, to, ractive, type) { if (from === to) { return snap(to); } if (type) { var interpol = findInViewHierarchy("interpolators", ractive, type); if (interpol) { return interpol(from, to) || snap(to); } fatal(missingPlugin(type, "interpolator")); } return static_interpolators.number(from, to) || static_interpolators.array(from, to) || static_interpolators.object(from, to) || snap(to); }; var shared_interpolate = interpolate; function snap(to) { return function () { return to; }; } var interpolators = { number: function (from, to) { var delta; if (!is__isNumeric(from) || !is__isNumeric(to)) { return null; } from = +from; to = +to; delta = to - from; if (!delta) { return function () { return from; }; } return function (t) { return from + t * delta; }; }, array: function (from, to) { var intermediate, interpolators, len, i; if (!isArray(from) || !isArray(to)) { return null; } intermediate = []; interpolators = []; i = len = Math.min(from.length, to.length); while (i--) { interpolators[i] = shared_interpolate(from[i], to[i]); } // surplus values - don't interpolate, but don't exclude them either for (i = len; i < from.length; i += 1) { intermediate[i] = from[i]; } for (i = len; i < to.length; i += 1) { intermediate[i] = to[i]; } return function (t) { var i = len; while (i--) { intermediate[i] = interpolators[i](t); } return intermediate; }; }, object: function (from, to) { var properties, len, interpolators, intermediate, prop; if (!isObject(from) || !isObject(to)) { return null; } properties = []; intermediate = {}; interpolators = {}; for (prop in from) { if (hasOwn.call(from, prop)) { if (hasOwn.call(to, prop)) { properties.push(prop); interpolators[prop] = shared_interpolate(from[prop], to[prop]); } else { intermediate[prop] = from[prop]; } } } for (prop in to) { if (hasOwn.call(to, prop) && !hasOwn.call(from, prop)) { intermediate[prop] = to[prop]; } } len = properties.length; return function (t) { var i = len, prop; while (i--) { prop = properties[i]; intermediate[prop] = interpolators[prop](t); } return intermediate; }; } }; var static_interpolators = interpolators; // This function takes a keypath such as 'foo.bar.baz', and returns // all the variants of that keypath that include a wildcard in place // of a key, such as 'foo.bar.*', 'foo.*.baz', 'foo.*.*' and so on. // These are then checked against the dependants map (ractive.viewmodel.depsMap) // to see if any pattern observers are downstream of one or more of // these wildcard keypaths (e.g. 'foo.bar.*.status') var utils_getPotentialWildcardMatches = getPotentialWildcardMatches; var starMaps = {}; function getPotentialWildcardMatches(keypath) { var keys, starMap, mapper, i, result, wildcardKeypath; keys = keypath.split("."); if (!(starMap = starMaps[keys.length])) { starMap = getStarMap(keys.length); } result = []; mapper = function (star, i) { return star ? "*" : keys[i]; }; i = starMap.length; while (i--) { wildcardKeypath = starMap[i].map(mapper).join("."); if (!result.hasOwnProperty(wildcardKeypath)) { result.push(wildcardKeypath); result[wildcardKeypath] = true; } } return result; } // This function returns all the possible true/false combinations for // a given number - e.g. for two, the possible combinations are // [ true, true ], [ true, false ], [ false, true ], [ false, false ]. // It does so by getting all the binary values between 0 and e.g. 11 function getStarMap(num) { var ones = "", max, binary, starMap, mapper, i, j, l, map; if (!starMaps[num]) { starMap = []; while (ones.length < num) { ones += 1; } max = parseInt(ones, 2); mapper = function (digit) { return digit === "1"; }; for (i = 0; i <= max; i += 1) { binary = i.toString(2); while (binary.length < num) { binary = "0" + binary; } map = []; l = binary.length; for (j = 0; j < l; j++) { map.push(mapper(binary[j])); } starMap[i] = map; } starMaps[num] = starMap; } return starMaps[num]; } var refPattern = /\[\s*(\*|[0-9]|[1-9][0-9]+)\s*\]/g; var patternPattern = /\*/; var keypathCache = {}; var Keypath = function (str) { var keys = str.split("."); this.str = str; if (str[0] === "@") { this.isSpecial = true; this.value = decodeKeypath(str); } this.firstKey = keys[0]; this.lastKey = keys.pop(); this.isPattern = patternPattern.test(str); this.parent = str === "" ? null : getKeypath(keys.join(".")); this.isRoot = !str; }; Keypath.prototype = { equalsOrStartsWith: function (keypath) { return keypath === this || this.startsWith(keypath); }, join: function (str) { return getKeypath(this.isRoot ? String(str) : this.str + "." + str); }, replace: function (oldKeypath, newKeypath) { if (this === oldKeypath) { return newKeypath; } if (this.startsWith(oldKeypath)) { return newKeypath === null ? newKeypath : getKeypath(this.str.replace(oldKeypath.str + ".", newKeypath.str + ".")); } }, startsWith: function (keypath) { if (!keypath) { // TODO under what circumstances does this happen? return false; } return keypath && this.str.substr(0, keypath.str.length + 1) === keypath.str + "."; }, toString: function () { throw new Error("Bad coercion"); }, valueOf: function () { throw new Error("Bad coercion"); }, wildcardMatches: function () { return this._wildcardMatches || (this._wildcardMatches = utils_getPotentialWildcardMatches(this.str)); } }; function assignNewKeypath(target, property, oldKeypath, newKeypath) { var existingKeypath = target[property]; if (existingKeypath && (existingKeypath.equalsOrStartsWith(newKeypath) || !existingKeypath.equalsOrStartsWith(oldKeypath))) { return; } target[property] = existingKeypath ? existingKeypath.replace(oldKeypath, newKeypath) : newKeypath; return true; } function decodeKeypath(keypath) { var value = keypath.slice(2); if (keypath[1] === "i") { return is__isNumeric(value) ? +value : value; } else { return value; } } function getKeypath(str) { if (str == null) { return str; } // TODO it *may* be worth having two versions of this function - one where // keypathCache inherits from null, and one for IE8. Depends on how // much of an overhead hasOwnProperty is - probably negligible if (!keypathCache.hasOwnProperty(str)) { keypathCache[str] = new Keypath(str); } return keypathCache[str]; } function getMatchingKeypaths(ractive, keypath) { var keys, key, matchingKeypaths; keys = keypath.str.split("."); matchingKeypaths = [rootKeypath]; while (key = keys.shift()) { if (key === "*") { // expand to find all valid child keypaths matchingKeypaths = matchingKeypaths.reduce(expand, []); } else { if (matchingKeypaths[0] === rootKeypath) { // first key matchingKeypaths[0] = getKeypath(key); } else { matchingKeypaths = matchingKeypaths.map(concatenate(key)); } } } return matchingKeypaths; function expand(matchingKeypaths, keypath) { var wrapper, value, keys; if (keypath.isRoot) { keys = [].concat(Object.keys(ractive.viewmodel.data), Object.keys(ractive.viewmodel.mappings), Object.keys(ractive.viewmodel.computations)); } else { wrapper = ractive.viewmodel.wrapped[keypath.str]; value = wrapper ? wrapper.get() : ractive.viewmodel.get(keypath); keys = value ? Object.keys(value) : null; } if (keys) { keys.forEach(function (key) { if (key !== "_ractive" || !isArray(value)) { matchingKeypaths.push(keypath.join(key)); } }); } return matchingKeypaths; } } function concatenate(key) { return function (keypath) { return keypath.join(key); }; } function normalise(ref) { return ref ? ref.replace(refPattern, ".$1") : ""; } var rootKeypath = getKeypath(""); var shared_add = add; var shared_add__errorMessage = "Cannot add to a non-numeric value"; function add(root, keypath, d) { if (typeof keypath !== "string" || !is__isNumeric(d)) { throw new Error("Bad arguments"); } var value = undefined, changes = undefined; if (/\*/.test(keypath)) { changes = {}; getMatchingKeypaths(root, getKeypath(normalise(keypath))).forEach(function (keypath) { var value = root.viewmodel.get(keypath); if (!is__isNumeric(value)) { throw new Error(shared_add__errorMessage); } changes[keypath.str] = value + d; }); return root.set(changes); } value = root.get(keypath); if (!is__isNumeric(value)) { throw new Error(shared_add__errorMessage); } return root.set(keypath, +value + d); } var prototype_add = Ractive$add; function Ractive$add(keypath, d) { return shared_add(this, keypath, d === undefined ? 1 : +d); } var requestAnimationFrame; // If window doesn't exist, we don't need requestAnimationFrame if (typeof window === "undefined") { requestAnimationFrame = null; } else { // https://gist.github.com/paulirish/1579671 (function (vendors, lastTime, window) { var x, setTimeout; if (window.requestAnimationFrame) { return; } for (x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { window.requestAnimationFrame = window[vendors[x] + "RequestAnimationFrame"]; } if (!window.requestAnimationFrame) { setTimeout = window.setTimeout; window.requestAnimationFrame = function (callback) { var currTime, timeToCall, id; currTime = Date.now(); timeToCall = Math.max(0, 16 - (currTime - lastTime)); id = setTimeout(function () { callback(currTime + timeToCall); }, timeToCall); lastTime = currTime + timeToCall; return id; }; } })(vendors, 0, window); requestAnimationFrame = window.requestAnimationFrame; } var rAF = requestAnimationFrame; var getTime; if (typeof window !== "undefined" && window.performance && typeof window.performance.now === "function") { getTime = function () { return window.performance.now(); }; } else { getTime = function () { return Date.now(); }; } var utils_getTime = getTime; var deprecations = { construct: { deprecated: "beforeInit", replacement: "onconstruct" }, render: { deprecated: "init", message: "The \"init\" method has been deprecated " + "and will likely be removed in a future release. " + "You can either use the \"oninit\" method which will fire " + "only once prior to, and regardless of, any eventual ractive " + "instance being rendered, or if you need to access the " + "rendered DOM, use \"onrender\" instead. " + "See http://docs.ractivejs.org/latest/migrating for more information." }, complete: { deprecated: "complete", replacement: "oncomplete" } }; function Hook(event) { this.event = event; this.method = "on" + event; this.deprecate = deprecations[event]; } Hook.prototype.fire = function (ractive, arg) { function call(method) { if (ractive[method]) { arg ? ractive[method](arg) : ractive[method](); return true; } } call(this.method); if (!ractive[this.method] && this.deprecate && call(this.deprecate.deprecated)) { if (this.deprecate.message) { warnIfDebug(this.deprecate.message); } else { warnIfDebug("The method \"%s\" has been deprecated in favor of \"%s\" and will likely be removed in a future release. See http://docs.ractivejs.org/latest/migrating for more information.", this.deprecate.deprecated, this.deprecate.replacement); } } arg ? ractive.fire(this.event, arg) : ractive.fire(this.event); }; var hooks_Hook = Hook; function addToArray(array, value) { var index = array.indexOf(value); if (index === -1) { array.push(value); } } function arrayContains(array, value) { for (var i = 0, c = array.length; i < c; i++) { if (array[i] == value) { return true; } } return false; } function arrayContentsMatch(a, b) { var i; if (!isArray(a) || !isArray(b)) { return false; } if (a.length !== b.length) { return false; } i = a.length; while (i--) { if (a[i] !== b[i]) { return false; } } return true; } function ensureArray(x) { if (typeof x === "string") { return [x]; } if (x === undefined) { return []; } return x; } function lastItem(array) { return array[array.length - 1]; } function removeFromArray(array, member) { var index = array.indexOf(member); if (index !== -1) { array.splice(index, 1); } } function toArray(arrayLike) { var array = [], i = arrayLike.length; while (i--) { array[i] = arrayLike[i]; } return array; } var _Promise, PENDING = {}, FULFILLED = {}, REJECTED = {}; if (typeof Promise === "function") { // use native Promise _Promise = Promise; } else { _Promise = function (callback) { var fulfilledHandlers = [], rejectedHandlers = [], state = PENDING, result, dispatchHandlers, makeResolver, fulfil, reject, promise; makeResolver = function (newState) { return function (value) { if (state !== PENDING) { return; } result = value; state = newState; dispatchHandlers = makeDispatcher(state === FULFILLED ? fulfilledHandlers : rejectedHandlers, result); // dispatch onFulfilled and onRejected handlers asynchronously wait(dispatchHandlers); }; }; fulfil = makeResolver(FULFILLED); reject = makeResolver(REJECTED); try { callback(fulfil, reject); } catch (err) { reject(err); } promise = { // `then()` returns a Promise - 2.2.7 then: function (onFulfilled, onRejected) { var promise2 = new _Promise(function (fulfil, reject) { var processResolutionHandler = function (handler, handlers, forward) { // 2.2.1.1 if (typeof handler === "function") { handlers.push(function (p1result) { var x; try { x = handler(p1result); utils_Promise__resolve(promise2, x, fulfil, reject); } catch (err) { reject(err); } }); } else { // Forward the result of promise1 to promise2, if resolution handlers // are not given handlers.push(forward); } }; // 2.2 processResolutionHandler(onFulfilled, fulfilledHandlers, fulfil); processResolutionHandler(onRejected, rejectedHandlers, reject); if (state !== PENDING) { // If the promise has resolved already, dispatch the appropriate handlers asynchronously wait(dispatchHandlers); } }); return promise2; } }; promise["catch"] = function (onRejected) { return this.then(null, onRejected); }; return promise; }; _Promise.all = function (promises) { return new _Promise(function (fulfil, reject) { var result = [], pending, i, processPromise; if (!promises.length) { fulfil(result); return; } processPromise = function (promise, i) { if (promise && typeof promise.then === "function") { promise.then(function (value) { result[i] = value; --pending || fulfil(result); }, reject); } else { result[i] = promise; --pending || fulfil(result); } }; pending = i = promises.length; while (i--) { processPromise(promises[i], i); } }); }; _Promise.resolve = function (value) { return new _Promise(function (fulfil) { fulfil(value); }); }; _Promise.reject = function (reason) { return new _Promise(function (fulfil, reject) { reject(reason); }); }; } var utils_Promise = _Promise; // TODO use MutationObservers or something to simulate setImmediate function wait(callback) { setTimeout(callback, 0); } function makeDispatcher(handlers, result) { return function () { var handler; while (handler = handlers.shift()) { handler(result); } }; } function utils_Promise__resolve(promise, x, fulfil, reject) { // Promise Resolution Procedure var then; // 2.3.1 if (x === promise) { throw new TypeError("A promise's fulfillment handler cannot return the same promise"); } // 2.3.2 if (x instanceof _Promise) { x.then(fulfil, reject); } // 2.3.3 else if (x && (typeof x === "object" || typeof x === "function")) { try { then = x.then; // 2.3.3.1 } catch (e) { reject(e); // 2.3.3.2 return; } // 2.3.3.3 if (typeof then === "function") { var called, resolvePromise, rejectPromise; resolvePromise = function (y) { if (called) { return; } called = true; utils_Promise__resolve(promise, y, fulfil, reject); }; rejectPromise = function (r) { if (called) { return; } called = true; reject(r); }; try { then.call(x, resolvePromise, rejectPromise); } catch (e) { if (!called) { // 2.3.3.3.4.1 reject(e); // 2.3.3.3.4.2 called = true; return; } } } else { fulfil(x); } } else { fulfil(x); } } var getInnerContext = function (fragment) { do { if (fragment.context !== undefined) { return fragment.context; } } while (fragment = fragment.parent); return rootKeypath; }; var shared_resolveRef = resolveRef; function resolveRef(ractive, ref, fragment) { var keypath; ref = normalise(ref); // If a reference begins '~/', it's a top-level reference if (ref.substr(0, 2) === "~/") { keypath = getKeypath(ref.substring(2)); createMappingIfNecessary(ractive, keypath.firstKey, fragment); } // If a reference begins with '.', it's either a restricted reference or // an ancestor reference... else if (ref[0] === ".") { keypath = resolveAncestorRef(getInnerContext(fragment), ref); if (keypath) { createMappingIfNecessary(ractive, keypath.firstKey, fragment); } } // ...otherwise we need to figure out the keypath based on context else { keypath = resolveAmbiguousReference(ractive, getKeypath(ref), fragment); } return keypath; } function resolveAncestorRef(baseContext, ref) { var contextKeys; // TODO... if (baseContext != undefined && typeof baseContext !== "string") { baseContext = baseContext.str; } // {{.}} means 'current context' if (ref === ".") return getKeypath(baseContext); contextKeys = baseContext ? baseContext.split(".") : []; // ancestor references (starting "../") go up the tree if (ref.substr(0, 3) === "../") { while (ref.substr(0, 3) === "../") { if (!contextKeys.length) { throw new Error("Could not resolve reference - too many \"../\" prefixes"); } contextKeys.pop(); ref = ref.substring(3); } contextKeys.push(ref); return getKeypath(contextKeys.join(".")); } // not an ancestor reference - must be a restricted reference (prepended with "." or "./") if (!baseContext) { return getKeypath(ref.replace(/^\.\/?/, "")); } return getKeypath(baseContext + ref.replace(/^\.\//, ".")); } function resolveAmbiguousReference(ractive, ref, fragment, isParentLookup) { var context, key, parentValue, hasContextChain, parentKeypath; if (ref.isRoot) { return ref; } key = ref.firstKey; while (fragment) { context = fragment.context; fragment = fragment.parent; if (!context) { continue; } hasContextChain = true; parentValue = ractive.viewmodel.get(context); if (parentValue && (typeof parentValue === "object" || typeof parentValue === "function") && key in parentValue) { return context.join(ref.str); } } // Root/computed/mapped property? if (isRootProperty(ractive.viewmodel, key)) { return ref; } // If this is an inline component, and it's not isolated, we // can try going up the scope chain if (ractive.parent && !ractive.isolated) { hasContextChain = true; fragment = ractive.component.parentFragment; key = getKeypath(key); if (parentKeypath = resolveAmbiguousReference(ractive.parent, key, fragment, true)) { // We need to create an inter-component binding ractive.viewmodel.map(key, { origin: ractive.parent.viewmodel, keypath: parentKeypath }); return ref; } } // If there's no context chain, and the instance is either a) isolated or // b) an orphan, then we know that the keypath is identical to the reference if (!isParentLookup && !hasContextChain) { // the data object needs to have a property by this name, // to prevent future failed lookups ractive.viewmodel.set(ref, undefined); return ref; } } function createMappingIfNecessary(ractive, key) { var parentKeypath; if (!ractive.parent || ractive.isolated || isRootProperty(ractive.viewmodel, key)) { return; } key = getKeypath(key); if (parentKeypath = resolveAmbiguousReference(ractive.parent, key, ractive.component.parentFragment, true)) { ractive.viewmodel.map(key, { origin: ractive.parent.viewmodel, keypath: parentKeypath }); } } function isRootProperty(viewmodel, key) { // special case for reference to root return key === "" || key in viewmodel.data || key in viewmodel.computations || key in viewmodel.mappings; } function teardown(x) { x.teardown(); } function methodCallers__unbind(x) { x.unbind(); } function methodCallers__unrender(x) { x.unrender(); } function cancel(x) { x.cancel(); } var TransitionManager = function (callback, parent) { this.callback = callback; this.parent = parent; this.intros = []; this.outros = []; this.children = []; this.totalChildren = this.outroChildren = 0; this.detachQueue = []; this.decoratorQueue = []; this.outrosComplete = false; if (parent) { parent.addChild(this); } }; TransitionManager.prototype = { addChild: function (child) { this.children.push(child); this.totalChildren += 1; this.outroChildren += 1; }, decrementOutros: function () { this.outroChildren -= 1; check(this); }, decrementTotal: function () { this.totalChildren -= 1; check(this); }, add: function (transition) { var list = transition.isIntro ? this.intros : this.outros; list.push(transition); }, addDecorator: function (decorator) { this.decoratorQueue.push(decorator); }, remove: function (transition) { var list = transition.isIntro ? this.intros : this.outros; removeFromArray(list, transition); check(this); }, init: function () { this.ready = true; check(this); }, detachNodes: function () { this.decoratorQueue.forEach(teardown); this.detachQueue.forEach(detach); this.children.forEach(detachNodes); } }; function detach(element) { element.detach(); } function detachNodes(tm) { tm.detachNodes(); } function check(tm) { if (!tm.ready || tm.outros.length || tm.outroChildren) return; // If all outros are complete, and we haven't already done this, // we notify the parent if there is one, otherwise // start detaching nodes if (!tm.outrosComplete) { if (tm.parent) { tm.parent.decrementOutros(tm); } else { tm.detachNodes(); } tm.outrosComplete = true; } // Once everything is done, we can notify parent transition // manager and call the callback if (!tm.intros.length && !tm.totalChildren) { if (typeof tm.callback === "function") { tm.callback(); } if (tm.parent) { tm.parent.decrementTotal(); } } } var global_TransitionManager = TransitionManager; var batch, runloop, unresolved = [], changeHook = new hooks_Hook("change"); runloop = { start: function (instance, returnPromise) { var promise, fulfilPromise; if (returnPromise) { promise = new utils_Promise(function (f) { return fulfilPromise = f; }); } batch = { previousBatch: batch, transitionManager: new global_TransitionManager(fulfilPromise, batch && batch.transitionManager), views: [], tasks: [], ractives: [], instance: instance }; if (instance) { batch.ractives.push(instance); } return promise; }, end: function () { flushChanges(); batch.transitionManager.init(); if (!batch.previousBatch && !!batch.instance) batch.instance.viewmodel.changes = []; batch = batch.previousBatch; }, addRactive: function (ractive) { if (batch) { addToArray(batch.ractives, ractive); } }, registerTransition: function (transition) { transition._manager = batch.transitionManager; batch.transitionManager.add(transition); }, registerDecorator: function (decorator) { batch.transitionManager.addDecorator(decorator); }, addView: function (view) { batch.views.push(view); }, addUnresolved: function (thing) { unresolved.push(thing); }, removeUnresolved: function (thing) { removeFromArray(unresolved, thing); }, // synchronise node detachments with transition ends detachWhenReady: function (thing) { batch.transitionManager.detachQueue.push(thing); }, scheduleTask: function (task, postRender) { var _batch; if (!batch) { task(); } else { _batch = batch; while (postRender && _batch.previousBatch) { // this can't happen until the DOM has been fully updated // otherwise in some situations (with components inside elements) // transitions and decorators will initialise prematurely _batch = _batch.previousBatch; } _batch.tasks.push(task); } } }; var global_runloop = runloop; function flushChanges() { var i, thing, changeHash; while (batch.ractives.length) { thing = batch.ractives.pop(); changeHash = thing.viewmodel.applyChanges(); if (changeHash) { changeHook.fire(thing, changeHash); } } attemptKeypathResolution(); // Now that changes have been fully propagated, we can update the DOM // and complete other tasks for (i = 0; i < batch.views.length; i += 1) { batch.views[i].update(); } batch.views.length = 0; for (i = 0; i < batch.tasks.length; i += 1) { batch.tasks[i](); } batch.tasks.length = 0; // If updating the view caused some model blowback - e.g. a triple // containing