<commit_before>'use strict';
// var debug = require('debug')('metalsmith-navigation');
var merge = require('merge');
var path = require('path');

// var util = require('util');
// var inspect = function(obj){
//     console.log(util.inspect(obj, {
//         depth: null,
//         showHidden: true,
//         colors: true
//     }));
// };

var SETTINGS_DEFAULT = {

    /*
     * metadata key all navs will be assigned to metatdata[navListProperty]
     * not set if false
     */
    navListProperty: 'navs',

    /*
     * if true, paths will be transformed to use metalsmith-permalinks
     * metalsmith-permalinks must be run before metalsmith-navigation
     */
    permalinks: false
};

var NAV_CONFIG_DEFAULT = {

    /*
     * sortby function or property name
     * function example: function(navNode){ return navNode.getValueToSortBy(); }
     */
    sortBy: false,

    /*
     * if true nodes will be sorted by path before sortBy
     * if false the sorting will not be stable unless ALL nodes have a unique sort value
     */
    sortByNameFirst: true,

    /*
     * to be included in this nav config, a file's metadata[filterProperty] must equal filterValue
     * ex:
     *   navConfigs key = 'footer'
     *   filterProperty = 'nav_group'
     *   files only added if files[path].nav_group == 'footer'
     */
    filterProperty: false,

    /*
     * if false nav name (navConfigs key) used instead
     * if files[path][filterProperty] equals or contains (string or array containing) filterValue
     * it will be included
     */
    filterValue: false,

    /*
     * the file object property that breadcrumb array is assigned to on each file object
     * breadcrumbs not generated or set if false
     * typically only one navConfig should generate breadcrumbs, often one specifically for them
     */
    breadcrumbProperty: 'breadcrumb_path',

    /**
     * the file object property that the nav path is assigned to on each file object, not set if false
     */
    pathProperty: 'nav_path',

    /**
     * the file object property that an array of nav child nodes will be assigned to
     */
    childrenProperty: 'nav_children',

    /*
     * if a file and sibling dir have matching names the file will be used as the parent in the nav tree
     * ex: /foo /foo.html
     */
    mergeMatchingFilesAndDirs: true,

    /*
     * if ALL dirs should be included as nav nodes
     */
    includeDirs: false

};

/**
 This should be replaced by a module such as a custom lodash.sortBy build. A suitable
 module (allowing property key or simple callback) could not be found at the time of this writting
 **/

/**
 * Returns a (stably) sorted copy of list, ranked in ascending order by the
 * results of running each value through sortBy. (derrived from underscore.js _.sortBy())
 * @method arraySortBy
 * @param {Array} list - array to be sorted
 * @param {Function|String} sortBy - ( function(item){ return item.sort_property; } )
 *    If string used as the name of the property to sort by (eg. length).
 * @return {Array} copy of sorted array
 */
var arraySortBy = function(list, sortBy) {

    if(typeof sortBy !== 'function'){
        var sortByKey = sortBy;
        sortBy = function(obj){
            return obj[sortByKey];
        };
    }

    var mapped = list.map(function(value, index, list) {
        return {
            value: value,
            index: index,
            criteria: sortBy(value, index, list)
        };
    });

    var compareFunction = function(left, right) {
        var a = left.criteria,
            b = right.criteria;
        if (a !== b) {
            if (a > b || a === void 0) return 1;
            if (a < b || b === void 0) return -1;
        }
        return left.index < right.index ? -1 : 1;
    };

    mapped.sort(compareFunction);

    return mapped.map(function(item){
        return item.value;
    });
};

/**
 * @method eachTreeNode
 * @param {Array} tree - nav node tree
 * @param {Function} callback - function(node, parent, depth), if callback returns false the node will be removed
 * @return {Array} tree
 */
var eachTreeNode = function(tree, callback){
    var iterate = function(nodes, parent, depth){
        depth = (depth || 0) + 1;
        for(var i = nodes.length - 1; i >= 0; i--){
            var node = nodes[i];
            var result = callback(node, parent, depth);
            if(result === false){
                nodes.splice(i, 1);
            }
            iterate(node.children, node, depth);
        }
    };
    iterate(tree);
    return tree;
};

/**
 * Convert list of files to tree structure
 * @method pathsToTree
 * @param {Array} paths - list of file paths to act on.
 * @return Array
 */
var pathsToTree = function(paths){
    var items = [];
    for(var i = 0, l = paths.length; i < l; i++) {
        var path = paths[i];
        var name = path[0];
        var rest = path.slice(1);
        var item = null;
        for(var j = 0, m = items.length; j < m; j++) {
            if(items[j].name === name) {
                item = items[j];
                break;
            }
        }

        if(item === null) {
            item = {
                name: name,
                path: null,
                type: null,
                sort: 0,
                children: []
            };
            items.push(item);
        }
        if(rest.length > 0) {
            item.children.push(rest);
        }
    }
    for(i = 0, l = items.length; i < l; i++) {
        var node = items[i];
        node.children = pathsToTree(node.children);
    }
    return items;
};

/**
 * Loop over each node and do the following:
 *     set node.path - full path to file.
 *     set node.type - 'file' or 'dir'.
 *
 * @method addNodeMetadata
 * @param {Array} tree - tree of nodes to act on
 * @param {Array} files - List of files being processed by metalsmith.
 * @param {String} sortBy - Sort by property key.
 * @return {Array} converted array (same obj as tree param)
 */
var addNodeMetadata = function(tree, files, sortBy){
    var fileList = Object.keys(files);

    eachTreeNode(tree, function(node, parent, depth){
        var path = node.name,
            type = 'dir';

        if(parent){
            path = parent.path + '/' + node.name;
        }

        if(fileList.indexOf(path) !== -1){
            type = 'file';
            var file = files[path];
            node.file = file;
            if(sortBy){
                node.sort = file[sortBy];
            }
        }

        node.path = path;
        node.type = type;
        node.depth = depth;
    });

    return tree;
};

/**
 * Sort nodes recursively
 * @method sortNodes
 * @param {Array} nodes - tree of nodes to act on
 * @return {Array} sorted node tree
 */
var sortNodes = function(tree, sortBy){
    sortBy = sortBy || 'sort';
    tree = arraySortBy(tree, sortBy);
    for (var i = 0; i < tree.length; i++) {
        var node = tree[i];
        if(node.children){
            node.children = sortNodes(node.children);
        }
    }
    return tree;
};

/**
 * Find dirs with a sibling with afile name matching the dir name,
 * remove the dir and move child nodes from it to the matching file node
 * @method mergeFileDirs
 * @param {Array} tree - Tree of nodes to act on.
 * @return {Array} converted array (same obj as tree param)
 */
var mergeFileDirs = function(tree){

    var merge = function(siblings){
        for (var i = siblings.length - 1; i >= 0; i--){
            var node = siblings[i];
            if(node.type === 'dir'){
                for(var j = siblings.length - 1; j >= 0; j--){
                    var sibling = siblings[j];
                    if(node !== sibling && sibling.type !== 'dir'){
                        // remove file extension
                        var siblingBaseName = sibling.path.replace(/\.[^/.]+$/, "");
                        // if dir name matches node without file extension
                        if(siblingBaseName === node.path){
                            // copy children from dir to file node and mark the dir to be removed

                            // prevent copying duplicates
                            for(var k = node.children.length - 1; k >= 0; k--){
                                var nodeChild = node.children[k];
                                if(sibling.children.indexOf(nodeChild) === -1){
                                    sibling.children.push(nodeChild);
                                }
                            }
                            var index = siblings.indexOf(node);
                            siblings.splice(index, 1);
                            break;
                        }
                    }
                }
            }
        }

        // recurse children
        for(var i = siblings.length - 1; i >= 0; i--){
            var node = siblings[i];
            if(node.children){
                merge(node.children);
            }
        }
    };

    merge(tree);
    return tree;
};

/**
 * @method setFileObjectReferences
 * @param {Array} tree - nav node tree
 * @param {Object} files - Metalsmith files object
 * @param {String} pathProperty - the file object property that the nav path is assigned to on each file object, not set if false
 * @param {String} navName - the name of the current nav
 * @return {Array} tree
 */
var setFileObjectReferences = function(tree, files, pathProperty, childrenProperty, navName){
    eachTreeNode(tree, function(node, parent, depth){
        var path = node.path;
        if(files[path]){
            if(pathProperty){
                node.file[pathProperty] = path;
            }
            if(childrenProperty){
                node.file[childrenProperty] = node.file[childrenProperty] || {};
                node.file[childrenProperty][navName] = node.children;
            }
        }
        node.parent = parent;
    });
    return tree;
};

/**
 * @method setBreadcrumbs
 * @param {Array} tree - nav node tree
 * @param {String} breadcrumbProperty -
 * @return {Array} tree
 */
var setBreadcrumbs = function(tree, breadcrumbProperty){
    eachTreeNode(tree, function(node, parent, depth){
        var breadcrumbPath = [];
        if(parent){
            if(parent[breadcrumbProperty]){
                breadcrumbPath = parent[breadcrumbProperty].concat(parent);
            } else {
                breadcrumbPath = [parent];
            }
        }
        node[breadcrumbProperty] = breadcrumbPath;
        if(node.file){
            node.file[breadcrumbProperty] = breadcrumbPath;
        }
    });
    return tree;
};

/**
 * @method filterFiles
 * @param {Object} files - Metalsmith files object
 * @param {String} key - file metadata key
 * @param {String} val - file metadata key value to check for
 * @return {Object} filtered files object
 */
var filterFiles = function(files, key, val){
    var out = {};
    for(var fileKey in files){
        var file = files[fileKey],
            groups = file[key];

        if(typeof groups === 'string'){
            if(groups === val){
                out[fileKey] = file;
            }
        } else if(groups && groups.indexOf(val) !== -1){
            out[fileKey] = file;
        }
    }
    return out;
};

/**
 * @method transformPermalinks
 * @param {Array} tree - nav nodes
 * @return {Array} tree
 */
var transformPermalinks = function(tree){
    eachTreeNode(tree, function(node, parent, depth){
        if(node.file){
            if(node.depth > 1){
                if(!node.children || !node.children.length){
                    node.path = path.dirname(node.path);
                    node.parent.path = node.path;
                    node.parent.file = node.file;
                    node.parent.add_trailing = true;
                }
                return false;
            }
        }
    });
    return tree;
};

/*
 * @method removeDirs
 * @param {Array} tree - nav nodes
 * @return {Array} tree
 */
var removeDirs = function(tree){
    eachTreeNode(tree, function(node, parent, depth){
        if(node.type === 'dir'){
            return false;
        }
    });
    return tree;
};

/**
 * Replace \\ from directories by /
 *
 * @method replaceDirsSeparator
 * @param {Array} tree - nav nodes
 * @return {Array} tree
 */
var replaceDirsSeparator = function(navFiles) {
    // Path is used as key in navFiles, some paths have \\ as separator between folders
    for (var oldKey in navFiles) {
        // Replace \\ separator by /
        var newKey = oldKey.replace(/\\/g, "/");
        if (oldKey !== newKey) {
            Object.defineProperty(navFiles, newKey,
                Object.getOwnPropertyDescriptor(navFiles, oldKey));
            delete navFiles[oldKey];
        }
    }
}

/**
 * @method getNav
 * @param {String} navName - name of this nav config
 * @param {Object} config - A nav config object
 * @param {Object} files - metalsmith files plugin param
 * @return {Array} nav tree
 */
var getNav = function(navName, config, files){
    config = merge(NAV_CONFIG_DEFAULT, config);

    var sortByNameFirst             = config.sortByNameFirst,
        sortBy                      = config.sortBy,
        breadcrumbProperty          = config.breadcrumbProperty,
        pathProperty                = config.pathProperty,
        mergeMatchingFilesAndDirs   = config.mergeMatchingFilesAndDirs,
        includeDirs                 = config.includeDirs,
        childrenProperty            = config.childrenProperty,
        filterProperty              = config.filterProperty,
        filterValue                 = config.filterValue || navName;

    var navFiles = files;

    navFiles = replaceDirsSeparator(navFiles);

    // filter to files that match the filterProperty and filterValue
    if(filterProperty){
        navFiles = filterFiles(files, filterProperty, filterValue);
    }

    var paths = Object.keys(navFiles).map(function(path) {
        path = path.replace(/\\/g, "/");
        return path.split('/');
    });

    var nodes = pathsToTree(paths);
    nodes = addNodeMetadata(nodes, files, sortBy);

    if(mergeMatchingFilesAndDirs){
        nodes = mergeFileDirs(nodes);
    }

    if(sortByNameFirst){
        nodes = sortNodes(nodes, 'path');
    }

    if(sortBy){
        nodes = sortNodes(nodes);
    }

    nodes = setFileObjectReferences(nodes, files, pathProperty, childrenProperty, navName);

    if(breadcrumbProperty){
        nodes = setBreadcrumbs(nodes, breadcrumbProperty);
    }

    if(!includeDirs){
        nodes = removeDirs(nodes);
    }

    return nodes;
};

/**
 * @method plugin
 * @param {Object} navConfigs - list of nav config objects indexed by name
 * @param {Object} settings - list of non nav specific plugin settings
 * @return {Function}
 */
var plugin = function plugin(navConfigs, settings){
    navConfigs = navConfigs || {};
    settings = settings || {};

    settings = merge(SETTINGS_DEFAULT, settings);

    var navListProperty = settings.navListProperty;
    var permalinks      = settings.permalinks;

    return function(files, metalsmith, done){

        var metadata = metalsmith.metadata();

        if(navListProperty){
            metadata[navListProperty] = {};
        }

        // key is the name of the nav
        for(var key in navConfigs){

            var config = navConfigs[key];

            // get processed nav
            var nav = getNav(key, config, files);

            // apply permalinks if set in settings
            if(permalinks){
                nav = transformPermalinks(nav);
            }

            // add to global metadata
            if(navListProperty){
                metadata[navListProperty][key] = nav;
            }
        }
        done();
    };
};

module.exports = plugin;<commit_msg>Removed improved formatting improvements for pull request<commit_after>'use strict';
// var debug = require('debug')('metalsmith-navigation');
var merge = require('merge');
var path = require('path');

// var util = require('util');
// var inspect = function(obj){
//     console.log(util.inspect(obj, {
//         depth: null,
//         showHidden: true,
//         colors: true
//     }));
// };

var SETTINGS_DEFAULT = {

    /*
     * metadata key all navs will be assigned to metatdata[navListProperty]
     * not set if false
     */
    navListProperty: 'navs',

    /*
     * if true, paths will be transformed to use metalsmith-permalinks
     * metalsmith-permalinks must be run before metalsmith-navigation
     */
    permalinks: false
};

var NAV_CONFIG_DEFAULT = {

    /*
     * sortby function or property name
     * function example: function(navNode){ return navNode.getValueToSortBy(); }
     */
    sortBy: false,

    /*
     * if true nodes will be sorted by path before sortBy
     * if false the sorting will not be stable unless ALL nodes have a unique sort value
     */
    sortByNameFirst: true,

    /*
     * to be included in this nav config, a file's metadata[filterProperty] must equal filterValue
     * ex:
     *   navConfigs key = 'footer'
     *   filterProperty = 'nav_group'
     *   files only added if files[path].nav_group == 'footer'
     */
    filterProperty: false,

    /*
     * if false nav name (navConfigs key) used instead
     * if files[path][filterProperty] equals or contains (string or array containing) filterValue
     * it will be included
     */
    filterValue: false,

    /*
     * the file object property that breadcrumb array is assigned to on each file object
     * breadcrumbs not generated or set if false
     * typically only one navConfig should generate breadcrumbs, often one specifically for them
     */
    breadcrumbProperty: 'breadcrumb_path',

    /**
     * the file object property that the nav path is assigned to on each file object, not set if false
     */
    pathProperty: 'nav_path',

    /**
     * the file object property that an array of nav child nodes will be assigned to
     */
    childrenProperty: 'nav_children',

    /*
     * if a file and sibling dir have matching names the file will be used as the parent in the nav tree
     * ex: /foo /foo.html
     */
    mergeMatchingFilesAndDirs: true,

    /*
     * if ALL dirs should be included as nav nodes
     */
    includeDirs: false

};

/**
 This should be replaced by a module such as a custom lodash.sortBy build. A suitable
 module (allowing property key or simple callback) could not be found at the time of this writting
**/

/**
* Returns a (stably) sorted copy of list, ranked in ascending order by the
* results of running each value through sortBy. (derrived from underscore.js _.sortBy())
* @method arraySortBy
* @param {Array} list - array to be sorted
* @param {Function|String} sortBy - ( function(item){ return item.sort_property; } )
*    If string used as the name of the property to sort by (eg. length).
* @return {Array} copy of sorted array
*/
var arraySortBy = function(list, sortBy) {

    if(typeof sortBy !== 'function'){
        var sortByKey = sortBy;
        sortBy = function(obj){
            return obj[sortByKey];
        };
    }

    var mapped = list.map(function(value, index, list) {
        return {
            value: value,
            index: index,
            criteria: sortBy(value, index, list)
        };
    });

    var compareFunction = function(left, right) {
        var a = left.criteria,
            b = right.criteria;
        if (a !== b) {
            if (a > b || a === void 0) return 1;
            if (a < b || b === void 0) return -1;
        }
        return left.index < right.index ? -1 : 1;
    };

    mapped.sort(compareFunction);

    return mapped.map(function(item){
        return item.value;
    });
};

/**
* @method eachTreeNode
* @param {Array} tree - nav node tree
* @param {Function} callback - function(node, parent, depth), if callback returns false the node will be removed
* @return {Array} tree
*/
var eachTreeNode = function(tree, callback){
    var iterate = function(nodes, parent, depth){
        depth = (depth || 0) + 1;
        for(var i = nodes.length - 1; i >= 0; i--){
            var node = nodes[i];
            var result = callback(node, parent, depth);
            if(result === false){
                nodes.splice(i, 1);
            }
            iterate(node.children, node, depth);
        }
    };
    iterate(tree);
    return tree;
};

/**
* Convert list of files to tree structure
* @method pathsToTree
* @param {Array} paths - list of file paths to act on.
* @return Array
*/
var pathsToTree = function(paths){
    var items = [];
    for(var i = 0, l = paths.length; i < l; i++) {
        var path = paths[i];
        var name = path[0];
        var rest = path.slice(1);
        var item = null;
        for(var j = 0, m = items.length; j < m; j++) {
            if(items[j].name === name) {
                item = items[j];
                break;
            }
        }

        if(item === null) {
            item = {
                name: name,
                path: null,
                type: null,
                sort: 0,
                children: []
            };
            items.push(item);
        }
        if(rest.length > 0) {
            item.children.push(rest);
        }
    }
    for(i = 0, l = items.length; i < l; i++) {
        var node = items[i];
        node.children = pathsToTree(node.children);
    }
    return items;
};

/**
* Loop over each node and do the following:
*     set node.path - full path to file.
*     set node.type - 'file' or 'dir'.
*
* @method addNodeMetadata
* @param {Array} tree - tree of nodes to act on
* @param {Array} files - List of files being processed by metalsmith.
* @param {String} sortBy - Sort by property key.
* @return {Array} converted array (same obj as tree param)
*/
var addNodeMetadata = function(tree, files, sortBy){
    var fileList = Object.keys(files);

    eachTreeNode(tree, function(node, parent, depth){
        var path = node.name,
            type = 'dir';

        if(parent){
            path = parent.path + '/' + node.name;
        }

        if(fileList.indexOf(path) !== -1){
            type = 'file';
            var file = files[path];
            node.file = file;
            if(sortBy){
                node.sort = file[sortBy];
            }
        }

        node.path = path;
        node.type = type;
        node.depth = depth;
    });

    return tree;
};

/**
* Sort nodes recursively
* @method sortNodes
* @param {Array} nodes - tree of nodes to act on
* @return {Array} sorted node tree
*/
var sortNodes = function(tree, sortBy){
    sortBy = sortBy || 'sort';
    tree = arraySortBy(tree, sortBy);
    for (var i = 0; i < tree.length; i++) {
        var node = tree[i];
        if(node.children){
            node.children = sortNodes(node.children);
        }
    }
    return tree;
};

/**
* Find dirs with a sibling with afile name matching the dir name,
* remove the dir and move child nodes from it to the matching file node
* @method mergeFileDirs
* @param {Array} tree - Tree of nodes to act on.
* @return {Array} converted array (same obj as tree param)
*/
var mergeFileDirs = function(tree){

    var merge = function(siblings){
        for (var i = siblings.length - 1; i >= 0; i--){
            var node = siblings[i];
            if(node.type === 'dir'){
                for(var j = siblings.length - 1; j >= 0; j--){
                    var sibling = siblings[j];
                    if(node !== sibling && sibling.type !== 'dir'){
                        // remove file extension
                        var siblingBaseName = sibling.path.replace(/\.[^/.]+$/, "");
                        // if dir name matches node without file extension
                        if(siblingBaseName === node.path){
                            // copy children from dir to file node and mark the dir to be removed

                            // prevent copying duplicates
                            for(var k = node.children.length - 1; k >= 0; k--){
                                var nodeChild = node.children[k];
                                if(sibling.children.indexOf(nodeChild) === -1){
                                    sibling.children.push(nodeChild);
                                }
                            }
                            var index = siblings.indexOf(node);
                            siblings.splice(index, 1);
                            break;
                        }
                    }
                }
            }
        }

        // recurse children
        for(var i = siblings.length - 1; i >= 0; i--){
            var node = siblings[i];
            if(node.children){
                merge(node.children);
            }
        }
    };

    merge(tree);
    return tree;
};

/**
* @method setFileObjectReferences
* @param {Array} tree - nav node tree
* @param {Object} files - Metalsmith files object
* @param {String} pathProperty - the file object property that the nav path is assigned to on each file object, not set if false
* @param {String} navName - the name of the current nav
* @return {Array} tree
*/
var setFileObjectReferences = function(tree, files, pathProperty, childrenProperty, navName){
    eachTreeNode(tree, function(node, parent, depth){
        var path = node.path;
        if(files[path]){
            if(pathProperty){
                node.file[pathProperty] = path;
            }
            if(childrenProperty){
                node.file[childrenProperty] = node.file[childrenProperty] || {};
                node.file[childrenProperty][navName] = node.children;
            }
        }
        node.parent = parent;
    });
    return tree;
};

/**
* @method setBreadcrumbs
* @param {Array} tree - nav node tree
* @param {String} breadcrumbProperty -
* @return {Array} tree
*/
var setBreadcrumbs = function(tree, breadcrumbProperty){
    eachTreeNode(tree, function(node, parent, depth){
        var breadcrumbPath = [];
        if(parent){
            if(parent[breadcrumbProperty]){
                breadcrumbPath = parent[breadcrumbProperty].concat(parent);
            } else {
                breadcrumbPath = [parent];
            }
        }
        node[breadcrumbProperty] = breadcrumbPath;
        if(node.file){
            node.file[breadcrumbProperty] = breadcrumbPath;
        }
    });
    return tree;
};

/**
* @method filterFiles
* @param {Object} files - Metalsmith files object
* @param {String} key - file metadata key
* @param {String} val - file metadata key value to check for
* @return {Object} filtered files object
*/
var filterFiles = function(files, key, val){
    var out = {};
    for(var fileKey in files){
        var file = files[fileKey],
            groups = file[key];

        if(typeof groups === 'string'){
            if(groups === val){
                out[fileKey] = file;
            }
        } else if(groups && groups.indexOf(val) !== -1){
            out[fileKey] = file;
        }
    }
    return out;
};

/**
* @method transformPermalinks
* @param {Array} tree - nav nodes
* @return {Array} tree
*/
var transformPermalinks = function(tree){
    eachTreeNode(tree, function(node, parent, depth){
        if(node.file){
            if(node.depth > 1){
                if(!node.children || !node.children.length){
                    node.path = path.dirname(node.path);
                    node.parent.path = node.path;
                    node.parent.file = node.file;
                    node.parent.add_trailing = true;
                }
                return false;
            }
        }
    });
    return tree;
};

/*
* @method removeDirs
* @param {Array} tree - nav nodes
* @return {Array} tree
*/
var removeDirs = function(tree){
    eachTreeNode(tree, function(node, parent, depth){
        if(node.type === 'dir'){
            return false;
        }
    });
    return tree;
};

/**
 * Replace \\ from directories by /
 *
 * @method replaceDirsSeparator
 * @param {Array} tree - nav nodes
 * @return {Array} tree
 */
var replaceDirsSeparator = function(navFiles) {
    // Path is used as key in navFiles, some paths have \\ as separator between folders
    for (var oldKey in navFiles) {
        // Replace \\ separator by /
        var newKey = oldKey.replace(/\\/g, "/");
        if (oldKey !== newKey) {
            Object.defineProperty(navFiles, newKey,
                Object.getOwnPropertyDescriptor(navFiles, oldKey));
            delete navFiles[oldKey];
        }
    }
}

/**
 * @method getNav
 * @param {String} navName - name of this nav config
 * @param {Object} config - A nav config object
 * @param {Object} files - metalsmith files plugin param
 * @return {Array} nav tree
 */
var getNav = function(navName, config, files){
    config = merge(NAV_CONFIG_DEFAULT, config);

    var sortByNameFirst             = config.sortByNameFirst,
        sortBy                      = config.sortBy,
        breadcrumbProperty          = config.breadcrumbProperty,
        pathProperty                = config.pathProperty,
        mergeMatchingFilesAndDirs   = config.mergeMatchingFilesAndDirs,
        includeDirs                 = config.includeDirs,
        childrenProperty            = config.childrenProperty,
        filterProperty              = config.filterProperty,
        filterValue                 = config.filterValue || navName;

    var navFiles = files;

    navFiles = replaceDirsSeparator(navFiles);

    // filter to files that match the filterProperty and filterValue
    if(filterProperty){
        navFiles = filterFiles(files, filterProperty, filterValue);
    }

    var paths = Object.keys(navFiles).map(function(path) {
        path = path.replace(/\\/g, "/");
        return path.split('/');
    });

    var nodes = pathsToTree(paths);

    nodes = addNodeMetadata(nodes, files, sortBy);

    if(mergeMatchingFilesAndDirs){
        nodes = mergeFileDirs(nodes);
    }

    if(sortByNameFirst){
        nodes = sortNodes(nodes, 'path');
    }

    if(sortBy){
        nodes = sortNodes(nodes);
    }

    nodes = setFileObjectReferences(nodes, files, pathProperty, childrenProperty, navName);

    if(breadcrumbProperty){
        nodes = setBreadcrumbs(nodes, breadcrumbProperty);
    }

    if(!includeDirs){
        nodes = removeDirs(nodes);
    }

    return nodes;
};

/**
* @method plugin
* @param {Object} navConfigs - list of nav config objects indexed by name
* @param {Object} settings - list of non nav specific plugin settings
* @return {Function}
*/
var plugin = function plugin(navConfigs, settings){
    navConfigs = navConfigs || {};
    settings = settings || {};

    settings = merge(SETTINGS_DEFAULT, settings);

    var navListProperty = settings.navListProperty;
    var permalinks      = settings.permalinks;

    return function(files, metalsmith, done){

        var metadata = metalsmith.metadata();

        if(navListProperty){
            metadata[navListProperty] = {};
        }

        // key is the name of the nav
        for(var key in navConfigs){

            var config = navConfigs[key];

            // get processed nav
            var nav = getNav(key, config, files);

            // apply permalinks if set in settings
            if(permalinks){
                nav = transformPermalinks(nav);
            }

            // add to global metadata
            if(navListProperty){
                metadata[navListProperty][key] = nav;
            }
        }
        done();
    };
};

module.exports = plugin;<|endoftext|>


<commit_before>import React from 'react';
import { Link } from 'react-router';
import { } from 'meteor/meteor';
import AccountsUIWrapper from '../AccountsUIWrapper';
import './index.css';

export default function Navbar() {
  return (

          <button type="button" className="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
            Toggle navigation

          </button>
          <Link to="/dashboard">
SmirkSpace
</Link>

 <AccountsUIWrapper />

  );
}
<commit_msg>Changed the logo in the navBar back to the original one to see how it looks. Simple change, if everyone likes it we can change it or can ignore this and leave it as is<commit_after>import React from 'react';
import { Link } from 'react-router';
import { } from 'meteor/meteor';
import AccountsUIWrapper from '../AccountsUIWrapper';
import './index.css';

export default function Navbar() {
  return (

          <button type="button" className="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
            Toggle navigation

          </button>
          <Link to="/dashboard">

</Link>

 <AccountsUIWrapper />

  );
}
<|endoftext|>


<commit_before>
google.load('search', '1');

function player_init(){
  var width = (($(document).width())>1280? 1280: $(document).width());
  $('body').css('width', width);
  $.app.playerAction = 'play';

  $('#player_wrapper #album_artwork').attr('src', '/images/nothumb.png');
  google.search.Search.getBranding('google_branding');
}

function loadMusic(files) {
  var musicFiles = _(files).select(function (file) {
    var path = file.webkitRelativePath || file.mozFullPath;
    var fileName = file.fileName;
    var size = file.fileSize;
    var lpath = path.toLowerCase();
    return !/\.$/.test(lpath) &&
      (/\.mp3$/.test(lpath)) ||
      (/\.ogg$/.test(lpath)) ||
      (/\.m4a$/.test(lpath))
  });

  var total = musicFiles.length, progress = 0;

  function incProgress() {
    progress++;
    $('#addmusic').html(progress+' / '+total);
    if(progress == total) {
      $.app.Storage.save();
    } else {
      newMuFile(musicFiles[progress]);
    }
  }

  $.app.liveFiles = {};

  function newMuFile(f) {
    $.app.liveFiles[f.webkitRelativePath] = f;
    var muFile = new $.app.MuFile(f, incProgress);
  }

  newMuFile(musicFiles[progress]);
}

function getPrettySongName(muFile) {
  var name = muFile.name.length > muFile.title.length ?
    muFile.name : muFile.title;
  return name.replace(/\.\w+$/,'');
}

function getYoutubeSearchURL(muFile) {
  var name = getPrettySongName(muFile);
  return 'http://www.youtube.com/results?search_query='+
    name.split(/\s+/).join('+')+'+'+
    muFile.artist.split(/\s+/).join('+');
}

function getLyricsSearchURL(muFile) {
  var name = getPrettySongName(muFile);
  return 'http://www.google.com/search?q='+
    name.split(/\s+/).join('+')+'+'+
    muFile.artist.split(/\s+/).join('+')+'+lyrics';
}

function getArtistWikipediaURL(muFile) {
  return 'http://en.wikipedia.org/w/index.php?search='+
          muFile.artist.split(/\s+/).join('+');
}

function getAmazonLink(muFile) {
  return 'http://www.amazon.com/s?ie=UTF8&x=0&ref_=nb_sb_noss&y=0&field-keywords='+encodeURI(muFile.artist)+'&url=search-alias%3Ddigital-music&_encoding=UTF8&tag=myfreq-20&linkCode=ur2&camp=1789&creative=390957'
}

function getSongEntryHtml(muFile, asSearchResult) {
  var name = getPrettySongName(muFile);
  var entryHTML = '
'+
    '  '+
    ''+getPrettySongName(muFile)+''+
    '  '+
    ''+muFile.album+''+
    '  '+
    ''+muFile.artist+''+
    '  '+
    (asSearchResult ?
      '+' :
      '-'+
      '>')+
  '
';
  var entry = $(entryHTML);

  entry
    .hover(
      function () { $(this).addClass('focused_entry'); },
      function () { $(this).removeClass('focused_entry'); }
    )
    .find('.entry_action').data('muFile',muFile);

  return entry;
}

function getObjectURL(path) {
  if($.app.liveFiles) {
    var liveFile = $.app.liveFiles[path];
    return liveFile ? window.webkitURL.createObjectURL(liveFile) : null;
  } else {
    return null;
  }
}

function searchImage(query, callback) {
  function onComplete() {
    if (imageSearch.results && imageSearch.results.length > 0) {
      var results = imageSearch.results;
      callback(results[0].tbUrl);
    }
  }

  var imageSearch = new google.search.ImageSearch();
  imageSearch.setSearchCompleteCallback(this, onComplete, null);
  imageSearch.execute(query);
}

function updateSongInfo(muFile){
  var songInfo = $('#player_column #player_controls #song_info');
  $('#title', songInfo).text(muFile.title);
  $('#artist', songInfo).text(muFile.artist);
  $('#album', songInfo).text(muFile.album);
}

var currentPlaying = null;
$.app.nextSong = function () {
  var nextDiv = currentPlaying.next();
  if(nextDiv.length == 0) {
    nextDiv = currentPlaying.parent().children().first();
  }
  play(nextDiv);
}

function pause() {
  $('#player').get(0).pause();
}

function play(div, resumeFlag) {
  var muFile = div.find('.entry_action').data('muFile');
  var url = getObjectURL(muFile.path);

  if(url) {
    if(!resumeFlag) { $('#player').get(0).src = url; }

    $('#player').get(0).play();
    $('img', '#play').attr('src', '/images/pause.png');
    currentPlaying = div;
    currentPlaying.parent().children().removeClass('active_entry');
    currentPlaying.addClass('active_entry');
    searchImage(muFile.album+' '+muFile.artist,function (tbUrl) {
      $('#player_wrapper #album_artwork').attr('src',tbUrl);
    });
    updateSongInfo(muFile);
  } else {
    alert('Add music again');
  }
}

$(document).ready(function () {
  player_init();

  $.app.Storage.load();

  $('#addmusic').click(function () {
    $('input[name=actual_addmusic]').click();
  });

  $('#play', '#player_column').hover(
    function () {
      $('img', '#play').attr('src', '/images/'+$.app.playerAction+'_hover.png');
    },
    function () {
      $('img', '#play').attr('src', '/images/'+$.app.playerAction+'.png');
    }
  )
  .click(function () {
    if($.app.playerAction == 'play'){
      play(currentPlaying, true);
      $.app.playerAction = 'pause';
    } else {
      pause();
      $.app.playerAction = 'play';
    }
    $('img', '#play').attr('src', '/images/'+$.app.playerAction+'_hover.png');
  });
  $('input[name=search]').keyup(function (e) {

    var divResults = $('#search_results');
    divResults.empty();
    var text = $(this).val();

    if(!text || (text && text.length < 4)) { return; }

    var results = $.app.Storage.search($(this).val());
    _(results).each(function (muFile) {
      divResults.append(getSongEntryHtml(muFile, true));
    });

    divResults.find('.entry_action')
      .click(function () {
        var muFile = $(this).data('muFile');
        var player_entry = getSongEntryHtml(muFile, false);
        player_entry.find('.entry_action').click(function () {
          play($(this).parent());
          $.app.playerAction = 'pause';
        });
        player_entry.find('.remove_action').click(function () {
          play($(this).parent().remove());
        });
        $('#playlist_wrapper #playlist').append(player_entry);
      });

  })
  .focus(function (e){
    $(this).val('');
  })
  .focusout(function (e){
    if($(this).val() == ''){
      $(this).val('Search...');
    }
  });
});

<commit_msg>input validation<commit_after>
google.load('search', '1');

function player_init(){
  var width = (($(document).width())>1280? 1280: $(document).width());
  $('body').css('width', width);
  $.app.playerAction = 'play';

  $('#player_wrapper #album_artwork').attr('src', '/images/nothumb.png');
  google.search.Search.getBranding('google_branding');
}

function loadMusic(files) {
  var musicFiles = _(files).select(function (file) {
    var path = file.webkitRelativePath || file.mozFullPath;
    var fileName = file.fileName;
    var size = file.fileSize;
    var lpath = path.toLowerCase();
    return !/\.$/.test(lpath) &&
      (/\.mp3$/.test(lpath)) ||
      (/\.ogg$/.test(lpath)) ||
      (/\.m4a$/.test(lpath))
  });

  var total = musicFiles.length, progress = 0;

  function incProgress() {
    progress++;
    $('#addmusic').html(progress+' / '+total);
    if(progress == total) {
      $.app.Storage.save();
    } else {
      newMuFile(musicFiles[progress]);
    }
  }

  $.app.liveFiles = {};

  function newMuFile(f) {
    $.app.liveFiles[f.webkitRelativePath] = f;
    var muFile = new $.app.MuFile(f, incProgress);
  }

  newMuFile(musicFiles[progress]);
}

function getPrettySongName(muFile) {
  var name = muFile.name.length > muFile.title.length ?
    muFile.name : muFile.title;
  return name.replace(/\.\w+$/,'');
}

function getYoutubeSearchURL(muFile) {
  var name = getPrettySongName(muFile);
  return 'http://www.youtube.com/results?search_query='+
    name.split(/\s+/).join('+')+'+'+
    muFile.artist.split(/\s+/).join('+');
}

function getLyricsSearchURL(muFile) {
  var name = getPrettySongName(muFile);
  return 'http://www.google.com/search?q='+
    name.split(/\s+/).join('+')+'+'+
    muFile.artist.split(/\s+/).join('+')+'+lyrics';
}

function getArtistWikipediaURL(muFile) {
  return 'http://en.wikipedia.org/w/index.php?search='+
          muFile.artist.split(/\s+/).join('+');
}

function getAmazonLink(muFile) {
  return 'http://www.amazon.com/s?ie=UTF8&x=0&ref_=nb_sb_noss&y=0&field-keywords='+encodeURI(muFile.artist)+'&url=search-alias%3Ddigital-music&_encoding=UTF8&tag=myfreq-20&linkCode=ur2&camp=1789&creative=390957'
}

function getSongEntryHtml(muFile, asSearchResult) {
  var name = getPrettySongName(muFile);
  var entryHTML = '
'+
    '  '+
    ''+getPrettySongName(muFile)+''+
    '  '+
    ''+muFile.album+''+
    '  '+
    ''+muFile.artist+''+
    '  '+
    (asSearchResult ?
      '+' :
      '-'+
      '>')+
  '
';
  var entry = $(entryHTML);

  entry
    .hover(
      function () { $(this).addClass('focused_entry'); },
      function () { $(this).removeClass('focused_entry'); }
    )
    .find('.entry_action').data('muFile',muFile);

  return entry;
}

function getObjectURL(path) {
  if($.app.liveFiles) {
    var liveFile = $.app.liveFiles[path];
    return liveFile ? window.webkitURL.createObjectURL(liveFile) : null;
  } else {
    return null;
  }
}

function searchImage(query, callback) {
  function onComplete() {
    if (imageSearch.results && imageSearch.results.length > 0) {
      var results = imageSearch.results;
      callback(results[0].tbUrl);
    }
  }

  var imageSearch = new google.search.ImageSearch();
  imageSearch.setSearchCompleteCallback(this, onComplete, null);
  imageSearch.execute(query);
}

function updateSongInfo(muFile){
  var songInfo = $('#player_column #player_controls #song_info');
  $('#title', songInfo).text(muFile.title);
  $('#artist', songInfo).text(muFile.artist);
  $('#album', songInfo).text(muFile.album);
}

var currentPlaying = null;
$.app.nextSong = function () {
  var nextDiv = currentPlaying.next();
  if(nextDiv.length == 0) {
    nextDiv = currentPlaying.parent().children().first();
  }
  play(nextDiv);
}

function pause() {
  $('#player').get(0).pause();
  $.app.playerAction = 'play';
}

function play(div, resumeFlag) {
  if(!div){
    alert('No selction made.');
    return;
  }
  var muFile = div.find('.entry_action').data('muFile');
  var url = getObjectURL(muFile.path);
  $.app.playerAction = 'pause';

  if(url) {
    if(!resumeFlag) { $('#player').get(0).src = url; }

    $('#player').get(0).play();
    $('img', '#play').attr('src', '/images/pause.png');
    currentPlaying = div;
    currentPlaying.parent().children().removeClass('active_entry');
    currentPlaying.addClass('active_entry');
    searchImage(muFile.album+' '+muFile.artist,function (tbUrl) {
      $('#player_wrapper #album_artwork').attr('src',tbUrl);
    });
    updateSongInfo(muFile);
  } else {
    alert('Add music again');
  }
}

$(document).ready(function () {
  player_init();

  $.app.Storage.load();

  $('#addmusic').click(function () {
    $('input[name=actual_addmusic]').click();
  });

  $('#play', '#player_column').hover(
    function () {
      $('img', '#play').attr('src', '/images/'+$.app.playerAction+'_hover.png');
    },
    function () {
      $('img', '#play').attr('src', '/images/'+$.app.playerAction+'.png');
    }
  )
  .click(function () {
    if($.app.playerAction == 'play'){
      play(currentPlaying, true);
    } else {
      pause();
    }
    $('img', '#play').attr('src', '/images/'+$.app.playerAction+'_hover.png');
  });
  $('input[name=search]').keyup(function (e) {

    var divResults = $('#search_results');
    divResults.empty();
    var text = $(this).val();

    if(!text || (text && text.length < 4)) { return; }

    var results = $.app.Storage.search($(this).val());
    _(results).each(function (muFile) {
      divResults.append(getSongEntryHtml(muFile, true));
    });

    divResults.find('.entry_action')
      .click(function () {
        var muFile = $(this).data('muFile');
        var player_entry = getSongEntryHtml(muFile, false);
        player_entry.find('.entry_action').click(function () {
          play($(this).parent());
          $.app.playerAction = 'pause';
        });
        player_entry.find('.remove_action').click(function () {
          play($(this).parent().remove());
        });
        $('#playlist_wrapper #playlist').append(player_entry);
      });

  })
  .focus(function (e){
    $(this).val('');
  })
  .focusout(function (e){
    if($(this).val() == ''){
      $(this).val('Search...');
    }
  });
});

<|endoftext|>


<commit_before>var util = require('util');
var webutil = require('../util/web');
var Tab = require('../client/tab').Tab;
var Amount = ripple.Amount;

var TradeTab = function ()
{
  Tab.call(this);
};

util.inherits(TradeTab, Tab);

TradeTab.prototype.mainMenu = 'advanced';

TradeTab.prototype.generateHtml = function ()
{
  return require('../../jade/tabs/trade.jade')();
};

TradeTab.prototype.angularDeps = Tab.prototype.angularDeps.concat(['books']);

TradeTab.prototype.angular = function(module)
{
  var self = this;
  var app = this.app;

  module.controller('TradeCtrl', ['rpBooks', '$scope', 'rpId', 'rpNetwork',
                                  function (books, $scope, $id, $network)
  {
    if (!$id.loginStatus) return $id.goId();

    $scope.mode = "confirm";
    $scope.asum = [];
    $scope.bsum = [];

    var pairs = $scope.pairs_all;
    $scope.pairs_query = webutil.queryFromOptions(pairs);

    $scope.$watch('userBlob.data.contacts', function (contacts) {
      $scope.issuer_query = webutil.queryFromOptions(contacts);
    }, true);

    $scope.reset = function (keepPair) {
      var pair = keepPair ? $scope.order.currency_pair :
            store.get('ripple_trade_currency_pair') || pairs[0].name;
      var fIssuer = keepPair ? $scope.order.first_issuer : $id.account;
      var sIssuer = keepPair ? $scope.order.second_issuer : $id.account;
      var type = keepPair ? $scope.order.type : 'buy';

      if ($scope.orderForm) $scope.orderForm.$setPristine();
      $scope.mode = "trade";
      $scope.order = {
        type: type,
        first: '',
        price: '',
        second: '',
        currency_pair: pair,
        first_currency: pair.slice(0, 3),
        second_currency: pair.slice(4, 7),
        first_issuer: fIssuer,
        second_issuer: sIssuer,
        listing: 'orderbook',

        // This variable is true if both the pair and the issuers are set to
        // valid values. It is used to enable or disable all the functionality
        // on the page.
        valid_settings: false
      };

      updateSettings();
    };

    // Show "My Orders" if account has been funded
    $scope.$watch('account.Balance', function(){
      if ($scope.account.Balance) {
        $scope.order.listing = 'my';
      }
    }, true);

    $scope.back = function () {
      $scope.mode = "trade";
    };

    $scope.place_order = function () {
      $scope.mode = "confirm";
      if ($scope.order.type === 'buy') {
        $scope.order.sell_amount = $scope.order.second_amount;
        $scope.order.buy_amount = $scope.order.first_amount;
      } else {
        $scope.order.sell_amount = $scope.order.first_amount;
        $scope.order.buy_amount = $scope.order.second_amount;
      }
    };

    $scope.cancel_order = function ()
    {
      var tx = $network.remote.transaction();
      tx.offer_cancel($id.account, this.entry.seq);
      tx.on('success', function () {
      });
      tx.on('error', function () {
        $scope.mode = "error";
        $scope.$digest();
      });
      tx.submit();

      this.cancelled = true;
    };

    $scope.order_confirmed = function ()
    {
      var tx = $network.remote.transaction();
      tx.offer_create($id.account, $scope.order.buy_amount, $scope.order.sell_amount);

      tx.on('success', function (res) {
        setEngineStatus(res, false);
        $scope.done(this.hash);

        // Remember pair and increase order
        var found;

        for (var i = 0; i < $scope.pairs_all.length; i++) {
          if ($scope.pairs_all[i].name == $scope.order.currency_pair) {
            $scope.pairs_all[i].order++;
            found = true;
            break;
          }
        }

        if (!found) {
          $scope.pairs_all.push({
            "name": $scope.order.currency_pair,
            "order": 1
          });
        }

        $scope.$digest();
      });
      tx.on('error', function () {
        $scope.mode = "error";
        $scope.$digest();
      });
      tx.submit();

      $scope.mode = "sending";
    };

    $scope.done = function (hash)
    {
      $scope.mode = "done";
      $network.remote.on('transaction', handleAccountEvent);

      function handleAccountEvent(e) {
        if (e.transaction.hash === hash) {
          setEngineStatus(e, true);
          $scope.$digest();
          $network.remote.removeListener('transaction', handleAccountEvent);
        }
      }
    };

    function setEngineStatus(res, accepted) {
      $scope.engine_result = res.engine_result;
      $scope.engine_result_message = res.engine_result_message;
      switch (res.engine_result.slice(0, 3)) {
        case 'tes':
          $scope.tx_result = accepted ? "cleared" : "pending";
          break;
        case 'tem':
          $scope.tx_result = "malformed";
          break;
        case 'ter':
          $scope.tx_result = "failed";
          break;
        case 'tec':
          $scope.tx_result = "claim";
          break;
        case 'tep':
          console.warn("Unhandled engine status encountered!");
      }
    }

    $scope.$watch('order.currency_pair', function (pair) {
      store.set('ripple_trade_currency_pair', pair);
      updateSettings();
      resetIssuers(true);
     }, true);

    $scope.$watch('order.first', function (amount_str) {
      $scope.update_first();
    }, true);

    $scope.$watch('order.price', function (amount_str) {
      $scope.update_price();
    }, true);

    $scope.$watch('order.second', function (amount_str) {
      $scope.update_second();
    }, true);

    $scope.$watch('userBlob', function () {
      resetIssuers(false);
    }, true);

    $scope.update_first = function () {
      var first_currency = $scope.order.first_currency || "XRP";
      var formatted = "" + $scope.order.first + " " + first_currency;

      $scope.order.first_amount = ripple.Amount.from_human(formatted);

      if (first_currency !== 'XRP') $scope.order.first_amount.set_issuer($scope.order.first_issuer);
    };

    $scope.update_price = function () {
      var second_currency = $scope.order.second_currency || "XRP";
      var formatted = "" + $scope.order.price + " " + second_currency;

      $scope.order.price_amount = ripple.Amount.from_human(formatted);

      if (second_currency !== 'XRP') $scope.order.price_amount.set_issuer($scope.order.second_issuer);
    };

    $scope.update_second = function () {
      var second_currency = $scope.order.second_currency || "XRP";
      var formatted = "" + $scope.order.second + " " + second_currency;

      $scope.order.second_amount = ripple.Amount.from_human(formatted);

      if (second_currency !== 'XRP') $scope.order.second_amount.set_issuer($scope.order.second_issuer);
    };

    $scope.calc_second = function () {
      $scope.update_first();
      $scope.update_price();
      if ($scope.order.price_amount && $scope.order.price_amount.is_valid() &&
          $scope.order.first_amount && $scope.order.first_amount.is_valid()) {
        $scope.order.second_amount =
          $scope.order.price_amount.product_human($scope.order.first_amount);
        $scope.order.second = +$scope.order.second_amount.to_human({group_sep: false});
      }
    };

    $scope.calc_first = function () {
      $scope.update_second();
      $scope.update_price();
      if ($scope.order.price_amount  && $scope.order.price_amount.is_valid() &&
          $scope.order.second_amount && $scope.order.second_amount.is_valid()) {
        $scope.order.first_amount =
          $scope.order.second_amount.ratio_human($scope.order.price_amount);
        $scope.order.first = +$scope.order.first_amount.to_human({group_sep: false});
      }
    };

    // This functions is called whenever the settings, specifically the pair and
    // the issuer(s) have been modified. It checks the new configuration and
    // sets $scope.valid_settings.
    function updateSettings() {
      var order = $scope.order;

      var pair = order.currency_pair;
      if ("string" !== typeof pair ||
          !pair.match(/^[a-z]{3}\/[a-z]{3}$/i)) {
        order.first_currency = 'XRP';
        order.second_currency = 'XRP';
        order.valid_settings = false;
        return;
      }
      var first_currency = order.first_currency = pair.slice(0, 3);
      var second_currency = order.second_currency = pair.slice(4, 7);

      var first_issuer = ripple.UInt160.from_json(order.first_issuer);
      var second_issuer = ripple.UInt160.from_json(order.second_issuer);
      if ((first_currency !== 'XRP' && !first_issuer.is_valid()) ||
          (second_currency !== 'XRP' && !second_issuer.is_valid())) {
        order.valid_settings = false;
        return;
      }

      if (order.type === "buy") {
        order.sell_currency = order.second_currency;
        order.buy_currency = order.first_currency;
        order.sell_issuer = order.second_issuer;
        order.buy_issuer = order.first_issuer;
      } else {
        order.sell_currency = order.first_currency;
        order.buy_currency = order.second_currency;
        order.sell_issuer = order.first_issuer;
        order.buy_issuer = order.second_issuer;
      }
      order.valid_settings = true;

      var key = "" +
            order.first_currency +
            (order.first_currency === 'XRP' ? "" : "/" +order.first_issuer) +
            ":" +
            order.second_currency +
            (order.second_currency === 'XRP' ? "" : "/" +order.second_issuer);

      if (order.prev_settings !== key) {
        loadOffers();

        order.prev_settings = key;
      }
    }

    function guessIssuer(currency, exclude_issuer) {
      var guess;

      // First guess: An explicit issuer preference setting in the user's blob
      try {
        guess = $scope.userBlob.data.preferred_issuer[currency];
        if (guess && guess == exclude_issuer) {
          guess = $scope.userBlob.data.preferred_second_issuer[currency];
        }
        if (guess) return guess;
      } catch (e) {}

      // Second guess: The user's highest trust line in this currency
      try {
        var issuers = $scope.balances[currency].components;
        for (var counterparty in issuers) {
          if (counterparty != exclude) {
            return counterparty;
          }
        }
      } catch (e) {}

      // We found nothing
      return null;
    }

    function resetIssuers(force) {
      var guess;

      if (force) {
        $scope.order.first_issuer = null;
        $scope.order.second_issuer = null;
      }

      if (!$scope.order.first_issuer &&
          $scope.order.first_currency &&
          $scope.order.first_currency !== 'XRP' &&
          (guess = guessIssuer($scope.order.first_currency))) {
        $scope.order.first_issuer = guess;
      }

      if (!$scope.order.second_issuer &&
          $scope.order.second_currency &&
          $scope.order.second_currency !== 'XRP' &&
          (guess = guessIssuer($scope.order.second_currency))) {
        $scope.order.second_issuer = guess;
      }

      // If the same currency, exclude first issuer for second issuer guess
      if ($scope.order.first_currency == $scope.order.second_currency &&
          $scope.order.first_issuer == $scope.order.second_issuer &&
          (guess = guessIssuer($scope.order.first_currency, $scope.order.first_issuer))) {
        $scope.order.second_issuer = guess;
      }
    }

    $scope.edit_first_issuer = function () {
      $scope.show_issuer_form = 'first';
      $scope.order.first_issuer_edit = webutil.unresolveContact($scope.userBlob.data.contacts, $scope.order.first_issuer);

      setImmediate(function () {
        $('#first_issuer').select();
      });

    };

    $scope.edit_second_issuer = function () {
      $scope.show_issuer_form = 'second';
      $scope.order.second_issuer_edit = webutil.unresolveContact($scope.userBlob.data.contacts, $scope.order.second_issuer);

      setImmediate(function () {
        $('#second_issuer').select();
      });
    };

    $scope.save_first_issuer = function () {
      $scope.order.first_issuer = webutil.resolveContact($scope.userBlob.data.contacts, $scope.order.first_issuer_edit);
      $scope.show_issuer_form = false;

      updateSettings();

      // Persist issuer setting
      if ($scope.order.valid_settings &&
          $scope.order.first_currency !== 'XRP') {
        $scope.userBlob.data.preferred_issuer[$scope.order.first_currency] =
          $scope.order.first_issuer;
      }
    };

    $scope.save_second_issuer = function () {
      $scope.order.second_issuer = webutil.resolveContact($scope.userBlob.data.contacts, $scope.order.second_issuer_edit);
      $scope.show_issuer_form = false;

      updateSettings();

      // Persist issuer setting
      if ($scope.order.valid_settings &&
          $scope.order.second_currency !== 'XRP') {

        if ($scope.order.first_currency == $scope.order.second_currency) {
          $scope.userBlob.data.preferred_second_issuer[$scope.order.second_currency] =
            $scope.order.second_issuer;
        } else {
          $scope.userBlob.data.preferred_issuer[$scope.order.second_currency] =
            $scope.order.second_issuer;
        }
      }
    };

    function loadOffers() {
      // Make sure we unsubscribe from any previously loaded orderbook
      if ($scope.book &&
          "function" === typeof $scope.book.unsubscribe) {
        $scope.book.unsubscribe();
      }

      $scope.asum = [];
      $scope.book = books.get({
        currency: $scope.order.first_currency,
        issuer: $scope.order.first_issuer
      }, {
        currency: $scope.order.second_currency,
        issuer: $scope.order.second_issuer
      }, $scope.address);
    }

    $scope.$watch('order.first_issuer', function (issuer) {
      updateSettings();
    });

    $scope.$watch('order.second_issuer', function (issuer) {
      updateSettings();
    });

    $scope.$watch('balances', function () {
      resetIssuers(false);
    }, true);

    $scope.$watch('book.asks', function (asks) {
      $scope.asum = [];

      if (!asks) return;

      var sum;
      for (var i = 0, l = asks.length; i < l; i++) {
        if (sum) sum = sum.add(asks[i].TakerGets);
        else sum = Amount.from_json(asks[i].TakerGets);
        $scope.asum[i] = sum;
      }
    }, true);

    $scope.$watch('book.bids', function (bids) {
      $scope.bsum = [];

      if (!bids) return;

      var sum;
      for (var i = 0, l = bids.length; i < l; i++) {
        if (sum) sum = sum.add(bids[i].TakerPays);
        else sum = Amount.from_json(bids[i].TakerPays);
        $scope.bsum[i] = sum;
      }
    }, true);

    $scope.reset();
  }]);
};

module.exports = TradeTab;
<commit_msg>Trade tab: Simplify asks/bids watch functions<commit_after>var util = require('util');
var webutil = require('../util/web');
var Tab = require('../client/tab').Tab;
var Amount = ripple.Amount;

var TradeTab = function ()
{
  Tab.call(this);
};

util.inherits(TradeTab, Tab);

TradeTab.prototype.mainMenu = 'advanced';

TradeTab.prototype.generateHtml = function ()
{
  return require('../../jade/tabs/trade.jade')();
};

TradeTab.prototype.angularDeps = Tab.prototype.angularDeps.concat(['books']);

TradeTab.prototype.angular = function(module)
{
  var self = this;
  var app = this.app;

  module.controller('TradeCtrl', ['rpBooks', '$scope', 'rpId', 'rpNetwork',
                                  function (books, $scope, $id, $network)
  {
    if (!$id.loginStatus) return $id.goId();

    $scope.mode = "confirm";
    $scope.asum = [];
    $scope.bsum = [];

    var pairs = $scope.pairs_all;
    $scope.pairs_query = webutil.queryFromOptions(pairs);

    $scope.$watch('userBlob.data.contacts', function (contacts) {
      $scope.issuer_query = webutil.queryFromOptions(contacts);
    }, true);

    $scope.reset = function (keepPair) {
      var pair = keepPair ? $scope.order.currency_pair :
            store.get('ripple_trade_currency_pair') || pairs[0].name;
      var fIssuer = keepPair ? $scope.order.first_issuer : $id.account;
      var sIssuer = keepPair ? $scope.order.second_issuer : $id.account;
      var type = keepPair ? $scope.order.type : 'buy';

      if ($scope.orderForm) $scope.orderForm.$setPristine();
      $scope.mode = "trade";
      $scope.order = {
        type: type,
        first: '',
        price: '',
        second: '',
        currency_pair: pair,
        first_currency: pair.slice(0, 3),
        second_currency: pair.slice(4, 7),
        first_issuer: fIssuer,
        second_issuer: sIssuer,
        listing: 'orderbook',

        // This variable is true if both the pair and the issuers are set to
        // valid values. It is used to enable or disable all the functionality
        // on the page.
        valid_settings: false
      };

      updateSettings();
    };

    // Show "My Orders" if account has been funded
    $scope.$watch('account.Balance', function(){
      if ($scope.account.Balance) {
        $scope.order.listing = 'my';
      }
    }, true);

    $scope.back = function () {
      $scope.mode = "trade";
    };

    $scope.place_order = function () {
      $scope.mode = "confirm";
      if ($scope.order.type === 'buy') {
        $scope.order.sell_amount = $scope.order.second_amount;
        $scope.order.buy_amount = $scope.order.first_amount;
      } else {
        $scope.order.sell_amount = $scope.order.first_amount;
        $scope.order.buy_amount = $scope.order.second_amount;
      }
    };

    $scope.cancel_order = function ()
    {
      var tx = $network.remote.transaction();
      tx.offer_cancel($id.account, this.entry.seq);
      tx.on('success', function () {
      });
      tx.on('error', function () {
        $scope.mode = "error";
        $scope.$digest();
      });
      tx.submit();

      this.cancelled = true;
    };

    $scope.order_confirmed = function ()
    {
      var tx = $network.remote.transaction();
      tx.offer_create($id.account, $scope.order.buy_amount, $scope.order.sell_amount);

      tx.on('success', function (res) {
        setEngineStatus(res, false);
        $scope.done(this.hash);

        // Remember pair and increase order
        var found;

        for (var i = 0; i < $scope.pairs_all.length; i++) {
          if ($scope.pairs_all[i].name == $scope.order.currency_pair) {
            $scope.pairs_all[i].order++;
            found = true;
            break;
          }
        }

        if (!found) {
          $scope.pairs_all.push({
            "name": $scope.order.currency_pair,
            "order": 1
          });
        }

        $scope.$digest();
      });
      tx.on('error', function () {
        $scope.mode = "error";
        $scope.$digest();
      });
      tx.submit();

      $scope.mode = "sending";
    };

    $scope.done = function (hash)
    {
      $scope.mode = "done";
      $network.remote.on('transaction', handleAccountEvent);

      function handleAccountEvent(e) {
        if (e.transaction.hash === hash) {
          setEngineStatus(e, true);
          $scope.$digest();
          $network.remote.removeListener('transaction', handleAccountEvent);
        }
      }
    };

    function setEngineStatus(res, accepted) {
      $scope.engine_result = res.engine_result;
      $scope.engine_result_message = res.engine_result_message;
      switch (res.engine_result.slice(0, 3)) {
        case 'tes':
          $scope.tx_result = accepted ? "cleared" : "pending";
          break;
        case 'tem':
          $scope.tx_result = "malformed";
          break;
        case 'ter':
          $scope.tx_result = "failed";
          break;
        case 'tec':
          $scope.tx_result = "claim";
          break;
        case 'tep':
          console.warn("Unhandled engine status encountered!");
      }
    }

    $scope.$watch('order.currency_pair', function (pair) {
      store.set('ripple_trade_currency_pair', pair);
      updateSettings();
      resetIssuers(true);
     }, true);

    $scope.$watch('order.first', function (amount_str) {
      $scope.update_first();
    }, true);

    $scope.$watch('order.price', function (amount_str) {
      $scope.update_price();
    }, true);

    $scope.$watch('order.second', function (amount_str) {
      $scope.update_second();
    }, true);

    $scope.$watch('userBlob', function () {
      resetIssuers(false);
    }, true);

    $scope.update_first = function () {
      var first_currency = $scope.order.first_currency || "XRP";
      var formatted = "" + $scope.order.first + " " + first_currency;

      $scope.order.first_amount = ripple.Amount.from_human(formatted);

      if (first_currency !== 'XRP') $scope.order.first_amount.set_issuer($scope.order.first_issuer);
    };

    $scope.update_price = function () {
      var second_currency = $scope.order.second_currency || "XRP";
      var formatted = "" + $scope.order.price + " " + second_currency;

      $scope.order.price_amount = ripple.Amount.from_human(formatted);

      if (second_currency !== 'XRP') $scope.order.price_amount.set_issuer($scope.order.second_issuer);
    };

    $scope.update_second = function () {
      var second_currency = $scope.order.second_currency || "XRP";
      var formatted = "" + $scope.order.second + " " + second_currency;

      $scope.order.second_amount = ripple.Amount.from_human(formatted);

      if (second_currency !== 'XRP') $scope.order.second_amount.set_issuer($scope.order.second_issuer);
    };

    $scope.calc_second = function () {
      $scope.update_first();
      $scope.update_price();
      if ($scope.order.price_amount && $scope.order.price_amount.is_valid() &&
          $scope.order.first_amount && $scope.order.first_amount.is_valid()) {
        $scope.order.second_amount =
          $scope.order.price_amount.product_human($scope.order.first_amount);
        $scope.order.second = +$scope.order.second_amount.to_human({group_sep: false});
      }
    };

    $scope.calc_first = function () {
      $scope.update_second();
      $scope.update_price();
      if ($scope.order.price_amount  && $scope.order.price_amount.is_valid() &&
          $scope.order.second_amount && $scope.order.second_amount.is_valid()) {
        $scope.order.first_amount =
          $scope.order.second_amount.ratio_human($scope.order.price_amount);
        $scope.order.first = +$scope.order.first_amount.to_human({group_sep: false});
      }
    };

    // This functions is called whenever the settings, specifically the pair and
    // the issuer(s) have been modified. It checks the new configuration and
    // sets $scope.valid_settings.
    function updateSettings() {
      var order = $scope.order;

      var pair = order.currency_pair;
      if ("string" !== typeof pair ||
          !pair.match(/^[a-z]{3}\/[a-z]{3}$/i)) {
        order.first_currency = 'XRP';
        order.second_currency = 'XRP';
        order.valid_settings = false;
        return;
      }
      var first_currency = order.first_currency = pair.slice(0, 3);
      var second_currency = order.second_currency = pair.slice(4, 7);

      var first_issuer = ripple.UInt160.from_json(order.first_issuer);
      var second_issuer = ripple.UInt160.from_json(order.second_issuer);
      if ((first_currency !== 'XRP' && !first_issuer.is_valid()) ||
          (second_currency !== 'XRP' && !second_issuer.is_valid())) {
        order.valid_settings = false;
        return;
      }

      if (order.type === "buy") {
        order.sell_currency = order.second_currency;
        order.buy_currency = order.first_currency;
        order.sell_issuer = order.second_issuer;
        order.buy_issuer = order.first_issuer;
      } else {
        order.sell_currency = order.first_currency;
        order.buy_currency = order.second_currency;
        order.sell_issuer = order.first_issuer;
        order.buy_issuer = order.second_issuer;
      }
      order.valid_settings = true;

      var key = "" +
            order.first_currency +
            (order.first_currency === 'XRP' ? "" : "/" +order.first_issuer) +
            ":" +
            order.second_currency +
            (order.second_currency === 'XRP' ? "" : "/" +order.second_issuer);

      if (order.prev_settings !== key) {
        loadOffers();

        order.prev_settings = key;
      }
    }

    function guessIssuer(currency, exclude_issuer) {
      var guess;

      // First guess: An explicit issuer preference setting in the user's blob
      try {
        guess = $scope.userBlob.data.preferred_issuer[currency];
        if (guess && guess == exclude_issuer) {
          guess = $scope.userBlob.data.preferred_second_issuer[currency];
        }
        if (guess) return guess;
      } catch (e) {}

      // Second guess: The user's highest trust line in this currency
      try {
        var issuers = $scope.balances[currency].components;
        for (var counterparty in issuers) {
          if (counterparty != exclude) {
            return counterparty;
          }
        }
      } catch (e) {}

      // We found nothing
      return null;
    }

    function resetIssuers(force) {
      var guess;

      if (force) {
        $scope.order.first_issuer = null;
        $scope.order.second_issuer = null;
      }

      if (!$scope.order.first_issuer &&
          $scope.order.first_currency &&
          $scope.order.first_currency !== 'XRP' &&
          (guess = guessIssuer($scope.order.first_currency))) {
        $scope.order.first_issuer = guess;
      }

      if (!$scope.order.second_issuer &&
          $scope.order.second_currency &&
          $scope.order.second_currency !== 'XRP' &&
          (guess = guessIssuer($scope.order.second_currency))) {
        $scope.order.second_issuer = guess;
      }

      // If the same currency, exclude first issuer for second issuer guess
      if ($scope.order.first_currency == $scope.order.second_currency &&
          $scope.order.first_issuer == $scope.order.second_issuer &&
          (guess = guessIssuer($scope.order.first_currency, $scope.order.first_issuer))) {
        $scope.order.second_issuer = guess;
      }
    }

    $scope.edit_first_issuer = function () {
      $scope.show_issuer_form = 'first';
      $scope.order.first_issuer_edit = webutil.unresolveContact($scope.userBlob.data.contacts, $scope.order.first_issuer);

      setImmediate(function () {
        $('#first_issuer').select();
      });

    };

    $scope.edit_second_issuer = function () {
      $scope.show_issuer_form = 'second';
      $scope.order.second_issuer_edit = webutil.unresolveContact($scope.userBlob.data.contacts, $scope.order.second_issuer);

      setImmediate(function () {
        $('#second_issuer').select();
      });
    };

    $scope.save_first_issuer = function () {
      $scope.order.first_issuer = webutil.resolveContact($scope.userBlob.data.contacts, $scope.order.first_issuer_edit);
      $scope.show_issuer_form = false;

      updateSettings();

      // Persist issuer setting
      if ($scope.order.valid_settings &&
          $scope.order.first_currency !== 'XRP') {
        $scope.userBlob.data.preferred_issuer[$scope.order.first_currency] =
          $scope.order.first_issuer;
      }
    };

    $scope.save_second_issuer = function () {
      $scope.order.second_issuer = webutil.resolveContact($scope.userBlob.data.contacts, $scope.order.second_issuer_edit);
      $scope.show_issuer_form = false;

      updateSettings();

      // Persist issuer setting
      if ($scope.order.valid_settings &&
          $scope.order.second_currency !== 'XRP') {

        if ($scope.order.first_currency == $scope.order.second_currency) {
          $scope.userBlob.data.preferred_second_issuer[$scope.order.second_currency] =
            $scope.order.second_issuer;
        } else {
          $scope.userBlob.data.preferred_issuer[$scope.order.second_currency] =
            $scope.order.second_issuer;
        }
      }
    };

    function loadOffers() {
      // Make sure we unsubscribe from any previously loaded orderbook
      if ($scope.book &&
          "function" === typeof $scope.book.unsubscribe) {
        $scope.book.unsubscribe();
      }

      $scope.asum = [];
      $scope.book = books.get({
        currency: $scope.order.first_currency,
        issuer: $scope.order.first_issuer
      }, {
        currency: $scope.order.second_currency,
        issuer: $scope.order.second_issuer
      }, $scope.address);
    }

    $scope.$watch('order.first_issuer', function (issuer) {
      updateSettings();
    });

    $scope.$watch('order.second_issuer', function (issuer) {
      updateSettings();
    });

    $scope.$watch('balances', function () {
      resetIssuers(false);
    }, true);

    function calculateSum(key,val,offers) {
      $scope[val] = [];

      if (!offers) return;

      var sum;
      for (var i = 0, l = offers.length; i < l; i++) {
        sum = sum ? sum.add(offers[i][key]) : Amount.from_json(offers[i][key]);
        $scope[val][i] = sum;
      }
    }

    $scope.$watch('book.asks', calculateSum.bind({},'TakerGets','asum'), true);
    $scope.$watch('book.bids', calculateSum.bind({},'TakerPays','bsum'), true);

    $scope.reset();
  }]);
};

module.exports = TradeTab;
<|endoftext|>


<commit_before>define(
  'ephox.snooker.model.TableMerge',

  [
    'ephox.compass.Arr',
    'ephox.peanut.Fun',
    'ephox.snooker.model.Fitment',
    'ephox.snooker.operate.MergingOperations'
  ],

  function (Arr, Fun, Fitment, MergingOperations) {
    // an arbitary limit, to stop retrying incase we hit stack overflows.
    // Its expected that most retries will be under 3 and thats for really edgy cases.
    var RETRIES = 1000;

    var skip = function (start, current, replacement, x) {
      /*
        If the current Position is before or after the insertion point skip over it.
                      let,
                         listA = a, b, c, d, e
                         listB =       1, 2
        merge gridB into listA = a, b, 1, 2, e
        this fn will skip over a, b and e, same concept applies for both rows & columns
      */
      var end = start + replacement;
      return start > current || end <= current;
    };

    var isSpanning = function (grid, row, col, candidate, comparator) {
      var matching = Fun.curry(comparator, candidate);
      var rowArray = grid[row];

      // sanity check
      return grid.length > 1 && rowArray.length > 1 &&
      (
        // search left, if we're not on the left edge
        (row > 0 && matching(grid[row-1][col])) ||
        // search right, if we're not on the right edge
        (row < grid.length - 1 && matching(grid[row+1][col])) ||
        // search up, if we're not on the top edge
        (col > 0 && matching(rowArray[col-1])) ||
        // search down, if we're not on the bottom edge
        (col > row.length - 1 && matching(rowArray[col+1]))
      );
    };

    var detectSpan = function (startAddress, gridA, gridB, comparator) {
      var target = false;

      var rowsA = gridA.length;
      var rowsB = gridB.length;

      for (var r = 0, skipRow; r < rowsA && target === false; r++) {
        skipRow = skip(startAddress.row(), r, rowsB);
        if (!skipRow) {
          for (var c = 0, skipCell; c < gridA[r].length && target === false; c++) {
            skipCell = skip(startAddress.column(), c, gridB[0].length);
            var candidate = gridA[r][c];
            if (!skipCell && isSpanning(gridA, r, c, candidate, comparator)) {
            // if (!skipCell && Arr.exists(knownSpans, Fun.curry(comparator, candidate))) {
              target = candidate;
            }
          }
        }
      }
      return target;
    };

    var unmergeIntersections = function (startAddress, gridA, gridB, generator, comparator) {
      var unmergedGrid = gridA;
      var attempts = 0;
      var intersectsSpan = false;

var start = new Date().getTime();
      do {
        if (intersectsSpan !== false) {
          unmergedGrid = MergingOperations.unmerge(unmergedGrid, intersectsSpan, comparator, generator.cell);
        }
        intersectsSpan = detectSpan(startAddress, unmergedGrid, gridB, comparator);
        attempts++;
      } while (attempts < RETRIES && intersectsSpan !== false);

var end = new Date().getTime();
var time = end - start;
console.log('Execution time: ' + time);
      return unmergedGrid;
    };

    var mergeTables = function (startAddress, gridA, gridB, generator) {
      // Assumes
      //  - gridA is square and gridB is square
      //  - inserting gridB does not interesect any cell spans on gridA
      return Arr.map(gridA, function (row, r) {
        var repRow = r - startAddress.row();
        var skipRow = skip(startAddress.row(), r, gridB.length);
        if (skipRow) return row;

        else return Arr.map(row, function (cell, c) {
          var repCol = c - startAddress.column();
          var skipCell = skip(startAddress.column(), c, gridB[0].length);

          if (skipCell) return cell;
          else return generator.replace(gridB[repRow][repCol]);
        });
      });
    };

    var merge = function (startAddress, gridA, gridB, generator, comparator) {
      var delta = Fitment.measure(startAddress, gridA, gridB);
      var fittedGrid = Fitment.tailor(startAddress, gridA, delta, generator);
      var cleanGrid = unmergeIntersections(startAddress, fittedGrid, gridB, generator, comparator);
      return mergeTables(startAddress, cleanGrid, gridB, generator);
    };

    return {
      merge: merge,
      detectSpan: detectSpan,
      unmergeIntersections: unmergeIntersections
    };
  }
);<commit_msg>Match the checks to their comments. Fix incorrect check for looking right.<commit_after>define(
  'ephox.snooker.model.TableMerge',

  [
    'ephox.compass.Arr',
    'ephox.peanut.Fun',
    'ephox.snooker.model.Fitment',
    'ephox.snooker.operate.MergingOperations',
    'ephox.snooker.util.CellUtils'
  ],

  function (Arr, Fun, Fitment, MergingOperations, CellUtils) {
    // an arbitary limit, to stop retrying incase we hit stack overflows.
    // Its expected that most retries will be under 3 and thats for really edgy cases.
    var RETRIES = 1000;

    var skip = function (start, current, replacement, x) {
      /*
        If the current Position is before or after the insertion point skip over it.
                      let,
                         listA = a, b, c, d, e
                         listB =       1, 2
        merge gridB into listA = a, b, 1, 2, e
        this fn will skip over a, b and e, same concept applies for both rows & columns
      */
      var end = start + replacement;
      return start > current || end <= current;
    };

    var isSpanning = function (grid, row, col, candidate, comparator) {
      var matching = Fun.curry(comparator, candidate);
      var rowArray = grid[row];

      // sanity check
      return grid.length > 1 && rowArray.length > 1 &&
      (
        // search left, if we're not on the left edge
        (col > 0 && matching(rowArray[col-1])) ||
        // search right, if we're not on the right edge
        (col < rowArray.length - 1 && matching(rowArray[col+1])) ||
        // search up, if we're not on the top edge
        (row > 0 && matching(grid[row-1][col])) ||
        // search down, if we're not on the bottom edge
        (row < grid.length - 1 && matching(grid[row+1][col]))
      );
    };

    var detectSpan = function (startAddress, gridA, gridB, comparator) {
      var target = false;

      var rowsA = gridA.length;
      var rowsB = gridB.length;

      for (var r = 0, skipRow; r < rowsA && target === false; r++) {
        skipRow = skip(startAddress.row(), r, rowsB);
        if (!skipRow) {
          for (var c = 0, skipCell; c < gridA[r].length && target === false; c++) {
            skipCell = skip(startAddress.column(), c, gridB[0].length);
            var candidate = gridA[r][c];
            if (!skipCell && isSpanning(gridA, r, c, candidate, comparator)) {
              target = candidate;
            }
          }
        }
      }
      return target;
    };

    var unmergeIntersections = function (startAddress, gridA, gridB, generator, comparator) {
      var unmergedGrid = gridA;
      var attempts = 0;
      var intersectsSpan = false;

var start = new Date().getTime();
      do {
        if (intersectsSpan !== false) {
          unmergedGrid = MergingOperations.unmerge(unmergedGrid, intersectsSpan, comparator, generator.cell);
        }
        intersectsSpan = detectSpan(startAddress, unmergedGrid, gridB, comparator);
        attempts++;
      } while (attempts < RETRIES && intersectsSpan !== false);

var end = new Date().getTime();
var time = end - start;
console.log('Execution time: ' + time);
      return unmergedGrid;
    };

    var mergeTables = function (startAddress, gridA, gridB, generator) {
      // Assumes
      //  - gridA is square and gridB is square
      //  - inserting gridB does not interesect any cell spans on gridA
      return Arr.map(gridA, function (row, r) {
        var repRow = r - startAddress.row();
        var skipRow = skip(startAddress.row(), r, gridB.length);
        if (skipRow) return row;

        else return Arr.map(row, function (cell, c) {
          var repCol = c - startAddress.column();
          var skipCell = skip(startAddress.column(), c, gridB[0].length);

          if (skipCell) return cell;
          else return generator.replace(gridB[repRow][repCol]);
        });
      });
    };

    var merge = function (startAddress, gridA, gridB, generator, comparator) {
      var delta = Fitment.measure(startAddress, gridA, gridB);
      var fittedGrid = Fitment.tailor(startAddress, gridA, delta, generator);
      var cleanGrid = unmergeIntersections(startAddress, fittedGrid, gridB, generator, comparator);
      return mergeTables(startAddress, cleanGrid, gridB, generator);
    };

    return {
      merge: merge,
      detectSpan: detectSpan,
      unmergeIntersections: unmergeIntersections
    };
  }
);<|endoftext|>


<commit_before>
'use strict';

// dependencies -----------------------------------------------------------

    var gulp = require('gulp'),
        changed = require('gulp-changed'),
        sass = require('gulp-sass'),
        csso = require('gulp-csso'),
        autoprefixer = require('gulp-autoprefixer'),
        browserify = require('browserify'),
        watchify = require('watchify'),
        source = require('vinyl-source-stream'),
        buffer = require('vinyl-buffer'),
        babelify = require('babelify'),
        uglify = require('gulp-uglify'),
        del = require('del'),
        notify = require('gulp-notify'),
        browserSync = require('browser-sync'),
        sourcemaps = require('gulp-sourcemaps'),
        reload = browserSync.reload;

// project config ---------------------------------------------------------

    var p = {
        html:           './src/index.html',
        jsx:            './src/scripts/client.jsx',
        md5worker:      './src/scripts/utils/md5worker.js',
        scss:           './src/sass/**/*.scss',
        scssmain:       './src/sass/main.scss',
        libs:           './src/scripts/libs/*',
        assets:         './src/assets/*',
        fonts:          './src/sass/fonts/*',
        bundle:         'app.min.js',
        md5bundle:      'md5worker.min.js',

        dist:           'dist',
        distCss:        'dist/css',
        distAssets:     'dist/assets',
        distFonts:      'dist/fonts'
    };

// primary tasks ----------------------------------------------------------

    gulp.task('build', ['clean'], function() {
        process.env.NODE_ENV = 'production';
        gulp.start(['styles', 'copy', 'buildApp', 'buildMD5Worker']);
    });

    gulp.task('watch', [], function() {
        gulp.start(['watchStyles', 'watchApp', 'watchMD5Worker', 'styles', 'copy', 'browserSync']);
    });

    gulp.task('default',['watch'], function() {
        console.log('Running"');
    });

// tasks ------------------------------------------------------------------

    // clean before build
    gulp.task('clean', function(cb) {
          del(['dist'], cb);
    });

    // server and sync changes
    gulp.task('browserSync', function() {
        browserSync.init({
            server: './dist',
            port: 9876
        });
    });

    // copy
    gulp.task('copy', function () {
        gulp.src(p.html).pipe(gulp.dest(p.dist));
        gulp.src(p.assets).pipe(gulp.dest(p.distAssets));
        gulp.src(p.fonts).pipe(gulp.dest(p.distFonts));
    });

    // watch for changes
    gulp.task('watchApp', function() {
        var bundler = watchify(browserify(p.jsx, watchify.args));
        function rebundle() {
            return bundler
                .bundle()
                .on('error', notify.onError())
                .pipe(source(p.bundle))
                .pipe(gulp.dest(p.dist))
                .pipe(reload({stream: true}));
        }
        bundler.transform(babelify).on('update', rebundle);
        return rebundle();
    });

    // watch for changes
    gulp.task('watchMD5Worker', function() {
        var bundler = watchify(browserify(p.md5worker, watchify.args));
        function rebundle() {
            return bundler
                .bundle()
                .on('error', notify.onError())
                .pipe(source(p.md5bundle))
                .pipe(gulp.dest(p.dist))
                .pipe(reload({stream: true}));
        }
        bundler.transform(babelify).on('update', rebundle);
        return rebundle();
    });

    // bundle js
    gulp.task('buildApp', function() {
        browserify(p.jsx)
            .transform(babelify)
            .bundle()
            .pipe(source(p.bundle))
            .pipe(buffer())
            .pipe(sourcemaps.init({loadMaps: true}))
            .pipe(uglify())
            .pipe(sourcemaps.write('./'))
            .pipe(gulp.dest(p.dist));
    });

    // bundle md5 worker
    gulp.task('buildMD5Worker', function() {
        browserify(p.md5worker)
            .transform(babelify)
            .bundle()
            .pipe(source(p.md5bundle))
            .pipe(buffer())
            .pipe(sourcemaps.init({loadMaps: true}))
            .pipe(uglify())
            .pipe(sourcemaps.write('./'))
            .pipe(gulp.dest(p.dist));
    });

    // compile & minify scss
    gulp.task('styles', function() {
        return gulp.src(p.scssmain)
            .pipe(changed(p.distCss))
            .pipe(sass({errLogToConsole: true}))
            .on('error', notify.onError())
            .pipe(csso())
            .pipe(gulp.dest(p.distCss))
            .pipe(reload({stream: true}));
    });

    // watch styles
    gulp.task('watchStyles', function() {
        gulp.watch(p.scssmain, ['styles']);
        gulp.watch(p.scss, ['styles']);
    });
<commit_msg>Turned off css minification due to node module change conflicts<commit_after>
'use strict';

// dependencies -----------------------------------------------------------

    var gulp = require('gulp'),
        changed = require('gulp-changed'),
        sass = require('gulp-sass'),
        csso = require('gulp-csso'),
        autoprefixer = require('gulp-autoprefixer'),
        browserify = require('browserify'),
        watchify = require('watchify'),
        source = require('vinyl-source-stream'),
        buffer = require('vinyl-buffer'),
        babelify = require('babelify'),
        uglify = require('gulp-uglify'),
        del = require('del'),
        notify = require('gulp-notify'),
        browserSync = require('browser-sync'),
        sourcemaps = require('gulp-sourcemaps'),
        reload = browserSync.reload;

// project config ---------------------------------------------------------

    var p = {
        html:           './src/index.html',
        jsx:            './src/scripts/client.jsx',
        md5worker:      './src/scripts/utils/md5worker.js',
        scss:           './src/sass/**/*.scss',
        scssmain:       './src/sass/main.scss',
        libs:           './src/scripts/libs/*',
        assets:         './src/assets/*',
        fonts:          './src/sass/fonts/*',
        bundle:         'app.min.js',
        md5bundle:      'md5worker.min.js',

        dist:           'dist',
        distCss:        'dist/css',
        distAssets:     'dist/assets',
        distFonts:      'dist/fonts'
    };

// primary tasks ----------------------------------------------------------

    gulp.task('build', ['clean'], function() {
        process.env.NODE_ENV = 'production';
        gulp.start(['styles', 'copy', 'buildApp', 'buildMD5Worker']);
    });

    gulp.task('watch', [], function() {
        gulp.start(['watchStyles', 'watchApp', 'watchMD5Worker', 'styles', 'copy', 'browserSync']);
    });

    gulp.task('default',['watch'], function() {
        console.log('Running"');
    });

// tasks ------------------------------------------------------------------

    // clean before build
    gulp.task('clean', function(cb) {
          del(['dist'], cb);
    });

    // server and sync changes
    gulp.task('browserSync', function() {
        browserSync.init({
            server: './dist',
            port: 9876
        });
    });

    // copy
    gulp.task('copy', function () {
        gulp.src(p.html).pipe(gulp.dest(p.dist));
        gulp.src(p.assets).pipe(gulp.dest(p.distAssets));
        gulp.src(p.fonts).pipe(gulp.dest(p.distFonts));
    });

    // watch for changes
    gulp.task('watchApp', function() {
        var bundler = watchify(browserify(p.jsx, watchify.args));
        function rebundle() {
            return bundler
                .bundle()
                .on('error', notify.onError())
                .pipe(source(p.bundle))
                .pipe(gulp.dest(p.dist))
                .pipe(reload({stream: true}));
        }
        bundler.transform(babelify).on('update', rebundle);
        return rebundle();
    });

    // watch for changes
    gulp.task('watchMD5Worker', function() {
        var bundler = watchify(browserify(p.md5worker, watchify.args));
        function rebundle() {
            return bundler
                .bundle()
                .on('error', notify.onError())
                .pipe(source(p.md5bundle))
                .pipe(gulp.dest(p.dist))
                .pipe(reload({stream: true}));
        }
        bundler.transform(babelify).on('update', rebundle);
        return rebundle();
    });

    // bundle js
    gulp.task('buildApp', function() {
        browserify(p.jsx)
            .transform(babelify)
            .bundle()
            .pipe(source(p.bundle))
            .pipe(buffer())
            .pipe(sourcemaps.init({loadMaps: true}))
            .pipe(uglify())
            .pipe(sourcemaps.write('./'))
            .pipe(gulp.dest(p.dist));
    });

    // bundle md5 worker
    gulp.task('buildMD5Worker', function() {
        browserify(p.md5worker)
            .transform(babelify)
            .bundle()
            .pipe(source(p.md5bundle))
            .pipe(buffer())
            .pipe(sourcemaps.init({loadMaps: true}))
            .pipe(uglify())
            .pipe(sourcemaps.write('./'))
            .pipe(gulp.dest(p.dist));
    });

    // compile & minify scss
    gulp.task('styles', function() {
        return gulp.src(p.scssmain)
            .pipe(changed(p.distCss))
            .pipe(sass({errLogToConsole: true}))
            .on('error', notify.onError())
            // .pipe(csso())
            .pipe(gulp.dest(p.distCss))
            .pipe(reload({stream: true}));
    });

    // watch styles
    gulp.task('watchStyles', function() {
        gulp.watch(p.scssmain, ['styles']);
        gulp.watch(p.scss, ['styles']);
    });
<|endoftext|>


<commit_before>// ###############################################################################
"use strict";
// ###############################################################################
var thisFile = 'mouse.js';

var image = {};
image.img = {};
image.img['atlas'] = {filename:"../assets/atlas2048_32.png",file:undefined};
image.img['blank'] = {filename:"../assets/eagle_640x360.png",file:undefined};
image.keys = Object.keys(image.img);
image.loadCounter = 0;

// ***********************************************************
image.load = function(callback) {
    var thisFunc = 'image.load()';
    dbg.func(thisFile, thisFunc);

    for (var i=0; i<image.keys.length; i++){

	    image.img[image.keys[i]].file = new Image();
	    image.img[image.keys[i]].file.src = image.img[image.keys[i]].filename;
	    image.img[image.keys[i]].file.onload = function() {
	    	image.loadCounter++;
	    	if (image.loadCounter === image.keys.length) callback();
	    }

    }
}

// ***********************************************************
<commit_msg>fixed thisFile to correct filename<commit_after>// ###############################################################################
"use strict";
// ###############################################################################
var thisFile = 'image.js';

var image = {};
image.img = {};
image.img['atlas'] = {filename:"../assets/atlas2048_32.png",file:undefined};
image.img['blank'] = {filename:"../assets/eagle_640x360.png",file:undefined};
image.keys = Object.keys(image.img);
image.loadCounter = 0;

// ***********************************************************
image.load = function(callback) {
    var thisFunc = 'image.load()';
    dbg.func(thisFile, thisFunc);

    for (var i=0; i<image.keys.length; i++){

	    image.img[image.keys[i]].file = new Image();
	    image.img[image.keys[i]].file.src = image.img[image.keys[i]].filename;
	    image.img[image.keys[i]].file.onload = function() {
	    	image.loadCounter++;
	    	if (image.loadCounter === image.keys.length) callback();
	    }

    }
}

// ***********************************************************
<|endoftext|>


<commit_before>import listenStoreChanges from '../listenStoreChanges';
import { workFinished } from '../actions';
import { playSoundOptions, stopSound } from '../playSound';

const handleWork = (store, chrome) => {
  let workTabId = null;
  window.audioPlayer = null;

  const handleAssignWork = ({ worker: prevWorker }, { worker: curWorker }) => {
    if (prevWorker.get('state') !== 'working' && curWorker.get('state') === 'working') {
      const url = curWorker.get('workUrl');
      if (!url) {
        throw new Error("Worker moved to 'working' state without a work URL");
      }

      const oldWorkTabId = workTabId;
      chrome.tabs.create({ url }, tab => {
        workTabId = tab.id;
        window.audioPlayer = playSoundOptions(store.getState().plugin.get('options'));
      });
      if (oldWorkTabId) {
        chrome.tabs.remove(oldWorkTabId);
      }
    }

    if (curWorker.get('state') !== 'working') {
      stopSound(window.audioPlayer);
    }
  };

  const workTabClosed = () => {
    const { worker } = store.getState();
    if (worker.get('state') === 'working' && !worker.get('workStarted')) {
      store.dispatch(workFinished());
    }
    workTabId = null;
  };

  const handleUpdate = (previous, current) => {
    handleAssignWork(previous, current);
  };

  chrome.tabs.onRemoved.addListener(tabId => {
    if (tabId === workTabId) {
      workTabClosed();
    }
  });

  listenStoreChanges(store, handleUpdate);
};

export default handleWork;
<commit_msg>handle stopping sound<commit_after>import listenStoreChanges from '../listenStoreChanges';
import { workFinished } from '../actions';
import { playSoundOptions, stopSound } from '../playSound';

const handleWork = (store, chrome) => {
  let workTabId = null;
  window.audioPlayer = null;

  const handleAssignWork = ({ worker: prevWorker }, { worker: curWorker }) => {
    if (prevWorker.get('state') !== 'working' && curWorker.get('state') === 'working') {
      const url = curWorker.get('workUrl');
      if (!url) {
        throw new Error("Worker moved to 'working' state without a work URL");
      }

      const oldWorkTabId = workTabId;
      chrome.tabs.create({ url }, tab => {
        workTabId = tab.id;
        window.audioPlayer = playSoundOptions(store.getState().plugin.get('options'));
      });
      if (oldWorkTabId) {
        chrome.tabs.remove(oldWorkTabId);
      }
    }

    if (prevWorker.get('state') === 'working' && curWorker.get('state') !== 'working') {
      stopSound(window.audioPlayer);
    }
  };

  const workTabClosed = () => {
    const { worker } = store.getState();
    if (worker.get('state') === 'working' && !worker.get('workStarted')) {
      store.dispatch(workFinished());
    }
    workTabId = null;
  };

  const handleUpdate = (previous, current) => {
    handleAssignWork(previous, current);
  };

  chrome.tabs.onRemoved.addListener(tabId => {
    if (tabId === workTabId) {
      workTabClosed();
    }
  });

  listenStoreChanges(store, handleUpdate);
};

export default handleWork;
<|endoftext|>