<commit_before>;
(function (global) {

    // Helpers
    // ----------------

    var toString = Object.prototype.toString;

    function isFunction(val) {
        return toString.call(val) === '[object Function]';
    }

    function isArray(val) {
        if (Array.isArray) {
            return Array.isArray(val);
        } else {
            return toString.call(val) === '[object Array]';
        }
    }

    function isObject(value) {
        return typeof value === 'object';
    }

    function isString(value) {
        return typeof value === 'string';
    }

    function int(value) {
        return parseInt(value, 10);
    }

    function copyArray(array) {
        var arrayCopy = [],
            i = 0,
            len = array.length;
        for (; i < len; i++) {
            arrayCopy.push(array[i]);
        }
        return arrayCopy;
    }

    function trim(str) {
        str = str.replace(/^\s+/g, '');
        for (var i = str.length - 1; i >= 0; i--) {
            if (/\S/.test(str.charAt(i))) {
                str = str.substring(0, i + 1);
                break;
            }
        }
        return str;
    }

    function compare(value1, value2) {
        var type1 = typeof value1;
        var type2 = typeof value2;
        if (type1 == type2) {
            if (type1 === 'string') {
                value1 = value1.toLowerCase();
                value2 = value2.toLowerCase();
            } else if (type1 === 'undefined') {
                return 0;
            }
            if (value1 === value2) return 0;
            return value1 < value2 ? -1 : 1;
        } else {
            return type1 < type2 ? -1 : 1;
        }
    }

    function compareEx(value1, value2) {
        var type1 = typeof value1;
        if (type1 === 'boolean') {
            if ((/true|false/ig).test(value2)) {
                value2 = value2.toLowerCase() === 'true';
            } else {
                value2 = !!value2;
            }
        } else if (type1 === 'number') {
            var temp = parseFloat(value2);
            if (!isNaN(temp)) {
                value2 = temp;
            }
        }
        return compare(value1, value2);
    }

    // ---------------

    var register;
    if (global.diglett) {
        register = global.diglett.registerFilter;
        if (!register || !isFunction(register)) {
            return;
        }
    } else {
        return;
    }

    register('filter', function (array) {

        if (!isArray(array)) {
            return array;
        }

        var expressions = Array.prototype.slice.call(arguments, 1),
            i = 0,
            len = expressions.length,
            getUserFilter = function (name) {
                var cache = diglett.userFilter || (diglett.userFilter = {});
                return cache[name];
            };

        if (len === 1) {
            var cusFilter = getUserFilter(expressions[0]);
            if (cusFilter && isFunction(cusFilter)) {
                return cusFilter(array);
            }
        }

        var
            out = [],
            rField = /(\S+?)\s*([>=<^]{1,2})\s*(.+)/g,
            field,
            operator,
            value,
            compares = [],
            compare,
            matches;

        for (; i < len; i++) {
//            matches = rField.exec(expressions[i]);
            matches = expressions[i].match(/(\S+?)\s*([>=<^]{1,2})\s*(.+)/);
            if (matches) {
                field = matches[1];
                operator = matches[2];
                value = matches[3];

                compare = (function (field, operator, value) {

                    switch (operator) {
                        case '>':
                            return function (obj) {
                                if (!isObject(obj)) {
                                    return true;
                                }
                                var comp = compareEx(obj[field], value);
                                return comp === 1;
                            };
                            break;
                        case '>=':
                            return function (obj) {
                                if (!isObject(obj)) {
                                    return true;
                                }
                                var comp = compareEx(obj[field], value);
                                return comp === 1 || comp === 0;
                            };
                            break;
                        case '<':
                            return function (obj) {
                                if (!isObject(obj)) {
                                    return true;
                                }
                                var comp = compareEx(obj[field], value);
                                return comp === -1;
                            };
                            break;
                        case '<=':
                            return function (obj) {
                                if (!isObject(obj)) {
                                    return true;
                                }
                                var comp = compareEx(obj[field], value);
                                return comp === -1 || comp === 0;
                            };
                            break;
                        case '<>':
                            return function (obj) {
                                if (!isObject(obj)) {
                                    return true;
                                }
                                var comp = compareEx(obj[field], value);
                                return  comp !== 0;
                            };
                            break;
                        case '=':
                        case '==':
                            return function (obj) {
                                if (!isObject(obj)) {
                                    return true;
                                }
                                var comp = compareEx(obj[field], value);
                                return comp === 0;
                            };
                            break;
                        case '^': // contains
                        case '^^':
                            return function (obj) {
                                if (!isObject(obj)) {
                                    return true;
                                }

                                function contains(value1, value2) {
                                    var type = typeof value1;
                                    if (type === 'string') {
                                        return value1.toLowerCase().indexOf(value2.toLowerCase()) > -1;
                                    } else if (isArray(value1)) {
                                        var nativeItem;
                                        while (nativeItem = value1.shift()) {
                                            if (compareEx(nativeItem, value2) === 0) {
                                                return true;
                                            }
                                        }
                                        return false;
                                    } else if (type === 'object') {
                                        return contains(value1[field], value2);
                                    } else if (type === 'undefined') {
                                        return false;
                                    }
                                    return true;
                                }

                                return contains(obj[field], value);
                            };
                            break;
                        default :
                            break;
                    }
                })(field, operator, value);

                if (compare) {
                    compares.push(compare);
                }
            }
        }

        i = 0;
        len = array.length;
        var j = 0,
            k = compares.length,
            flag;

        for (; i < len; i++) {
            flag = true;
            for (j = 0; j < k; j++) {
                if (!compares[j](array[i])) {
                    flag = false;
                    break;
                }
            }
            if (flag) {
                out.push(array[i]);
            }
        }
        return out;
    });

    diglett.addUserFilter = function (name, fn, overwrite) {
        var cache = diglett.userFilter || (diglett.userFilter = {});
        if (cache.hasOwnProperty(name)) {
            if (overwrite) {
                cache[name] = fn;
            }
        } else {
            cache[name] = fn;
        }
    };

    diglett.removeUserFilter = function (name) {
        var cache = diglett.userFilter || (diglett.userFilter = {});
        if (cache.hasOwnProperty(name)) {
            delete cache[name];
        }
    };

    register('orderBy', function (array) {
            // not an array or not assigned sort predicate
            if (!isArray(array)) {
                return array;
            }

            var sortFields = Array.prototype.slice.call(arguments, 1),
                getUserOrderBy = function (name) {
                    var cache = diglett.userOrderBy || (diglett.userOrderBy = {});
                    return cache[name];
                },
                reverse,
                i = 0,
                len = sortFields.length;

            if (len > 0) {
                // the last argument is boolean
                if (typeof sortFields[len - 1] === 'boolean') {
                    reverse = sortFields[len - 1] === true;
                    sortFields.pop();
                    len = sortFields.length;
                }
            }

            var arrayCopy = copyArray(array);

            if (len === 1) {
                var cusOrder = getUserOrderBy(sortFields[0]);
                if (cusOrder && isFunction(cusOrder)) {
                    return arrayCopy.sort(cusOrder);
                }
            } else if (len === 0) {
                sortFields.push('+');
                len = 1;
            }

            var sorters = [],
                sorter,
                flag,
                field;
            for (; i < len; i++) {
                field = sortFields[i];
                flag = field.charAt(0);
                if (flag === '+' || flag === '-') {
                    reverse = flag === '-' || reverse;
                    field = field.substring(1);
                }

                sorter = (function (field, descending) {
                    return function (obj1, obj2) {
                        if (isObject(obj1)) {
                            obj1 = obj1[field];
                        }
                        if (isObject(obj2)) {
                            obj2 = obj2[field];
                        }
                        if (descending) {
                            return compare(obj2, obj1);
                        }
                        return compare(obj1, obj2);
                    }
                })(field, reverse);

                sorters.push(sorter);
            }

            function doCompare(obj1, obj2) {
                var i = 0,
                    len = sorters.length;
                for (; i < len; i++) {
                    var comp = sorters[i](obj1, obj2);
                    if (comp !== 0) {
                        return comp;
                    }
                }
                return 0;
            }

            return arrayCopy.sort(doCompare);
        }
    );

    diglett.addUserOrderBy = function (name, fn, overwrite) {
        var cache = diglett.userOrderBy || (diglett.userOrderBy = {});
        if (cache.hasOwnProperty(name)) {
            if (overwrite) {
                cache[name] = fn;
            }
        } else {
            cache[name] = fn;
        }
    };

    diglett.removeUserOrderBy = function (name) {
        var cache = diglett.userOrderBy || (diglett.userOrderBy = {});
        if (cache.hasOwnProperty(name)) {
            delete cache[name];
        }
    };

    // limitTo filter
    // limit the length of an array or string
    register('limitTo', function (input, limit) {
        limit = int(limit);
        if (isNaN(limit)) {
            return input;
        }
        if (isString(input)) {
            if (limit) {
                return limit >= 0 ? input.slice(0, limit) : input.slice(limit, input.length);
            } else {
                return '';
            }
        } else if (isArray(input)) {
            var out = [],
                start,
                end,
                len = input.length;
            if (limit > len) {
                limit = len;
            } else if (limit < -len) {
                limit = -len;
            }

            if (limit > 0) {
                start = 0;
                end = limit;
            } else {
                start = len + limit;
                end = len;
            }

            for (; start < end; start++) {
                out.push(input[start]);
            }
            return out;
        } else {
            return input;
        }
    });

    // register datetime filter
    var datetimeFilter = global.datetime && global.datetime.parse;
    if (datetimeFilter && isFunction(datetimeFilter)) {
        register('datetime', datetimeFilter);
    }

    // register currency and number filter
    var numberFilter = global.currency && global.currency.formatNumber;
    if (numberFilter && isFunction(numberFilter)) {
        register('number', numberFilter);
    }

    var currencyFilter = global.currency && global.currency.formatMoney;
    if (currencyFilter && isFunction(currencyFilter)) {
        register('currency', currencyFilter);
    }

    if (typeof define === 'function') {
        // RequireJS && SeaJS
        define('registerFilter', 'diglett', function () {
            return diglett;
        });
    } else if (typeof exports !== 'undefined') {
        if (typeof module !== 'undefined' && module.exports) {
            // NodeJS
            module.exports = diglett;
        }
    } else {
        global.diglett = diglett;
    }
})(this);<commit_msg>bugfix: orderby filter<commit_after>;
(function (global) {

    // Helpers
    // ----------------

    var toString = Object.prototype.toString;

    function isFunction(val) {
        return toString.call(val) === '[object Function]';
    }

    function isArray(val) {
        if (Array.isArray) {
            return Array.isArray(val);
        } else {
            return toString.call(val) === '[object Array]';
        }
    }

    function isObject(value) {
        return typeof value === 'object';
    }

    function isString(value) {
        return typeof value === 'string';
    }

    function int(value) {
        return parseInt(value, 10);
    }

    function copyArray(array) {
        var arrayCopy = [],
            i = 0,
            len = array.length;
        for (; i < len; i++) {
            arrayCopy.push(array[i]);
        }
        return arrayCopy;
    }

    function trim(str) {
        str = str.replace(/^\s+/g, '');
        for (var i = str.length - 1; i >= 0; i--) {
            if (/\S/.test(str.charAt(i))) {
                str = str.substring(0, i + 1);
                break;
            }
        }
        return str;
    }

    function compare(value1, value2) {
        var type1 = typeof value1;
        var type2 = typeof value2;
        if (type1 == type2) {
            if (type1 === 'string') {
                value1 = value1.toLowerCase();
                value2 = value2.toLowerCase();
            } else if (type1 === 'undefined') {
                return 0;
            }
            if (value1 === value2) return 0;
            return value1 < value2 ? -1 : 1;
        } else {
            return type1 < type2 ? -1 : 1;
        }
    }

    function compareEx(value1, value2) {
        var type1 = typeof value1;
        if (type1 === 'boolean') {
            if ((/true|false/ig).test(value2)) {
                value2 = value2.toLowerCase() === 'true';
            } else {
                value2 = !!value2;
            }
        } else if (type1 === 'number') {
            var temp = parseFloat(value2);
            if (!isNaN(temp)) {
                value2 = temp;
            }
        }
        return compare(value1, value2);
    }

    // ---------------

    var register;
    if (global.diglett) {
        register = global.diglett.registerFilter;
        if (!register || !isFunction(register)) {
            return;
        }
    } else {
        return;
    }

    register('filter', function (array) {

        if (!isArray(array)) {
            return array;
        }

        var expressions = Array.prototype.slice.call(arguments, 1),
            i = 0,
            len = expressions.length,
            getUserFilter = function (name) {
                var cache = diglett.userFilter || (diglett.userFilter = {});
                return cache[name];
            };

        if (len === 1) {
            var cusFilter = getUserFilter(expressions[0]);
            if (cusFilter && isFunction(cusFilter)) {
                return cusFilter(array);
            }
        }

        var
            out = [],
            rField = /(\S+?)\s*([>=<^]{1,2})\s*(.+)/g,
            field,
            operator,
            value,
            compares = [],
            compare,
            matches;

        for (; i < len; i++) {
//            matches = rField.exec(expressions[i]);
            matches = expressions[i].match(/(\S+?)\s*([>=<^]{1,2})\s*(.+)/);
            if (matches) {
                field = matches[1];
                operator = matches[2];
                value = matches[3];

                compare = (function (field, operator, value) {

                    switch (operator) {
                        case '>':
                            return function (obj) {
                                if (!isObject(obj)) {
                                    return true;
                                }
                                var comp = compareEx(obj[field], value);
                                return comp === 1;
                            };
                            break;
                        case '>=':
                            return function (obj) {
                                if (!isObject(obj)) {
                                    return true;
                                }
                                var comp = compareEx(obj[field], value);
                                return comp === 1 || comp === 0;
                            };
                            break;
                        case '<':
                            return function (obj) {
                                if (!isObject(obj)) {
                                    return true;
                                }
                                var comp = compareEx(obj[field], value);
                                return comp === -1;
                            };
                            break;
                        case '<=':
                            return function (obj) {
                                if (!isObject(obj)) {
                                    return true;
                                }
                                var comp = compareEx(obj[field], value);
                                return comp === -1 || comp === 0;
                            };
                            break;
                        case '<>':
                            return function (obj) {
                                if (!isObject(obj)) {
                                    return true;
                                }
                                var comp = compareEx(obj[field], value);
                                return  comp !== 0;
                            };
                            break;
                        case '=':
                        case '==':
                            return function (obj) {
                                if (!isObject(obj)) {
                                    return true;
                                }
                                var comp = compareEx(obj[field], value);
                                return comp === 0;
                            };
                            break;
                        case '^': // contains
                        case '^^':
                            return function (obj) {
                                if (!isObject(obj)) {
                                    return true;
                                }

                                function contains(value1, value2) {
                                    var type = typeof value1;
                                    if (type === 'string') {
                                        return value1.toLowerCase().indexOf(value2.toLowerCase()) > -1;
                                    } else if (isArray(value1)) {
                                        var nativeItem;
                                        while (nativeItem = value1.shift()) {
                                            if (compareEx(nativeItem, value2) === 0) {
                                                return true;
                                            }
                                        }
                                        return false;
                                    } else if (type === 'object') {
                                        return contains(value1[field], value2);
                                    } else if (type === 'undefined') {
                                        return false;
                                    }
                                    return true;
                                }

                                return contains(obj[field], value);
                            };
                            break;
                        default :
                            break;
                    }
                })(field, operator, value);

                if (compare) {
                    compares.push(compare);
                }
            }
        }

        i = 0;
        len = array.length;
        var j = 0,
            k = compares.length,
            flag;

        for (; i < len; i++) {
            flag = true;
            for (j = 0; j < k; j++) {
                if (!compares[j](array[i])) {
                    flag = false;
                    break;
                }
            }
            if (flag) {
                out.push(array[i]);
            }
        }
        return out;
    });

    diglett.addUserFilter = function (name, fn, overwrite) {
        var cache = diglett.userFilter || (diglett.userFilter = {});
        if (cache.hasOwnProperty(name)) {
            if (overwrite) {
                cache[name] = fn;
            }
        } else {
            cache[name] = fn;
        }
    };

    diglett.removeUserFilter = function (name) {
        var cache = diglett.userFilter || (diglett.userFilter = {});
        if (cache.hasOwnProperty(name)) {
            delete cache[name];
        }
    };

    register('orderBy', function (array) {
            // not an array or not assigned sort predicate
            if (!isArray(array)) {
                return array;
            }

            var sortFields = Array.prototype.slice.call(arguments, 1),
                getUserOrderBy = function (name) {
                    var cache = diglett.userOrderBy || (diglett.userOrderBy = {});
                    return cache[name];
                },
                reverse,
                i = 0,
                len = sortFields.length;

            if (len > 0) {
                // the last argument is boolean
                if (typeof sortFields[len - 1] === 'boolean') {
                    reverse = sortFields[len - 1] === true;
                    sortFields.pop();
                    len = sortFields.length;
                }
            }

            var arrayCopy = copyArray(array);

            if (len === 1) {
                var cusOrder = getUserOrderBy(sortFields[0]);
                if (cusOrder && isFunction(cusOrder)) {
                    return arrayCopy.sort(cusOrder);
                }
            } else if (len === 0) {
                sortFields.push('+');
                len = 1;
            }

            var sorters = [],
                sorter,
                flag,
                field;
            for (; i < len; i++) {
                field = sortFields[i];
                if (!field) {
                    continue;
                }
                flag = field.charAt(0);
                if (flag === '+' || flag === '-') {
                    reverse = flag === '-' || reverse;
                    field = field.substring(1);
                }

                sorter = (function (field, descending) {
                    return function (obj1, obj2) {
                        if (isObject(obj1)) {
                            obj1 = obj1[field];
                        }
                        if (isObject(obj2)) {
                            obj2 = obj2[field];
                        }
                        if (descending) {
                            return compare(obj2, obj1);
                        }
                        return compare(obj1, obj2);
                    }
                })(field, reverse);

                sorters.push(sorter);
            }

            function doCompare(obj1, obj2) {
                var i = 0,
                    len = sorters.length;
                for (; i < len; i++) {
                    var comp = sorters[i](obj1, obj2);
                    if (comp !== 0) {
                        return comp;
                    }
                }
                return 0;
            }

            return arrayCopy.sort(doCompare);
        }
    );

    diglett.addUserOrderBy = function (name, fn, overwrite) {
        var cache = diglett.userOrderBy || (diglett.userOrderBy = {});
        if (cache.hasOwnProperty(name)) {
            if (overwrite) {
                cache[name] = fn;
            }
        } else {
            cache[name] = fn;
        }
    };

    diglett.removeUserOrderBy = function (name) {
        var cache = diglett.userOrderBy || (diglett.userOrderBy = {});
        if (cache.hasOwnProperty(name)) {
            delete cache[name];
        }
    };

    // limitTo filter
    // limit the length of an array or string
    register('limitTo', function (input, limit) {
        limit = int(limit);
        if (isNaN(limit)) {
            return input;
        }
        if (isString(input)) {
            if (limit) {
                return limit >= 0 ? input.slice(0, limit) : input.slice(limit, input.length);
            } else {
                return '';
            }
        } else if (isArray(input)) {
            var out = [],
                start,
                end,
                len = input.length;
            if (limit > len) {
                limit = len;
            } else if (limit < -len) {
                limit = -len;
            }

            if (limit > 0) {
                start = 0;
                end = limit;
            } else {
                start = len + limit;
                end = len;
            }

            for (; start < end; start++) {
                out.push(input[start]);
            }
            return out;
        } else {
            return input;
        }
    });

    // register datetime filter
    var datetimeFilter = global.datetime && global.datetime.parse;
    if (datetimeFilter && isFunction(datetimeFilter)) {
        register('datetime', datetimeFilter);
    }

    // register currency and number filter
    var numberFilter = global.currency && global.currency.formatNumber;
    if (numberFilter && isFunction(numberFilter)) {
        register('number', numberFilter);
    }

    var currencyFilter = global.currency && global.currency.formatMoney;
    if (currencyFilter && isFunction(currencyFilter)) {
        register('currency', currencyFilter);
    }

    if (typeof define === 'function') {
        // RequireJS && SeaJS
        define('registerFilter', 'diglett', function () {
            return diglett;
        });
    } else if (typeof exports !== 'undefined') {
        if (typeof module !== 'undefined' && module.exports) {
            // NodeJS
            module.exports = diglett;
        }
    } else {
        global.diglett = diglett;
    }
})(this);<|endoftext|>


<commit_before>import { version } from '../package.json'
import { Constants } from './constants'
import { Logger } from './logger'
import { Tools } from './tools'
import { Utils } from './utils'

export class Router {

  /**
   * Represents the riot-view-router mixin.
   * @constructor
   * @param {riot} instance - Riot instance to target.
   * @param {object} settings - Router options.
   * @param {array} states - States for router to read from.
   * @returns {Router}
   */
  constructor (instance, settings, states) {
    var self = this

    self.constants = Constants
    self.version = version
    self.settings = {}

    self.$riot = instance
    self.$logger = new Logger(self)
    self.$tools = new Tools(self)
    self.$utils = new Utils(self)

    self.$riot.observable(self)

    Object.defineProperty(self, 'location', {
      get: function() {
        return {
          hash: window.location.hash,
          href: window.location.href
        }
      },
      set: function(location) {
        window.history.pushState(null, null, location)
      }
    })

    self.running = false

    const requiredSettings = ['default']
    const optionalSettings = ['debugging', 'fallback', 'href', 'fragments', 'marker', 'titleRoot']
    const acceptedSettings = requiredSettings.concat(optionalSettings)
    for (const setting in settings) {
      if (acceptedSettings.indexOf(setting) === -1)
        throw Error(`Unknown setting "${setting}" is not supported`)
    } // # check for unaccepted settings
    requiredSettings.forEach(setting => {
      if (typeof settings[setting] === 'undefined')
        throw ReferenceError(`Required setting "${setting}" not specified`)
    }) // # check for required settings

    if (settings.default.indexOf(':') > -1)
      throw Error('Default state route cannot take variable parameters')

    for (const setting in self.settings) {
      const validator = self.constants.regex.settings[setting]
      if (validator && !setting.match(validator))
        throw Error(`Setting "${setting}" has an invalid value of "${settings[setting]}"`)
    }

    self.settings = Object.assign({}, settings)
    self.settings.debugging = self.settings.debugging || false
    self.settings.fragments = self.settings.fragments || true
    self.settings.marker = self.settings.marker || self.constants.defaults.marker

    if (self.settings.href)
      if (self.location.href.indexOf(self.settings.href) == -1)
        throw Error('Defined href not found within location context')

    self.settings.href = self.settings.href || self.location.href.split(self.constants.defaults.hash)[0]
    if (!self.settings.href.endsWith('/'))
      self.settings.href = `${self.settings.href}/`

    const stateProperties = ['name', 'route', 'tag']
    states = !Array.isArray(states) ? [Object.assign({}, state)] : states.map(state => Object.assign({}, state))
    stateProperties.forEach((prop) => {
      states.forEach((state) => {
        if (!state[prop])
          throw ReferenceError(`Required state option "${prop}" not specified`)
      })
    }) // # validate state properties
    states.forEach((state) => {
      for (const property in state) {
        const validator = self.constants.regex.state[property]
        if (validator && !state[property].match(validator))
          throw Error(`State "${state.name}" property "${property}" has an invalid value of "${state[property]}"`)
      }
    }) // # validate state property values
    states.forEach(function(item) {
      item.route = self.$utils.splitRoute(item.route)
    }) // # get route pattern
    self.states = states

    if (!self.$utils.stateByName(self.settings.default))
      throw Error(`State "${self.settings.default}" not found in specified states`)

    if (self.settings.fallback) {
      if (!self.$utils.stateByName(self.settings.fallback))
        throw Error(`Fallback state "${self.settings.fallback}" not found in specified states`)
    }
    else {
      self.$logger.warn(`Fallback state not specified, defaulting to "${self.settings.default}"`)
      self.settings.fallback = self.settings.default
    }
  }

  /**
   * Used to navigate with hash pattern.
   * @param {string} route - Route to relocate to.
   * @param {boolean} push - Push state after navigation.
   * @returns {Promise}
   */
  navigate (route, skipPush) {
    const self = this

    return new Promise((resolve, reject) => {
      if (!self.running) {
        self.$logger.warn('(navigate) Router has not yet been started')
        return reject()
      }

      self.location = self.settings.href + self.constants.defaults.hash + route
      var route_check = setInterval(() => {
        if (self.location.hash == self.constants.defaults.hash + route) {
          window.clearInterval(route_check)
          self.trigger('navigation', { route })
          if (!skipPush)
            self.push().then(() => resolve())
          else
            resolve()
        }
      }, self.constants.intervals.navigate)
      setTimeout(reject, self.constants.defaults.timeout)
    })
  }

  /**
   * Used to change states.
   * @param {string} name - Name of state to transition to.
   * @param {object} opts - Arguments to pass to mounted tag.
   * @returns {Promise}
   */
  push (name, opts) {
    const self = this

    return new Promise((resolve) => {
      if (!self.running) {
        self.$logger.error('(push) Router has not yet been started')
        return reject()
      }

      if (!name) {
        var state = self.$utils.stateByRoute()
        opts = self.$utils.extractRouteVars(state)
      }
      else
        var state = self.$utils.stateByName(name)

      const location = self.location.hash.split(self.constants.defaults.hash)[1]

      if (location !== state.route.route) {
        if (!state.route.variables.length) {
          self.navigate(state.route.route)
          return resolve() // # assume function will be retriggered
        }
        else {
          self.$logger.warn('(push) State does not match current route.')
          self.$logger.warn('(push) Could not re-route due to route variables.')
        }
      }

      if (self.$state && self.$state.onLeave)
        self.$state.onLeave(state) // # call onLeave, pass old state

      self.$tools.transition(state, opts)

      if (state.onEnter)
        state.onEnter(state) // # call onEnter, pass new state

      self.$state = state
      self.trigger('push')
      resolve()
    })
  }

  /**
   * Used to initialize the router and listeners.
   * @returns {Promise}
   */
  start () {
    const self = this

    return new Promise((resolve, reject) => {
      if (!self.running) {
        self.running = true

        function _start() {
          var view_check = window.setInterval(() => {
            const context = document.querySelector(self.settings.marker) || document.querySelector(`[${self.settings.marker}]`)
            if (context) {
              self.context = context
              self.push() // # route to initial state
              window.onhashchange = () => self.push()
              window.clearInterval(view_check)
              self.trigger('start')
              resolve()
            }
          }, self.constants.intervals.start) // # search for view context
          setTimeout(reject, self.constants.defaults.timeout)
        }

        if (self.location.hash.split(self.constants.defaults.hash).length !== 2) {
          self.navigate(
            self.$utils.stateByName(
              self.settings.default
            ).route.route, true).then(_start)
        }
        else
          _start()
      }
      else {
        self.$logger.error('(start) Router already running')
        reject()
      }
    })
  }

  /**
   * Used to stop the router and listeners.
   * @returns {Promise}
   */
  stop () {
    const self = this

    return new Promise((resolve, reject) => {
      if (!self.running) {
        self.$logger.error('(stop) Router was not running')
        reject()
      }
      self.running = false
      delete window.onhashchange
      self.trigger('stop')
      resolve()
    })
  }

  /**
   * Used to refresh the current route.
   */
  reload () {
    const self = this

    return new Promise((resolve, reject) => {
      if (!self.running) {
        self.$logger.error('(reload) Router has not yet been started')
        return reject()
      }

      self.push().then(() => {
        self.trigger('reload')
        resolve()
      })
    })
  }

}
<commit_msg>Added mixin init to core<commit_after>import { version } from '../package.json'
import { Constants } from './constants'
import { Logger } from './logger'
import { Tools } from './tools'
import { Utils } from './utils'

export class Router {

  /**
   * Represents the riot-view-router mixin.
   * @constructor
   * @param {riot} instance - Riot instance to target.
   * @param {object} settings - Router options.
   * @param {array} states - States for router to read from.
   * @returns {Router}
   */
  constructor (instance, settings, states) {
    var self = this

    self.constants = Constants
    self.version = version
    self.settings = {}

    self.$riot = instance
    self.$riot.observable(self)
    self.$riot.mixin({ router: self })

    self.$logger = new Logger(self)
    self.$tools = new Tools(self)
    self.$utils = new Utils(self)

    Object.defineProperty(self, 'location', {
      get: function() {
        return {
          hash: window.location.hash,
          href: window.location.href
        }
      },
      set: function(location) {
        window.history.pushState(null, null, location)
      }
    })

    self.running = false

    const requiredSettings = ['default']
    const optionalSettings = ['debugging', 'fallback', 'href', 'fragments', 'marker', 'titleRoot']
    const acceptedSettings = requiredSettings.concat(optionalSettings)
    for (const setting in settings) {
      if (acceptedSettings.indexOf(setting) === -1)
        throw Error(`Unknown setting "${setting}" is not supported`)
    } // # check for unaccepted settings
    requiredSettings.forEach(setting => {
      if (typeof settings[setting] === 'undefined')
        throw ReferenceError(`Required setting "${setting}" not specified`)
    }) // # check for required settings

    if (settings.default.indexOf(':') > -1)
      throw Error('Default state route cannot take variable parameters')

    for (const setting in self.settings) {
      const validator = self.constants.regex.settings[setting]
      if (validator && !setting.match(validator))
        throw Error(`Setting "${setting}" has an invalid value of "${settings[setting]}"`)
    }

    self.settings = Object.assign({}, settings)
    self.settings.debugging = self.settings.debugging || false
    self.settings.fragments = self.settings.fragments || true
    self.settings.marker = self.settings.marker || self.constants.defaults.marker

    if (self.settings.href)
      if (self.location.href.indexOf(self.settings.href) == -1)
        throw Error('Defined href not found within location context')

    self.settings.href = self.settings.href || self.location.href.split(self.constants.defaults.hash)[0]
    if (!self.settings.href.endsWith('/'))
      self.settings.href = `${self.settings.href}/`

    const stateProperties = ['name', 'route', 'tag']
    states = !Array.isArray(states) ? [Object.assign({}, state)] : states.map(state => Object.assign({}, state))
    stateProperties.forEach((prop) => {
      states.forEach((state) => {
        if (!state[prop])
          throw ReferenceError(`Required state option "${prop}" not specified`)
      })
    }) // # validate state properties
    states.forEach((state) => {
      for (const property in state) {
        const validator = self.constants.regex.state[property]
        if (validator && !state[property].match(validator))
          throw Error(`State "${state.name}" property "${property}" has an invalid value of "${state[property]}"`)
      }
    }) // # validate state property values
    states.forEach(function(item) {
      item.route = self.$utils.splitRoute(item.route)
    }) // # get route pattern
    self.states = states

    if (!self.$utils.stateByName(self.settings.default))
      throw Error(`State "${self.settings.default}" not found in specified states`)

    if (self.settings.fallback) {
      if (!self.$utils.stateByName(self.settings.fallback))
        throw Error(`Fallback state "${self.settings.fallback}" not found in specified states`)
    }
    else {
      self.$logger.warn(`Fallback state not specified, defaulting to "${self.settings.default}"`)
      self.settings.fallback = self.settings.default
    }
  }

  /**
   * Used to navigate with hash pattern.
   * @param {string} route - Route to relocate to.
   * @param {boolean} push - Push state after navigation.
   * @returns {Promise}
   */
  navigate (route, skipPush) {
    const self = this

    return new Promise((resolve, reject) => {
      if (!self.running) {
        self.$logger.warn('(navigate) Router has not yet been started')
        return reject()
      }

      self.location = self.settings.href + self.constants.defaults.hash + route
      var route_check = setInterval(() => {
        if (self.location.hash == self.constants.defaults.hash + route) {
          window.clearInterval(route_check)
          self.trigger('navigation', { route })
          if (!skipPush)
            self.push().then(() => resolve())
          else
            resolve()
        }
      }, self.constants.intervals.navigate)
      setTimeout(reject, self.constants.defaults.timeout)
    })
  }

  /**
   * Used to change states.
   * @param {string} name - Name of state to transition to.
   * @param {object} opts - Arguments to pass to mounted tag.
   * @returns {Promise}
   */
  push (name, opts) {
    const self = this

    return new Promise((resolve) => {
      if (!self.running) {
        self.$logger.error('(push) Router has not yet been started')
        return reject()
      }

      if (!name) {
        var state = self.$utils.stateByRoute()
        opts = self.$utils.extractRouteVars(state)
      }
      else
        var state = self.$utils.stateByName(name)

      const location = self.location.hash.split(self.constants.defaults.hash)[1]

      if (location !== state.route.route) {
        if (!state.route.variables.length) {
          self.navigate(state.route.route)
          return resolve() // # assume function will be retriggered
        }
        else {
          self.$logger.warn('(push) State does not match current route.')
          self.$logger.warn('(push) Could not re-route due to route variables.')
        }
      }

      if (self.$state && self.$state.onLeave)
        self.$state.onLeave(state) // # call onLeave, pass old state

      self.$tools.transition(state, opts)

      if (state.onEnter)
        state.onEnter(state) // # call onEnter, pass new state

      self.$state = state
      self.trigger('push')
      resolve()
    })
  }

  /**
   * Used to initialize the router and listeners.
   * @returns {Promise}
   */
  start () {
    const self = this

    return new Promise((resolve, reject) => {
      if (!self.running) {
        self.running = true

        function _start() {
          var view_check = window.setInterval(() => {
            const context = document.querySelector(self.settings.marker) || document.querySelector(`[${self.settings.marker}]`)
            if (context) {
              self.context = context
              self.push() // # route to initial state
              window.onhashchange = () => self.push()
              window.clearInterval(view_check)
              self.trigger('start')
              resolve()
            }
          }, self.constants.intervals.start) // # search for view context
          setTimeout(reject, self.constants.defaults.timeout)
        }

        if (self.location.hash.split(self.constants.defaults.hash).length !== 2) {
          self.navigate(
            self.$utils.stateByName(
              self.settings.default
            ).route.route, true).then(_start)
        }
        else
          _start()
      }
      else {
        self.$logger.error('(start) Router already running')
        reject()
      }
    })
  }

  /**
   * Used to stop the router and listeners.
   * @returns {Promise}
   */
  stop () {
    const self = this

    return new Promise((resolve, reject) => {
      if (!self.running) {
        self.$logger.error('(stop) Router was not running')
        reject()
      }
      self.running = false
      delete window.onhashchange
      self.trigger('stop')
      resolve()
    })
  }

  /**
   * Used to refresh the current route.
   */
  reload () {
    const self = this

    return new Promise((resolve, reject) => {
      if (!self.running) {
        self.$logger.error('(reload) Router has not yet been started')
        return reject()
      }

      self.push().then(() => {
        self.trigger('reload')
        resolve()
      })
    })
  }

}
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2016-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @flow
 */

import * as fs from "../util/fs.js";
import Registry from "./_base.js";

let userHome = require("user-home");
let path     = require("path");
let _        = require("lodash");
let ini      = require("ini");

function getGlobalPrefix(): string {
  if (process.env.PREFIX) {
    return process.env.PREFIX;
  } else if (process.platform === "win32") {
    // c:\node\node.exe --> prefix=c:\node\
    return path.dirname(process.execPath);
  } else {
    // /usr/local/bin/node --> prefix=/usr/local
    let prefix = path.dirname(path.dirname(process.execPath));

    // destdir only is respected on Unix
    if (process.env.DESTDIR) {
      prefix = path.join(process.env.DESTDIR, prefix);
    }

    return prefix;
  }
}

export default class NpmRegistry extends Registry {
  static filenames = ["fbkpm.json", "package.json"];

  async loadConfig(): Promise<void> {
    // docs: https://docs.npmjs.com/misc/config
    this.folder = "node_modules";

    let possibles = [
      path.join(getGlobalPrefix(), ".npmrc"),
      path.join(userHome, ".npmrc"),
      path.join(this.cwd, ".npmrc"),
    ];

    this.mergeEnv("npm_config_");

    for (let loc of possibles) {
      if (!(await fs.exists(loc))) continue;

      let config = ini.parse(await fs.readFile(loc));

      // normalise kpm offline mirror path relative to the current npmrc
      let offlineLoc = config["kpm-offline-mirror"];
      if (!this.config["kpm-offline-mirror"] && offlineLoc) {
        let mirrorLoc = config["kpm-offline-mirror"] = path.resolve(path.dirname(loc), offlineLoc);
        await fs.mkdirp(mirrorLoc);
      }

      _.defaults(this.config, config);
    }

    _.defaults(this.config, {
      registry: "http://registry.npmjs.org"
    });
  }
}
<commit_msg>Added ability to load .npmrc anywhere from cwd and up<commit_after>/**
 * Copyright (c) 2016-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @flow
 */

import * as fs from "../util/fs.js";
import Registry from "./_base.js";

let userHome = require("user-home");
let path     = require("path");
let _        = require("lodash");
let ini      = require("ini");

function getGlobalPrefix(): string {
  if (process.env.PREFIX) {
    return process.env.PREFIX;
  } else if (process.platform === "win32") {
    // c:\node\node.exe --> prefix=c:\node\
    return path.dirname(process.execPath);
  } else {
    // /usr/local/bin/node --> prefix=/usr/local
    let prefix = path.dirname(path.dirname(process.execPath));

    // destdir only is respected on Unix
    if (process.env.DESTDIR) {
      prefix = path.join(process.env.DESTDIR, prefix);
    }

    return prefix;
  }
}

export default class NpmRegistry extends Registry {
  static filenames = ["fbkpm.json", "package.json"];

  async loadConfig(): Promise<void> {
    // docs: https://docs.npmjs.com/misc/config
    this.folder = "node_modules";

    let possibles = [
      path.join(getGlobalPrefix(), ".npmrc"),
      path.join(userHome, ".npmrc"),
      path.join(this.cwd, ".npmrc"),
    ];
    let foldersFromRootToCwd = this.cwd.split(path.sep);
    while (foldersFromRootToCwd.length > 1) {
      possibles.push(path.join(foldersFromRootToCwd.join(path.sep), ".npmrc"));
      foldersFromRootToCwd.pop();
    }

    this.mergeEnv("npm_config_");

    for (let loc of possibles) {
      if (!(await fs.exists(loc))) continue;

      let config = ini.parse(await fs.readFile(loc));

      // normalise kpm offline mirror path relative to the current npmrc
      let offlineLoc = config["kpm-offline-mirror"];
      if (!this.config["kpm-offline-mirror"] && offlineLoc) {
        let mirrorLoc = config["kpm-offline-mirror"] = path.resolve(path.dirname(loc), offlineLoc);
        await fs.mkdirp(mirrorLoc);
      }

      _.defaults(this.config, config);
    }

    _.defaults(this.config, {
      registry: "http://registry.npmjs.org"
    });
  }
}
<|endoftext|>


<commit_before>/**
 * PowerTip Core
 *
 * @fileoverview  Core variables, plugin object, and API.
 * @link          http://stevenbenner.github.com/jquery-powertip/
 * @author        Steven Benner (http://stevenbenner.com/)
 * @requires      jQuery 1.7+
 */

// useful private variables
var $document = $(window.document),
	$window = $(window),
	$body = $('body'),
	// constants
	DATA_DISPLAYCONTROLLER = 'displayController',
	DATA_HASACTIVEHOVER = 'hasActiveHover',
	DATA_FORCEDOPEN = 'forcedOpen',
	DATA_HASMOUSEMOVE = 'hasMouseMove',
	DATA_MOUSEONTOTIP = 'mouseOnToPopup',
	DATA_ORIGINALTITLE = 'originalTitle',
	DATA_POWERTIP = 'powertip',
	DATA_POWERTIPJQ = 'powertipjq',
	DATA_POWERTIPTARGET = 'powertiptarget',
	RAD2DEG = 180 / Math.PI;

/**
 * Session data
 * Private properties global to all powerTip instances
 */
var session = {
	isTipOpen: false,
	isFixedTipOpen: false,
	isClosing: false,
	tipOpenImminent: false,
	activeHover: null,
	currentX: 0,
	currentY: 0,
	previousX: 0,
	previousY: 0,
	desyncTimeout: null,
	mouseTrackingActive: false
};

/**
 * Display hover tooltips on the matched elements.
 * @param {(Object|string)} opts The options object to use for the plugin, or the
 *     name of a method to invoke on the first matched element.
 * @param {*=} [arg] Argument for an invoked method (optional).
 * @return {jQuery} jQuery object for the matched selectors.
 */
$.fn.powerTip = function(opts, arg) {

	// don't do any work if there were no matched elements
	if (!this.length) {
		return this;
	}

	// handle api method calls on the plugin, e.g. powerTip('hide')
	if (typeof opts === 'string' && $.powerTip[opts]) {
		return $.powerTip[opts].call(this, this, arg);
	}

	// extend options
	var options = $.extend({}, $.fn.powerTip.defaults, opts),
		tipController = new TooltipController(options);

	// hook mouse tracking
	initMouseTracking();

	// setup the elements
	this.each(function elementSetup() {
		var $this = $(this),
			dataPowertip = $this.data(DATA_POWERTIP),
			dataElem = $this.data(DATA_POWERTIPJQ),
			dataTarget = $this.data(DATA_POWERTIPTARGET),
			title = $this.attr('title');

		// handle repeated powerTip calls on the same element by destroying
		// the original instance hooked to it and replacing it with this call
		if ($this.data(DATA_DISPLAYCONTROLLER)) {
			$.powerTip.destroy($this);
			title = $this.attr('title');
		}

		// attempt to use title attribute text if there is no data-powertip,
		// data-powertipjq or data-powertiptarget. If we do use the title
		// attribute, delete the attribute so the browser will not show it
		if (!dataPowertip && !dataTarget && !dataElem && title) {
			$this.data(DATA_POWERTIP, title);
			$this.data(DATA_ORIGINALTITLE, title);
			$this.removeAttr('title');
		}

		// create hover controllers for each element
		$this.data(
			DATA_DISPLAYCONTROLLER,
			new DisplayController($this, options, tipController)
		);
	});

	// attach events to matched elements if the manual options is not enabled
	if (!options.manual) {
		this.on({
			// mouse events
			'mouseenter.powertip': function elementMouseEnter(event) {
				elementShowAndTrack(this, event);
			},
			'mouseleave.powertip': function elementMouseLeave() {
				elementHideTip(this);
			},

			// keyboard events
			'focus.powertip': function elementFocus() {
				elementShowTip(this, true);
			},
			'blur.powertip': function elementBlur() {
				elementHideTip(this, true);
			},
			'keydown.powertip': function elementKeyDown(event) {
				// close tooltip when the escape key is pressed
				if (event.keyCode === 27) {
					elementHideTip(this, true);
				}
			}
		});
	}

	return this;

};

/**
 * Default options for the powerTip plugin.
 */
$.fn.powerTip.defaults = {
	fadeInTime: 200,
	fadeOutTime: 100,
	followMouse: false,
	popupId: 'powerTip',
	intentSensitivity: 7,
	intentPollInterval: 100,
	closeDelay: 100,
	placement: 'n',
	smartPlacement: false,
	offset: 10,
	mouseOnToPopup: false,
	manual: false
};

/**
 * Default smart placement priority lists.
 * The first item in the array is the highest priority, the last is the
 * lowest. The last item is also the default, which will be used if all
 * previous options do not fit.
 */
$.fn.powerTip.smartPlacementLists = {
	n: ['n', 'ne', 'nw', 's'],
	e: ['e', 'ne', 'se', 'w', 'nw', 'sw', 'n', 's', 'e'],
	s: ['s', 'se', 'sw', 'n'],
	w: ['w', 'nw', 'sw', 'e', 'ne', 'se', 'n', 's', 'w'],
	nw: ['nw', 'w', 'sw', 'n', 's', 'se', 'nw'],
	ne: ['ne', 'e', 'se', 'n', 's', 'sw', 'ne'],
	sw: ['sw', 'w', 'nw', 's', 'n', 'ne', 'sw'],
	se: ['se', 'e', 'ne', 's', 'n', 'nw', 'se'],
	'nw-alt': ['nw-alt', 'n', 'ne-alt', 'nw', 'ne', 'w', 'sw-alt', 's', 'se-alt', 'sw', 'se', 'e'],
	'ne-alt': ['ne-alt', 'n', 'nw-alt', 'ne', 'nw', 'e', 'se-alt', 's', 'sw-alt', 'se', 'sw', 'w'],
	'sw-alt': ['sw-alt', 's', 'se-alt', 'sw', 'se', 'w', 'nw-alt', 'n', 'ne-alt', 'nw', 'ne', 'e'],
	'se-alt': ['se-alt', 's', 'sw-alt', 'se', 'sw', 'e', 'ne-alt', 'n', 'nw-alt', 'ne', 'nw', 'w']
};

/**
 * Public API
 */
$.powerTip = {

	/**
	 * Attempts to show the tooltip for the specified element.
	 * @public
	 * @param {jQuery} element The element that the tooltip should for.
	 * @param {jQuery.Event=} event jQuery event for hover intent and mouse tracking (optional).
	 */
	showTip: function apiShowTip(element, event) {
		if (event) {
			elementShowAndTrack(element.first(), event);
		} else {
			elementShowTip(element.first(), true, true);
		}
		return element;
	},

	/**
	 * Repositions the tooltip on the element.
	 * @public
	 * @param {jQuery} element The element that the tooltip is shown for.
	 */
	resetPosition: function apiResetPosition(element) {
		element.first().data(DATA_DISPLAYCONTROLLER).resetPosition();
		return element;
	},

	/**
	 * Attempts to close any open tooltips.
	 * @public
	 * @param {jQuery=} element A specific element whose tip should be closed (optional).
	 * @param {boolean=} immediate Disable close delay (optional).
	 */
	closeTip: function apiCloseTip(element, immediate) {
		if (element) {
			elementHideTip(element.first(), immediate);
		} else {
			if (session.activeHover) {
				elementHideTip(session.activeHover, true);
			}
		}
		return element;
	},

	/**
	 * Destroy and roll back any powerTip() instance on the specified element.
	 * @public
	 * @param {jQuery} element The element with the powerTip instance.
	 */
	destroy: function apiDestroy(element) {
		return element.off('.powertip').each(function destroy() {
			var $this = $(this);

			if ($this.data(DATA_ORIGINALTITLE)) {
				$this.attr('title', $this.data(DATA_ORIGINALTITLE));
			}

			$this.removeData([
				DATA_ORIGINALTITLE,
				DATA_DISPLAYCONTROLLER,
				DATA_HASACTIVEHOVER,
				DATA_FORCEDOPEN
			]);
		});
	}

};

// API aliasing
$.powerTip.show = $.powerTip.showTip;
$.powerTip.hide = $.powerTip.closeTip;

/**
 * Creates a new CSSCordinate object.
 * @private
 * @constructor
 */
function CSSCordinate() {
	var me = this;

	// initialize object properties
	me.top = 'auto';
	me.left = 'auto';
	me.right = 'auto';

	/**
	 * Set a property to a value.
	 * @private
	 * @param {string} property The name of the property.
	 * @param {number} value The value of the property.
	 */
	me.set = function(property, value) {
		if ($.isNumeric(value)) {
			me[property] = Math.round(value);
		}
	};
}

// Common utility functions

/**
 * Asks the DisplayController for the specified element to show() its tooltip.
 * @private
 * @param {jQuery} el The element that the tooltip should be shown for.
 * @param {boolean=} immediate Skip intent testing (optional).
 * @param {boolean=} forcedOpen Ignore cursor position and force tooltip to open (optional).
 */
function elementShowTip(el, immediate, forcedOpen) {
	$(el).data(DATA_DISPLAYCONTROLLER).show(immediate, forcedOpen);
}

/**
 * Tracks the mouse cursor position specified in the event and attempts to open
 * the tooltip for the specified element.
 * @private
 * @param {jQuery} el The element that the tooltip should be shown for.
 * @param {jQuery.Event} event The event with pageX and pageY info.
 */
function elementShowAndTrack(el, event) {
	trackMouse(event);
	session.previousX = event.pageX;
	session.previousY = event.pageY;
	elementShowTip(el);
}

/**
 * Asks the DisplayController for the specified element to hide() its tooltip.
 * @private
 * @param {jQuery} el The element that the tooltip should be shown for.
 * @param {boolean=} immediate Disable close delay (optional).
 */
function elementHideTip(el, immediate) {
	$(el).data(DATA_DISPLAYCONTROLLER).hide(immediate);
}
<commit_msg>Updated argument names for core helper functions.<commit_after>/**
 * PowerTip Core
 *
 * @fileoverview  Core variables, plugin object, and API.
 * @link          http://stevenbenner.github.com/jquery-powertip/
 * @author        Steven Benner (http://stevenbenner.com/)
 * @requires      jQuery 1.7+
 */

// useful private variables
var $document = $(window.document),
	$window = $(window),
	$body = $('body'),
	// constants
	DATA_DISPLAYCONTROLLER = 'displayController',
	DATA_HASACTIVEHOVER = 'hasActiveHover',
	DATA_FORCEDOPEN = 'forcedOpen',
	DATA_HASMOUSEMOVE = 'hasMouseMove',
	DATA_MOUSEONTOTIP = 'mouseOnToPopup',
	DATA_ORIGINALTITLE = 'originalTitle',
	DATA_POWERTIP = 'powertip',
	DATA_POWERTIPJQ = 'powertipjq',
	DATA_POWERTIPTARGET = 'powertiptarget',
	RAD2DEG = 180 / Math.PI;

/**
 * Session data
 * Private properties global to all powerTip instances
 */
var session = {
	isTipOpen: false,
	isFixedTipOpen: false,
	isClosing: false,
	tipOpenImminent: false,
	activeHover: null,
	currentX: 0,
	currentY: 0,
	previousX: 0,
	previousY: 0,
	desyncTimeout: null,
	mouseTrackingActive: false
};

/**
 * Display hover tooltips on the matched elements.
 * @param {(Object|string)} opts The options object to use for the plugin, or the
 *     name of a method to invoke on the first matched element.
 * @param {*=} [arg] Argument for an invoked method (optional).
 * @return {jQuery} jQuery object for the matched selectors.
 */
$.fn.powerTip = function(opts, arg) {

	// don't do any work if there were no matched elements
	if (!this.length) {
		return this;
	}

	// handle api method calls on the plugin, e.g. powerTip('hide')
	if (typeof opts === 'string' && $.powerTip[opts]) {
		return $.powerTip[opts].call(this, this, arg);
	}

	// extend options
	var options = $.extend({}, $.fn.powerTip.defaults, opts),
		tipController = new TooltipController(options);

	// hook mouse tracking
	initMouseTracking();

	// setup the elements
	this.each(function elementSetup() {
		var $this = $(this),
			dataPowertip = $this.data(DATA_POWERTIP),
			dataElem = $this.data(DATA_POWERTIPJQ),
			dataTarget = $this.data(DATA_POWERTIPTARGET),
			title = $this.attr('title');

		// handle repeated powerTip calls on the same element by destroying
		// the original instance hooked to it and replacing it with this call
		if ($this.data(DATA_DISPLAYCONTROLLER)) {
			$.powerTip.destroy($this);
			title = $this.attr('title');
		}

		// attempt to use title attribute text if there is no data-powertip,
		// data-powertipjq or data-powertiptarget. If we do use the title
		// attribute, delete the attribute so the browser will not show it
		if (!dataPowertip && !dataTarget && !dataElem && title) {
			$this.data(DATA_POWERTIP, title);
			$this.data(DATA_ORIGINALTITLE, title);
			$this.removeAttr('title');
		}

		// create hover controllers for each element
		$this.data(
			DATA_DISPLAYCONTROLLER,
			new DisplayController($this, options, tipController)
		);
	});

	// attach events to matched elements if the manual options is not enabled
	if (!options.manual) {
		this.on({
			// mouse events
			'mouseenter.powertip': function elementMouseEnter(event) {
				elementShowAndTrack(this, event);
			},
			'mouseleave.powertip': function elementMouseLeave() {
				elementHideTip(this);
			},

			// keyboard events
			'focus.powertip': function elementFocus() {
				elementShowTip(this, true);
			},
			'blur.powertip': function elementBlur() {
				elementHideTip(this, true);
			},
			'keydown.powertip': function elementKeyDown(event) {
				// close tooltip when the escape key is pressed
				if (event.keyCode === 27) {
					elementHideTip(this, true);
				}
			}
		});
	}

	return this;

};

/**
 * Default options for the powerTip plugin.
 */
$.fn.powerTip.defaults = {
	fadeInTime: 200,
	fadeOutTime: 100,
	followMouse: false,
	popupId: 'powerTip',
	intentSensitivity: 7,
	intentPollInterval: 100,
	closeDelay: 100,
	placement: 'n',
	smartPlacement: false,
	offset: 10,
	mouseOnToPopup: false,
	manual: false
};

/**
 * Default smart placement priority lists.
 * The first item in the array is the highest priority, the last is the
 * lowest. The last item is also the default, which will be used if all
 * previous options do not fit.
 */
$.fn.powerTip.smartPlacementLists = {
	n: ['n', 'ne', 'nw', 's'],
	e: ['e', 'ne', 'se', 'w', 'nw', 'sw', 'n', 's', 'e'],
	s: ['s', 'se', 'sw', 'n'],
	w: ['w', 'nw', 'sw', 'e', 'ne', 'se', 'n', 's', 'w'],
	nw: ['nw', 'w', 'sw', 'n', 's', 'se', 'nw'],
	ne: ['ne', 'e', 'se', 'n', 's', 'sw', 'ne'],
	sw: ['sw', 'w', 'nw', 's', 'n', 'ne', 'sw'],
	se: ['se', 'e', 'ne', 's', 'n', 'nw', 'se'],
	'nw-alt': ['nw-alt', 'n', 'ne-alt', 'nw', 'ne', 'w', 'sw-alt', 's', 'se-alt', 'sw', 'se', 'e'],
	'ne-alt': ['ne-alt', 'n', 'nw-alt', 'ne', 'nw', 'e', 'se-alt', 's', 'sw-alt', 'se', 'sw', 'w'],
	'sw-alt': ['sw-alt', 's', 'se-alt', 'sw', 'se', 'w', 'nw-alt', 'n', 'ne-alt', 'nw', 'ne', 'e'],
	'se-alt': ['se-alt', 's', 'sw-alt', 'se', 'sw', 'e', 'ne-alt', 'n', 'nw-alt', 'ne', 'nw', 'w']
};

/**
 * Public API
 */
$.powerTip = {

	/**
	 * Attempts to show the tooltip for the specified element.
	 * @public
	 * @param {jQuery} element The element that the tooltip should for.
	 * @param {jQuery.Event=} event jQuery event for hover intent and mouse tracking (optional).
	 */
	showTip: function apiShowTip(element, event) {
		if (event) {
			elementShowAndTrack(element.first(), event);
		} else {
			elementShowTip(element.first(), true, true);
		}
		return element;
	},

	/**
	 * Repositions the tooltip on the element.
	 * @public
	 * @param {jQuery} element The element that the tooltip is shown for.
	 */
	resetPosition: function apiResetPosition(element) {
		element.first().data(DATA_DISPLAYCONTROLLER).resetPosition();
		return element;
	},

	/**
	 * Attempts to close any open tooltips.
	 * @public
	 * @param {jQuery=} element A specific element whose tip should be closed (optional).
	 * @param {boolean=} immediate Disable close delay (optional).
	 */
	closeTip: function apiCloseTip(element, immediate) {
		if (element) {
			elementHideTip(element.first(), immediate);
		} else {
			if (session.activeHover) {
				elementHideTip(session.activeHover, true);
			}
		}
		return element;
	},

	/**
	 * Destroy and roll back any powerTip() instance on the specified element.
	 * @public
	 * @param {jQuery} element The element with the powerTip instance.
	 */
	destroy: function apiDestroy(element) {
		return element.off('.powertip').each(function destroy() {
			var $this = $(this);

			if ($this.data(DATA_ORIGINALTITLE)) {
				$this.attr('title', $this.data(DATA_ORIGINALTITLE));
			}

			$this.removeData([
				DATA_ORIGINALTITLE,
				DATA_DISPLAYCONTROLLER,
				DATA_HASACTIVEHOVER,
				DATA_FORCEDOPEN
			]);
		});
	}

};

// API aliasing
$.powerTip.show = $.powerTip.showTip;
$.powerTip.hide = $.powerTip.closeTip;

/**
 * Creates a new CSSCordinate object.
 * @private
 * @constructor
 */
function CSSCordinate() {
	var me = this;

	// initialize object properties
	me.top = 'auto';
	me.left = 'auto';
	me.right = 'auto';

	/**
	 * Set a property to a value.
	 * @private
	 * @param {string} property The name of the property.
	 * @param {number} value The value of the property.
	 */
	me.set = function(property, value) {
		if ($.isNumeric(value)) {
			me[property] = Math.round(value);
		}
	};
}

// Common utility functions

/**
 * Asks the DisplayController for the specified element to show() its tooltip.
 * @private
 * @param {jQuery} element The element that the tooltip should be shown for.
 * @param {boolean=} immediate Skip intent testing (optional).
 * @param {boolean=} forcedOpen Ignore cursor position and force tooltip to open (optional).
 */
function elementShowTip(element, immediate, forcedOpen) {
	$(element).data(DATA_DISPLAYCONTROLLER).show(immediate, forcedOpen);
}

/**
 * Tracks the mouse cursor position specified in the event and attempts to open
 * the tooltip for the specified element.
 * @private
 * @param {jQuery} element The element that the tooltip should be shown for.
 * @param {jQuery.Event} event The event with pageX and pageY info.
 */
function elementShowAndTrack(element, event) {
	trackMouse(event);
	session.previousX = event.pageX;
	session.previousY = event.pageY;
	elementShowTip(element);
}

/**
 * Asks the DisplayController for the specified element to hide() its tooltip.
 * @private
 * @param {jQuery} element The element that the tooltip should be shown for.
 * @param {boolean=} immediate Disable close delay (optional).
 */
function elementHideTip(element, immediate) {
	$(element).data(DATA_DISPLAYCONTROLLER).hide(immediate);
}
<|endoftext|>


<commit_before>/**
 * The Core module provides the Editable class that defines the Editable.JS
 * API and is the main entry point for Editable.JS.
 * It also provides the cursor module for cross-browser cursors, and the dom
 * submodule.
 *
 * @module core
 */

/**
 * Constructor for the Editable.JS API that is externally visible.
 * Note that the Editable literal is defined
 * first in editable.prefix in order for it to be the only externally visible
 * variable.
 *
 * @class Editable
 */
Editable = function(userConfig) {
  this.config = $.extend(true, {}, config, userConfig);
  this.win = this.config.window || window;
  this.editableSelector = '.' + this.config.editableClass;

  if (!rangy.initialized) {
    rangy.init();
  }

  this.dispatcher = new Dispatcher(this);
  if (this.config.defaultBehavior === true) {
    this.dispatcher.on(createDefaultEvents(this));
  }
};

window.Editable = Editable;

/**
 * Adds the Editable.JS API to the given target elements.
 * Opposite of {{#crossLink "Editable/remove"}}{{/crossLink}}.
 * Calls dispatcher.setup to setup all event listeners.
 *
 * @method add
 * @param {HTMLElement|Array(HTMLElement)|String} target A HTMLElement, an
 *    array of HTMLElement or a query selector representing the target where
 *    the API should be added on.
 * @param {Object} [elementConfiguration={}] Configuration options override.
 * @chainable
 */
Editable.prototype.add = function(target, elementConfiguration) {
  var elemConfig = $.extend(true, {}, config, elementConfiguration);
  // todo: store element configuration
  this.enable($(target));

  // todo: check css whitespace settings
  return this;
};

/**
 * Removes the Editable.JS API from the given target elements.
 * Opposite of {{#crossLink "Editable/add"}}{{/crossLink}}.
 *
 * @method remove
 * @param {HTMLElement|Array(HTMLElement)|String} target A HTMLElement, an
 *    array of HTMLElement or a query selector representing the target where
 *    the API should be removed from.
 * @chainable
 */
Editable.prototype.remove = function(target) {
  var $target = $(target);
  this.disable($target);
  $target.removeClass(config.editableDisabledClass);
  return this;
};

/**
 * Removes the Editable.JS API from the given target elements.
 * The target elements are marked as disabled.
 *
 * @method disable
 * @param { jQuery element | undefined  } target editable root element(s)
 *    If no param is specified all editables are disabled.
 * @chainable
 */
Editable.prototype.disable = function($elem) {
  var body = this.win.document.body;
  $elem = $elem || $('.' + config.editableClass, body);
  $elem
    .removeAttr('contenteditable')
    .removeClass(config.editableClass)
    .addClass(config.editableDisabledClass);

  return this;
};

/**
 * Adds the Editable.JS API to the given target elements.
 *
 * @method enable
 * @param { jQuery element | undefined } target editable root element(s)
 *    If no param is specified all editables marked as disabled are enabled.
 * @chainable
 */
Editable.prototype.enable = function($elem, normalize) {
  var body = this.win.document.body;
  $elem = $elem || $('.' + config.editableDisabledClass, body);
  $elem
    .attr('contenteditable', true)
    .removeClass(config.editableDisabledClass)
    .addClass(config.editableClass);

  if (normalize) {
    $elem.each(function(index, el) {
      content.normalizeTags(el);
      content.normalizeSpaces(el);
    });
  }

  return this;
};

/**
 * Temporarily disable an editable.
 * Can be used to prevent text selction while dragging an element
 * for example.
 *
 * @method suspend
 * @param jQuery object
 */
Editable.prototype.suspend = function($elem) {
  var body = this.win.document.body;
  $elem = $elem || $('.' + config.editableClass, body);
  $elem.removeAttr('contenteditable');
  return this;
};

/**
 * Reverse the effects of suspend()
 *
 * @method continue
 * @param jQuery object
 */
Editable.prototype.continue = function($elem) {
  var body = this.win.document.body;
  $elem = $elem || $('.' + config.editableClass, body);
  $elem.attr('contenteditable', true);
  return this;
};

/**
 * Set the cursor inside of an editable block.
 *
 * @method createCursor
 * @param position 'beginning', 'end', 'before', 'after'
 */
Editable.prototype.createCursor = function(element, position) {
  var cursor;
  var $host = $(element).closest(this.editableSelector);
  position = position || 'beginning';

  if ($host.length) {
    var range = rangy.createRange();

    if (position === 'beginning' || position === 'end') {
      range.selectNodeContents(element);
      range.collapse(position === 'beginning' ? true : false);
    } else if (element !== $host[0]) {
      if (position === 'before') {
        range.setStartBefore(element);
        range.setEndBefore(element);
      } else if (position === 'after') {
        range.setStartAfter(element);
        range.setEndAfter(element);
      }
    } else {
      error('EditableJS: cannot create cursor outside of an editable block.');
    }

    cursor = new Cursor($host[0], range);
  }

  return cursor;
};

Editable.prototype.createCursorAtBeginning = function(element) {
  return this.createCursor(element, 'beginning');
};

Editable.prototype.createCursorAtEnd = function(element) {
  return this.createCursor(element, 'end');
};

Editable.prototype.createCursorBefore = function(element) {
  return this.createCursor(element, 'before');
};

Editable.prototype.createCursorAfter = function(element) {
  return this.createCursor(element, 'after');
};

Editable.prototype.getContent = function(element) {
  return content.extractContent(element);
};

/**
 * Get the current selection.
 * Only returns something if the selection is within an editable element.
 * If you pass an editable host as param it only returns something if the selection is inside this
 * very editable element.
 *
 * @param {DOMNode} Optional. An editable host where the selection needs to be contained.
 * @returns A Cursor or Selection object or undefined.
 */
Editable.prototype.getSelection = function(editableHost) {
  var selection = this.dispatcher.selectionWatcher.getFreshSelection();
  if (editableHost && selection) {
    var range = selection.range;
    // check if the selection is inside the editableHost
    if (range.compareNode(editableHost) !== range.NODE_BEFORE_AND_AFTER) {
      selection = undefined;
    }
  }
  return selection;
};

/**
 * Enable spellchecking
 *
 * @chainable
 */
Editable.prototype.setupSpellcheck = function(spellcheckConfig) {
  this.spellcheck = new Spellcheck(this, spellcheckConfig);

  return this;
};

/**
 * Subscribe a callback function to a custom event fired by the API.
 *
 * @param {String} event The name of the event.
 * @param {Function} handler The callback to execute in response to the
 *     event.
 *
 * @chainable
 */
Editable.prototype.on = function(event, handler) {
  // TODO throw error if event is not one of EVENTS
  // TODO throw error if handler is not a function
  this.dispatcher.on(event, handler);
  return this;
};

/**
 * Unsubscribe a callback function from a custom event fired by the API.
 * Opposite of {{#crossLink "Editable/on"}}{{/crossLink}}.
 *
 * @param {String} event The name of the event.
 * @param {Function} handler The callback to remove from the
 *     event or the special value false to remove all callbacks.
 *
 * @chainable
 */
Editable.prototype.off = function(event, handler) {
  var args = Array.prototype.slice.call(arguments);
  this.dispatcher.off.apply(this.dispatcher, args);
  return this;
};

/**
 * Unsubscribe all callbacks and event listeners.
 *
 * @chainable
 */
Editable.prototype.unload = function() {
  this.dispatcher.unload();
  return this;
};

/**
 * Generate a callback function to subscribe to an event.
 *
 * @method createEventSubscriber
 * @param {String} Event name
 */
var createEventSubscriber = function(name) {
  Editable.prototype[name] = function(handler) {
    return this.on(name, handler);
  };
};

/**
 * Set up callback functions for several events.
 */
var events = ['focus', 'blur', 'flow', 'selection', 'cursor', 'newline', 'insert',
              'split', 'merge', 'empty', 'change', 'switch', 'move', 'clipboard'];

for (var i = 0; i < events.length; ++i) {
  var eventName = events[i];
  createEventSubscriber(eventName);
}
<commit_msg>Fix bug in editable.getSelection()<commit_after>/**
 * The Core module provides the Editable class that defines the Editable.JS
 * API and is the main entry point for Editable.JS.
 * It also provides the cursor module for cross-browser cursors, and the dom
 * submodule.
 *
 * @module core
 */

/**
 * Constructor for the Editable.JS API that is externally visible.
 * Note that the Editable literal is defined
 * first in editable.prefix in order for it to be the only externally visible
 * variable.
 *
 * @class Editable
 */
Editable = function(userConfig) {
  this.config = $.extend(true, {}, config, userConfig);
  this.win = this.config.window || window;
  this.editableSelector = '.' + this.config.editableClass;

  if (!rangy.initialized) {
    rangy.init();
  }

  this.dispatcher = new Dispatcher(this);
  if (this.config.defaultBehavior === true) {
    this.dispatcher.on(createDefaultEvents(this));
  }
};

window.Editable = Editable;

/**
 * Adds the Editable.JS API to the given target elements.
 * Opposite of {{#crossLink "Editable/remove"}}{{/crossLink}}.
 * Calls dispatcher.setup to setup all event listeners.
 *
 * @method add
 * @param {HTMLElement|Array(HTMLElement)|String} target A HTMLElement, an
 *    array of HTMLElement or a query selector representing the target where
 *    the API should be added on.
 * @param {Object} [elementConfiguration={}] Configuration options override.
 * @chainable
 */
Editable.prototype.add = function(target, elementConfiguration) {
  var elemConfig = $.extend(true, {}, config, elementConfiguration);
  // todo: store element configuration
  this.enable($(target));

  // todo: check css whitespace settings
  return this;
};

/**
 * Removes the Editable.JS API from the given target elements.
 * Opposite of {{#crossLink "Editable/add"}}{{/crossLink}}.
 *
 * @method remove
 * @param {HTMLElement|Array(HTMLElement)|String} target A HTMLElement, an
 *    array of HTMLElement or a query selector representing the target where
 *    the API should be removed from.
 * @chainable
 */
Editable.prototype.remove = function(target) {
  var $target = $(target);
  this.disable($target);
  $target.removeClass(config.editableDisabledClass);
  return this;
};

/**
 * Removes the Editable.JS API from the given target elements.
 * The target elements are marked as disabled.
 *
 * @method disable
 * @param { jQuery element | undefined  } target editable root element(s)
 *    If no param is specified all editables are disabled.
 * @chainable
 */
Editable.prototype.disable = function($elem) {
  var body = this.win.document.body;
  $elem = $elem || $('.' + config.editableClass, body);
  $elem
    .removeAttr('contenteditable')
    .removeClass(config.editableClass)
    .addClass(config.editableDisabledClass);

  return this;
};

/**
 * Adds the Editable.JS API to the given target elements.
 *
 * @method enable
 * @param { jQuery element | undefined } target editable root element(s)
 *    If no param is specified all editables marked as disabled are enabled.
 * @chainable
 */
Editable.prototype.enable = function($elem, normalize) {
  var body = this.win.document.body;
  $elem = $elem || $('.' + config.editableDisabledClass, body);
  $elem
    .attr('contenteditable', true)
    .removeClass(config.editableDisabledClass)
    .addClass(config.editableClass);

  if (normalize) {
    $elem.each(function(index, el) {
      content.normalizeTags(el);
      content.normalizeSpaces(el);
    });
  }

  return this;
};

/**
 * Temporarily disable an editable.
 * Can be used to prevent text selction while dragging an element
 * for example.
 *
 * @method suspend
 * @param jQuery object
 */
Editable.prototype.suspend = function($elem) {
  var body = this.win.document.body;
  $elem = $elem || $('.' + config.editableClass, body);
  $elem.removeAttr('contenteditable');
  return this;
};

/**
 * Reverse the effects of suspend()
 *
 * @method continue
 * @param jQuery object
 */
Editable.prototype.continue = function($elem) {
  var body = this.win.document.body;
  $elem = $elem || $('.' + config.editableClass, body);
  $elem.attr('contenteditable', true);
  return this;
};

/**
 * Set the cursor inside of an editable block.
 *
 * @method createCursor
 * @param position 'beginning', 'end', 'before', 'after'
 */
Editable.prototype.createCursor = function(element, position) {
  var cursor;
  var $host = $(element).closest(this.editableSelector);
  position = position || 'beginning';

  if ($host.length) {
    var range = rangy.createRange();

    if (position === 'beginning' || position === 'end') {
      range.selectNodeContents(element);
      range.collapse(position === 'beginning' ? true : false);
    } else if (element !== $host[0]) {
      if (position === 'before') {
        range.setStartBefore(element);
        range.setEndBefore(element);
      } else if (position === 'after') {
        range.setStartAfter(element);
        range.setEndAfter(element);
      }
    } else {
      error('EditableJS: cannot create cursor outside of an editable block.');
    }

    cursor = new Cursor($host[0], range);
  }

  return cursor;
};

Editable.prototype.createCursorAtBeginning = function(element) {
  return this.createCursor(element, 'beginning');
};

Editable.prototype.createCursorAtEnd = function(element) {
  return this.createCursor(element, 'end');
};

Editable.prototype.createCursorBefore = function(element) {
  return this.createCursor(element, 'before');
};

Editable.prototype.createCursorAfter = function(element) {
  return this.createCursor(element, 'after');
};

Editable.prototype.getContent = function(element) {
  return content.extractContent(element);
};

/**
 * Get the current selection.
 * Only returns something if the selection is within an editable element.
 * If you pass an editable host as param it only returns something if the selection is inside this
 * very editable element.
 *
 * @param {DOMNode} Optional. An editable host where the selection needs to be contained.
 * @returns A Cursor or Selection object or undefined.
 */
Editable.prototype.getSelection = function(editableHost) {
  var selection = this.dispatcher.selectionWatcher.getFreshSelection();
  if (editableHost && selection) {
    var range = selection.range;
    // Check if the selection is inside the editableHost
    // The try...catch is required if the editableHost was removed from the DOM.
    try {
      if (range.compareNode(editableHost) !== range.NODE_BEFORE_AND_AFTER) {
        selection = undefined;
      }
    } catch (e) {
      selection = undefined;
    }
  }
  return selection;
};

/**
 * Enable spellchecking
 *
 * @chainable
 */
Editable.prototype.setupSpellcheck = function(spellcheckConfig) {
  this.spellcheck = new Spellcheck(this, spellcheckConfig);

  return this;
};

/**
 * Subscribe a callback function to a custom event fired by the API.
 *
 * @param {String} event The name of the event.
 * @param {Function} handler The callback to execute in response to the
 *     event.
 *
 * @chainable
 */
Editable.prototype.on = function(event, handler) {
  // TODO throw error if event is not one of EVENTS
  // TODO throw error if handler is not a function
  this.dispatcher.on(event, handler);
  return this;
};

/**
 * Unsubscribe a callback function from a custom event fired by the API.
 * Opposite of {{#crossLink "Editable/on"}}{{/crossLink}}.
 *
 * @param {String} event The name of the event.
 * @param {Function} handler The callback to remove from the
 *     event or the special value false to remove all callbacks.
 *
 * @chainable
 */
Editable.prototype.off = function(event, handler) {
  var args = Array.prototype.slice.call(arguments);
  this.dispatcher.off.apply(this.dispatcher, args);
  return this;
};

/**
 * Unsubscribe all callbacks and event listeners.
 *
 * @chainable
 */
Editable.prototype.unload = function() {
  this.dispatcher.unload();
  return this;
};

/**
 * Generate a callback function to subscribe to an event.
 *
 * @method createEventSubscriber
 * @param {String} Event name
 */
var createEventSubscriber = function(name) {
  Editable.prototype[name] = function(handler) {
    return this.on(name, handler);
  };
};

/**
 * Set up callback functions for several events.
 */
var events = ['focus', 'blur', 'flow', 'selection', 'cursor', 'newline', 'insert',
              'split', 'merge', 'empty', 'change', 'switch', 'move', 'clipboard'];

for (var i = 0; i < events.length; ++i) {
  var eventName = events[i];
  createEventSubscriber(eventName);
}
<|endoftext|>


<commit_before>'use strict';

import d3 from 'd3';
import Graph from '../graph';
import Layouter from '../layout';
import verticesRenderer from './vertices-renderer';
import edgesRenderer from './edges-renderer';
import TextVertexRenderer from './vertex-renderer/text-vertex-renderer';
import CurvedEdgeRenderer from './edge-renderer/curved-edge-renderer';
import defineAccessors from '../utils/define-accessors';

const render = ({vertexVisibility, edgeVisibility, layouter, vertexRenderer, edgeRenderer}) => {
  return (selection) => {
    selection.each(function (gOrig) {
      const g = new Graph();
      for (const u of gOrig.vertices()) {
        g.addVertex(u, gOrig.vertex(u));
      }
      for (const [u, v] of gOrig.edges()) {
        const ud = gOrig.vertex(u),
              vd = gOrig.vertex(v),
              d = gOrig.edge(u, v);
        if (edgeVisibility({u, v, ud, vd, d})) {
          g.addEdge(u, v, gOrig.edge(u, v));
        }
      }
      for (const u of g.vertices()) {
        if (!vertexVisibility({u, d: g.vertex(u)})) {
          for (const v of g.inVertices(u)) {
            for (const w of g.outVertices(u)) {
              g.addEdge(v, w);
            }
          }
          g.removeVertex(u);
        }
      }

      const positions = layouter.layout(g);

      const element = d3.select(this);

      let contentsSelection = element.selectAll('g.contents');
      if (contentsSelection.empty()) {
        contentsSelection = element.append('g')
          .classed('contents', true);
      }
      let edgesSelection = element.selectAll('g.edges');
      if (edgesSelection.empty()) {
        edgesSelection = contentsSelection.append('g')
          .classed('edges', true)
          .datum({});
      }
      let verticesSelection = element.selectAll('g.vertices');
      if (verticesSelection.empty()) {
        verticesSelection = contentsSelection.append('g')
          .classed('vertices', true)
          .datum({});
      }

      const vertices = verticesSelection.datum(),
            edges = edgesSelection.datum(),
            activeVertices = new Set(),
            activeEdges = new Set();

      for (const u of g.vertices()) {
        const d = g.vertex(u);
        if (vertices[u] === undefined) {
          vertices[u] = {
            key: u,
            x: 0,
            y: 0,
            data: d
          };
        }
        vertices[u].px = vertices[u].x;
        vertices[u].py = vertices[u].y;
        vertices[u].x = positions.vertices[u].x;
        vertices[u].y = positions.vertices[u].y;
        vertices[u].width = positions.vertices[u].width;
        vertices[u].height = positions.vertices[u].height;
        activeVertices.add(u.toString());
      }

      for (const [u, v] of g.edges()) {
        const key = `${u}:${v}`;
        if (edges[key] === undefined) {
          edges[key] = {
            key: key,
            source: vertices[u],
            target: vertices[v],
            points: [[vertices[u].px, vertices[u].py], [vertices[u].px, vertices[u].py], [vertices[v].px, vertices[v].py], [vertices[v].px, vertices[v].py]],
            data: g.edge(u, v)
          };
        }
        edges[key].ppoints = edges[key].points;
        edges[key].points = positions.edges[u][v].points;
        edges[key].width = positions.edges[u][v].width;
        activeEdges.add(key);
      }

      for (const key in vertices) {
        if (!activeVertices.has(key)) {
          delete vertices[key];
        }
      }
      for (const key in edges) {
        if (!activeEdges.has(key)) {
          delete edges[key];
        }
      }
    });

    selection.selectAll('g.edges')
      .call(edgesRenderer(edgeRenderer));
    selection.selectAll('g.vertices')
      .call(verticesRenderer(vertexRenderer));
  };
};

class Renderer {
  constructor() {
    defineAccessors(this, {}, {
      vertexVisibility: () => true,
      edgeVisibility: () => true,
      layouter: new Layouter()
        .layerMargin(200)
        .vertexMargin(3)
        .edgeMargin(3),
      vertexRenderer: new TextVertexRenderer(),
      edgeRenderer: new CurvedEdgeRenderer()
    });
  }

  render() {
    return render({
      vertexVisibility: this.vertexVisibility(),
      edgeVisibility: this.edgeVisibility(),
      layouter: this.layouter(),
      vertexRenderer: this.vertexRenderer(),
      edgeRenderer: this.edgeRenderer()
    });
  }
}

export default Renderer;
<commit_msg>Refactor renderer<commit_after>'use strict';

import d3 from 'd3';
import Graph from '../graph';
import Layouter from '../layout';
import verticesRenderer from './vertices-renderer';
import edgesRenderer from './edges-renderer';
import TextVertexRenderer from './vertex-renderer/text-vertex-renderer';
import CurvedEdgeRenderer from './edge-renderer/curved-edge-renderer';
import accessor from '../utils/accessor';

const render = ({vertexVisibility, edgeVisibility, layouter, vertexRenderer, edgeRenderer}) => {
  return (selection) => {
    selection.each(function (gOrig) {
      const g = new Graph();
      for (const u of gOrig.vertices()) {
        g.addVertex(u, gOrig.vertex(u));
      }
      for (const [u, v] of gOrig.edges()) {
        const ud = gOrig.vertex(u),
              vd = gOrig.vertex(v),
              d = gOrig.edge(u, v);
        if (edgeVisibility({u, v, ud, vd, d})) {
          g.addEdge(u, v, gOrig.edge(u, v));
        }
      }
      for (const u of g.vertices()) {
        if (!vertexVisibility({u, d: g.vertex(u)})) {
          for (const v of g.inVertices(u)) {
            for (const w of g.outVertices(u)) {
              g.addEdge(v, w);
            }
          }
          g.removeVertex(u);
        }
      }

      const positions = layouter.layout(g);

      const element = d3.select(this);

      let contentsSelection = element.selectAll('g.contents');
      if (contentsSelection.empty()) {
        contentsSelection = element.append('g')
          .classed('contents', true);
      }
      let edgesSelection = element.selectAll('g.edges');
      if (edgesSelection.empty()) {
        edgesSelection = contentsSelection.append('g')
          .classed('edges', true)
          .datum({});
      }
      let verticesSelection = element.selectAll('g.vertices');
      if (verticesSelection.empty()) {
        verticesSelection = contentsSelection.append('g')
          .classed('vertices', true)
          .datum({});
      }

      const vertices = verticesSelection.datum(),
            edges = edgesSelection.datum(),
            activeVertices = new Set(),
            activeEdges = new Set();

      for (const u of g.vertices()) {
        const d = g.vertex(u);
        if (vertices[u] === undefined) {
          vertices[u] = {
            key: u,
            x: 0,
            y: 0,
            data: d
          };
        }
        vertices[u].px = vertices[u].x;
        vertices[u].py = vertices[u].y;
        vertices[u].x = positions.vertices[u].x;
        vertices[u].y = positions.vertices[u].y;
        vertices[u].width = positions.vertices[u].width;
        vertices[u].height = positions.vertices[u].height;
        activeVertices.add(u.toString());
      }

      for (const [u, v] of g.edges()) {
        const key = `${u}:${v}`;
        if (edges[key] === undefined) {
          edges[key] = {
            key: key,
            source: vertices[u],
            target: vertices[v],
            points: [[vertices[u].px, vertices[u].py], [vertices[u].px, vertices[u].py], [vertices[v].px, vertices[v].py], [vertices[v].px, vertices[v].py]],
            data: g.edge(u, v)
          };
        }
        edges[key].ppoints = edges[key].points;
        edges[key].points = positions.edges[u][v].points;
        edges[key].width = positions.edges[u][v].width;
        activeEdges.add(key);
      }

      for (const key in vertices) {
        if (!activeVertices.has(key)) {
          delete vertices[key];
        }
      }
      for (const key in edges) {
        if (!activeEdges.has(key)) {
          delete edges[key];
        }
      }
    });

    selection.selectAll('g.edges')
      .call(edgesRenderer(edgeRenderer));
    selection.selectAll('g.vertices')
      .call(verticesRenderer(vertexRenderer));
  };
};

const privates = new WeakMap();

class Renderer {
  constructor() {
    privates.set(this, {
      vertexVisibility: () => true,
      edgeVisibility: () => true,
      layouter: new Layouter()
        .layerMargin(200)
        .vertexMargin(3)
        .edgeMargin(3),
      vertexRenderer: new TextVertexRenderer(),
      edgeRenderer: new CurvedEdgeRenderer()
    });
  }

  render() {
    return render({
      vertexVisibility: this.vertexVisibility(),
      edgeVisibility: this.edgeVisibility(),
      layouter: this.layouter(),
      vertexRenderer: this.vertexRenderer(),
      edgeRenderer: this.edgeRenderer()
    });
  }

  vertexVisibility(arg) {
    return accessor(this, privates, 'vertexVisibility', arguments);
  }

  edgeVisibility(arg) {
    return accessor(this, privates, 'edgeVisibility', arguments);
  }

  layouter(arg) {
    return accessor(this, privates, 'layouter', arguments);
  }

  vertexRenderer(arg) {
    return accessor(this, privates, 'vertexRenderer', arguments);
  }

  edgeRenderer(arg) {
    return accessor(this, privates, 'edgeRenderer', arguments);
  }
}

export default Renderer;
<|endoftext|>