<commit_before>// Handle the mixing of colors.

import '/imports/lib/color.js';
import Colormap from '/imports/mapPage/color/Colormap';
import hexagons from '/imports/mapPage/viewport/hexagons';
import Layer from '/imports/mapPage/longlist/Layer';
import legend from '/imports/mapPage/color/legend';
import rx from '/imports/common/rx';
import shortlist from '/imports/mapPage/shortlist/shortlist';
import util from '/imports/common/util';

let refreshTimer

function get_range_position(scoreIn, low, high, filterBy) {

    // Given a score float, and the lower and upper bounds of an interval (which
    // may be equal, but not backwards), return a number in the range -1 to 1
    // that expresses the position of the score in the [low, high] interval.
    // Positions out of bounds are clamped to -1 or 1 as appropriate.

    // This holds the length of the input interval
    var interval_length = high - low;
    var score = scoreIn
    if(interval_length > 0) {

        if (filterBy === 'threshold') {

            // If the score is out of bounds, set it to lower or upper bound.
            if (score < low) {
                score = low
            } else if (score > high) {
                score = high
            }
        }

        // First rescale 0 to 1
        score = (score - low) / interval_length;

        // Clamp
        score = Math.min(Math.max(score, 0), 1);

        // Now re-scale to -1 to 1
        score = 2 * score - 1;
    } else {
        // The interval is just a point
        // Just use 1 if we're above the point, and 0 if below.
        score = (score > low)? 1 : -1;
    }

    return score;
}

function findContinuousFilters (attrNames) {

    // Set up the limits for continuous attrs.
    let filters = rx.get('shortEntry.filter')
    let limits = {}
    attrNames.forEach(attr => {
        let filter = filters[attr]
        if (filter) {
            limits[attr] = filter
        } else {

            // With no filter, use the attr's min and max.
            limits[attr] = {
                by: 'range',
                low: layers[attr].minimum,
                high: layers[attr].maximum,
            }
        }
    })

    return limits
}

function refreshColorsInner () {

    // Make the view display the correct hexagons in the colors of the current
    // layer(s), as read from the values of the layer pickers in the global
    // layer pickers array.
    // All pickers must have selected layers that are in the object of
    // layers.
    // Instead of calling this, you probably want to call refreshColors().

    // Don't do multiple refreshes during initialization.
    if (rx.get('init.map') !== 'rendered' ||
        !rx.get('inited.coloringAttrs')) {
        return;
    }

    // Special case of no layers at all.
    if (_.isUndefined(layers) || Object.keys(layers) < 1) {
        for(var signature in polygons) {
            hexagons.setOneColor(polygons[signature], Colormap.noAttrsColor());
        }
        return;
    }

    // This holds a list of the string names of the currently selected layers,
    // in order.
    var actives = shortlist.get_active_coloring_layers();

    // Obtain the layer objects (mapping from signatures/hex labels to colors)
    Layer.with_many(actives, function(attrs) {

        // This holds arrays of the lower and upper limit we want to use for
        // each layer, by layer number. The lower limit corresponds to u or
        // v = -1, and the upper to u or v = 1. The entries we make for
        // colormaps are ignored.

        // We need to do this inside the callback, once we already have the
        // layers, so that we use the newest slider range endpoints.
        var at_least_one_layer = attrs.length >= 1;
        var two_layers = attrs.length >= 2;

        // Get the limits for continuous attrs.
        let limits = findContinuousFilters(actives)

        // Determine the color of nodes not passing the filters.
        let bgNodeColor = Colormap.noDataColor();
        if (rx.get('shortEntry.menu.hideBgNodes')) {
            bgNodeColor = (rx.get('background') === 'black') ?
                '#000000' : '#ffffff';
        }

        // Go get the list of filter-passing hexes.
        shortlist.with_filtered_signatures(function(signatures) {
            for (let sig in polygons) {
                if (signatures.indexOf(sig) < 0) {
                    hexagons.setOneColor(polygons[sig], bgNodeColor);
                    continue
                }
                var label = sig

                // This holds the color we are calculating for this hexagon.
                // Start with the no data color.
                var computed_color = Colormap.noDataColor();

                if(at_least_one_layer) {
                    // We need to compute colors given the layers we found.

                    // Get the heat along u and v axes. This puts us in a square
                    // of side length 2. Fun fact: undefined / number = NaN, but
                    // !(NaN == NaN)
                    var u = attrs[0].data[label];

                    if(util.is_continuous(actives[0])) {

                        // Take into account the slider values and re-scale the
                        // layer value to express its position between them.
                        u = get_range_position(u, limits[actives[0]].low,
                            limits[actives[0]].high, limits[actives[0]].by);
                    }

                    if (two_layers) {
                        // There's a second layer, so use the v axis.
                        var v = attrs[1].data[label];

                        if(util.is_continuous(actives[1])) {
                            // Take into account the slider values and re-scale
                            // the layer value to express its position between
                            // them.
                            v = get_range_position(v, limits[actives[1]].low,
                                limits[actives[1]].high, limits[actives[1]].by);
                        }

                    } else {
                        // No second layer, so v axis is unused. Don't make it
                        // undefined (it's not missing data), but set it to 0.
                        v = 0;
                    }

                    // Either of u or v may be undefined (or both) if the layer
                    // did not contain an entry for this signature. But that's
                    // OK. Compute the color that we should use to express this
                    // combination of layer values. It's OK to pass undefined
                    // names here for layers.
                    computed_color = get_color(actives[0], u, actives[1], v);
                }

                // Set the color by the composed layers.
                hexagons.setOneColor(polygons[label], computed_color);
            }
        });

        legend.redraw(attrs, actives);
    });

    // Make sure to also redraw the info window, which may be open.
    import infoWindow from '/imports/mapPage/viewport/infoWindow.js';
    infoWindow.redraw();
}

export function refreshColors() {

    // Use a timeout to throttle the color refreshes.
    //console.log('refreshColors()')
    clearTimeout(refreshTimer)
    refreshTimer = setTimeout(refreshColorsInner)
}

export function get_color (layerName1, layerVal1, layerName2, layerVal2) {
    // Either layer value may be undefined (or both), in which case the no-data color
    // is returned. If a layer name is undefined, that layer dimension is
    // ignored.

    var color,
        base_color,
        hsv_value;

    // These are all cases we need to pay attention to.
    var onlyOneLayer = _.isUndefined(layerName2);

    var oneBinaryLayer = (
        onlyOneLayer &&
        util.is_binary(layerName1)
    );

    var oneContinuousLayer = (
        onlyOneLayer &&
        !oneBinaryLayer &&
        util.is_continuous(layerName1)
    );

    var oneCategoricalLayer = (
        onlyOneLayer &&
        !oneBinaryLayer &&
        !oneContinuousLayer
    );

    var bothContinuous = (
        !onlyOneLayer &&
        util.is_continuous(layerName1) &&
        util.is_continuous(layerName2)
    );

    var continuousAndCatOrBin =(
        !onlyOneLayer &&
        util.is_cat_or_bin(layerName2) &&
        util.is_continuous(layerName1)
    );

    var catOrBinAndContinous = (
        !onlyOneLayer &&
        !continuousAndCatOrBin &&
        util.is_cat_or_bin(layerName1) &&
        util.is_continuous(layerName2)
    );

    var catAndCatOrBin=(
        !onlyOneLayer &&
        util.is_categorical(layerName1) &&
        util.is_cat_or_bin(layerName2) ||
        util.is_categorical(layerName2) &&
        util.is_cat_or_bin(layerName1)
    );

    var bothLayersBinary = (
        util.is_binary(layerName1) && util.is_binary(layerName2)
    );

    var any_missing_values =(
        isNaN(layerVal1) ||
        isNaN(layerVal2) ||
        _.isUndefined(layerVal1) ||
        _.isUndefined(layerVal2)
    );

    var on = 1;
    var off = 0;

    if(continuousAndCatOrBin) {
        //Manipulate the ordering of arguments so an extra case is not needed.
        color = get_color(layerName2, layerVal2, layerName1, layerVal1);

    } else if (any_missing_values) {
        color = Colormap.noDataColor();

    } else if(oneBinaryLayer) {
        // User's choice from color map or default.
        if(layerVal1 === on) { // layerVal1 is 1
            color =  Colormap.binaryOnColor(layerName1);
        } else if (layerVal1 === off) { // layerVal1 is 0
            color = Colormap.binaryOffColor(layerName1);
        } else {
            throw "There was an error making the color of the binary layer";
        }

    } else if(bothLayersBinary) {
        // Special color scheme for two binaries,
        // always uses default, never choice from user.
        if(layerVal1 === on) {
            if(layerVal2 === on) {
                // Both are on
                return Colormap.defaultBinBothOn();
            } else if (layerVal2 === off) {
                // Only the first is on
                return Colormap.defaultBinaryOn();
            }
        } else if (layerVal1 === off) {
            if(layerVal2 === on) {
                // Only the second is on
                return Colormap.defaultSecondBinOn();
            } else if (layerVal2 === off) {
                // Neither is on
                return Colormap.defaultBinaryOff();
            }
        }

    } else if (oneCategoricalLayer) {
        base_color = baseCategoricalColor(layerName1, layerVal1);
        base_color.value(base_color.value());
        color = base_color.hexString();

    } else if (catAndCatOrBin) {
        base_color = baseCategoricalColor(layerName1, layerVal1);
        // Do discrete shades of second layer.
        // Calculate what shade we need from second layer value
        // We want 100 to be included (since that's full brightness), but we
        // want to skip 0 (since no color can be seen at 0), so we add 5 to
        // the second layer's value.
        var colorCountL2 = Colormap.findColorCount(layerName2);
        hsv_value = (layerVal2 + 1) / colorCountL2 * 100;
        base_color.value(hsv_value);
        color = base_color.hexString();

    } else if (catOrBinAndContinous) {
        base_color = baseCategoricalColor(layerName1, layerVal1);
        // Calculate what shade we need from v on -1 to 1, with a minimum
        // value of 20 to avoid blacks.
        hsv_value = 60 + layerVal2 * 40;
        base_color.value(hsv_value);
        color = base_color.hexString();

    } else if(oneContinuousLayer) {
        // Sets the interpolation so the second color is not mixed.
        // Chooses the user's specified coloring or
        color = mixOneContinuous(layerName1, layerVal1);
    } else if (bothContinuous) {
        color = mix2Continuos(layerVal1, layerVal2);
    } else {
        throw "An error occurred when determining a nodes color.";
    }

    return color;
}

function baseCategoricalColor(layerName, layerValue) {
    var base_color;
    if(colormaps[layerName].hasOwnProperty(layerValue)) {
        // And the colormap has an entry here. Use it as the base color.
        var to_clone = colormaps[layerName][layerValue].color;

        base_color = Color({
            hue: to_clone.hue(),
            saturation: to_clone.saturationv(),
            value: to_clone.value()
        });
    } else {
        // Something went wrong. This case catches when a value in the layer
        // doesn't have a colormap entry.
        // This conforms to the old code (since refactored),
        // which hid the error by skipping the category.
        base_color = Color({
            red : undefined,
            blue : undefined,
            green : undefined
        });
    }
    return base_color;
}

function mix (a, b, amount) {
    // Mix between the numbers a and b, where an amount of -1 corresponds to a,
    // and an amount of +1 corresponds to b.

    // Convert to 0 to 1 range.
    var i = (amount + 1) / 2;

    // Do the linear interpolation.
    return i * a + (1 - i) * b;

}

function mix2 (a, b, c, d, amount1, amount2) {
    // Mix between a and b (or c and d) on amount1, and then mix between the
    // results on amount2. Amounts are in range -1 to 1.

    return mix(mix(a, b, amount1), mix(c, d, amount1), amount2);
}

function parseColorPortions(hexStr){

    var portion_prefix = "0x";
    var red_portion = parseInt(portion_prefix + hexStr.slice(1,3));
    var green_portion = parseInt(portion_prefix + hexStr.slice(3,5));
    var blue_portion = parseInt(portion_prefix + hexStr.slice(5,7));

    return {
        "red" : red_portion,
        "green" : green_portion,
        "blue" : blue_portion,
    };
}

function colorify(red, green, blue){
    var color = "rgb(" + red + "," + green + "," + blue + ")";
    return color;
}

function mixOneContinuous(layerName, layerValue){
    var ignoreValue = -1;
    var ignoreColor = 0;
    var highColor = Colormap.continuousHighColor(layerName);
    var lowColor = Colormap.continuousLowColor(layerName);
    var highColorParsed = parseColorPortions(highColor);
    var lowColorParsed = parseColorPortions(lowColor);

    var red = mix2(
        lowColorParsed["red"],
        highColorParsed["red"],
        ignoreColor, ignoreColor,
        -layerValue, -ignoreValue
    ).toFixed(0);

    var green = mix2(
        lowColorParsed["green"],
        highColorParsed["green"],
        ignoreColor, ignoreColor,
        -layerValue, -ignoreValue
    ).toFixed(0);

    var blue = mix2(
        lowColorParsed["blue"],
        highColorParsed["blue"],
        ignoreColor, ignoreColor,
        -layerValue, -ignoreValue
    ).toFixed(0);

    var color = colorify(red, green, blue);

    return color;
}

function mix2Continuos(layerVal1, layerVal2){
    // Ignore color map entries.
    var highColor1 = Colormap.defaultContHigh();
    var lowColor1 = Colormap.defaultContLow();
    var lowColor1HighColor2 = Colormap.defaultCont2High1Low();
    var bothHighColor = Colormap.defaultContBothHigh();

    var high1Parsed = parseColorPortions(highColor1);
    var low1Parsed = parseColorPortions(lowColor1);
    var lowHighParsed = parseColorPortions(lowColor1HighColor2);
    var bothHighParsed = parseColorPortions(bothHighColor);

    var red = mix2(
        low1Parsed["red"],
        high1Parsed["red"],
        lowHighParsed["red"],
        bothHighParsed["red"],
        -layerVal1, -layerVal2
    ).toFixed(0);

    var green = mix2(
        low1Parsed["green"],
        high1Parsed["green"],
        lowHighParsed["green"],
        bothHighParsed["green"],
        -layerVal1, -layerVal2
    ).toFixed(0);

    var blue = mix2(
        low1Parsed["blue"],
        high1Parsed["blue"],
        lowHighParsed["blue"],
        bothHighParsed["blue"],
        -layerVal1, -layerVal2
    ).toFixed(0);

    var color = colorify(red, green , blue);

    return color;
}
<commit_msg>show map snake when re-rendering map<commit_after>// Handle the mixing of colors.

import '/imports/lib/color.js';
import Colormap from '/imports/mapPage/color/Colormap';
import hexagons from '/imports/mapPage/viewport/hexagons';
import Layer from '/imports/mapPage/longlist/Layer';
import legend from '/imports/mapPage/color/legend';
import rx from '/imports/common/rx';
import shortlist from '/imports/mapPage/shortlist/shortlist';
import util from '/imports/common/util';

let refreshTimer

function get_range_position(scoreIn, low, high, filterBy) {

    // Given a score float, and the lower and upper bounds of an interval (which
    // may be equal, but not backwards), return a number in the range -1 to 1
    // that expresses the position of the score in the [low, high] interval.
    // Positions out of bounds are clamped to -1 or 1 as appropriate.

    // This holds the length of the input interval
    var interval_length = high - low;
    var score = scoreIn
    if(interval_length > 0) {

        if (filterBy === 'threshold') {

            // If the score is out of bounds, set it to lower or upper bound.
            if (score < low) {
                score = low
            } else if (score > high) {
                score = high
            }
        }

        // First rescale 0 to 1
        score = (score - low) / interval_length;

        // Clamp
        score = Math.min(Math.max(score, 0), 1);

        // Now re-scale to -1 to 1
        score = 2 * score - 1;
    } else {
        // The interval is just a point
        // Just use 1 if we're above the point, and 0 if below.
        score = (score > low)? 1 : -1;
    }

    return score;
}

function findContinuousFilters (attrNames) {

    // Set up the limits for continuous attrs.
    let filters = rx.get('shortEntry.filter')
    let limits = {}
    attrNames.forEach(attr => {
        let filter = filters[attr]
        if (filter) {
            limits[attr] = filter
        } else {

            // With no filter, use the attr's min and max.
            limits[attr] = {
                by: 'range',
                low: layers[attr].minimum,
                high: layers[attr].maximum,
            }
        }
    })

    return limits
}

function refreshColorsInner () {

    // Make the view display the correct hexagons in the colors of the current
    // layer(s), as read from the values of the layer pickers in the global
    // layer pickers array.
    // All pickers must have selected layers that are in the object of
    // layers.
    // Instead of calling this, you probably want to call refreshColors().

    // This holds a list of the string names of the currently selected layers,
    // in order.
    var actives = shortlist.get_active_coloring_layers();

    // Obtain the layer objects (mapping from signatures/hex labels to colors)
    Layer.with_many(actives, function(attrs) {

        // This holds arrays of the lower and upper limit we want to use for
        // each layer, by layer number. The lower limit corresponds to u or
        // v = -1, and the upper to u or v = 1. The entries we make for
        // colormaps are ignored.

        // We need to do this inside the callback, once we already have the
        // layers, so that we use the newest slider range endpoints.
        var at_least_one_layer = attrs.length >= 1;
        var two_layers = attrs.length >= 2;

        // Get the limits for continuous attrs.
        let limits = findContinuousFilters(actives)

        // Determine the color of nodes not passing the filters.
        let bgNodeColor = Colormap.noDataColor();
        if (rx.get('shortEntry.menu.hideBgNodes')) {
            bgNodeColor = (rx.get('background') === 'black') ?
                '#000000' : '#ffffff';
        }

        // Go get the list of filter-passing hexes.
        shortlist.with_filtered_signatures(function(signatures) {
            for (let sig in polygons) {
                if (signatures.indexOf(sig) < 0) {
                    hexagons.setOneColor(polygons[sig], bgNodeColor);
                    continue
                }
                var label = sig

                // This holds the color we are calculating for this hexagon.
                // Start with the no data color.
                var computed_color = Colormap.noDataColor();

                if(at_least_one_layer) {
                    // We need to compute colors given the layers we found.

                    // Get the heat along u and v axes. This puts us in a square
                    // of side length 2. Fun fact: undefined / number = NaN, but
                    // !(NaN == NaN)
                    var u = attrs[0].data[label];

                    if(util.is_continuous(actives[0])) {

                        // Take into account the slider values and re-scale the
                        // layer value to express its position between them.
                        u = get_range_position(u, limits[actives[0]].low,
                            limits[actives[0]].high, limits[actives[0]].by);
                    }

                    if (two_layers) {
                        // There's a second layer, so use the v axis.
                        var v = attrs[1].data[label];

                        if(util.is_continuous(actives[1])) {
                            // Take into account the slider values and re-scale
                            // the layer value to express its position between
                            // them.
                            v = get_range_position(v, limits[actives[1]].low,
                                limits[actives[1]].high, limits[actives[1]].by);
                        }

                    } else {
                        // No second layer, so v axis is unused. Don't make it
                        // undefined (it's not missing data), but set it to 0.
                        v = 0;
                    }

                    // Either of u or v may be undefined (or both) if the layer
                    // did not contain an entry for this signature. But that's
                    // OK. Compute the color that we should use to express this
                    // combination of layer values. It's OK to pass undefined
                    // names here for layers.
                    computed_color = get_color(actives[0], u, actives[1], v);
                }

                // Set the color by the composed layers.
                hexagons.setOneColor(polygons[label], computed_color);
            }
        });

        legend.redraw(attrs, actives);
    });

    // Make sure to also redraw the info window, which may be open.
    import infoWindow from '/imports/mapPage/viewport/infoWindow.js';
    infoWindow.redraw();

    // Allow time to re-render before hiding the busy snake.
    setTimeout(function () {
        rx.set('snake.map.hide');
    });
}

export function refreshColors() {

    // Don't do multiple refreshes during initialization.
    if (rx.get('init.map') !== 'rendered' ||
        !rx.get('inited.coloringAttrs')) {
        return;
    }

    // Special case of no layers at all.
    if (_.isUndefined(layers) || Object.keys(layers) < 1) {
        for(var signature in polygons) {
            hexagons.setOneColor(polygons[signature], Colormap.noAttrsColor());
        }
        return;
    }

    // Show the map snake while we refresh.
    rx.set('snake.map.show');

    // Use a timeout to throttle the color refreshes.
    clearTimeout(refreshTimer)
    refreshTimer = setTimeout(refreshColorsInner)
}

export function get_color (layerName1, layerVal1, layerName2, layerVal2) {
    // Either layer value may be undefined (or both), in which case the no-data color
    // is returned. If a layer name is undefined, that layer dimension is
    // ignored.

    var color,
        base_color,
        hsv_value;

    // These are all cases we need to pay attention to.
    var onlyOneLayer = _.isUndefined(layerName2);

    var oneBinaryLayer = (
        onlyOneLayer &&
        util.is_binary(layerName1)
    );

    var oneContinuousLayer = (
        onlyOneLayer &&
        !oneBinaryLayer &&
        util.is_continuous(layerName1)
    );

    var oneCategoricalLayer = (
        onlyOneLayer &&
        !oneBinaryLayer &&
        !oneContinuousLayer
    );

    var bothContinuous = (
        !onlyOneLayer &&
        util.is_continuous(layerName1) &&
        util.is_continuous(layerName2)
    );

    var continuousAndCatOrBin =(
        !onlyOneLayer &&
        util.is_cat_or_bin(layerName2) &&
        util.is_continuous(layerName1)
    );

    var catOrBinAndContinous = (
        !onlyOneLayer &&
        !continuousAndCatOrBin &&
        util.is_cat_or_bin(layerName1) &&
        util.is_continuous(layerName2)
    );

    var catAndCatOrBin=(
        !onlyOneLayer &&
        util.is_categorical(layerName1) &&
        util.is_cat_or_bin(layerName2) ||
        util.is_categorical(layerName2) &&
        util.is_cat_or_bin(layerName1)
    );

    var bothLayersBinary = (
        util.is_binary(layerName1) && util.is_binary(layerName2)
    );

    var any_missing_values =(
        isNaN(layerVal1) ||
        isNaN(layerVal2) ||
        _.isUndefined(layerVal1) ||
        _.isUndefined(layerVal2)
    );

    var on = 1;
    var off = 0;

    if(continuousAndCatOrBin) {
        //Manipulate the ordering of arguments so an extra case is not needed.
        color = get_color(layerName2, layerVal2, layerName1, layerVal1);

    } else if (any_missing_values) {
        color = Colormap.noDataColor();

    } else if(oneBinaryLayer) {
        // User's choice from color map or default.
        if(layerVal1 === on) { // layerVal1 is 1
            color =  Colormap.binaryOnColor(layerName1);
        } else if (layerVal1 === off) { // layerVal1 is 0
            color = Colormap.binaryOffColor(layerName1);
        } else {
            throw "There was an error making the color of the binary layer";
        }

    } else if(bothLayersBinary) {
        // Special color scheme for two binaries,
        // always uses default, never choice from user.
        if(layerVal1 === on) {
            if(layerVal2 === on) {
                // Both are on
                return Colormap.defaultBinBothOn();
            } else if (layerVal2 === off) {
                // Only the first is on
                return Colormap.defaultBinaryOn();
            }
        } else if (layerVal1 === off) {
            if(layerVal2 === on) {
                // Only the second is on
                return Colormap.defaultSecondBinOn();
            } else if (layerVal2 === off) {
                // Neither is on
                return Colormap.defaultBinaryOff();
            }
        }

    } else if (oneCategoricalLayer) {
        base_color = baseCategoricalColor(layerName1, layerVal1);
        base_color.value(base_color.value());
        color = base_color.hexString();

    } else if (catAndCatOrBin) {
        base_color = baseCategoricalColor(layerName1, layerVal1);
        // Do discrete shades of second layer.
        // Calculate what shade we need from second layer value
        // We want 100 to be included (since that's full brightness), but we
        // want to skip 0 (since no color can be seen at 0), so we add 5 to
        // the second layer's value.
        var colorCountL2 = Colormap.findColorCount(layerName2);
        hsv_value = (layerVal2 + 1) / colorCountL2 * 100;
        base_color.value(hsv_value);
        color = base_color.hexString();

    } else if (catOrBinAndContinous) {
        base_color = baseCategoricalColor(layerName1, layerVal1);
        // Calculate what shade we need from v on -1 to 1, with a minimum
        // value of 20 to avoid blacks.
        hsv_value = 60 + layerVal2 * 40;
        base_color.value(hsv_value);
        color = base_color.hexString();

    } else if(oneContinuousLayer) {
        // Sets the interpolation so the second color is not mixed.
        // Chooses the user's specified coloring or
        color = mixOneContinuous(layerName1, layerVal1);
    } else if (bothContinuous) {
        color = mix2Continuos(layerVal1, layerVal2);
    } else {
        throw "An error occurred when determining a nodes color.";
    }

    return color;
}

function baseCategoricalColor(layerName, layerValue) {
    var base_color;
    if(colormaps[layerName].hasOwnProperty(layerValue)) {
        // And the colormap has an entry here. Use it as the base color.
        var to_clone = colormaps[layerName][layerValue].color;

        base_color = Color({
            hue: to_clone.hue(),
            saturation: to_clone.saturationv(),
            value: to_clone.value()
        });
    } else {
        // Something went wrong. This case catches when a value in the layer
        // doesn't have a colormap entry.
        // This conforms to the old code (since refactored),
        // which hid the error by skipping the category.
        base_color = Color({
            red : undefined,
            blue : undefined,
            green : undefined
        });
    }
    return base_color;
}

function mix (a, b, amount) {
    // Mix between the numbers a and b, where an amount of -1 corresponds to a,
    // and an amount of +1 corresponds to b.

    // Convert to 0 to 1 range.
    var i = (amount + 1) / 2;

    // Do the linear interpolation.
    return i * a + (1 - i) * b;

}

function mix2 (a, b, c, d, amount1, amount2) {
    // Mix between a and b (or c and d) on amount1, and then mix between the
    // results on amount2. Amounts are in range -1 to 1.

    return mix(mix(a, b, amount1), mix(c, d, amount1), amount2);
}

function parseColorPortions(hexStr){

    var portion_prefix = "0x";
    var red_portion = parseInt(portion_prefix + hexStr.slice(1,3));
    var green_portion = parseInt(portion_prefix + hexStr.slice(3,5));
    var blue_portion = parseInt(portion_prefix + hexStr.slice(5,7));

    return {
        "red" : red_portion,
        "green" : green_portion,
        "blue" : blue_portion,
    };
}

function colorify(red, green, blue){
    var color = "rgb(" + red + "," + green + "," + blue + ")";
    return color;
}

function mixOneContinuous(layerName, layerValue){
    var ignoreValue = -1;
    var ignoreColor = 0;
    var highColor = Colormap.continuousHighColor(layerName);
    var lowColor = Colormap.continuousLowColor(layerName);
    var highColorParsed = parseColorPortions(highColor);
    var lowColorParsed = parseColorPortions(lowColor);

    var red = mix2(
        lowColorParsed["red"],
        highColorParsed["red"],
        ignoreColor, ignoreColor,
        -layerValue, -ignoreValue
    ).toFixed(0);

    var green = mix2(
        lowColorParsed["green"],
        highColorParsed["green"],
        ignoreColor, ignoreColor,
        -layerValue, -ignoreValue
    ).toFixed(0);

    var blue = mix2(
        lowColorParsed["blue"],
        highColorParsed["blue"],
        ignoreColor, ignoreColor,
        -layerValue, -ignoreValue
    ).toFixed(0);

    var color = colorify(red, green, blue);

    return color;
}

function mix2Continuos(layerVal1, layerVal2){
    // Ignore color map entries.
    var highColor1 = Colormap.defaultContHigh();
    var lowColor1 = Colormap.defaultContLow();
    var lowColor1HighColor2 = Colormap.defaultCont2High1Low();
    var bothHighColor = Colormap.defaultContBothHigh();

    var high1Parsed = parseColorPortions(highColor1);
    var low1Parsed = parseColorPortions(lowColor1);
    var lowHighParsed = parseColorPortions(lowColor1HighColor2);
    var bothHighParsed = parseColorPortions(bothHighColor);

    var red = mix2(
        low1Parsed["red"],
        high1Parsed["red"],
        lowHighParsed["red"],
        bothHighParsed["red"],
        -layerVal1, -layerVal2
    ).toFixed(0);

    var green = mix2(
        low1Parsed["green"],
        high1Parsed["green"],
        lowHighParsed["green"],
        bothHighParsed["green"],
        -layerVal1, -layerVal2
    ).toFixed(0);

    var blue = mix2(
        low1Parsed["blue"],
        high1Parsed["blue"],
        lowHighParsed["blue"],
        bothHighParsed["blue"],
        -layerVal1, -layerVal2
    ).toFixed(0);

    var color = colorify(red, green , blue);

    return color;
}
<|endoftext|>


<commit_before>'use babel';

/* global atom */

const helpers = require('atom-linter');

const CONFIG_KEY = 'linter-slim-lint.config';
const COMMAND_KEY = 'linter-slim-lint.command';
const URL_BASE = 'https://github.com/sds/slim-lint/blob/master/lib/slim_lint/linter/README.md'

module.exports = {
  activate() {
    this.command = atom.config.get(COMMAND_KEY);
  },
  provideLinter() {
    return {
      name: 'slim-lint',
      grammarScopes: ['text.slim'],
      scope: 'file',
      lintOnFly: true,

      lint: textEditor => {
        let buffer = textEditor.getBuffer();
        if (buffer.isModified()) return;
        if (buffer.isEmpty()) return;

        this.path = textEditor.getPath();
        this.config = helpers.find(this.path, atom.config.get(CONFIG_KEY));
        this.parameters = [];

        if (this.config) {
          this.parameters.push('-c', this.config, this.path);
        } else {
          this.parameters.push(this.path);
        }

        return new Promise((resolve, reject) => {
          let results = [];

          helpers.exec(
            this.command,
            this.parameters, {
              stream: 'stdout',
              ignoreExitCode: true,
              throwOnStdErr: false
            }
          ).then(value => {
            value.split('\n').map(line => {
              // Example line:
              // /fullpath/example.slim:7 [W] RedundantDiv: `div` is redundant when id attribute shortcut is present
              //
              // result for this line:
              //   0: :7 [W] RedundantDiv: `div` is redundant when id attribute shortcut is present
              //   1: 7
              //   2: W
              //   3: RedundantDiv
              //   4: `div` is redundant when id attribute shortcut is present
              let result = line.match(/:([0-9].*)\s\[([A-Z])\]\s([\w].*):\s(.*)/);

              if (!result) return;

              let lineNumber = parseInt(result[1], 10) - 1;
              let range = helpers.rangeFromLineNumber(textEditor, lineNumber);
              let docLink = `${result[3]}`;
              let htmlMessage = `${result[4]} (${docLink})`;

              results.push({
                type: result[2] === 'E' ? 'Error' : 'Warning',
                html: htmlMessage,
                range: range,
                filePath: textEditor.getPath()
              });
            });

            resolve(results);
          }, reject);
        });
      }
    };
  },

  config: {
    command: {
      type: 'string',
      title: 'Command',
      default: 'slim-lint',
      description: 'The absolute path to your `slim-lint` command.  use the command `which slim-lint` to find out \
                    the correct command for the ruby version you are using.'
    },
    config: {
      type: 'string',
      title: 'Config Filename',
      default: '.slim-lint.yml',
      description: 'The name of your ".slim-lint" config file.  This file will be searched recursively down from the \
                    current file\'s location'
    }
  }

};
<commit_msg>Change lintOnFly to lintsOnChange<commit_after>'use babel';

/* global atom */

const helpers = require('atom-linter');

const CONFIG_KEY = 'linter-slim-lint.config';
const COMMAND_KEY = 'linter-slim-lint.command';
const URL_BASE = 'https://github.com/sds/slim-lint/blob/master/lib/slim_lint/linter/README.md'

module.exports = {
  activate() {
    this.command = atom.config.get(COMMAND_KEY);
  },
  provideLinter() {
    return {
      name: 'slim-lint',
      grammarScopes: ['text.slim'],
      scope: 'file',
      lintsOnChange: true,

      lint: textEditor => {
        let buffer = textEditor.getBuffer();
        if (buffer.isModified()) return;
        if (buffer.isEmpty()) return;

        this.path = textEditor.getPath();
        this.config = helpers.find(this.path, atom.config.get(CONFIG_KEY));
        this.parameters = [];

        if (this.config) {
          this.parameters.push('-c', this.config, this.path);
        } else {
          this.parameters.push(this.path);
        }

        return new Promise((resolve, reject) => {
          let results = [];

          helpers.exec(
            this.command,
            this.parameters, {
              stream: 'stdout',
              ignoreExitCode: true,
              throwOnStdErr: false
            }
          ).then(value => {
            value.split('\n').map(line => {
              // Example line:
              // /fullpath/example.slim:7 [W] RedundantDiv: `div` is redundant when id attribute shortcut is present
              //
              // result for this line:
              //   0: :7 [W] RedundantDiv: `div` is redundant when id attribute shortcut is present
              //   1: 7
              //   2: W
              //   3: RedundantDiv
              //   4: `div` is redundant when id attribute shortcut is present
              let result = line.match(/:([0-9].*)\s\[([A-Z])\]\s([\w].*):\s(.*)/);

              if (!result) return;

              let lineNumber = parseInt(result[1], 10) - 1;
              let range = helpers.rangeFromLineNumber(textEditor, lineNumber);
              let docLink = `${result[3]}`;
              let htmlMessage = `${result[4]} (${docLink})`;

              results.push({
                type: result[2] === 'E' ? 'Error' : 'Warning',
                html: htmlMessage,
                range: range,
                filePath: textEditor.getPath()
              });
            });

            resolve(results);
          }, reject);
        });
      }
    };
  },

  config: {
    command: {
      type: 'string',
      title: 'Command',
      default: 'slim-lint',
      description: 'The absolute path to your `slim-lint` command.  use the command `which slim-lint` to find out \
                    the correct command for the ruby version you are using.'
    },
    config: {
      type: 'string',
      title: 'Config Filename',
      default: '.slim-lint.yml',
      description: 'The name of your ".slim-lint" config file.  This file will be searched recursively down from the \
                    current file\'s location'
    }
  }

};
<|endoftext|>


<commit_before>'use strict';

const google = require('google');

module.exports = exports = {};

const questions = (function() {

  let methods = {};

  methods.search = function(response, convo) {
    google(response.text, (err, results) => {
      if(err) {
        console.log(err);
        convo.say('Sorry, but an error occurred while attempting to execute your query.');
        return convo.next();
      }

      let links = '';

      for(let i = 0; i < results.links.length; i++) {
        let link = results.links[i];

        links += link.title + ' - ' + link.href + "\n\n";
      }

      let message = 'Okay I found ' + results.links.length + ' results on ' + response.text + ', here they are: ' + "\n" + '```' + links + '```';

      convo.say(message);
      convo.next();
    });
  };

  methods.what = function(response, convo) {
    convo.ask('What would you like to know?', (response, convo) => {
      convo.say(`Okay searching for...${response.text}`);
      methods.search(response, convo);
      convo.next();
    })
  };

  return methods;

}());

exports.command = (bot, message) => bot.startConversation(message, questions.what);;<commit_msg>Switch to ES6 Temlate Literals<commit_after>'use strict';

const google = require('google');

module.exports = exports = {};

const questions = (function() {

  let methods = {};

  methods.search = function(response, convo) {
    google(response.text, (err, results) => {
      if(err) {
        console.log(err);
        convo.say('Sorry, but an error occurred while attempting to execute your query.');
        return convo.next();
      }

      let links = '';

      for(let i = 0; i < results.links.length; i++) {
        let link = results.links[i];

        links += `${link.title} - ${link.href} \n\n`;
      }

      links = '```' + links + '```';

      let message = `Okay I found ${results.links.length} results on ${response.text}, here they are: \n ${links}`;

      convo.say(message);
      convo.next();
    });
  };

  methods.what = function(response, convo) {
    convo.ask('What would you like to know?', (response, convo) => {
      convo.say(`Okay searching for...${response.text}`);
      methods.search(response, convo);
      convo.next();
    })
  };

  return methods;

}());

exports.command = (bot, message) => bot.startConversation(message, questions.what);;<|endoftext|>


<commit_before>var path = require('path'),
    _ = require('underscore'),
    async = require('async'),
    urlTools = require('urltools'),
    passError = require('passerror'),
    AssetGraph = require('../');

module.exports = function (queryObj) {
    var stylus;
    try {
        stylus = require('stylus');
    } catch (e) {
        throw new Error('transforms.compileStylusToCss: The "stylus" module is required. Please run "npm install stylus" and try again (tested with version 0.23.0).');
    }

    return function compileStylusToCss(assetGraph, cb) {
        async.each(assetGraph.findAssets(_.extend({type: 'Stylus'}, queryObj)), function (stylusAsset, cb) {
            var nonInlineAncestor = stylusAsset.nonInlineAncestor,
                stylusRenderer = stylus(stylusAsset.text);
            // If the Stylus asset has a file:// url, tell the renderer so relative urls in @import statements work:
            if (/^file:/.test(nonInlineAncestor.url)) {
                stylusRenderer.set('paths', [path.dirname(urlTools.fileUrlToFsPath(nonInlineAncestor.url))]);
            }
            stylusRenderer.render(passError(cb, function (cssText) {
                var cssAsset = new AssetGraph.Css({
                    text: cssText
                });
                if (stylusAsset.url) {
                    cssAsset.url = stylusAsset.url.replace(/\.styl$|$/, cssAsset.defaultExtension);
                }
                stylusAsset.replaceWith(cssAsset);
                cb();
            }));
        }, cb);
    };
};
<commit_msg>compileStylusToJavaScript: Only try to load the stylus compiler if stylus assets are actually found.<commit_after>/*global setImmediate:true*/
// node 0.8 compat
if (typeof setImmediate === 'undefined') {
    setImmediate = process.nextTick;
}

var path = require('path'),
    _ = require('underscore'),
    async = require('async'),
    urlTools = require('urltools'),
    passError = require('passerror'),
    AssetGraph = require('../');

module.exports = function (queryObj) {
    return function compileStylusToCss(assetGraph, cb) {
        var stylusAssets = assetGraph.findAssets(_.extend({type: 'Stylus'}, queryObj)),
            stylus;
        if (stylusAssets.length > 0) {
            try {
                stylus = require('stylus');
            } catch (e) {
                assetGraph.emit('warn', new Error('compileStylusToCss: Found ' + stylusAssets.length + ' Stylus asset(s), but no Stylus compiler is available. Please install Stylus in your project so compileStylusToCss can require it.'));
                return setImmediate(cb);
            }
        }
        async.each(stylusAssets, function (stylusAsset, cb) {
            var nonInlineAncestor = stylusAsset.nonInlineAncestor,
                stylusRenderer = stylus(stylusAsset.text);
            // If the Stylus asset has a file:// url, tell the renderer so relative urls in @import statements work:
            if (/^file:/.test(nonInlineAncestor.url)) {
                stylusRenderer.set('paths', [path.dirname(urlTools.fileUrlToFsPath(nonInlineAncestor.url))]);
            }
            stylusRenderer.render(passError(cb, function (cssText) {
                var cssAsset = new AssetGraph.Css({
                    text: cssText
                });
                if (stylusAsset.url) {
                    cssAsset.url = stylusAsset.url.replace(/\.styl$|$/, cssAsset.defaultExtension);
                }
                stylusAsset.replaceWith(cssAsset);
                cb();
            }));
        }, cb);
    };
};
<|endoftext|>


<commit_before>var _ = require('underscore'),
    os = require('os'),
    seq = require('seq'),
    uglifyJs = require('uglify-js'),
    childProcess = require('child_process'),
    passError = require('passerror'),
    compressorsByName = {};

require('bufferjs');

function shouldJavaScriptBeCompressedInExternalProcess(javaScript) {
    if (javaScript._text) {
        return javaScript._text.length > 2048;
    } else if (javaScript._rawSrc) {
        return javaScript._rawSrc.length > 2048;
    } else {
        var numNodesSeen = 0;
        // FIXME: Does the TreeWalker API support stopping the walk when eg. numNodesSeen has reached 100?
        javaScript.parseTree.walk(new uglifyJs.TreeWalker(function (node) {
            numNodesSeen += 1;
        }));
        return numNodesSeen > 100;
    }
}

compressorsByName.uglifyJs = function (assetGraph, javaScript, compressorOptions, cb) {
    compressorOptions = _.extend({no_warnings: true}, compressorOptions);
    if (shouldJavaScriptBeCompressedInExternalProcess(javaScript)) {
        var switches = []; // FIXME: Turn all compressorOptions properties into switches
        if (compressorOptions.defines) {
            Object.keys(compressorOptions.defines).forEach(function (symbol) {
                switches.push('--define', symbol + '=' + uglifyJs.uglify.gen_code(compressorOptions.defines[symbol]));
            });
        }
        if (compressorOptions.toplevel) {
            switches.push('--mangle-toplevel');
        }
        var uglifyJsBinaryPath = require.resolve('uglify-js/bin/uglifyjs'),
            uglifyJsProcess = childProcess.spawn(uglifyJsBinaryPath, switches),
            chunks = [],
            stderrChunks = [],
            stdoutHasEnded = false,
            callbackCalled = false;

        function proceedIfStdoutHasEnded() {
            if (stdoutHasEnded && !callbackCalled) {
                callbackCalled = true;
                cb(null, new assetGraph.JavaScript({
                    rawSrc: Buffer.concat(chunks)
                }));
            }
        }
        uglifyJsProcess.stdout.on('data', function (chunk) {
            chunks.push(chunk);
        }).on('end', function () {
            stdoutHasEnded = true;
            proceedIfStdoutHasEnded();
        });
        uglifyJsProcess.stderr.on('data', function (chunk) {
            stderrChunks.push(chunk);
        });
        uglifyJsProcess.on('exit', function (exitCode) {
            if (exitCode) {
                callbackCalled = true;
                cb(new Error(uglifyJsBinaryPath + ' ' + switches.join(' ') + ' returned non-zero exit code: ' + exitCode + (stderrChunks.length > 0 ? '\nSTDERR: ' + Buffer.concat(stderrChunks).toString('utf-8') : '')));
            } else {
                proceedIfStdoutHasEnded();
            }
        });
        uglifyJsProcess.stdin.end(javaScript.rawSrc);
    } else {
        process.nextTick(function () {
            javaScript.parseTree.figure_out_scope();
            var compressor = uglifyJs.Compressor(compressorOptions);
            cb(null, new assetGraph.JavaScript({
                copyrightNoticeComments: javaScript.copyrightNoticeComments,
                parseTree: javaScript.parseTree.clone().transform(compressor)
            }));
        });
    }
};

compressorsByName.yuicompressor = function (assetGraph, javaScript, compressorOptions, cb) {
    var yuicompressor;
    try {
        yuicompressor = require('yui-compressor');
    } catch (e) {
        return cb(new Error("transforms.compressJavaScript: node-yui-compressor not found. Please run 'npm install yui-compressor' and try again (tested with version 0.1.3)."));
    }
    compressorOptions = compressorOptions || {};
    yuicompressor.compile(javaScript.text, compressorOptions, passError(cb, function (compressedText) {
        cb(null, new assetGraph.JavaScript({
            copyrightNoticeComments: javaScript.copyrightNoticeComments,
            text: compressedText
        }));
    }));
};

compressorsByName.closurecompiler = function (assetGraph, javaScript, compressorOptions, cb) {
    var closurecompiler;
    try {
        closurecompiler = require('closure-compiler');
    } catch (e) {
        return cb(new Error("transforms.compressJavaScript: node-closure-compiler not found. Please run 'npm install closure-compiler' and try again (tested with version 0.1.1)."));
    }
    compressorOptions = compressorOptions || {};
    closurecompiler.compile(javaScript.text, compressorOptions, passError(cb, function (compressedText) {
        cb(null, new assetGraph.JavaScript({
            copyrightNoticeComments: javaScript.copyrightNoticeComments,
            text: compressedText
        }));
    }));
};

module.exports = function (queryObj, compressorName, compressorOptions) {
    if (!compressorName) {
        compressorName = 'uglifyJs';
    }
    if (!(compressorName in compressorsByName)) {
        throw new Error("transforms.compressJavaScript: Unknown compressor: " + compressorName);
    }
    return function compressJavaScript(assetGraph, cb) {
        seq(assetGraph.findAssets(_.extend({type: 'JavaScript'}, queryObj)))
            .parEach(os.cpus().length + 1, function (javaScript) {
                compressorsByName[compressorName](assetGraph, javaScript, compressorOptions, this.into(javaScript.id));
            })
            .parEach(function (javaScript) {
                var compressedJavaScript = this.vars[javaScript.id];
                javaScript.replaceWith(compressedJavaScript);
                // FIXME: These belong elsewhere:
                compressedJavaScript.quoteChar = javaScript.quoteChar;
                compressedJavaScript.initialComments = javaScript.initialComments;
                this();
            })
            .seq(function () {
                cb();
            })
            ['catch'](cb);
    };
};
<commit_msg>Fixed transforms.compressJavaScript. Ditched compression in external processes for now.<commit_after>var _ = require('underscore'),
    os = require('os'),
    seq = require('seq'),
    uglifyJs = require('uglify-js'),
    childProcess = require('child_process'),
    passError = require('passerror'),
    compressorsByName = {};

require('bufferjs');

compressorsByName.uglifyJs = function (assetGraph, javaScript, compressorOptions, cb) {
    compressorOptions = _.extend({}, compressorOptions);
    process.nextTick(function () {
        javaScript.parseTree.figure_out_scope();
        var compressor = uglifyJs.Compressor(compressorOptions);
        cb(null, new assetGraph.JavaScript({
            copyrightNoticeComments: javaScript.copyrightNoticeComments,
            parseTree: javaScript.parseTree.clone().transform(compressor)
        }));
    });
};

compressorsByName.yuicompressor = function (assetGraph, javaScript, compressorOptions, cb) {
    var yuicompressor;
    try {
        yuicompressor = require('yui-compressor');
    } catch (e) {
        return cb(new Error("transforms.compressJavaScript: node-yui-compressor not found. Please run 'npm install yui-compressor' and try again (tested with version 0.1.3)."));
    }
    compressorOptions = compressorOptions || {};
    yuicompressor.compile(javaScript.text, compressorOptions, passError(cb, function (compressedText) {
        cb(null, new assetGraph.JavaScript({
            copyrightNoticeComments: javaScript.copyrightNoticeComments,
            text: compressedText
        }));
    }));
};

compressorsByName.closurecompiler = function (assetGraph, javaScript, compressorOptions, cb) {
    var closurecompiler;
    try {
        closurecompiler = require('closure-compiler');
    } catch (e) {
        return cb(new Error("transforms.compressJavaScript: node-closure-compiler not found. Please run 'npm install closure-compiler' and try again (tested with version 0.1.1)."));
    }
    compressorOptions = compressorOptions || {};
    closurecompiler.compile(javaScript.text, compressorOptions, passError(cb, function (compressedText) {
        cb(null, new assetGraph.JavaScript({
            copyrightNoticeComments: javaScript.copyrightNoticeComments,
            text: compressedText
        }));
    }));
};

module.exports = function (queryObj, compressorName, compressorOptions) {
    if (!compressorName) {
        compressorName = 'uglifyJs';
    }
    if (!(compressorName in compressorsByName)) {
        throw new Error("transforms.compressJavaScript: Unknown compressor: " + compressorName);
    }
    return function compressJavaScript(assetGraph, cb) {
        seq(assetGraph.findAssets(_.extend({type: 'JavaScript'}, queryObj)))
            .parEach(os.cpus().length + 1, function (javaScript) {
                compressorsByName[compressorName](assetGraph, javaScript, compressorOptions, this.into(javaScript.id));
            })
            .parEach(function (javaScript) {
                var compressedJavaScript = this.vars[javaScript.id];
                javaScript.replaceWith(compressedJavaScript);
                // FIXME: These belong elsewhere:
                compressedJavaScript.quoteChar = javaScript.quoteChar;
                compressedJavaScript.initialComments = javaScript.initialComments;
                this();
            })
            .seq(function () {
                cb();
            })
            ['catch'](cb);
    };
};
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import {Point, Range} from 'atom';
import {RelayConnectionPropType} from '../prop-types';

import {toNativePathSep} from '../helpers';
import Marker from '../atom/marker';
import Decoration from '../atom/decoration';
import GithubDotcomMarkdown from './github-dotcom-markdown';
import Timeago from './timeago';

let count = 0;

export default class PullRequestCommentsView extends React.Component {
  static propTypes = {
    relay: PropTypes.shape({
      hasMore: PropTypes.func.isRequired,
      loadMore: PropTypes.func.isRequired,
      isLoading: PropTypes.func.isRequired,
    }).isRequired,
    commentThreads: PropTypes.arrayOf(PropTypes.shape({
      rootCommentId: PropTypes.string.isRequired,
      comments: PropTypes.arrayOf(PropTypes.object).isRequired,
    })),
    multiFilePatch: PropTypes.object.isRequired,
  }

  render() {
    return [...this.props.commentThreads].reverse().map(({rootCommentId, comments}) => {
      const rootComment = comments[0];
      if (!rootComment.position) {
        return null;
      }

      const nativePath = toNativePathSep(rootComment.path);
      const row = this.props.multiFilePatch.getBufferRowForDiffPosition(nativePath, rootComment.position);
      const point = new Point(row, 0);
      const range = new Range(point, point);
      // TODO: find way to re-use nodes by using same key. this count++ hack is in place to get the comments to show up
      // in the correct order after new pages of data are fetched. Test it by reducing the reviewCount to a small number
      return (
        <Marker key={`pr-comment-${rootCommentId + count++}`} bufferRange={range} invalidate="never">
          <Decoration type="block" position="after" className="github-PrCommentThread">
            {comments.map(comment => {
              return (
                <PullRequestCommentView
                  key={comment.id}
                  comment={comment}
                  switchToIssueish={this.props.switchToIssueish}
                />
              );
            })}
          </Decoration>
        </Marker>
      );
    });
  }
}

export class PullRequestCommentView extends React.Component {
  render() {
    const author = this.props.comment.author;
    const login = author ? author.login : 'someone';
    return (

          {login} commented{' '}

            <Timeago time={this.props.comment.createdAt} />

          <GithubDotcomMarkdown html={this.props.comment.bodyHTML} switchToIssueish={this.props.switchToIssueish} />

    );
  }
}
<commit_msg>:fire: unused prop<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import {Point, Range} from 'atom';

import {toNativePathSep} from '../helpers';
import Marker from '../atom/marker';
import Decoration from '../atom/decoration';
import GithubDotcomMarkdown from './github-dotcom-markdown';
import Timeago from './timeago';

let count = 0;

export default class PullRequestCommentsView extends React.Component {
  static propTypes = {
    relay: PropTypes.shape({
      hasMore: PropTypes.func.isRequired,
      loadMore: PropTypes.func.isRequired,
      isLoading: PropTypes.func.isRequired,
    }).isRequired,
    commentThreads: PropTypes.arrayOf(PropTypes.shape({
      rootCommentId: PropTypes.string.isRequired,
      comments: PropTypes.arrayOf(PropTypes.object).isRequired,
    })),
    multiFilePatch: PropTypes.object.isRequired,
  }

  render() {
    return [...this.props.commentThreads].reverse().map(({rootCommentId, comments}) => {
      const rootComment = comments[0];
      if (!rootComment.position) {
        return null;
      }

      const nativePath = toNativePathSep(rootComment.path);
      const row = this.props.multiFilePatch.getBufferRowForDiffPosition(nativePath, rootComment.position);
      const point = new Point(row, 0);
      const range = new Range(point, point);
      // TODO: find way to re-use nodes by using same key. this count++ hack is in place to get the comments to show up
      // in the correct order after new pages of data are fetched. Test it by reducing the reviewCount to a small number
      return (
        <Marker key={`pr-comment-${rootCommentId + count++}`} bufferRange={range} invalidate="never">
          <Decoration type="block" position="after" className="github-PrCommentThread">
            {comments.map(comment => {
              return (
                <PullRequestCommentView
                  key={comment.id}
                  comment={comment}
                  switchToIssueish={this.props.switchToIssueish}
                />
              );
            })}
          </Decoration>
        </Marker>
      );
    });
  }
}

export class PullRequestCommentView extends React.Component {
  render() {
    const author = this.props.comment.author;
    const login = author ? author.login : 'someone';
    return (

          {login} commented{' '}

            <Timeago time={this.props.comment.createdAt} />

          <GithubDotcomMarkdown html={this.props.comment.bodyHTML} switchToIssueish={this.props.switchToIssueish} />

    );
  }
}
<|endoftext|>


<commit_before>'use strict';

/*************************************************************
 * MIDDLEWARE
 ************************************************************/
//TODO: Load all files in this dir that are not index.js
//TODO: Load all files in any passed source directory.

let middleware = {
    poweredBy: require('./poweredBy'),
    compression: require('./compression'),
    livereload: require('./livereload'),
    viewEngine: require('./viewEngine'),
    logger: require('./logger'),
    //TODO: cors support
    // cors: require('./cors'),
    favicon: require('./favicon'),
    bodyParser: require('./bodyParser'),
    cookieParser: require('./cookieParser'),
    session: require('./session'),
    csrf: require('./csrf'),
    passport: require('./passport'),
    flash: require('./flash'),
    expressStatic: require('./expressStatic'),
    expressLayouts: require('./expressLayouts'),
    appVariables: require('./appVariables'),
    routes: require('./routes'),
    locals: require('./locals'),
};

middleware.use = [
    'poweredBy',
    'compression',
    // 'livereload',
    'viewEngine',
    'logger',
    'favicon',
    'bodyParser',
    'cookieParser',
    'session', //This must be before passport!!
    'flash',
    'passport',
    'csrf',
    'expressStatic',
    'expressLayouts',
    'appVariables',
    'routes',
    'locals'
];

middleware.dependencies = [
    'trackfinder',
    'compression',
    'serve-favicon',
    'body-parser',
    'cookie-parser',
    'passport',
    'express-session',
    'express-ejs-layouts',
    'morgan',
    'ejs',
    'express-flash-2',
    'csurf'
];

module.exports = middleware;
<commit_msg>middleware: require express layouts ejs before passport<commit_after>'use strict';

/*************************************************************
 * MIDDLEWARE
 ************************************************************/
//TODO: Load all files in this dir that are not index.js
//TODO: Load all files in any passed source directory.

let middleware = {
    poweredBy: require('./poweredBy'),
    compression: require('./compression'),
    livereload: require('./livereload'),
    viewEngine: require('./viewEngine'),
    logger: require('./logger'),
    //TODO: cors support
    // cors: require('./cors'),
    favicon: require('./favicon'),
    bodyParser: require('./bodyParser'),
    cookieParser: require('./cookieParser'),
    session: require('./session'),
    csrf: require('./csrf'),
    passport: require('./passport'),
    flash: require('./flash'),
    expressStatic: require('./expressStatic'),
    expressLayouts: require('./expressLayouts'),
    appVariables: require('./appVariables'),
    routes: require('./routes'),
    locals: require('./locals'),
};

middleware.use = [
    'poweredBy',
    'compression',
    // 'livereload',
    'viewEngine',
    'logger',
    'favicon',
    'bodyParser',
    'cookieParser',
    'session', //This must be before passport!!
    'flash',
    'expressStatic',
    'expressLayouts',
    'passport',
    'csrf',
    'appVariables',
    'routes',
    'locals'
];

middleware.dependencies = [
    'trackfinder',
    'compression',
    'serve-favicon',
    'body-parser',
    'cookie-parser',
    'passport',
    'express-session',
    'express-ejs-layouts',
    'morgan',
    'ejs',
    'express-flash-2',
    'csurf'
];

module.exports = middleware;
<|endoftext|>


<commit_before>/*
** Module dependencies
*/
var mongoose = require('mongoose');

var Schema = mongoose.Schema;

var SituationSchema = new Schema({
    _updated: Date,
    status: { type: String, default: 'new' },
    startId: { type: String, required: true },
    individus: [Schema.Types.Mixed],
    logements: [Schema.Types.Mixed],
    contact: {
      firstName: { type: String },
      lastName: { type: String },
      address: { type: String },
      postalCode: { type: String },
      city: { type: String },
      numeroSecu: { type: String },
      email: { type: String }
    }
}, { minimize: false });

module.exports = mongoose.model('Situation', SituationSchema);
<commit_msg>fix jshint<commit_after>/*
** Module dependencies
*/
var mongoose = require('mongoose');

var Schema = mongoose.Schema;

var SituationSchema = new Schema({
    _updated: Date,
    status: { type: String, default: 'new' },
    startId: { type: String, required: true },
    individus: [Schema.Types.Mixed],
    logements: [Schema.Types.Mixed],
    contact: {
        firstName: { type: String },
        lastName: { type: String },
        address: { type: String },
        postalCode: { type: String },
        city: { type: String },
        numeroSecu: { type: String },
        email: { type: String }
    }
}, { minimize: false });

module.exports = mongoose.model('Situation', SituationSchema);
<|endoftext|>


<commit_before>(function(e){e.fn.shush=function(t){var n=e(this);var r=t||"input[type=submit]";if(typeof r=="string"){r=n.find(r)[0];if(r.length==0){if(window.console)console.error("Couldn't find element with selector "+t);return false}}else if(r instanceof Object){r=n.children(t[0])[0]}else{if(window.console)console.error("Expected parameter type 'string', 'jQuery', or 'Object', got "+typeof t);return false}n.submit(function(t){t.preventDefault();e(r).attr("disabled","disabled");e(r).addClass("shush-disabled");n.unbind("submit").submit()})}})(window.Zepto||window.jQuery)<commit_msg>Updated minified version<commit_after>(function(e){e.fn.shush=function(t){var n=e(this);var r=t||"input[type=submit]";if(typeof r==="string"){r=n.find(r)[0];if(r.length===0){if(window.console)console.error("Couldn't find element with selector "+t);return false}}else if(r instanceof Object){r=n.children(t[0])[0]}else{if(window.console)console.error("Expected parameter type 'string', 'jQuery', or 'Object', got "+typeof t);return false}n.submit(function(t){t.preventDefault();e(r).attr("disabled","disabled");e(r).addClass("shush-disabled");n.unbind("submit").submit()})}})(window.Zepto||window.jQuery)<|endoftext|>


<commit_before>"use strict";

var Base = require('../base')
var Event = require('../event')
var proto = Base.prototype

var set = proto.$set
var addNewProperty = proto.addNewProperty

var observable = new Base()

observable.inject(
  require('./on'),
  require('./subscribe'),
  require('./stamp')
)

var Observable = module.exports = observable.$Constructor

observable.define({
  $ChildConstructor: Observable,
  $generateConstructor: function() {
    return (function derivedObservable( val, event, parent, key ) {
      this.setParent( val, event, parent, key )
      if(val) {
        event = this.$set( val, event )
      }
      if(this.$on) {
        this.$on.$newParent( this )
      }
      this.$emit( '$new', event )
    })
  },
  $setValueInternal: function( val, event ) {
    if( val instanceof Base) {
      if(val.on) {
        val.on( '$change', this )
      }
      this.$emit( '$reference', event, this._$val )
    } else if( this._$val instanceof Base ) {
      this.$emit( '$reference', event, this._$val )
    }
    this._$val = val
  },
  $set: function( val, event ) {
    if( event === void 0 ) {
      event = new Event()
      event.$val = val
      event.$origin = this
    }
    if( set.call( this, val, event ) === true ) {
      return true
    }
    if(event) {
      this.$emit( '$change', event )
    }
    return event
  },
  addNewProperty: function( key, val, property, event ) {
    addNewProperty.call( this, key, val, property, event )
    if(event) {
      this.$emit('$property', event, key )
    }
  }
})

<commit_msg>$new emits the current __proto__<commit_after>"use strict";

var Base = require('../base')
var Event = require('../event')
var proto = Base.prototype

var set = proto.$set
var addNewProperty = proto.addNewProperty

var observable = new Base()

observable.inject(
  require('./on'),
  require('./subscribe'),
  require('./stamp')
)

var Observable = module.exports = observable.$Constructor

observable.define({
  $ChildConstructor: Observable,
  $generateConstructor: function() {
    return (function derivedObservable( val, event, parent, key ) {
      this.setParent( val, event, parent, key )
      if(val) {
        event = this.$set( val, event )
      }
      if(this.$on) {
        this.$on.$newParent( this )
      }
      this.$emit( '$new', event, this.__proto__ )
    })
  },
  $setValueInternal: function( val, event ) {
    if( val instanceof Base) {
      if(val.on) {
        val.on( '$change', this )
      }
      this.$emit( '$reference', event, this._$val )
    } else if( this._$val instanceof Base ) {
      this.$emit( '$reference', event, this._$val )
    }
    this._$val = val
  },
  $set: function( val, event ) {
    if( event === void 0 ) {
      event = new Event()
      event.$val = val
      event.$origin = this
    }
    if( set.call( this, val, event ) === true ) {
      return true
    }
    if(event) {
      this.$emit( '$change', event )
    }
    return event
  },
  addNewProperty: function( key, val, property, event ) {
    addNewProperty.call( this, key, val, property, event )
    if(event) {
      this.$emit('$property', event, key )
    }
  }
})

<|endoftext|>


<commit_before>var creep_assattacker = {

    /** @param {Creep} creep **/
    run: function(creep) {

        var unboostedTough = 0;
        var unboostedAttack = 0;

        creep.body.forEach(function(thisPart) {
            if (thisPart.type == ATTACK && !thisPart.boost) {
                unboostedAttack = unboostedAttack + 1;
            }

            if (thisPart.type == TOUGH && !thisPart.boost) {
                unboostedTough = unboostedTough + 1;
            }
        });

        var healerSquad = creep.pos.findInRange(FIND_MY_CREEPS, 2, {
            filter: (mCreep) => (mCreep.memory.priority == "asshealer")
        });
        var closeFoe = creep.pos.findClosestByRange(FIND_HOSTILE_CREEPS, {
            filter: (eCreep) => (!Memory.whiteList.includes(eCreep.owner.username))
        });

        if (healerSquad.length && healerSquad.length < 2) {
            if (Game.flags["RallyHere"] && Game.flags["RallyHere"].pos.roomName != creep.pos.roomName) {
                //Probably in a new room, hold.
                if (creep.pos.x == 0 || creep.pos.x == 49 || creep.pos.y == 0 || creep.pos.y == 49) {
                    var xTarget = 1;
                    var yTarget = 1;
                    if (creep.pos.x == 49) {
                        xTarget = 48;
                    }
                    if (creep.pos.y = 49) {
                        yTarget = 48;
                    }
                    creep.moveTo(xTarget, yTarget);
                }
            } else {
                if (!creep.memory.getOutOfStartRoom) {
                    creep.moveTo(Game.flags["RallyHere"]);
                } else {
                    creep.moveTo(Game.flags["Assault"]);
                }
            }
        } else if (healerSquad.length && healerSquad.length == 2) {
            creep.memory.getOutOfStartRoom = true;

            if (Game.flags["Assault"] && Game.flags["Assault"].pos.roomName != creep.pos.roomName) {
                creep.moveTo(new RoomPosition(25, 25, Game.flags["Assault"].pos.roomName));
            } else {
                //In target room
                var eTowers = creep.room.find(FIND_HOSTILE_STRUCTURES, {
                    filter: (structure) => (structure.structureType == STRUCTURE_TOWER)
                });
                if (eTowers.length) {
                    creep.moveTo(eTowers[0], {
                        ignoreDestructibleStructures: true
                    });
                    creep.attack(eTowers[0]);
                } else {
                    var eSpawns = creep.room.find(FIND_HOSTILE_SPAWNS)
                    if (eSpawns.length) {
                        creep.moveTo(eSpawns[0], {
                            ignoreDestructibleStructures: true
                        });
                        creep.attack(eSpawns[0]);
                    } else {
                        var eStructures = creep.room.find(FIND_HOSTILE_STRUCTURES)
                        if (eStructures.length) {
                            creep.moveTo(eStructures[0], {
                                ignoreDestructibleStructures: true
                            });
                            creep.attack(eStructures[0]);
                        } else if (closeFoe) {
                            creep.moveTo(closeFoe);
                        }
                    }
                }
            }
        } else {
            if (Game.flags["RallyHere"] && !creep.memory.getOutOfStartRoom) {
                creep.moveTo(Game.flags["RallyHere"]);
            } else {
                creep.moveTo(Game.flags["Assault"]);
            }
        }

        if (closeFoe) {
            //prioritize foebashing
            creep.attack(closeFoe);
        }

    }

};

module.exports = creep_assattacker;<commit_msg>whoops<commit_after>var creep_assattacker = {

    /** @param {Creep} creep **/
    run: function(creep) {

        var unboostedTough = 0;
        var unboostedAttack = 0;

        creep.body.forEach(function(thisPart) {
            if (thisPart.type == ATTACK && !thisPart.boost) {
                unboostedAttack = unboostedAttack + 1;
            }

            if (thisPart.type == TOUGH && !thisPart.boost) {
                unboostedTough = unboostedTough + 1;
            }
        });

        var healerSquad = creep.pos.findInRange(FIND_MY_CREEPS, 2, {
            filter: (mCreep) => (mCreep.memory.priority == "asshealer")
        });
        var closeFoe = creep.pos.findClosestByRange(FIND_HOSTILE_CREEPS, {
            filter: (eCreep) => (!Memory.whiteList.includes(eCreep.owner.username))
        });

        if (healerSquad.length && healerSquad.length < 2) {
            if (Game.flags["RallyHere"] && Game.flags["RallyHere"].pos.roomName != creep.pos.roomName) {
                //Probably in a new room, hold.
                if (creep.pos.x == 0 || creep.pos.x == 49 || creep.pos.y == 0 || creep.pos.y == 49) {
                    var xTarget = 1;
                    var yTarget = 1;
                    if (creep.pos.x == 49) {
                        xTarget = 48;
                    }
                    if (creep.pos.y = 49) {
                        yTarget = 48;
                    }
                    creep.moveTo(xTarget, yTarget);
                }
            } else {
                if (!creep.memory.getOutOfStartRoom) {
                    creep.moveTo(Game.flags["RallyHere"]);
                } else {
                    creep.moveTo(Game.flags["Assault"]);
                }
            }
        } else if (healerSquad.length && healerSquad.length == 2) {
            creep.memory.getOutOfStartRoom = true;

            if (Game.flags["Assault"] && Game.flags["Assault"].pos.roomName != creep.pos.roomName) {
                creep.moveTo(new RoomPosition(25, 25, Game.flags["Assault"].pos.roomName));
            } else {
                //In target room
                var eTowers = creep.room.find(FIND_HOSTILE_STRUCTURES, {
                    filter: (structure) => (structure.structureType == STRUCTURE_TOWER)
                });
                if (eTowers.length) {
                    creep.moveTo(eTowers[0], {
                        ignoreDestructibleStructures: true
                    });
                    creep.attack(eTowers[0]);
                } else {
                    var eSpawns = creep.room.find(FIND_HOSTILE_SPAWNS)
                    if (eSpawns.length) {
                        creep.moveTo(eSpawns[0], {
                            ignoreDestructibleStructures: true
                        });
                        creep.attack(eSpawns[0]);
                    } else {
                        var eStructures = creep.room.find(FIND_HOSTILE_STRUCTURES)
                        if (eStructures.length) {
                            creep.moveTo(eStructures[0], {
                                ignoreDestructibleStructures: true
                            });
                            creep.attack(eStructures[0]);
                        } else if (closeFoe) {
                            creep.moveTo(closeFoe);
                        }
                    }
                }
            }
        } else {
            if (Game.flags["RallyHere"] && !creep.memory.getOutOfStartRoom) {
                creep.moveTo(Game.flags["RallyHere"]);
            } else {
                //creep.moveTo(Game.flags["Assault"]);
            }
        }

        if (closeFoe) {
            //prioritize foebashing
            creep.attack(closeFoe);
        }

    }

};

module.exports = creep_assattacker;<|endoftext|>


<commit_before>import clone from 'clone';
import equal from 'deep-equal';
import extend from 'extend';
import Delta from 'rich-text/lib/delta';
import Parchment from 'parchment';
import Quill from '../core/quill';
import logger from '../core/logger';
import Module from '../core/module';
import Block from '../blots/block';

let debug = logger('quill:keyboard');

const SHORTKEY = /Mac/i.test(navigator.platform) ? 'metaKey' : 'ctrlKey';

class Keyboard extends Module {
  static match(evt, binding) {
    binding = normalize(binding);
    if (!!binding.shortKey !== evt[SHORTKEY] && binding.shortKey !== null) return false;
    if (['altKey', 'ctrlKey', 'metaKey', 'shiftKey'].some(function(key) {
      return (key != SHORTKEY && !!binding[key] !== evt[key] && binding[key] !== null);
    })) {
      return false;
    }
    return binding.key === (evt.which || evt.keyCode);
  }

  constructor(quill, options) {
    super(quill, options);
    this.bindings = {};
    Object.keys(this.options.bindings).forEach((name) => {
      if (this.options.bindings[name]) {
        this.addBinding(this.options.bindings[name]);
      }
    });
    this.addBinding({ key: Keyboard.keys.ENTER, shiftKey: null }, handleEnter);
    this.addBinding({ key: Keyboard.keys.ENTER, metaKey: null, ctrlKey: null, altKey: null }, function() {});
    this.addBinding({ key: Keyboard.keys.BACKSPACE }, { collapsed: true, prefix: /^.?$/ }, function(range) {
      if (range.index === 0) return;
      this.quill.deleteText(range.index - 1, 1, Quill.sources.USER);
      this.quill.selection.scrollIntoView();
    });
    this.addBinding({ key: Keyboard.keys.DELETE }, { collapsed: true, suffix: /^$/ }, function(range) {
      if (range.index >= this.quill.getLength() - 1) return;
      this.quill.deleteText(range.index, 1, Quill.sources.USER);
    });
    this.addBinding({ key: Keyboard.keys.BACKSPACE }, { collapsed: false }, handleDelete);
    this.addBinding({ key: Keyboard.keys.DELETE }, { collapsed: false }, handleDelete);
    this.listen();
  }

  addBinding(key, context = {}, handler = {}) {
    let binding = normalize(key);
    if (binding == null || binding.key == null) {
      return debug.warn('Attempted to add invalid keyboard binding', binding);
    }
    if (typeof context === 'function') {
      context = { handler: context };
    }
    if (typeof handler === 'function') {
      handler = { handler: handler };
    }
    binding = extend(binding, context, handler);
    this.bindings[binding.key] = this.bindings[binding.key] || [];
    this.bindings[binding.key].push(binding);
  }

  listen() {
    this.quill.root.addEventListener('keydown', (evt) => {
      if (evt.defaultPrevented) return;
      let which = evt.which || evt.keyCode;
      let bindings = (this.bindings[which] || []).filter(function(binding) {
        return Keyboard.match(evt, binding);
      });
      if (bindings.length === 0) return;
      let range = this.quill.getSelection();
      if (range == null) return;    // implies we do not have focus
      let [line, offset] = this.quill.scroll.line(range.index);
      let [leafStart, offsetStart] = this.quill.scroll.leaf(range.index);
      let [leafEnd, offsetEnd] = range.length === 0 ? [leafStart, offsetStart] : this.quill.scroll.leaf(range.index + range.length);
      let prefixText = leafStart instanceof Parchment.Text ? leafStart.value().slice(0, offsetStart) : '';
      let suffixText = leafEnd instanceof Parchment.Text ? leafEnd.value().slice(offsetEnd) : '';
      let curContext = {
        collapsed: range.length === 0,
        empty: range.length === 0 && line.length() <= 1,
        format: this.quill.getFormat(range),
        offset: offset,
        prefix: prefixText,
        suffix: suffixText
      };
      let prevented = bindings.some((binding) => {
        if (binding.collapsed != null && binding.collapsed !== curContext.collapsed) return false;
        if (binding.empty != null && binding.empty !== curContext.empty) return false;
        if (binding.offset != null && binding.offset !== curContext.offset) return false;
        if (Array.isArray(binding.format)) {
          // any format is present
          if (binding.format.every(function(name) {
            return curContext.format[name] == null;
          })) {
            return false;
          }
        } else if (typeof binding.format === 'object') {
          // all formats must match
          if (!Object.keys(binding.format).every(function(name) {
            if (binding.format[name] === true) return curContext.format[name] != null;
            if (binding.format[name] === false) return curContext.format[name] == null;
            return equal(binding.format[name], curContext.format[name]);
          })) {
            return false;
          }
        }
        if (binding.prefix != null && !binding.prefix.test(curContext.prefix)) return false;
        if (binding.suffix != null && !binding.suffix.test(curContext.suffix)) return false;
        return binding.handler.call(this, range, curContext) !== true;
      });
      if (prevented) {
        evt.preventDefault();
      }
    });
  }
}

Keyboard.keys = {
  BACKSPACE: 8,
  TAB: 9,
  ENTER: 13,
  ESCAPE: 27,
  LEFT: 37,
  UP: 38,
  RIGHT: 39,
  DOWN: 40,
  DELETE: 46
};

Keyboard.DEFAULTS = {
  bindings: {
    'bold'      : makeFormatHandler('bold'),
    'italic'    : makeFormatHandler('italic'),
    'underline' : makeFormatHandler('underline'),
    'indent': {
      // highlight tab or tab at beginning of list, indent or blockquote
      key: Keyboard.keys.TAB,
      format: ['blockquote', 'indent', 'list'],
      handler: function(range, context) {
        if (context.collapsed && context.offset !== 0) return true;
        this.quill.format('indent', '+1', Quill.sources.USER);
      }
    },
    'outdent': {
      key: Keyboard.keys.TAB,
      shiftKey: true,
      format: ['blockquote', 'indent', 'list'],
      // highlight tab or tab at beginning of list, indent or blockquote
      handler: function(range, context) {
        if (context.collapsed && context.offset !== 0) return true;
        this.quill.format('indent', '-1', Quill.sources.USER);
      }
    },
    'outdent backspace': {
      key: Keyboard.keys.BACKSPACE,
      collapsed: true,
      format: ['blockquote', 'indent', 'list'],
      offset: 0,
      handler: function(range, context) {
        if (context.format.indent != null) {
          this.quill.format('indent', '-1', Quill.sources.USER);
        } else if (context.format.blockquote != null) {
          this.quill.format('blockquote', false, Quill.sources.USER);
        } else if (context.format.list != null) {
          this.quill.format('list', false, Quill.sources.USER);
        }
      }
    },
    'indent code-block': makeCodeBlockHandler(true),
    'outdent code-block': makeCodeBlockHandler(false),
    'remove tab': {
      key: Keyboard.keys.TAB,
      shiftKey: true,
      collapsed: true,
      prefix: /\t$/,
      handler: function(range, context) {
        this.quill.deleteText(range.index - 1, 1, Quill.sources.USER);
      }
    },
    'tab': {
      key: Keyboard.keys.TAB,
      handler: function(range, context) {
        if (!context.collapsed) {
          this.quill.scroll.deleteAt(range.index, range.length);
        }
        this.quill.insertText(range.index, '\t', Quill.sources.USER);
        this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
      }
    },
    'list empty enter': {
      key: Keyboard.keys.ENTER,
      collapsed: true,
      format: ['list'],
      empty: true,
      handler: function(range, context) {
        this.quill.format('list', false, Quill.sources.USER);
        if (context.format.indent) {
          this.quill.format('indent', false, Quill.sources.USER);
        }
      }
    },
    'header enter': {
      key: Keyboard.keys.ENTER,
      collapsed: true,
      format: ['header'],
      suffix: /^$/,
      handler: function(range) {
        this.quill.scroll.insertAt(range.index, '\n');
        this.quill.formatText(range.index + 1, 1, 'header', false, Quill.sources.USER);
        this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
        this.quill.selection.scrollIntoView();
      }
    },
    'list autofill': {
      key: ' ',
      collapsed: true,
      format: { list: false },
      prefix: /^(1\.|-)$/,
      handler: function(range, context) {
        let length = context.prefix.length;
        this.quill.scroll.deleteAt(range.index - length, length);
        this.quill.formatLine(range.index - length, 1, 'list', length === 1 ? 'bullet' : 'ordered', Quill.sources.USER);
        this.quill.setSelection(range.index - length, Quill.sources.SILENT);
      }
    }
  }
};

function handleDelete(range) {
  this.quill.deleteText(range, Quill.sources.USER);
  this.quill.setSelection(range.index, Quill.sources.SILENT);
  this.quill.selection.scrollIntoView();
}

function handleEnter(range, context) {
  if (range.length > 0) {
    this.quill.scroll.deleteAt(range.index, range.length);  // So we do not trigger text-change
  }
  let lineFormats = Object.keys(context.format).reduce(function(lineFormats, format) {
    if (Parchment.query(format, Parchment.Scope.BLOCK) && !Array.isArray(context.format[format])) {
      lineFormats[format] = context.format[format];
    }
    return lineFormats;
  }, {});
  this.quill.insertText(range.index, '\n', lineFormats, Quill.sources.USER);
  this.quill.selection.scrollIntoView();
  Object.keys(context.format).forEach((name) => {
    if (lineFormats[name] != null) return;
    if (Array.isArray(context.format[name])) return;
    if (name === 'link') return;
    this.quill.format(name, context.format[name], Quill.sources.USER);
  });
}

function makeCodeBlockHandler(indent) {
  return {
    key: Keyboard.keys.TAB,
    shiftKey: !indent,
    format: {'code-block': true },
    handler: function(range) {
      let CodeBlock = Parchment.query('code-block');
      let index = range.index, length = range.length;
      let [block, offset] = this.quill.scroll.descendant(CodeBlock, index);
      if (block == null) return;
      let scrollOffset = this.quill.scroll.offset(block);
      let start = block.newlineIndex(offset, true) + 1;
      let end = block.newlineIndex(scrollOffset + offset + length);
      let lines = block.domNode.textContent.slice(start, end).split('\n');
      offset = 0;
      lines.forEach((line, i) => {
        if (indent) {
          block.insertAt(start + offset, CodeBlock.TAB);
          offset += CodeBlock.TAB.length;
          if (i === 0) {
            index += CodeBlock.TAB.length;
          } else {
            length += CodeBlock.TAB.length;
          }
        } else if (line.startsWith(CodeBlock.TAB)) {
          block.deleteAt(start + offset, CodeBlock.TAB.length);
          offset -= CodeBlock.TAB.length;
          if (i === 0) {
            index -= CodeBlock.TAB.length;
          } else {
            length -= CodeBlock.TAB.length;
          }
        }
        offset += line.length + 1;
      });
      this.quill.update(Quill.sources.USER);
      this.quill.setSelection(index, length, Quill.sources.SILENT);
    }
  };
}

function makeFormatHandler(format) {
  return {
    key: format[0].toUpperCase(),
    shortKey: true,
    handler: function(range, context) {
      this.quill.format(format, !context.format[format], Quill.sources.USER);
    }
  };
}

function normalize(binding) {
  if (typeof binding === 'string' || typeof binding === 'number') {
    return normalize({ key: binding });
  }
  if (typeof binding === 'object') {
    binding = clone(binding, false);
  }
  if (typeof binding.key === 'string') {
    if (Keyboard.keys[binding.key.toUpperCase()] != null) {
      binding.key = Keyboard.keys[binding.key.toUpperCase()];
    } else if (binding.key.length === 1) {
      binding.key = binding.key.toUpperCase().charCodeAt(0);
    } else {
      return null;
    }
  }
  return binding;
}

export default Keyboard;
<commit_msg>fix backspace behavior<commit_after>import clone from 'clone';
import equal from 'deep-equal';
import extend from 'extend';
import Delta from 'rich-text/lib/delta';
import Parchment from 'parchment';
import Quill from '../core/quill';
import logger from '../core/logger';
import Module from '../core/module';
import Block from '../blots/block';

let debug = logger('quill:keyboard');

const SHORTKEY = /Mac/i.test(navigator.platform) ? 'metaKey' : 'ctrlKey';

class Keyboard extends Module {
  static match(evt, binding) {
    binding = normalize(binding);
    if (!!binding.shortKey !== evt[SHORTKEY] && binding.shortKey !== null) return false;
    if (['altKey', 'ctrlKey', 'metaKey', 'shiftKey'].some(function(key) {
      return (key != SHORTKEY && !!binding[key] !== evt[key] && binding[key] !== null);
    })) {
      return false;
    }
    return binding.key === (evt.which || evt.keyCode);
  }

  constructor(quill, options) {
    super(quill, options);
    this.bindings = {};
    Object.keys(this.options.bindings).forEach((name) => {
      if (this.options.bindings[name]) {
        this.addBinding(this.options.bindings[name]);
      }
    });
    this.addBinding({ key: Keyboard.keys.ENTER, shiftKey: null }, handleEnter);
    this.addBinding({ key: Keyboard.keys.ENTER, metaKey: null, ctrlKey: null, altKey: null }, function() {});
    this.addBinding({ key: Keyboard.keys.BACKSPACE }, { collapsed: true, prefix: /^.?$/ }, function(range, context) {
      if (range.index === 0) return;
      let index = context.offset === 0 ? range.index : range.index - 1;
      this.quill.deleteText(index, 1, Quill.sources.USER);
      this.quill.setSelection(range.index-1, Quill.sources.SILENT);
      this.quill.selection.scrollIntoView();
    });
    this.addBinding({ key: Keyboard.keys.DELETE }, { collapsed: true, suffix: /^$/ }, function(range) {
      if (range.index >= this.quill.getLength() - 1) return;
      this.quill.deleteText(range.index, 1, Quill.sources.USER);
    });
    this.addBinding({ key: Keyboard.keys.BACKSPACE }, { collapsed: false }, handleDelete);
    this.addBinding({ key: Keyboard.keys.DELETE }, { collapsed: false }, handleDelete);
    this.listen();
  }

  addBinding(key, context = {}, handler = {}) {
    let binding = normalize(key);
    if (binding == null || binding.key == null) {
      return debug.warn('Attempted to add invalid keyboard binding', binding);
    }
    if (typeof context === 'function') {
      context = { handler: context };
    }
    if (typeof handler === 'function') {
      handler = { handler: handler };
    }
    binding = extend(binding, context, handler);
    this.bindings[binding.key] = this.bindings[binding.key] || [];
    this.bindings[binding.key].push(binding);
  }

  listen() {
    this.quill.root.addEventListener('keydown', (evt) => {
      if (evt.defaultPrevented) return;
      let which = evt.which || evt.keyCode;
      let bindings = (this.bindings[which] || []).filter(function(binding) {
        return Keyboard.match(evt, binding);
      });
      if (bindings.length === 0) return;
      let range = this.quill.getSelection();
      if (range == null) return;    // implies we do not have focus
      let [line, offset] = this.quill.scroll.line(range.index);
      let [leafStart, offsetStart] = this.quill.scroll.leaf(range.index);
      let [leafEnd, offsetEnd] = range.length === 0 ? [leafStart, offsetStart] : this.quill.scroll.leaf(range.index + range.length);
      let prefixText = leafStart instanceof Parchment.Text ? leafStart.value().slice(0, offsetStart) : '';
      let suffixText = leafEnd instanceof Parchment.Text ? leafEnd.value().slice(offsetEnd) : '';
      let curContext = {
        collapsed: range.length === 0,
        empty: range.length === 0 && line.length() <= 1,
        format: this.quill.getFormat(range),
        offset: offset,
        prefix: prefixText,
        suffix: suffixText
      };
      let prevented = bindings.some((binding) => {
        if (binding.collapsed != null && binding.collapsed !== curContext.collapsed) return false;
        if (binding.empty != null && binding.empty !== curContext.empty) return false;
        if (binding.offset != null && binding.offset !== curContext.offset) return false;
        if (Array.isArray(binding.format)) {
          // any format is present
          if (binding.format.every(function(name) {
            return curContext.format[name] == null;
          })) {
            return false;
          }
        } else if (typeof binding.format === 'object') {
          // all formats must match
          if (!Object.keys(binding.format).every(function(name) {
            if (binding.format[name] === true) return curContext.format[name] != null;
            if (binding.format[name] === false) return curContext.format[name] == null;
            return equal(binding.format[name], curContext.format[name]);
          })) {
            return false;
          }
        }
        if (binding.prefix != null && !binding.prefix.test(curContext.prefix)) return false;
        if (binding.suffix != null && !binding.suffix.test(curContext.suffix)) return false;
        return binding.handler.call(this, range, curContext) !== true;
      });
      if (prevented) {
        evt.preventDefault();
      }
    });
  }
}

Keyboard.keys = {
  BACKSPACE: 8,
  TAB: 9,
  ENTER: 13,
  ESCAPE: 27,
  LEFT: 37,
  UP: 38,
  RIGHT: 39,
  DOWN: 40,
  DELETE: 46
};

Keyboard.DEFAULTS = {
  bindings: {
    'bold'      : makeFormatHandler('bold'),
    'italic'    : makeFormatHandler('italic'),
    'underline' : makeFormatHandler('underline'),
    'indent': {
      // highlight tab or tab at beginning of list, indent or blockquote
      key: Keyboard.keys.TAB,
      format: ['blockquote', 'indent', 'list'],
      handler: function(range, context) {
        if (context.collapsed && context.offset !== 0) return true;
        this.quill.format('indent', '+1', Quill.sources.USER);
      }
    },
    'outdent': {
      key: Keyboard.keys.TAB,
      shiftKey: true,
      format: ['blockquote', 'indent', 'list'],
      // highlight tab or tab at beginning of list, indent or blockquote
      handler: function(range, context) {
        if (context.collapsed && context.offset !== 0) return true;
        this.quill.format('indent', '-1', Quill.sources.USER);
      }
    },
    'outdent backspace': {
      key: Keyboard.keys.BACKSPACE,
      collapsed: true,
      format: ['blockquote', 'indent', 'list'],
      offset: 0,
      handler: function(range, context) {
        if (context.format.indent != null) {
          this.quill.format('indent', '-1', Quill.sources.USER);
        } else if (context.format.blockquote != null) {
          this.quill.format('blockquote', false, Quill.sources.USER);
        } else if (context.format.list != null) {
          this.quill.format('list', false, Quill.sources.USER);
        }
      }
    },
    'indent code-block': makeCodeBlockHandler(true),
    'outdent code-block': makeCodeBlockHandler(false),
    'remove tab': {
      key: Keyboard.keys.TAB,
      shiftKey: true,
      collapsed: true,
      prefix: /\t$/,
      handler: function(range, context) {
        this.quill.deleteText(range.index - 1, 1, Quill.sources.USER);
      }
    },
    'tab': {
      key: Keyboard.keys.TAB,
      handler: function(range, context) {
        if (!context.collapsed) {
          this.quill.scroll.deleteAt(range.index, range.length);
        }
        this.quill.insertText(range.index, '\t', Quill.sources.USER);
        this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
      }
    },
    'list empty enter': {
      key: Keyboard.keys.ENTER,
      collapsed: true,
      format: ['list'],
      empty: true,
      handler: function(range, context) {
        this.quill.format('list', false, Quill.sources.USER);
        if (context.format.indent) {
          this.quill.format('indent', false, Quill.sources.USER);
        }
      }
    },
    'header enter': {
      key: Keyboard.keys.ENTER,
      collapsed: true,
      format: ['header'],
      suffix: /^$/,
      handler: function(range) {
        this.quill.scroll.insertAt(range.index, '\n');
        this.quill.formatText(range.index + 1, 1, 'header', false, Quill.sources.USER);
        this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
        this.quill.selection.scrollIntoView();
      }
    },
    'list autofill': {
      key: ' ',
      collapsed: true,
      format: { list: false },
      prefix: /^(1\.|-)$/,
      handler: function(range, context) {
        let length = context.prefix.length;
        this.quill.scroll.deleteAt(range.index - length, length);
        this.quill.formatLine(range.index - length, 1, 'list', length === 1 ? 'bullet' : 'ordered', Quill.sources.USER);
        this.quill.setSelection(range.index - length, Quill.sources.SILENT);
      }
    }
  }
};

function handleDelete(range) {
  this.quill.deleteText(range, Quill.sources.USER);
  this.quill.setSelection(range.index, Quill.sources.SILENT);
  this.quill.selection.scrollIntoView();
}

function handleEnter(range, context) {
  if (range.length > 0) {
    this.quill.scroll.deleteAt(range.index, range.length);  // So we do not trigger text-change
  }
  let lineFormats = Object.keys(context.format).reduce(function(lineFormats, format) {
    if (Parchment.query(format, Parchment.Scope.BLOCK) && !Array.isArray(context.format[format])) {
      lineFormats[format] = context.format[format];
    }
    return lineFormats;
  }, {});
  this.quill.insertText(range.index, '\n', lineFormats, Quill.sources.USER);
  this.quill.selection.scrollIntoView();
  Object.keys(context.format).forEach((name) => {
    if (lineFormats[name] != null) return;
    if (Array.isArray(context.format[name])) return;
    if (name === 'link') return;
    this.quill.format(name, context.format[name], Quill.sources.USER);
  });
}

function makeCodeBlockHandler(indent) {
  return {
    key: Keyboard.keys.TAB,
    shiftKey: !indent,
    format: {'code-block': true },
    handler: function(range) {
      let CodeBlock = Parchment.query('code-block');
      let index = range.index, length = range.length;
      let [block, offset] = this.quill.scroll.descendant(CodeBlock, index);
      if (block == null) return;
      let scrollOffset = this.quill.scroll.offset(block);
      let start = block.newlineIndex(offset, true) + 1;
      let end = block.newlineIndex(scrollOffset + offset + length);
      let lines = block.domNode.textContent.slice(start, end).split('\n');
      offset = 0;
      lines.forEach((line, i) => {
        if (indent) {
          block.insertAt(start + offset, CodeBlock.TAB);
          offset += CodeBlock.TAB.length;
          if (i === 0) {
            index += CodeBlock.TAB.length;
          } else {
            length += CodeBlock.TAB.length;
          }
        } else if (line.startsWith(CodeBlock.TAB)) {
          block.deleteAt(start + offset, CodeBlock.TAB.length);
          offset -= CodeBlock.TAB.length;
          if (i === 0) {
            index -= CodeBlock.TAB.length;
          } else {
            length -= CodeBlock.TAB.length;
          }
        }
        offset += line.length + 1;
      });
      this.quill.update(Quill.sources.USER);
      this.quill.setSelection(index, length, Quill.sources.SILENT);
    }
  };
}

function makeFormatHandler(format) {
  return {
    key: format[0].toUpperCase(),
    shortKey: true,
    handler: function(range, context) {
      this.quill.format(format, !context.format[format], Quill.sources.USER);
    }
  };
}

function normalize(binding) {
  if (typeof binding === 'string' || typeof binding === 'number') {
    return normalize({ key: binding });
  }
  if (typeof binding === 'object') {
    binding = clone(binding, false);
  }
  if (typeof binding.key === 'string') {
    if (Keyboard.keys[binding.key.toUpperCase()] != null) {
      binding.key = Keyboard.keys[binding.key.toUpperCase()];
    } else if (binding.key.length === 1) {
      binding.key = binding.key.toUpperCase().charCodeAt(0);
    } else {
      return null;
    }
  }
  return binding;
}

export default Keyboard;
<|endoftext|>