<commit_before>'use strict';
import * as _ from 'lodash';
let SortedSet = require('collections/sorted-set');

// checks for one row of the iterchunk board for a match like [0010].
export function hasMatchInSingleRow (row) {
    return _.max(_.values(_.countBy(row))) > 2;
};

// return the index of all occurrences of `value` in `list`. [5, 3, 7, 5], 5 -> [0, 3]
export function indexOfAll (list, value) {
    return _.reduce(list, (acc, e, i) => {
        if (e === value) {
            acc.push(i);
        }

        return acc;
    }, []);
};

// checks across both rows for a match, like [0101]
//                                           [2031]
export function hasMatchInPairOfRows (pairOfRows) {
    let allValues = _.uniq(_.flatten(pairOfRows));
    let allMatches = _.map(allValues, value => {
        return _.uniq([...indexOfAll(pairOfRows[0], value),
                       ...indexOfAll(pairOfRows[1], value)]).sort();
    });

    return _.some(allMatches, match => {
        return _.some([
            _.isEqual(match, [0, 1, 2]),
            _.isEqual(match, [1, 2, 3]),
            _.isEqual(match, [0, 1, 2, 3]),
        ]);
    });
};

/**
 * With `orbs` being
 * [ [ 6, 5, 4 ],
 *   [ 3, 2, 2 ],
 *   [ 6, 4, 0 ] ]
 * And with a [3, 2] `chunkLimitRange`, this will yield each 3x2
 * grouping, and then, each available 2x3 grouping.
 * [ [ [6, 5, 4], [3, 2, 2] ], [ [3, 2, 2], [6, 4, 0] ],
 *   [ [6, 3, 6], [5, 2, 4] ], [ [5, 2, 4], [4, 2, 0] ] ]
 *
 * If you want to also return the position of the first member of the chunk,
 * as X/Y coordinates, pass in `includePositionInformation`. That will return the
 * same data as above, but with a final piece of information, an object with a `position`
 * key that maps to the first and last X/Y coordinates of that chunk.
 * [
 *     [
 *         [6, 5, 4], [3, 2, 2],
 *         {
 *             position: {
 *                 first: [0, 0],
 *                 last: [1, 2]
 *             }
 *         }
 *     ],
 *     [
 *         [3, 2, 2], [6, 4, 0],
 *         {
 *             position: {
 *                 first: [1, 0],
 *                 last: [2, 2]
 *             }
 *         }
 *     ],
 *     [
 *         [6, 3, 6], [5, 2, 4],
 *         {
 *             position: {
 *                 first: [0, 0],
 *                 last: [2, 1]
 *             }
 *         }
 *     ],
 *     [
 *         [5, 2, 4], [4, 2, 0]
 *         {
 *             position: {
 *                 first: [0, 1],
 *                 last: [2, 2]
 *             }
 *         }
 *     ]
 * ]
 */
export function iterchunks (orbs, chunkLimitRange = [4, 2], includePositionInformation = false) {
    let _iterchunks = (orbs, isTransposed = false) => {
        let chunks = [];
        let [width, height] = chunkLimitRange;
        let [finalPositionWidth, finalPositionHeight] = [orbs[0].length - width, orbs.length - height];
        _.each(_.range(0, finalPositionHeight + 1), heightIndex => {
            _.each(_.range(0, finalPositionWidth + 1), widthIndex => {
                let chunkData = orbs.slice(heightIndex, heightIndex + height).map(row => {
                    return row.slice(widthIndex, widthIndex + width);
                });

                if (includePositionInformation) {
                    let heightCoordinates = [heightIndex + height - 1, widthIndex + width - 1];
                    let widthCoordinates = [heightIndex, widthIndex];
                    if (isTransposed) {
                        widthCoordinates = widthCoordinates.reverse();
                        heightCoordinates = heightCoordinates.reverse();
                    }

                    chunkData.push({
                        position: {
                            first: widthCoordinates,
                            last: heightCoordinates
                        }
                    });
                }

                chunks.push(chunkData);
            });
        });
        return chunks;
    };
    let transposedOrbs = _.zip(...orbs);
    return [..._iterchunks(orbs, false), ..._iterchunks(transposedOrbs, true)]
};

export function findMatches(orbs) {
    let matches = [];
    let b = orbs;
    let height = orbs.length;
    let width = orbs[0].length;

    _.each(_.range(height - 2), x => {
        _.each(_.range(width - 2), y => {
            if (b[x][y] == b[x][y + 1] && b[x][y] == b[x][y + 2]) {
                matches.push([[x, y], [x, y + 1], [x, y + 2]])
            } else if (b[x][y] == b[x + 1][y] && b[x][y] == b[x + 2][y]) {
                matches.push([[x, y], [x + 1, y], [x + 2, y]])
            };
        });
    });
    return matches;
};

export function combineMatches(matches) {
    let combinedMatches = [];
    let unused = matches;
    let couldMatch, before, currentMatch;

    while (unused[0] != null) {
        currentMatch = new SortedSet(unused[0]);
        unused.shift();
        couldMatch = _.clone(unused);

        _.each(couldMatch, m => { //only union if there is an overlap!
            if (currentMatch.intersection(m).toArray()[0] != null) {
                before = currentMatch.toArray();
                currentMatch.swap(0, currentMatch.length, currentMatch.union(m));
                if (before != currentMatch.toArray()) {
                    unused.splice(unused.indexOf(m), 1);
                }
            }
        });
        combinedMatches.push(currentMatch.toArray());
    }
    return combinedMatches;
};

export class Board {
    constructor (width = 8, height = 8, types = _.range(7)) {
        this.width = width;
        this.height = height;
        this.types = types;
        let chooseOrb = () => { return _.sample(types); };
        let sampleRow = () => { return _.times(width, chooseOrb); };
        this.orbs = _.zip(..._.times(height, sampleRow));
        // this.evaluateAll(this.orbs);
    }

    get size() {
        return [this.width, this.height];
    }

    get availableTypes() {
        return _.uniq(_.flatten(this.orbs)).sort();
    }

    /**
      * 1. logs data for each match and replaces each orb with 'X'
      * 2. replaces each 'X' and all above orbs with either the orb directly above or a random new orb
      * 3. returns the match data -> [[match1Type, match1Amount], [match2Type, match2Amount], ...]
      */
    evaluate(matches) {
        let matchData = [];

        _.each(matches, match => {
            // log data
            let [xx, yy] = match[0];
            matchData.push([this.orbs[xx][yy], match.length]);
            // replace each coordinate with 'X'
            _.each(match, coord => {
                let [x, y] = coord;
                this.orbs[x][y] = 'X'
            })
        });

        /**
          * drop down and generate matches
          * 1. reads across starting from the top
          * 2. when it hits 'X', loops from that position directly up
          * 3. if the row isn't 0, it takes the orb from above
          * 4. if the row is 0, it creates a random orb
        */
        _.each(_.range(this.height), x =>{
            _.each(_.range(this.width), y => { //1
                if (this.orbs[x][y] == 'X') {
                    for (var z = x; z >= 0; z--) { //2
                        if (z > 0) { //3
                            this.orbs[z][y] = this.orbs[z - 1][y];
                        } else { //4
                            this.orbs[z][y] = _.sample(this.types);;
                        };
                    };
                };
            });
        });

        return matchData;
    }

    evaluateAll() {
        var matchEvents = [];
        while (this.hasMatch()) {
            matchEvents.push(this.evaluate());
        }
    }

    needsShuffle() {
        return !this.hasMatch();
    }

    hasMatch() {
        let chunks = iterchunks(this.orbs);
        // [[[1, 2, 3], [2, 3, 4]], [[3, 4, 5], [4, 5, 6]]] becomes
        //  [[1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6]]
        let flatChunks = _.flattenDepth(chunks, 1);
        let hasWideStyleMatch = _.some(_.map(flatChunks, hasMatchInSingleRow));
        return hasWideStyleMatch || _.some(_.map(chunks), hasMatchInPairOfRows);
    }

    shuffle() {

    }
};
<commit_msg>Update iterchunks to assist findMatches<commit_after>'use strict';
import * as _ from 'lodash';
let SortedSet = require('collections/sorted-set');

// checks for one row of the iterchunk board for a match like [0010].
export function hasMatchInSingleRow (row) {
    return _.max(_.values(_.countBy(row))) > 2;
};

// return the index of all occurrences of `value` in `list`. [5, 3, 7, 5], 5 -> [0, 3]
export function indexOfAll (list, value) {
    return _.reduce(list, (acc, e, i) => {
        if (e === value) {
            acc.push(i);
        }

        return acc;
    }, []);
};

// checks across both rows for a match, like [0101]
//                                           [2031]
export function hasMatchInPairOfRows (pairOfRows) {
    let allValues = _.uniq(_.flatten(pairOfRows));
    let allMatches = _.map(allValues, value => {
        return _.uniq([...indexOfAll(pairOfRows[0], value),
                       ...indexOfAll(pairOfRows[1], value)]).sort();
    });

    return _.some(allMatches, match => {
        return _.some([
            _.isEqual(match, [0, 1, 2]),
            _.isEqual(match, [1, 2, 3]),
            _.isEqual(match, [0, 1, 2, 3]),
        ]);
    });
};

/**
 * @private
 * @description Used to provide more granularity to functions that need to call iterchunks
 * on a non-transposed set of orbs, as well as a transposed set of orbs.
 * @see findMatches
 */
let _iterchunks = (orbs, chunkLimitRange, includePositionInformation, isTransposed) => {
    let chunks = [];
    let [width, height] = chunkLimitRange;
    let [finalPositionWidth, finalPositionHeight] = [orbs[0].length - width, orbs.length - height];
    _.each(_.range(0, finalPositionHeight + 1), heightIndex => {
        _.each(_.range(0, finalPositionWidth + 1), widthIndex => {
            let chunkData = orbs.slice(heightIndex, heightIndex + height).map(row => {
                return row.slice(widthIndex, widthIndex + width);
            });

            if (includePositionInformation) {
                let startingCoordinates = [heightIndex, widthIndex];
                let endingCoordinates = [heightIndex + height - 1, widthIndex + width - 1];
                if (isTransposed) {
                    startingCoordinates = startingCoordinates.reverse();
                    endingCoordinates = endingCoordinates.reverse();
                }

                chunkData.push({
                    position: {
                        first: startingCoordinates,
                        last: endingCoordinates
                    }
                });
            }

            chunks.push(chunkData);
        });
    });
    return chunks;
};

/**
 * With `orbs` being
 * [ [ 6, 5, 4 ],
 *   [ 3, 2, 2 ],
 *   [ 6, 4, 0 ] ]
 * And with a [3, 2] `chunkLimitRange`, this will yield each 3x2
 * grouping, and then, each available 2x3 grouping.
 * [ [ [6, 5, 4], [3, 2, 2] ], [ [3, 2, 2], [6, 4, 0] ],
 *   [ [6, 3, 6], [5, 2, 4] ], [ [5, 2, 4], [4, 2, 0] ] ]
 *
 * If you want to also return the position of the first member of the chunk,
 * as X/Y coordinates, pass in `includePositionInformation`. That will return the
 * same data as above, but with a final piece of information, an object with a `position`
 * key that maps to the first and last X/Y coordinates of that chunk.
 * [
 *     [
 *         [6, 5, 4], [3, 2, 2],
 *         {
 *             position: {
 *                 first: [0, 0],
 *                 last: [1, 2]
 *             }
 *         }
 *     ],
 *     [
 *         [3, 2, 2], [6, 4, 0],
 *         {
 *             position: {
 *                 first: [1, 0],
 *                 last: [2, 2]
 *             }
 *         }
 *     ],
 *     [
 *         [6, 3, 6], [5, 2, 4],
 *         {
 *             position: {
 *                 first: [0, 0],
 *                 last: [2, 1]
 *             }
 *         }
 *     ],
 *     [
 *         [5, 2, 4], [4, 2, 0]
 *         {
 *             position: {
 *                 first: [0, 1],
 *                 last: [2, 2]
 *             }
 *         }
 *     ]
 * ]
 */
export function iterchunks (orbs, chunkLimitRange = [4, 2], includePositionInformation = false) {
    let transposedOrbs = _.zip(...orbs);
    return [
            ..._iterchunks(orbs, chunkLimitRange, includePositionInformation, false),
            ..._iterchunks(transposedOrbs, chunkLimitRange, includePositionInformation, true)
    ]
};

export function findMatches(orbs) {
    let matches = [];
    let b = orbs;
    let height = orbs.length;
    let width = orbs[0].length;

    _.each(_.range(height - 2), x => {
        _.each(_.range(width - 2), y => {
            if (b[x][y] == b[x][y + 1] && b[x][y] == b[x][y + 2]) {
                matches.push([[x, y], [x, y + 1], [x, y + 2]])
            } else if (b[x][y] == b[x + 1][y] && b[x][y] == b[x + 2][y]) {
                matches.push([[x, y], [x + 1, y], [x + 2, y]])
            };
        });
    });
    return matches;
};

export function combineMatches(matches) {
    let combinedMatches = [];
    let unused = matches;
    let couldMatch, before, currentMatch;

    while (unused[0] != null) {
        currentMatch = new SortedSet(unused[0]);
        unused.shift();
        couldMatch = _.clone(unused);

        _.each(couldMatch, m => { //only union if there is an overlap!
            if (currentMatch.intersection(m).toArray()[0] != null) {
                before = currentMatch.toArray();
                currentMatch.swap(0, currentMatch.length, currentMatch.union(m));
                if (before != currentMatch.toArray()) {
                    unused.splice(unused.indexOf(m), 1);
                }
            }
        });
        combinedMatches.push(currentMatch.toArray());
    }
    return combinedMatches;
};

export class Board {
    constructor (width = 8, height = 8, types = _.range(7)) {
        this.width = width;
        this.height = height;
        this.types = types;
        let chooseOrb = () => { return _.sample(types); };
        let sampleRow = () => { return _.times(width, chooseOrb); };
        this.orbs = _.zip(..._.times(height, sampleRow));
        // this.evaluateAll(this.orbs);
    }

    get size() {
        return [this.width, this.height];
    }

    get availableTypes() {
        return _.uniq(_.flatten(this.orbs)).sort();
    }

    /**
      * 1. logs data for each match and replaces each orb with 'X'
      * 2. replaces each 'X' and all above orbs with either the orb directly above or a random new orb
      * 3. returns the match data -> [[match1Type, match1Amount], [match2Type, match2Amount], ...]
      */
    evaluate(matches) {
        let matchData = [];

        _.each(matches, match => {
            // log data
            let [xx, yy] = match[0];
            matchData.push([this.orbs[xx][yy], match.length]);
            // replace each coordinate with 'X'
            _.each(match, coord => {
                let [x, y] = coord;
                this.orbs[x][y] = 'X'
            })
        });

        /**
          * drop down and generate matches
          * 1. reads across starting from the top
          * 2. when it hits 'X', loops from that position directly up
          * 3. if the row isn't 0, it takes the orb from above
          * 4. if the row is 0, it creates a random orb
        */
        _.each(_.range(this.height), x =>{
            _.each(_.range(this.width), y => { //1
                if (this.orbs[x][y] == 'X') {
                    for (var z = x; z >= 0; z--) { //2
                        if (z > 0) { //3
                            this.orbs[z][y] = this.orbs[z - 1][y];
                        } else { //4
                            this.orbs[z][y] = _.sample(this.types);;
                        };
                    };
                };
            });
        });

        return matchData;
    }

    evaluateAll() {
        var matchEvents = [];
        while (this.hasMatch()) {
            matchEvents.push(this.evaluate());
        }
    }

    needsShuffle() {
        return !this.hasMatch();
    }

    hasMatch() {
        let chunks = iterchunks(this.orbs);
        // [[[1, 2, 3], [2, 3, 4]], [[3, 4, 5], [4, 5, 6]]] becomes
        //  [[1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6]]
        let flatChunks = _.flattenDepth(chunks, 1);
        let hasWideStyleMatch = _.some(_.map(flatChunks, hasMatchInSingleRow));
        return hasWideStyleMatch || _.some(_.map(chunks), hasMatchInPairOfRows);
    }

    shuffle() {

    }
};
<|endoftext|>


<commit_before>import Prop from 'props/prop';
import canvas from 'canvas';

export default class Ball extends Prop {
  constructor() {
    const width = 10;
    const height = 10;
    const x = (canvas.width / 2) - (width / 2);
    const y = (canvas.height / 2) - (height / 2);

    super(x, y, width, height);
  }

  fire() {
    console.log('this in Ball class', this);
    setInterval(() => {
      this.move(1, 1);
    }, 10);
  }
}
<commit_msg>Remove unneeded log<commit_after>import Prop from 'props/prop';
import canvas from 'canvas';

export default class Ball extends Prop {
  constructor() {
    const width = 10;
    const height = 10;
    const x = (canvas.width / 2) - (width / 2);
    const y = (canvas.height / 2) - (height / 2);

    super(x, y, width, height);
  }

  fire() {
    setInterval(() => {
      this.move(1, 1);
    }, 10);
  }
}
<|endoftext|>


<commit_before>
/*
 * Book Leave request pop-up window.
 *
 * */
$(document).ready(function(){
  /*
   *  When FROM field in New absense form chnages: update TO one if necessary
   */
  $('input.book-leave-from-input').on('change', function(e){
    e.stopPropagation();

    var from_date = $('input.book-leave-from-input').datepicker('getDate');

    if ( ! from_date ) {
      // no new value for FROM part, do nothing
      console.log('No from date');
      return;
    }

    var to_date = $('input.book-leave-to-input').datepicker('getDate');

    if ( ! to_date || ( to_date && to_date.getTime() < from_date.getTime() )) {
      $('input.book-leave-to-input').datepicker('setDate', $('input.book-leave-from-input').datepicker('getFormattedDate'));
    }
  });
});

/*
 * Bootstrap-datepicker
 *
 * */
!function(a){
  a.fn.datepicker.dates["en-GB"] = {
    days : [
      "Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"
    ],
    daysShort : [
      "Sun","Mon","Tue","Wed","Thu","Fri","Sat"
    ],
    daysMin : [
      "Su","Mo","Tu","We","Th","Fr","Sa"
    ],
    months : [
      "January","February","March","April","May","June","July","August","September","October","November","December"
    ],
    monthsShort : [
      "Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"
    ],
    today       : "Today",
    monthsTitle : "Months",
    clear       : "Clear",
    weekStart   : 1,
    format      : "dd/mm/yyyy"
  }
}(jQuery);

$(function () {
  $('[data-toggle="tooltip"]').tooltip()
})

$(function () {
  $('[data-toggle="popover"]').popover()
})

/*
 * This is handler for invocation of "add secondary supervisors" modal
 *
 * */

$('#add_secondary_supervisers_modal').on('show.bs.modal', function (event) {
  var button = $(event.relatedTarget),
      department_name = button.data('department_name'),
      department_id = button.data('department_id');

  var modal = $(this);

  modal.find('.modal-title strong').text(department_name);

  // Make modal window to be no hiegher then window and its content
  // scrollable
  $('.modal .modal-body').css('overflow-y', 'auto');
  $('.modal .modal-body').css('max-height', $(window).height() * 0.7);

  $(this).find(".modal-body")
    // Show "loading" icon while content of modal is loaded
    .html('
Loading...
')
    .load('/settings/departments/available-supervisors/'+department_id+'/');
});

/*
 *  Given URL string return its query paramters as object.
 *
 *  If URL is not provided location of current page is used.
 * */

function getUrlVars(url){
  if ( ! url ) {
    url = window.location.href;
  }
  var vars = {}, hash;
  var hashes = url.slice( url.indexOf('?') + 1).split('&');
  for (var i = 0; i < hashes.length; i++) {
    hash = hashes[i].split('=');
    vars[hash[0]] = hash[1];
  }
  return vars;
}

/*
 * Evend that is fired when user change base date (current month) on Team View page.
 *
 * */

$(document).ready(function(){

  $('#team_view_month_select_btn')
    .datepicker()
    .on('changeDate', function(e) {
      var url = $(e.currentTarget).data('tom');

      var form = document.createElement("form");
      form.method = 'GET';
      form.action = url;

      var url_params = getUrlVars( url );
      url_params['date'] = e.format('yyyy-mm');

      // Move query parameters into the form
      $.each( url_params, function(key, val){
        var inp = document.createElement("input");
        inp.name = key;
        inp.value = val;
        form.appendChild(inp);
      });

      document.body.appendChild(form);

      return form.submit();
    });
});
<commit_msg>Remove phantom inputs on Team vew page<commit_after>
/*
 * Book Leave request pop-up window.
 *
 * */
$(document).ready(function(){
  /*
   *  When FROM field in New absense form chnages: update TO one if necessary
   */
  $('input.book-leave-from-input').on('change', function(e){
    e.stopPropagation();

    var from_date = $('input.book-leave-from-input').datepicker('getDate');

    if ( ! from_date ) {
      // no new value for FROM part, do nothing
      console.log('No from date');
      return;
    }

    var to_date = $('input.book-leave-to-input').datepicker('getDate');

    if ( ! to_date || ( to_date && to_date.getTime() < from_date.getTime() )) {
      $('input.book-leave-to-input').datepicker('setDate', $('input.book-leave-from-input').datepicker('getFormattedDate'));
    }
  });
});

/*
 * Bootstrap-datepicker
 *
 * */
!function(a){
  a.fn.datepicker.dates["en-GB"] = {
    days : [
      "Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"
    ],
    daysShort : [
      "Sun","Mon","Tue","Wed","Thu","Fri","Sat"
    ],
    daysMin : [
      "Su","Mo","Tu","We","Th","Fr","Sa"
    ],
    months : [
      "January","February","March","April","May","June","July","August","September","October","November","December"
    ],
    monthsShort : [
      "Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"
    ],
    today       : "Today",
    monthsTitle : "Months",
    clear       : "Clear",
    weekStart   : 1,
    format      : "dd/mm/yyyy"
  }
}(jQuery);

$(function () {
  $('[data-toggle="tooltip"]').tooltip()
})

$(function () {
  $('[data-toggle="popover"]').popover()
})

/*
 * This is handler for invocation of "add secondary supervisors" modal
 *
 * */

$('#add_secondary_supervisers_modal').on('show.bs.modal', function (event) {
  var button = $(event.relatedTarget),
      department_name = button.data('department_name'),
      department_id = button.data('department_id');

  var modal = $(this);

  modal.find('.modal-title strong').text(department_name);

  // Make modal window to be no hiegher then window and its content
  // scrollable
  $('.modal .modal-body').css('overflow-y', 'auto');
  $('.modal .modal-body').css('max-height', $(window).height() * 0.7);

  $(this).find(".modal-body")
    // Show "loading" icon while content of modal is loaded
    .html('
Loading...
')
    .load('/settings/departments/available-supervisors/'+department_id+'/');
});

/*
 *  Given URL string return its query paramters as object.
 *
 *  If URL is not provided location of current page is used.
 * */

function getUrlVars(url){
  if ( ! url ) {
    url = window.location.href;
  }
  var vars = {}, hash;
  var hashes = url.slice( url.indexOf('?') + 1).split('&');
  for (var i = 0; i < hashes.length; i++) {
    hash = hashes[i].split('=');
    vars[hash[0]] = hash[1];
  }
  return vars;
}

/*
 * Evend that is fired when user change base date (current month) on Team View page.
 *
 * */

$(document).ready(function(){

  $('#team_view_month_select_btn')
    .datepicker()
    .on('changeDate', function(e) {
      var url = $(e.currentTarget).data('tom');

      var form = document.createElement("form");
      form.method = 'GET';
      form.action = url;

      var url_params = getUrlVars( url );
      url_params['date'] = e.format('yyyy-mm');

      // Move query parameters into the form
      $.each( url_params, function(key, val){
        var inp = document.createElement("input");
        inp.name = key;
        inp.value = val;
        inp.type = 'hidden';
        form.appendChild(inp);
      });

      document.body.appendChild(form);

      return form.submit();
    });
});
<|endoftext|>


<commit_before>'use strict';

var BodyParser = require('body-parser');
var Colors = require('colors/safe');
var Express = require('express');
var Morgan = require('morgan');
var Version = require('./lib/version/version');
var Env = require('./config/environment');
var Path = require('path');
var PORT = Env.PORT;

var app = Express();
app.use(BodyParser.json());
app.use(function(req, res, next) {
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
    next();
});
app.use(Express.static(Path.join(__dirname, 'public')));
app.use(Morgan('combined'));

// curl http://localhost:3000/versions/fixtures:entrypoint/HEAD.json
app.get('/versions/:name/:tag.json', function(req, res) {
    var version = new Version();
    var name = req.params.name;
    var tag = req.params.tag;
    var url = version.getBundleURL(name, tag);
    if (url) {
        res.status(200).json({
            status: 200,
            url: url
        });
    }
    else {
        res.status(404).json({
            status: 404,
            error: 'No such version'
        });
    }
});

// curl -X POST -H "Content-Type: application/json" --data @test/fixtures/entrypoint.json http://localhost:3000/versions.json
app.post('/versions.json', function(req, res) {
    var body = req.body;
    if (!body.name || !body.files) {
        res.status(422).json({
            status: 422,
            error: 'Invalid params',
            request: body
        });
    }
    else {
        console.log('best-ecosystem: Attempting to save `' + body.name + '`');
        var version = new Version();
        version.save(body.name, body.files, function(saveErr, result) {
            if (saveErr) {
                res.status(500).json({
                    status: 500,
                    error: 'Server error',
                    request: body
                });
            }
            else {
                res.status(201).json({
                    status: 201,
                    name: result.name,
                    url: result.url,
                    tag: result.tag
                });
            }
        });
    }
});

app.listen(PORT);
console.log(Colors.green('best-ecosystem: Starting up...'));
console.log(Colors.green('best-ecosystem: Listening on port ' + PORT + '...'));
<commit_msg>Increase body parser max size to 128mb<commit_after>'use strict';

var BodyParser = require('body-parser');
var Colors = require('colors/safe');
var Express = require('express');
var Morgan = require('morgan');
var Version = require('./lib/version/version');
var Env = require('./config/environment');
var Path = require('path');
var PORT = Env.PORT;

var app = Express();
app.use(BodyParser.json({ limit: '128mb'}));
app.use(function(req, res, next) {
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
    next();
});
app.use(Express.static(Path.join(__dirname, 'public')));
app.use(Morgan('combined'));

// curl http://localhost:3000/versions/fixtures:entrypoint/HEAD.json
app.get('/versions/:name/:tag.json', function(req, res) {
    var version = new Version();
    var name = req.params.name;
    var tag = req.params.tag;
    var url = version.getBundleURL(name, tag);
    if (url) {
        res.status(200).json({
            status: 200,
            url: url
        });
    }
    else {
        res.status(404).json({
            status: 404,
            error: 'No such version'
        });
    }
});

// curl -X POST -H "Content-Type: application/json" --data @test/fixtures/entrypoint.json http://localhost:3000/versions.json
app.post('/versions.json', function(req, res) {
    var body = req.body;
    if (!body.name || !body.files) {
        res.status(422).json({
            status: 422,
            error: 'Invalid params',
            request: body
        });
    }
    else {
        console.log('best-ecosystem: Attempting to save `' + body.name + '`');
        var version = new Version();
        version.save(body.name, body.files, function(saveErr, result) {
            if (saveErr) {
                res.status(500).json({
                    status: 500,
                    error: 'Server error',
                    request: body
                });
            }
            else {
                res.status(201).json({
                    status: 201,
                    name: result.name,
                    url: result.url,
                    tag: result.tag
                });
            }
        });
    }
});

app.listen(PORT);
console.log(Colors.green('best-ecosystem: Starting up...'));
console.log(Colors.green('best-ecosystem: Listening on port ' + PORT + '...'));
<|endoftext|>


<commit_before>var util    = require('util'),
  events  = require('events'),
  qs      = require('querystring'),
  http    = require('http'),
  https   = require('https'),
  Logger  = require('./../util/logger');

module.exports = (function() {
  var Settings = {
    selenium_host : 'localhost',
    selenium_port : 4444,
    default_path  : '/wd/hub',
    credentials   : null,
    use_ssl       : false
  };

  var DO_NOT_LOG_ERRORS = [
    'Unable to locate element'
  ];

  function HttpRequest(options) {
    events.EventEmitter.call(this);
    this.setOptions(options);
  }

  util.inherits(HttpRequest, events.EventEmitter);

  HttpRequest.prototype.setOptions = function(options) {
    this.data          = options.data && jsonStringify(options.data) || '';
    this.contentLength = this.data.length;
    this.reqOptions    = this.createOptions(options);
    this.request       = null;

    return this;
  };

  HttpRequest.prototype.createOptions = function(options) {
    var defaultPathPrefix = options.path && options.path.indexOf(Settings.default_path) === -1 ?
      Settings.default_path : '';

    var reqOptions = {
      path    : defaultPathPrefix + (options.path || ''),
      host    : options.host || Settings.selenium_host,
      port    : options.selenium_port || Settings.selenium_port,
      method  : options.method || 'POST',
      headers : {}
    };
    var requestMethod = reqOptions.method.toUpperCase();
    if (options.sessionId) {
      reqOptions.path = reqOptions.path.replace(':sessionId', options.sessionId);
    }

    if (requestMethod === 'GET') {
      reqOptions.headers['Accept'] = 'application/json';
    }

    if (this.contentLength > 0) {
      reqOptions.headers['Content-Type'] = 'application/json; charset=utf-8';
    }

    if (needsContentLengthHeader(requestMethod)) {
      reqOptions.headers['Content-Length'] = this.contentLength;
    }

    if (Settings.credentials &&
      Settings.credentials.username && Settings.credentials.key
      ) {
      var authHeader = new Buffer(Settings.credentials.username + ':' + Settings.credentials.key).toString('base64');
      reqOptions.headers['Authorization'] = 'Basic ' + authHeader;
    }

    return reqOptions;
  };

  HttpRequest.prototype.send = function() {
    var self = this;
    this.request = (Settings.use_ssl ? https: http).request(this.reqOptions, function (response) {
      response.setEncoding('utf8');
      var redirected = false;
      if (isRedirect(response.statusCode)) {
        redirected = true;
      }

      var flushed = '';
      response.on('data', function (chunk) {
        if (self.reqOptions.method !== 'HEAD') {
          flushed += chunk;
        }
      });

      response.on('end', function () {
        var screenshotContent;
        var result, errorMessage = '';
        if (flushed) {
          result = parseResult(flushed);
          if (result.value) {
            if (result.value.screen) {
              screenshotContent = result.value.screen;
              delete result.value.screen;
            }

            if (result.value.stackTrace) {
              // Selenium stack traces won't help us here and they will pollute the output
              delete result.value.stackTrace;
            }

            if (result.value.message && shouldLogErrorMessage(result.value.message)) {
              errorMessage = result.value.message.replace(/\n/g,'\n\t');
              delete result.value.message;
            }
          }
        } else {
          result = {};
        }

        if (errorMessage !== '') {
          console.log(Logger.colors.yellow('There was an error while executing the Selenium command') +
            (!Logger.isEnabled() ? ' - enabling the --verbose option might offer more details.' : '')
          );
          console.log(errorMessage);
        }

        var logMethod = response.statusCode.toString().indexOf('5') === 0 ? 'error' : 'info';
        Logger[logMethod]('Response ' + response.statusCode + ' ' + self.reqOptions.method + ' ' + self.reqOptions.path, result);

        if (response.statusCode.toString().indexOf('2') === 0 || redirected) {
          self.emit('success', result, response, redirected);
        } else {
          self.emit('error', result, response, screenshotContent);
        }

        self.emit('complete', response);
      });
    });

    this.request.on('error', function(response) {
      self.emit('error', {}, response);
    });

    Logger.info('Request: ' + this.reqOptions.method + ' ' + this.reqOptions.path,
      '\n - data: ', this.data, '\n - headers: ', JSON.stringify(this.reqOptions.headers));

    this.request.write(this.data);
    this.request.end();

    return this;
  };

  /**
   *
   * @param s
   * @param emit_unicode
   * @returns {string}
   */
  HttpRequest.JSON_stringify = function(s, emit_unicode) {
    var json = JSON.stringify(s);
    if (json) {
      return emit_unicode ? json : json.replace(jsonRegex, jsonRegexReplace);
    }
  };

  HttpRequest.setSeleniumPort = function(port) {
    Settings.selenium_port = port;
  };
  HttpRequest.useSSL = function(value) {
    Settings.use_ssl = value;
  };
  HttpRequest.setSeleniumHost = function(host) {
    Settings.selenium_host = host;
  };
  HttpRequest.setCredentials = function(credentials) {
    Settings.credentials = credentials;
  };

  ///////////////////////////////////////////////////////////
  // Helpers
  ///////////////////////////////////////////////////////////
  var jsonRegex = new RegExp('[\\u007f-\\uffff]', 'g');
  var jsonRegexReplace = function(c) {
    return '\\u'+('0000'+c.charCodeAt(0).toString(16)).slice(-4);
  };

  /**
   * Built in JSON.stringify() will return unicode characters that require UTF-8 encoding on the wire.
   * This function will replace unicode characters with their escaped (ASCII-safe) equivalents to support
   * the keys sending command.
   *
   * @param {object} s
   * @returns {string}
   */
  function jsonStringify(s) {
    var json = JSON.stringify(s);
    if (json) {
      return json.replace(jsonRegex, jsonRegexReplace);
    }

    return json;
  }

  function isRedirect(statusCode) {
    return [302, 303, 304].indexOf(statusCode) > -1;
  }

  function needsContentLengthHeader(requestMethod) {
    return ['POST', 'DELETE'].indexOf(requestMethod) > -1;
  }

  function parseResult(data) {
    var result;
    data = stripUnknownChars(data);

    try {
      result = JSON.parse(data);
    } catch (err) {
      console.log(err.stack);
      result = {value: -1, error: err.message};
    }
    return result;
  }

  function shouldLogErrorMessage(msg) {
    return !DO_NOT_LOG_ERRORS.some(function(item) {
      return msg.indexOf(item) === 0;
    });
  }

  function stripUnknownChars(str) {
    var x = [], i = 0, length = str.length;

    for (i; i < length; i++) {
      if (str.charCodeAt(i)) {
        x.push(str.charAt(i));
      }
    }
    return x.join('');
  }

  return HttpRequest;
})();
<commit_msg>added <no such element> to array of DO_NOT_LOG_ERRORS<commit_after>var util    = require('util'),
  events  = require('events'),
  qs      = require('querystring'),
  http    = require('http'),
  https   = require('https'),
  Logger  = require('./../util/logger');

module.exports = (function() {
  var Settings = {
    selenium_host : 'localhost',
    selenium_port : 4444,
    default_path  : '/wd/hub',
    credentials   : null,
    use_ssl       : false
  };

  var DO_NOT_LOG_ERRORS = [
    'Unable to locate element',
    'no such element'
  ];

  function HttpRequest(options) {
    events.EventEmitter.call(this);
    this.setOptions(options);
  }

  util.inherits(HttpRequest, events.EventEmitter);

  HttpRequest.prototype.setOptions = function(options) {
    this.data          = options.data && jsonStringify(options.data) || '';
    this.contentLength = this.data.length;
    this.reqOptions    = this.createOptions(options);
    this.request       = null;

    return this;
  };

  HttpRequest.prototype.createOptions = function(options) {
    var defaultPathPrefix = options.path && options.path.indexOf(Settings.default_path) === -1 ?
      Settings.default_path : '';

    var reqOptions = {
      path    : defaultPathPrefix + (options.path || ''),
      host    : options.host || Settings.selenium_host,
      port    : options.selenium_port || Settings.selenium_port,
      method  : options.method || 'POST',
      headers : {}
    };
    var requestMethod = reqOptions.method.toUpperCase();
    if (options.sessionId) {
      reqOptions.path = reqOptions.path.replace(':sessionId', options.sessionId);
    }

    if (requestMethod === 'GET') {
      reqOptions.headers['Accept'] = 'application/json';
    }

    if (this.contentLength > 0) {
      reqOptions.headers['Content-Type'] = 'application/json; charset=utf-8';
    }

    if (needsContentLengthHeader(requestMethod)) {
      reqOptions.headers['Content-Length'] = this.contentLength;
    }

    if (Settings.credentials &&
      Settings.credentials.username && Settings.credentials.key
      ) {
      var authHeader = new Buffer(Settings.credentials.username + ':' + Settings.credentials.key).toString('base64');
      reqOptions.headers['Authorization'] = 'Basic ' + authHeader;
    }

    return reqOptions;
  };

  HttpRequest.prototype.send = function() {
    var self = this;
    this.request = (Settings.use_ssl ? https: http).request(this.reqOptions, function (response) {
      response.setEncoding('utf8');
      var redirected = false;
      if (isRedirect(response.statusCode)) {
        redirected = true;
      }

      var flushed = '';
      response.on('data', function (chunk) {
        if (self.reqOptions.method !== 'HEAD') {
          flushed += chunk;
        }
      });

      response.on('end', function () {
        var screenshotContent;
        var result, errorMessage = '';
        if (flushed) {
          result = parseResult(flushed);
          if (result.value) {
            if (result.value.screen) {
              screenshotContent = result.value.screen;
              delete result.value.screen;
            }

            if (result.value.stackTrace) {
              // Selenium stack traces won't help us here and they will pollute the output
              delete result.value.stackTrace;
            }

            if (result.value.message && shouldLogErrorMessage(result.value.message)) {
              errorMessage = result.value.message.replace(/\n/g,'\n\t');
              delete result.value.message;
            }
          }
        } else {
          result = {};
        }

        if (errorMessage !== '') {
          console.log(Logger.colors.yellow('There was an error while executing the Selenium command') +
            (!Logger.isEnabled() ? ' - enabling the --verbose option might offer more details.' : '')
          );
          console.log(errorMessage);
        }

        var logMethod = response.statusCode.toString().indexOf('5') === 0 ? 'error' : 'info';
        Logger[logMethod]('Response ' + response.statusCode + ' ' + self.reqOptions.method + ' ' + self.reqOptions.path, result);

        if (response.statusCode.toString().indexOf('2') === 0 || redirected) {
          self.emit('success', result, response, redirected);
        } else {
          self.emit('error', result, response, screenshotContent);
        }

        self.emit('complete', response);
      });
    });

    this.request.on('error', function(response) {
      self.emit('error', {}, response);
    });

    Logger.info('Request: ' + this.reqOptions.method + ' ' + this.reqOptions.path,
      '\n - data: ', this.data, '\n - headers: ', JSON.stringify(this.reqOptions.headers));

    this.request.write(this.data);
    this.request.end();

    return this;
  };

  /**
   *
   * @param s
   * @param emit_unicode
   * @returns {string}
   */
  HttpRequest.JSON_stringify = function(s, emit_unicode) {
    var json = JSON.stringify(s);
    if (json) {
      return emit_unicode ? json : json.replace(jsonRegex, jsonRegexReplace);
    }
  };

  HttpRequest.setSeleniumPort = function(port) {
    Settings.selenium_port = port;
  };
  HttpRequest.useSSL = function(value) {
    Settings.use_ssl = value;
  };
  HttpRequest.setSeleniumHost = function(host) {
    Settings.selenium_host = host;
  };
  HttpRequest.setCredentials = function(credentials) {
    Settings.credentials = credentials;
  };

  ///////////////////////////////////////////////////////////
  // Helpers
  ///////////////////////////////////////////////////////////
  var jsonRegex = new RegExp('[\\u007f-\\uffff]', 'g');
  var jsonRegexReplace = function(c) {
    return '\\u'+('0000'+c.charCodeAt(0).toString(16)).slice(-4);
  };

  /**
   * Built in JSON.stringify() will return unicode characters that require UTF-8 encoding on the wire.
   * This function will replace unicode characters with their escaped (ASCII-safe) equivalents to support
   * the keys sending command.
   *
   * @param {object} s
   * @returns {string}
   */
  function jsonStringify(s) {
    var json = JSON.stringify(s);
    if (json) {
      return json.replace(jsonRegex, jsonRegexReplace);
    }

    return json;
  }

  function isRedirect(statusCode) {
    return [302, 303, 304].indexOf(statusCode) > -1;
  }

  function needsContentLengthHeader(requestMethod) {
    return ['POST', 'DELETE'].indexOf(requestMethod) > -1;
  }

  function parseResult(data) {
    var result;
    data = stripUnknownChars(data);

    try {
      result = JSON.parse(data);
    } catch (err) {
      console.log(err.stack);
      result = {value: -1, error: err.message};
    }
    return result;
  }

  function shouldLogErrorMessage(msg) {
    return !DO_NOT_LOG_ERRORS.some(function(item) {
      return msg.indexOf(item) === 0;
    });
  }

  function stripUnknownChars(str) {
    var x = [], i = 0, length = str.length;

    for (i; i < length; i++) {
      if (str.charCodeAt(i)) {
        x.push(str.charAt(i));
      }
    }
    return x.join('');
  }

  return HttpRequest;
})();
<|endoftext|>


<commit_before>(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define(['underscore', 'backbone', 'exports'], function (Backbone, _, exports) {
            root.Backbone = factory(root, exports, Backbone, _);
        });
    } else if (typeof exports !== 'undefined') {
        var Backbone = require('backbone');
        var _ = require('underscore');
        factory(root, exports, Backbone, _);
    } else {
        root.BB = factory(root, {}, root.Backbone, root._);
    }
}(this, function (root, BB, Backbone, _) {
    /**
     * @author Enzo Martin <enzo.r.martin@gmail.com>
     * @module backbone/backbone-helper
     * @desc Backbone Helper functions to manage memory/instances smartly by keeping track of instanciated views/models/collections and making sure you don't have multiple instances of anything in the background by accident
     */

    BB = {
        view_definitions : {},
        model_definitions : {},
        collection_definitions : {},
        view_instances : {},
        model_instances : {},
        collection_instances : {}
    };

    // Private functions
    /**
     * Generic method for returning an object
     *
     * @param  {string} item model|collection
     * @param  {object} data
     * @return {instance}
     * @private
     */
    function get_item(item,data){
        var instances = this[item + '_instances'];
        var name = data.name;
        var defined = typeof instances[name] !== 'undefined';
        var instance;

        if(data.reset && defined){
            if(item == "model"){
                instances[name].destroy();
            } else if (item == "collection"){
                instances[name].reset();
            }
            delete instances[name];
            instance = create_item.call(this,item,data);
        } else {
            instance = defined ? instances[name] : create_item.call(this,item,data);
        }
        return instance;
    }

    /**
     * Generic method for creating an object
     *
     * @param  {string} item model|collection
     * @param  {object} data
     * @return {instance}
     * @private
     */
    function create_item(item,data){
        var definitions = this[item + '_definitions'];
        var name = data.name;
        var defined = typeof definitions[name] !== 'undefined';
        var instance;
        if(defined){
            instance = this[item + '_instances'][name] = new definitions[name];
            if(data.options){
                for(var option in data.options){
                    var value = data.options[option];
                    instance[option] = value;
                }
            }
            if(data.data){
                instance.set(data.data);
            }
            instance.name = name;
            if(item == "model"){
                this[item + '_instances'][name].listenTo(this[item + '_instances'][name],'destroy',_.bind(function(){
                    delete this[item + '_instances'][name];
                },this));
            }
        } else {
            throw new Error('Definition not found: ' + name +'. Did you add it to the BL.backbone.' + item + '_definitions window object with the extend function?');
        }
        return instance;
    }

    /**
     * Gets the specified model
     * @param model_data {string|*}
     * @returns {*}
     */
    BB.get_model = function(model_data){
        if(typeof model_data.name === 'undefined'){
            model_data = {name:model_data};
        }
        return get_item.call(this,'model',model_data);
    };

    /**
     * Gets the specified collection
     * @param collection_data {string|*}
     * @returns {*}
     */
    BB.get_collection = function(collection_data){
        if (typeof collection_data.name === 'undefined') {
            collection_data = {name: collection_data};
        }
        return get_item.call(this,'collection',collection_data);
    };

    /**
     * Get the specified view
     * @param view_data {string|*} The view's options to set
     * @param model_data {*} The model to bind to the view's model property
     * @param collection_data The collection to bind to the view's collection property
     * @returns {*}
     */
    BB.get_view = function(view_data,model_data,collection_data){
        if (typeof view_data.name === 'undefined') {
            view_data = {name: view_data};
        }
        var views = this.view_instances;
        var name = view_data.name;
        if(view_data.reset && views[name]){
            views[name].remove();
        }

        return views[name] ? views[name] : this.create_view(view_data,model_data,collection_data);
    };

    /**
     * Creates a view and sets it's options, including fetching and binding it's model/collection, also sets up removal of the view instance when removed
     * @param view_data {*} The view's options to set
     * @param model_data {*} The model to get and it's data
     * @param collection_data {*} The collection to get and it's data
     * @returns {*} The view with it's model and/or collection bound
     */
    BB.create_view = function(view_data,model_data,collection_data){
        var name = view_data.name;
        var view = this.view_definitions[name];
        if(view_data.options){
            for(var option in view_data.options){
                var value = view_data.options[option];
                view[option] = value;
            }
        }
        var model = model_data && !(model_data instanceof Backbone.Model) ? this.get_model(model_data) : model_data;
        var collection = collection_data && !(collection_data instanceof Backbone.Collection) ? this.get_collection(collection_data) : collection_data;
        view = view ? this.view_instances[name] = new view({model: model,collection: collection}) : '';
        this.view_instances[name].listenTo(this.view_instances[name],'remove',_.bind(function(){
            delete this.view_instances[name]
        },this));
        view.name = name;
        return view;
    };

    /**
     * @desc The main function of the helper library, it takes an object with view/collection/model as a key, and an object inside with the name of the type you want, and will return your requested item
     * @param object
     * @returns {{}}
     *
     * @example
     * {
     *	view: {
     *		name: 'view-name',
     *		reset: true, // Delete existing and create new instance
     *		options: {} // Set any number of view parameters like initialize, el, tagName, etc
     *	},
     *	model: {
     *		name: 'model-name',
     *		reset: true, // Delete existing and create new instance
     *		data: {}, // Set model initial data
     *		options: {} // Set any number of model parameters like attributeId, initialize, etc
     *	},
     *	collection: {
     *		name: 'collection-name',
     *		reset: true // Delete existing and create new instance
     *	}
     *}
     */
    BB.get = function(object){
        var item;
        if(object.view){
            item = this.get_view(object.view,object.model,object.collection);
        } else if(object.model){
            item = this.get_model(object.model);
        } else if(object.collection){
            item = this.get_collection(object.collection);
        }
        return item;
    };

    // Upgrading Backbone to fire a remove event when removing a view
    var _super = Backbone.View.prototype.remove;
    Backbone.View.prototype.remove = function(){
        this.trigger('remove');
        return _super.apply(this,arguments);
    };

    return BB;
}));<commit_msg>Fix stupid mistake - Was passing underscore as backbone and vice versa for RequireJS<commit_after>(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define(['backbone', 'underscore', 'exports'], function (Backbone, _, exports) {
            root.Backbone = factory(root, exports, Backbone, _);
        });
    } else if (typeof exports !== 'undefined') {
        var Backbone = require('backbone');
        var _ = require('underscore');
        factory(root, exports, Backbone, _);
    } else {
        root.BB = factory(root, {}, root.Backbone, root._);
    }
}(this, function (root, BB, Backbone, _) {
    /**
     * @author Enzo Martin <enzo.r.martin@gmail.com>
     * @module backbone/backbone-helper
     * @desc Backbone Helper functions to manage memory/instances smartly by keeping track of instanciated views/models/collections and making sure you don't have multiple instances of anything in the background by accident
     */

    BB = {
        view_definitions : {},
        model_definitions : {},
        collection_definitions : {},
        view_instances : {},
        model_instances : {},
        collection_instances : {}
    };

    // Private functions
    /**
     * Generic method for returning an object
     *
     * @param  {string} item model|collection
     * @param  {object} data
     * @return {instance}
     * @private
     */
    function get_item(item,data){
        var instances = this[item + '_instances'];
        var name = data.name;
        var defined = typeof instances[name] !== 'undefined';
        var instance;

        if(data.reset && defined){
            if(item == "model"){
                instances[name].destroy();
            } else if (item == "collection"){
                instances[name].reset();
            }
            delete instances[name];
            instance = create_item.call(this,item,data);
        } else {
            instance = defined ? instances[name] : create_item.call(this,item,data);
        }
        return instance;
    }

    /**
     * Generic method for creating an object
     *
     * @param  {string} item model|collection
     * @param  {object} data
     * @return {instance}
     * @private
     */
    function create_item(item,data){
        var definitions = this[item + '_definitions'];
        var name = data.name;
        var defined = typeof definitions[name] !== 'undefined';
        var instance;
        if(defined){
            instance = this[item + '_instances'][name] = new definitions[name];
            if(data.options){
                for(var option in data.options){
                    var value = data.options[option];
                    instance[option] = value;
                }
            }
            if(data.data){
                instance.set(data.data);
            }
            instance.name = name;
            if(item == "model"){
                this[item + '_instances'][name].listenTo(this[item + '_instances'][name],'destroy',_.bind(function(){
                    delete this[item + '_instances'][name];
                },this));
            }
        } else {
            console.trace();
            throw new Error('Definition not found: ' + name +'. Did you add it to the BL.backbone.' + item + '_definitions window object with the extend function?');
        }
        return instance;
    }

    /**
     * Gets the specified model
     * @param model_data {string|*}
     * @returns {*}
     */
    BB.get_model = function(model_data){
        if(typeof model_data.name === 'undefined'){
            model_data = {name:model_data};
        }
        return get_item.call(this,'model',model_data);
    };

    /**
     * Gets the specified collection
     * @param collection_data {string|*}
     * @returns {*}
     */
    BB.get_collection = function(collection_data){
        if (typeof collection_data.name === 'undefined') {
            collection_data = {name: collection_data};
        }
        return get_item.call(this,'collection',collection_data);
    };

    /**
     * Get the specified view
     * @param view_data {string|*} The view's options to set
     * @param model_data {*} The model to bind to the view's model property
     * @param collection_data The collection to bind to the view's collection property
     * @returns {*}
     */
    BB.get_view = function(view_data,model_data,collection_data){
        if (typeof view_data.name === 'undefined') {
            view_data = {name: view_data};
        }
        var views = this.view_instances;
        var name = view_data.name;
        if(view_data.reset && views[name]){
            views[name].remove();
        }

        return views[name] ? views[name] : this.create_view(view_data,model_data,collection_data);
    };

    /**
     * Creates a view and sets it's options, including fetching and binding it's model/collection, also sets up removal of the view instance when removed
     * @param view_data {*} The view's options to set
     * @param model_data {*} The model to get and it's data
     * @param collection_data {*} The collection to get and it's data
     * @returns {*} The view with it's model and/or collection bound
     */
    BB.create_view = function(view_data,model_data,collection_data){
        var name = view_data.name;
        var view = this.view_definitions[name];
        if(view_data.options){
            for(var option in view_data.options){
                var value = view_data.options[option];
                view[option] = value;
            }
        }
        var model = model_data && !(model_data instanceof Backbone.Model) ? this.get_model(model_data) : model_data;
        var collection = collection_data && !(collection_data instanceof Backbone.Collection) ? this.get_collection(collection_data) : collection_data;
        view = view ? this.view_instances[name] = new view({model: model,collection: collection}) : '';
        this.view_instances[name].listenTo(this.view_instances[name],'remove',_.bind(function(){
            delete this.view_instances[name]
        },this));
        view.name = name;
        return view;
    };

    /**
     * @desc The main function of the helper library, it takes an object with view/collection/model as a key, and an object inside with the name of the type you want, and will return your requested item
     * @param object
     * @returns {{}}
     *
     * @example
     * {
     *	view: {
     *		name: 'view-name',
     *		reset: true, // Delete existing and create new instance
     *		options: {} // Set any number of view parameters like initialize, el, tagName, etc
     *	},
     *	model: {
     *		name: 'model-name',
     *		reset: true, // Delete existing and create new instance
     *		data: {}, // Set model initial data
     *		options: {} // Set any number of model parameters like attributeId, initialize, etc
     *	},
     *	collection: {
     *		name: 'collection-name',
     *		reset: true // Delete existing and create new instance
     *	}
     *}
     */
    BB.get = function(object){
        var item;
        if(object.view){
            item = this.get_view(object.view,object.model,object.collection);
        } else if(object.model){
            item = this.get_model(object.model);
        } else if(object.collection){
            item = this.get_collection(object.collection);
        }
        return item;
    };

    // Upgrading Backbone to fire a remove event when removing a view
    var _super = Backbone.View.prototype.remove;
    Backbone.View.prototype.remove = function(){
        this.trigger('remove');
        return _super.apply(this,arguments);
    };

    return BB;
}));<|endoftext|>


<commit_before>
this.addEventListener('install', function (event) {
  event.waitUntil(
    caches.open(VERSION).then(function (cache) {
      let files = [
        '/',
        '/index.html',
        '/scripts/main.js',
        '/scripts/vendor.js',
        '/styles/main.css',
        '/styles/vendor.css',
        '/images/logo.svg',
        '/fonts/Material-Design-Icons.woff'
      ];

      groups.forEach(function (group) {
        group.features.forEach(function (feature) {
          files.push(`/${feature.id}.html`);
        });
      });

      return cache.addAll(files);
    })
  );
});

this.addEventListener('activate', function (event) {
  event.waitUntil(
    caches.keys().then(function (keyList) {
      return Promise.all(keyList.map(function (key) {
        if (key !== VERSION) {
          return caches.delete(key);
        }
      }));
    })
  );
});

this.addEventListener('fetch', function (event) {
  event.respondWith(
    caches.open(VERSION)
      .then(function (cache) {
        if (event.request.mode === 'navigate') {
  	    // network first, fallback to cache - to make sure html updates like new script & style revved urls are handled
  	      return fetch(event.request)
  		    .catch(function () {
      		  return cache.match(event.request);
    	    });
	    }

	    // else cache first, fallback to network
        return cache.match(event.request, {ignoreSearch: true})
          .then(function (response) {
            if (response) {
              return response;
            }

            return fetch(event.request)
              .then(function (fetchResponse) {
              	// cache already fetched caniuse data
                if (fetchResponse && fetchResponse.status === 200 && event.request.url.indexOf('https://raw.githubusercontent.com') === 0) {
                  cache.put(event.request, fetchResponse.clone());
                }
                return fetchResponse;
              });
          });
      })
  );
});
<commit_msg>perf+sw: Cache more stuff in Service Worker<commit_after>
this.addEventListener('install', function (event) {
  event.waitUntil(
    caches.open(VERSION).then(function (cache) {
      let files = [
        '/',
        '/index.html',
        '/scripts/main.js',
        '/scripts/vendor.js',
        '/styles/main.css',
        '/styles/layout.css',
        '/styles/vendor.css',
        '/images/logo.svg',
        '/fonts/Material-Design-Icons.woff',
        'https://fonts.gstatic.com/s/lato/v13/MDadn8DQ_3oT6kvnUq_2r_esZW2xOQ-xsNqO47m55DA.woff2'
      ];

      groups.forEach(function (group) {
        group.features.forEach(function (feature) {
          files.push(`/${feature.id}.html`);
        });
      });

      return cache.addAll(files);
    })
  );
});

this.addEventListener('activate', function (event) {
  event.waitUntil(
    caches.keys().then(function (keyList) {
      return Promise.all(keyList.map(function (key) {
        if (key !== VERSION) {
          return caches.delete(key);
        }
      }));
    })
  );
});

this.addEventListener('fetch', function (event) {
  event.respondWith(
    caches.open(VERSION)
      .then(function (cache) {
        if (event.request.mode === 'navigate') {
  	    // network first, fallback to cache - to make sure html updates like new script & style revved urls are handled
  	      return fetch(event.request)
  		    .catch(function () {
      		  return cache.match(event.request);
    	    });
	    }

	    // else cache first, fallback to network
        return cache.match(event.request, {ignoreSearch: true})
          .then(function (response) {
            if (response) {
              return response;
            }

            return fetch(event.request)
              .then(function (fetchResponse) {
              	// cache already fetched caniuse data
                if (fetchResponse && fetchResponse.status === 200 && event.request.url.indexOf('https://raw.githubusercontent.com') === 0) {
                  cache.put(event.request, fetchResponse.clone());
                }
                return fetchResponse;
              });
          });
      })
  );
});
<|endoftext|>


<commit_before>// This is include-able both in a browser environment and in a v8/node env,
// so it needs to figure out which situation it is in. If it's on the server,
// put everything in exports and behave like a module. If it's on the client,
// fake it and expect the client to understand how to deal with things.
var _ = require('underscore')._,
	crypto = require('crypto'),
    Backbone = require('backbone');

exports.Event = Backbone.Model.extend({
	idRoot: "event",

	urlRoot: "event",

	defaults: function() {
		// also need:
		// 	1. Some sort of image.
		return {
			title: "My Great Event",
			organizer: "MIT Media Lab",
			description: "This is my description about this great event. It has wonderful sessions in it.",
			start: new Date().getTime(),
			end: new Date().getTime()+60*60*2*1000,
			connectedUsers: new exports.UserList(),
			sessions: new exports.SessionList(this)
		}
	},

	isLive: function() {
		var curTime = new Date().getTime();
		return curTime > this.get("start") && curTime < this.get("end");
	},

	userConnected: function(user) {
		this.get("connectedUsers").add(user);
	},

	numUsersConnected: function() {
		return this.get("connectedUsers").length;
	},

	toJSON: function() {
		var attrs = _.clone(this.attributes);
		delete attrs["connectedUsers"];

		// for now just delete sessions; they'll save separately and will know their
		// event by id + url.
		delete attrs["sessions"];

		return attrs;
	},

	addSession: function(session) {
		this.get("sessions").add(session);
	},

	getStartTimeFormatted: function() {
		var date = new Date(this.get("start"));
		return date.toLocaleDateString() + " " + date.toLocaleTimeString();
	}
});

exports.EventList = Backbone.Collection.extend({
	model:exports.Event
})

exports.Session = Backbone.Model.extend({
	idRoot: "session",

	defaults: function() {
		return {
			title: "Great Session",
			description: "This session is really wonderful.",
			attendee_ids: []
		}
	}
});

exports.SessionList = Backbone.Collection.extend({
	model:exports.Session,

	initialize: function(event) {
		this.event = event;
	},

	url: function() {
		return this.event.url() + "/sessions";
	}
});

exports.USER_KEY_SALT = "SET ME EXTERNALLY";

exports.User = Backbone.Model.extend({
	idRoot: "user",
	urlRoot: "user",

	// This method generates time invariant key that gets embedded in all pages
	// and can be used on the sockjs channel to authenticate a sock connection
	// as belonging to this user. It is simply the id of the user plus some salt.
	// The user can then present this key plus the userid they wish to authenticate
	// as, and the server can verify that it matches the key it would have identified
	// using that salt.
	getSockKey: function() {
		if(_.isUndefined(this.get("sock-key"))) {
			var shasum = crypto.createHash('sha256');
			shasum.update(this.get("id"));
			shasum.update(exports.USER_KEY_SALT);
			this.set("sock-key", shasum.digest('hex'));
		}

		return this.get("sock-key");
	},

	validateSockKey: function(key) {
		return key == this.getSockKey();
	},

	isConnected: function() {
		return !_.isUndefined(this.get("sock")) && !_.isNull(this.get("sock"));
	},

	toJSON: function() {
		var attrs = _.clone(this.attributes);
		delete attrs["sock-key"];
		delete attrs["sock"];
		return attrs;
	}
});

exports.UserList = Backbone.Collection.extend({
	model:exports.User
});

<commit_msg>Started padding event urls, so they have a fixed width to make for easy redis key matching in the loaders.<commit_after>// This is include-able both in a browser environment and in a v8/node env,
// so it needs to figure out which situation it is in. If it's on the server,
// put everything in exports and behave like a module. If it's on the client,
// fake it and expect the client to understand how to deal with things.
var _ = require('underscore')._,
	crypto = require('crypto'),
    Backbone = require('backbone');

exports.Event = Backbone.Model.extend({
	idRoot: "event",

	urlRoot: "event",

	defaults: function() {
		// also need:
		// 	1. Some sort of image.
		return {
			title: "My Great Event",
			organizer: "MIT Media Lab",
			description: "This is my description about this great event. It has wonderful sessions in it.",
			start: new Date().getTime(),
			end: new Date().getTime()+60*60*2*1000,
			connectedUsers: new exports.UserList(),
			sessions: new exports.SessionList(this)
		}
	},

	isLive: function() {
		var curTime = new Date().getTime();
		return curTime > this.get("start") && curTime < this.get("end");
	},

	userConnected: function(user) {
		this.get("connectedUsers").add(user);
	},

	numUsersConnected: function() {
		return this.get("connectedUsers").length;
	},

	toJSON: function() {
		var attrs = _.clone(this.attributes);
		delete attrs["connectedUsers"];

		// for now just delete sessions; they'll save separately and will know their
		// event by id + url.
		delete attrs["sessions"];

		return attrs;
	},

	addSession: function(session) {
		this.get("sessions").add(session);
	},

	getStartTimeFormatted: function() {
		var date = new Date(this.get("start"));
		return date.toLocaleDateString() + " " + date.toLocaleTimeString();
	},

	url: function() {
		// okay this is sort of stupid, but we want to have a fixed width
		// url because that makes it easier to match events from redis with
		// the loader. We want to use ??? selectors instead of *, which
		// matches /event/id/session/id as well as /event/id
		return this.urlRoot + "/" + pad(this.id, 5);
	}
});

exports.EventList = Backbone.Collection.extend({
	model:exports.Event
})

exports.Session = Backbone.Model.extend({
	idRoot: "session",

	defaults: function() {
		return {
			title: "Great Session",
			description: "This session is really wonderful.",
			attendee_ids: []
		}
	}
});

exports.SessionList = Backbone.Collection.extend({
	model:exports.Session,

	initialize: function(event) {
		this.event = event;
	},

	url: function() {
		return this.event.url() + "/sessions";
	}
});

exports.USER_KEY_SALT = "SET ME EXTERNALLY";

exports.User = Backbone.Model.extend({
	idRoot: "user",
	urlRoot: "user",

	// This method generates time invariant key that gets embedded in all pages
	// and can be used on the sockjs channel to authenticate a sock connection
	// as belonging to this user. It is simply the id of the user plus some salt.
	// The user can then present this key plus the userid they wish to authenticate
	// as, and the server can verify that it matches the key it would have identified
	// using that salt.
	getSockKey: function() {
		if(_.isUndefined(this.get("sock-key"))) {
			var shasum = crypto.createHash('sha256');
			shasum.update(this.get("id"));
			shasum.update(exports.USER_KEY_SALT);
			this.set("sock-key", shasum.digest('hex'));
		}

		return this.get("sock-key");
	},

	validateSockKey: function(key) {
		return key == this.getSockKey();
	},

	isConnected: function() {
		return !_.isUndefined(this.get("sock")) && !_.isNull(this.get("sock"));
	},

	toJSON: function() {
		var attrs = _.clone(this.attributes);
		delete attrs["sock-key"];
		delete attrs["sock"];
		return attrs;
	}
});

exports.UserList = Backbone.Collection.extend({
	model:exports.User
});

function pad(n, width, z) {
  z = z || '0';
  n = n + '';
  return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
}

<|endoftext|>


<commit_before>const env = process.env;
const path = require('path');
const secrets = require('../../secrets');
const keyPath = path.join(__dirname, '../../google-key.json');

const mongoHost = env.MONGO_HOST || 'mongo';
const firebaseEnv = env.FIREBASE_ENV || 'local';
const clientEnv = env.CLIENT_ENV || 'dev';

module.exports = {

  server : {
    port : env.SERVER_PORT || '3000',
    url : env.SERVER_URL || 'http://localhost:3000',
    label : env.SERVER_LABEL || 'EcoSML', // used for auth redirect
    assets : (clientEnv === 'prod') ? 'dist' : 'public',
    loglevel : env.SERVER_LOG_LEVEL || 'debug',
    clientEnv,

    jwt : {
      secret : env.JWT_SECRET || secrets.ecosml.secret
    },

    auth : {
      redirect : env.AUTH_REDIRECT || 'http://localhost:5000/user/remotelogin'
    },

    session : {
      maxAge : (86400 * 365), // ms - year
      secret : env.SESSION_SECRET || 'not set'
    },

    // place all SPA base route names here
    appRoutes : ['package', 'search', 'edit',
    'create', 'account', 'about']
  },

  ecosis : {
    host : env.ECOSIS_HOST || 'http://localhost:5000'
  },

  redis : {
    host : 'redis',
    prefixes : {
      org : 'org',
      auth : 'auth',
      admin : 'admin'
    }
  },

  mongodb : {
    host : mongoHost,
    url : `mongodb://${mongoHost}:27017/ecosml`,
    collections : {
      package : 'package',
      githubTeam : 'github-team',
      stats : 'stats'
    },
    filters : ['keywords', 'theme', 'family', 'specific', 'language'],
    indexes : require('./mongo-indexes')
  },

  // control which attributes are allowed to post
  schemaFilter : {
    // required attributes for creating package
    REQUIRED_CREATE : ['name', 'description', 'owner', 'organization'],
    // attributes that are allowed to be updated
    UPDATE_ATTRIBUTES : ['name', 'description', 'overview', 'theme', 'organization',
      'family', 'specific', 'keywords']
  },

  github : {
    access : secrets.github,
    org : env.GITHUB_ORG || 'ecosml-dev',
    fsRoot : env.GITHUB_FS_ROOT || path.resolve(__dirname, '..', '..', 'gitdata'),
    default_license : 'mit'
  },

  travisCi : secrets.travisCi,

  git : {
    name : 'EcoSML Admin',
    email : 'admin@ecosml.org'
  },

  google : {
    key : secrets.google,
    keyPath : keyPath
  },

  firebase : {
    env : firebaseEnv,
    key : secrets.google,
    collections : {
      githubCommits : 'github-commit-events-'+firebaseEnv,
      githubTeams : 'github-team-events-'+firebaseEnv,
      travis : 'travis-events-'+firebaseEnv,
      ecosisUsers : 'ecosis-users-'+firebaseEnv,
      ecosisOrgs : 'ecosis-org-events-'+firebaseEnv
    }
  },

  cloudFunctions : {
    envs : {
      dev : 'Dev',
      prod : 'Prod',
      local : 'Local'
    },
    baseUrls : {
      githubWebhook : 'https://us-central1-ecosis-prod.cloudfunctions.net/githubWebhook',
      travisBuild : 'https://us-central1-ecosis-prod.cloudfunctions.net/travisBuild',
      ecosisSync : 'https://us-central1-ecosis-prod.cloudfunctions.net/ecosisSync'
    }
  }

}<commit_msg>quick fix<commit_after>const env = process.env;
const path = require('path');
const secrets = require('../../secrets');
const keyPath = path.join(__dirname, '../../google-key.json');

const mongoHost = env.MONGO_HOST || 'mongo';
const firebaseEnv = env.FIREBASE_ENV || 'local';
const clientEnv = env.CLIENT_ENV || 'dev';

module.exports = {

  server : {
    port : env.SERVER_PORT || '3000',
    url : env.SERVER_URL || 'http://localhost:3000',
    label : env.SERVER_LABEL || 'EcoSML', // used for auth redirect
    // assets : (clientEnv === 'prod') ? 'dist' : 'public',
    assets : env.SERVER_ASSET_DIR || 'public',
    loglevel : env.SERVER_LOG_LEVEL || 'debug',
    clientEnv,

    jwt : {
      secret : env.JWT_SECRET || secrets.ecosml.secret
    },

    auth : {
      redirect : env.AUTH_REDIRECT || 'http://localhost:5000/user/remotelogin'
    },

    session : {
      maxAge : (86400 * 365), // ms - year
      secret : env.SESSION_SECRET || 'not set'
    },

    // place all SPA base route names here
    appRoutes : ['package', 'search', 'edit',
    'create', 'account', 'about']
  },

  ecosis : {
    host : env.ECOSIS_HOST || 'http://localhost:5000'
  },

  redis : {
    host : 'redis',
    prefixes : {
      org : 'org',
      auth : 'auth',
      admin : 'admin'
    }
  },

  mongodb : {
    host : mongoHost,
    url : `mongodb://${mongoHost}:27017/ecosml`,
    collections : {
      package : 'package',
      githubTeam : 'github-team',
      stats : 'stats'
    },
    filters : ['keywords', 'theme', 'family', 'specific', 'language'],
    indexes : require('./mongo-indexes')
  },

  // control which attributes are allowed to post
  schemaFilter : {
    // required attributes for creating package
    REQUIRED_CREATE : ['name', 'description', 'owner', 'organization'],
    // attributes that are allowed to be updated
    UPDATE_ATTRIBUTES : ['name', 'description', 'overview', 'theme', 'organization',
      'family', 'specific', 'keywords']
  },

  github : {
    access : secrets.github,
    org : env.GITHUB_ORG || 'ecosml-dev',
    fsRoot : env.GITHUB_FS_ROOT || path.resolve(__dirname, '..', '..', 'gitdata'),
    default_license : 'mit'
  },

  travisCi : secrets.travisCi,

  git : {
    name : 'EcoSML Admin',
    email : 'admin@ecosml.org'
  },

  google : {
    key : secrets.google,
    keyPath : keyPath
  },

  firebase : {
    env : firebaseEnv,
    key : secrets.google,
    collections : {
      githubCommits : 'github-commit-events-'+firebaseEnv,
      githubTeams : 'github-team-events-'+firebaseEnv,
      travis : 'travis-events-'+firebaseEnv,
      ecosisUsers : 'ecosis-users-'+firebaseEnv,
      ecosisOrgs : 'ecosis-org-events-'+firebaseEnv
    }
  },

  cloudFunctions : {
    envs : {
      dev : 'Dev',
      prod : 'Prod',
      local : 'Local'
    },
    baseUrls : {
      githubWebhook : 'https://us-central1-ecosis-prod.cloudfunctions.net/githubWebhook',
      travisBuild : 'https://us-central1-ecosis-prod.cloudfunctions.net/travisBuild',
      ecosisSync : 'https://us-central1-ecosis-prod.cloudfunctions.net/ecosisSync'
    }
  }

}<|endoftext|>


<commit_before>/**
 * Backbone-Nested 0.1.0 - An extension of Backbone.js that keeps track of nested attributes
 *
 * https://github.com/afeld/backbone-nested
 *
 * Copyright (c) 2011-2012 Aidan Feldman
 * MIT Licensed (LICENSE)
 */
Backbone.NestedModel = Backbone.Model.extend({

  constructor: function(attrs, opts){
    Backbone.Model.prototype.constructor.apply( this, arguments );
  },

  get: function(attrStrOrPath){
    var attrPath = Backbone.NestedModel.attrPath(attrStrOrPath),
      childAttr = attrPath[0],
      result = Backbone.Model.prototype.get.call(this, childAttr);

    // walk through the child attributes
    for (var i = 1; i < attrPath.length; i++){
      childAttr = attrPath[i];
      result = result[childAttr];
    }

    // check if the result is an Object, Array, etc.
    if (_.isObject(result) && window.console){
      window.console.log("Backbone-Nested syntax is preferred for accesing values of attribute '" + attrStrOrPath + "'.");
    }
    // else it's a leaf

    return result;
  },

  has: function(attr){
    // for some reason this is not how Backbone.Model is implemented - it accesses the attributes object directly
    var result = this.get(attr);
    return !(result === null || _.isUndefined(result));
  },

  set: function(attrs, opts){
    opts || (opts = {});
    var newAttrs = _.deepClone(this.attributes);

    for (var attrStr in attrs){
      var attrPath = Backbone.NestedModel.attrPath(attrStr),
        attrObj = Backbone.NestedModel.createAttrObj(attrPath, attrs[attrStr]);

      this.mergeAttrs(attrObj, newAttrs);
    }

    return Backbone.Model.prototype.set.call(this, newAttrs, opts);
  },

  unset: function(attrStr, opts){
    var attrPath = Backbone.NestedModel.attrPath(attrStr);
    if (attrPath.length > 1){
      throw "unset() not yet implemented for nested attributes";
      // see https://github.com/afeld/backbone-nested/issues/1
    }

    return Backbone.Model.prototype.unset.apply(this, arguments);
  },

  toJSON: function(){
    var json = Backbone.Model.prototype.toJSON();
    return _.deepClone(json);
  },

  // private

  mergeAttrs: function(source, dest, stack){
    stack || (stack = []);

    var self = this,
      attrStr;

    _.each(source, function(sourceVal, prop){
      var destVal = dest[prop];

      var newStack = stack.concat([prop]);
      if (prop in dest && _.isObject(sourceVal) && _.isObject(destVal)){
        self.mergeAttrs(sourceVal, destVal, newStack);
      } else {
        dest[prop] = sourceVal;
      }

      attrStr = Backbone.NestedModel.createAttrStr(newStack);
      self.trigger('change:' + attrStr, self, dest[prop]); // TODO don't trigger if silent
    });

    attrStr = Backbone.NestedModel.createAttrStr(stack);
    return dest;
  }

}, {
  // class methods

  attrPath: function(attrStrOrPath){
    var path;

    if (_.isString(attrStrOrPath)){
      // TODO this parsing can probably be more efficient
      path = attrStrOrPath.match(/[^\.\[\]]+/g);
      path = _.map(path, function(val){
        // convert array accessors to numbers
        return val.match(/^\d+$/) ? parseInt(val) : val;
      });
    } else {
      path = attrStrOrPath;
    }

    return path;
  },

  createAttrObj: function(attrStrOrPath, val){
    var attrPath = this.attrPath(attrStrOrPath),
      newVal;

    switch (attrPath.length){
      case 0:
        throw "no valid attributes: '" + attrStrOrPath + "'";
        break;

      case 1: // leaf
        newVal = val;
        break;

      default: // nested attributes
        var otherAttrs = _.rest(attrPath);
        newVal = this.createAttrObj(otherAttrs, val);
        break;
    }

    var childAttr = attrPath[0],
      result = _.isNumber(childAttr) ? [] : {};

    result[childAttr] = newVal;
    return result;
  },

  createAttrStr: function(attrPath){
    var attrStr = attrPath[0];
    _.each(_.rest(attrPath), function(attr){
      attrStr += _.isNumber(attr) ? ('[' + attr + ']') : ('.' + attr);
    });

    return attrStr;
  }

});

_.mixin({

  deepMerge: function(dest){
    _.each(_.rest(arguments), function(source){
      var sourceVal, destVal;
      for (var prop in source){
        sourceVal = source[prop];
        destVal = dest[prop];
        if (prop in dest && _.isObject(sourceVal) && _.isObject(destVal)){
          _.deepMerge(destVal, sourceVal);
        } else {
          dest[prop] = sourceVal;
        }
      }
    });
    return dest;
  },

  deepClone: function(obj){
    var result = _.clone(obj); // shallow clone
    if (_.isObject(obj)){
      _.each(obj, function(val, key){
        result[key] = _.deepClone(val);
      });
    }
    return result;
  }

});
<commit_msg>let Backbone.Model handle top-level change events<commit_after>/**
 * Backbone-Nested 0.1.0 - An extension of Backbone.js that keeps track of nested attributes
 *
 * https://github.com/afeld/backbone-nested
 *
 * Copyright (c) 2011-2012 Aidan Feldman
 * MIT Licensed (LICENSE)
 */
Backbone.NestedModel = Backbone.Model.extend({

  constructor: function(attrs, opts){
    Backbone.Model.prototype.constructor.apply( this, arguments );
  },

  get: function(attrStrOrPath){
    var attrPath = Backbone.NestedModel.attrPath(attrStrOrPath),
      childAttr = attrPath[0],
      result = Backbone.Model.prototype.get.call(this, childAttr);

    // walk through the child attributes
    for (var i = 1; i < attrPath.length; i++){
      childAttr = attrPath[i];
      result = result[childAttr];
    }

    // check if the result is an Object, Array, etc.
    if (_.isObject(result) && window.console){
      window.console.log("Backbone-Nested syntax is preferred for accesing values of attribute '" + attrStrOrPath + "'.");
    }
    // else it's a leaf

    return result;
  },

  has: function(attr){
    // for some reason this is not how Backbone.Model is implemented - it accesses the attributes object directly
    var result = this.get(attr);
    return !(result === null || _.isUndefined(result));
  },

  set: function(attrs, opts){
    opts || (opts = {});
    var newAttrs = _.deepClone(this.attributes);

    for (var attrStr in attrs){
      var attrPath = Backbone.NestedModel.attrPath(attrStr),
        attrObj = Backbone.NestedModel.createAttrObj(attrPath, attrs[attrStr]);

      this.mergeAttrs(attrObj, newAttrs);
    }

    return Backbone.Model.prototype.set.call(this, newAttrs, opts);
  },

  unset: function(attrStr, opts){
    var attrPath = Backbone.NestedModel.attrPath(attrStr);
    if (attrPath.length > 1){
      throw "unset() not yet implemented for nested attributes";
      // see https://github.com/afeld/backbone-nested/issues/1
    }

    return Backbone.Model.prototype.unset.apply(this, arguments);
  },

  toJSON: function(){
    var json = Backbone.Model.prototype.toJSON();
    return _.deepClone(json);
  },

  // private

  mergeAttrs: function(source, dest, stack){
    stack || (stack = []);

    var self = this,
      attrStr;

    _.each(source, function(sourceVal, prop){
      var destVal = dest[prop];

      var newStack = stack.concat([prop]);
      if (prop in dest && _.isObject(sourceVal) && _.isObject(destVal)){
        self.mergeAttrs(sourceVal, destVal, newStack);
      } else {
        dest[prop] = sourceVal;
      }

      // let the superclass handle change events for top-level attributes
      if (newStack.length > 1){
        attrStr = Backbone.NestedModel.createAttrStr(newStack);
        self.trigger('change:' + attrStr, self, dest[prop]); // TODO don't trigger if silent
      }
    });
  }

}, {
  // class methods

  attrPath: function(attrStrOrPath){
    var path;

    if (_.isString(attrStrOrPath)){
      // TODO this parsing can probably be more efficient
      path = attrStrOrPath.match(/[^\.\[\]]+/g);
      path = _.map(path, function(val){
        // convert array accessors to numbers
        return val.match(/^\d+$/) ? parseInt(val) : val;
      });
    } else {
      path = attrStrOrPath;
    }

    return path;
  },

  createAttrObj: function(attrStrOrPath, val){
    var attrPath = this.attrPath(attrStrOrPath),
      newVal;

    switch (attrPath.length){
      case 0:
        throw "no valid attributes: '" + attrStrOrPath + "'";
        break;

      case 1: // leaf
        newVal = val;
        break;

      default: // nested attributes
        var otherAttrs = _.rest(attrPath);
        newVal = this.createAttrObj(otherAttrs, val);
        break;
    }

    var childAttr = attrPath[0],
      result = _.isNumber(childAttr) ? [] : {};

    result[childAttr] = newVal;
    return result;
  },

  createAttrStr: function(attrPath){
    var attrStr = attrPath[0];
    _.each(_.rest(attrPath), function(attr){
      attrStr += _.isNumber(attr) ? ('[' + attr + ']') : ('.' + attr);
    });

    return attrStr;
  }

});

_.mixin({

  deepMerge: function(dest){
    _.each(_.rest(arguments), function(source){
      var sourceVal, destVal;
      for (var prop in source){
        sourceVal = source[prop];
        destVal = dest[prop];
        if (prop in dest && _.isObject(sourceVal) && _.isObject(destVal)){
          _.deepMerge(destVal, sourceVal);
        } else {
          dest[prop] = sourceVal;
        }
      }
    });
    return dest;
  },

  deepClone: function(obj){
    var result = _.clone(obj); // shallow clone
    if (_.isObject(obj)){
      _.each(obj, function(val, key){
        result[key] = _.deepClone(val);
      });
    }
    return result;
  }

});
<|endoftext|>


<commit_before>require('../css/extra.css');
require('../css/slide-preview.css');
require('../css/site.css');

require('highlight.js/styles/github-gist.css');

/* other vendors plugin */
var S = require('string');

var extra = require('./extra');
var md = extra.md;
var finishView = extra.finishView;
var autoLinkify = extra.autoLinkify;
var deduplicatedHeaderId = extra.deduplicatedHeaderId;
var renderTOC = extra.renderTOC;
var generateToc = extra.generateToc;
var smoothHashScroll = extra.smoothHashScroll;
var postProcess = extra.postProcess;
var updateLastChange = extra.updateLastChange;
var parseMeta = extra.parseMeta;
var preventXSS = require('./render').preventXSS;

var markdown = $("#doc.markdown-body");
var text = S(markdown.html()).unescapeHTML().s;
var lastMeta = md.meta;
md.meta = {};
var rendered = md.render(text);
if (md.meta.type && md.meta.type === 'slide') {
    var slideOptions = {
        separator: '^(\r\n?|\n)---(\r\n?|\n)$',
        verticalSeparator: '^(\r\n?|\n)----(\r\n?|\n)$'
    };
    var slides = RevealMarkdown.slidify(text, slideOptions);
    markdown.html(slides);
    RevealMarkdown.initialize();
    // prevent XSS
    markdown.html(preventXSS(markdown.html()));
    markdown.addClass('slides');
} else {
    if (lastMeta.type && lastMeta.type === 'slide') {
        refreshView();
        markdown.removeClass('slides');
    }
    // only render again when meta changed
    if (JSON.stringify(md.meta) != JSON.stringify(lastMeta)) {
        parseMeta(md, null, markdown, $('#ui-toc'), $('#ui-toc-affix'));
        rendered = md.render(text);
    }
    // prevent XSS
    rendered = preventXSS(rendered);
    var result = postProcess(rendered);
    markdown.html(result.html());
}
$(document.body).show();
finishView(markdown);
autoLinkify(markdown);
deduplicatedHeaderId(markdown);
renderTOC(markdown);
generateToc('ui-toc');
generateToc('ui-toc-affix');
smoothHashScroll();
createtime = lastchangeui.time.attr('data-createtime');
lastchangetime = lastchangeui.time.attr('data-updatetime');
updateLastChange();
var url = window.location.pathname;
$('.ui-edit').attr('href', url + '/edit');
var toc = $('.ui-toc');
var tocAffix = $('.ui-affix-toc');
var tocDropdown = $('.ui-toc-dropdown');
//toc
tocDropdown.click(function (e) {
    e.stopPropagation();
});

var enoughForAffixToc = true;

function generateScrollspy() {
    $(document.body).scrollspy({
        target: ''
    });
    $(document.body).scrollspy('refresh');
    if (enoughForAffixToc) {
        toc.hide();
        tocAffix.show();
    } else {
        tocAffix.hide();
        toc.show();
    }
    $(document.body).scroll();
}

function windowResize() {
    //toc right
    var paddingRight = parseFloat(markdown.css('padding-right'));
    var right = ($(window).width() - (markdown.offset().left + markdown.outerWidth() - paddingRight));
    toc.css('right', right + 'px');
    //affix toc left
    var newbool;
    var rightMargin = (markdown.parent().outerWidth() - markdown.outerWidth()) / 2;
    //for ipad or wider device
    if (rightMargin >= 133) {
        newbool = true;
        var affixLeftMargin = (tocAffix.outerWidth() - tocAffix.width()) / 2;
        var left = markdown.offset().left + markdown.outerWidth() - affixLeftMargin;
        tocAffix.css('left', left + 'px');
    } else {
        newbool = false;
    }
    if (newbool != enoughForAffixToc) {
        enoughForAffixToc = newbool;
        generateScrollspy();
    }
}
$(window).resize(function () {
    windowResize();
});
$(document).ready(function () {
    windowResize();
    generateScrollspy();
    //tooltip
    $('[data-toggle="tooltip"]').tooltip();
});

function scrollToTop() {
    $('body, html').stop(true, true).animate({
        scrollTop: 0
    }, 100, "linear");
}

function scrollToBottom() {
    $('body, html').stop(true, true).animate({
        scrollTop: $(document.body)[0].scrollHeight
    }, 100, "linear");
}

window.scrollToTop = scrollToTop;
window.scrollToBottom = scrollToBottom;

module.exports = {
  scrollToBottom: scrollToBottom,
  scrollToTop: scrollToTop
}
<commit_msg>Fix published note won't scroll to hash on load<commit_after>require('../css/extra.css');
require('../css/slide-preview.css');
require('../css/site.css');

require('highlight.js/styles/github-gist.css');

/* other vendors plugin */
var S = require('string');

var extra = require('./extra');
var md = extra.md;
var finishView = extra.finishView;
var autoLinkify = extra.autoLinkify;
var deduplicatedHeaderId = extra.deduplicatedHeaderId;
var renderTOC = extra.renderTOC;
var generateToc = extra.generateToc;
var smoothHashScroll = extra.smoothHashScroll;
var postProcess = extra.postProcess;
var updateLastChange = extra.updateLastChange;
var parseMeta = extra.parseMeta;
var scrollToHash = extra.scrollToHash;
var preventXSS = require('./render').preventXSS;

var markdown = $("#doc.markdown-body");
var text = S(markdown.html()).unescapeHTML().s;
var lastMeta = md.meta;
md.meta = {};
var rendered = md.render(text);
if (md.meta.type && md.meta.type === 'slide') {
    var slideOptions = {
        separator: '^(\r\n?|\n)---(\r\n?|\n)$',
        verticalSeparator: '^(\r\n?|\n)----(\r\n?|\n)$'
    };
    var slides = RevealMarkdown.slidify(text, slideOptions);
    markdown.html(slides);
    RevealMarkdown.initialize();
    // prevent XSS
    markdown.html(preventXSS(markdown.html()));
    markdown.addClass('slides');
} else {
    if (lastMeta.type && lastMeta.type === 'slide') {
        refreshView();
        markdown.removeClass('slides');
    }
    // only render again when meta changed
    if (JSON.stringify(md.meta) != JSON.stringify(lastMeta)) {
        parseMeta(md, null, markdown, $('#ui-toc'), $('#ui-toc-affix'));
        rendered = md.render(text);
    }
    // prevent XSS
    rendered = preventXSS(rendered);
    var result = postProcess(rendered);
    markdown.html(result.html());
}
$(document.body).show();
finishView(markdown);
autoLinkify(markdown);
deduplicatedHeaderId(markdown);
renderTOC(markdown);
generateToc('ui-toc');
generateToc('ui-toc-affix');
smoothHashScroll();
createtime = lastchangeui.time.attr('data-createtime');
lastchangetime = lastchangeui.time.attr('data-updatetime');
updateLastChange();
var url = window.location.pathname;
$('.ui-edit').attr('href', url + '/edit');
var toc = $('.ui-toc');
var tocAffix = $('.ui-affix-toc');
var tocDropdown = $('.ui-toc-dropdown');
//toc
tocDropdown.click(function (e) {
    e.stopPropagation();
});

var enoughForAffixToc = true;

function generateScrollspy() {
    $(document.body).scrollspy({
        target: ''
    });
    $(document.body).scrollspy('refresh');
    if (enoughForAffixToc) {
        toc.hide();
        tocAffix.show();
    } else {
        tocAffix.hide();
        toc.show();
    }
    $(document.body).scroll();
}

function windowResize() {
    //toc right
    var paddingRight = parseFloat(markdown.css('padding-right'));
    var right = ($(window).width() - (markdown.offset().left + markdown.outerWidth() - paddingRight));
    toc.css('right', right + 'px');
    //affix toc left
    var newbool;
    var rightMargin = (markdown.parent().outerWidth() - markdown.outerWidth()) / 2;
    //for ipad or wider device
    if (rightMargin >= 133) {
        newbool = true;
        var affixLeftMargin = (tocAffix.outerWidth() - tocAffix.width()) / 2;
        var left = markdown.offset().left + markdown.outerWidth() - affixLeftMargin;
        tocAffix.css('left', left + 'px');
    } else {
        newbool = false;
    }
    if (newbool != enoughForAffixToc) {
        enoughForAffixToc = newbool;
        generateScrollspy();
    }
}
$(window).resize(function () {
    windowResize();
});
$(document).ready(function () {
    windowResize();
    generateScrollspy();
    setTimeout(scrollToHash, 0);
    //tooltip
    $('[data-toggle="tooltip"]').tooltip();
});

function scrollToTop() {
    $('body, html').stop(true, true).animate({
        scrollTop: 0
    }, 100, "linear");
}

function scrollToBottom() {
    $('body, html').stop(true, true).animate({
        scrollTop: $(document.body)[0].scrollHeight
    }, 100, "linear");
}

window.scrollToTop = scrollToTop;
window.scrollToBottom = scrollToBottom;

module.exports = {
  scrollToBottom: scrollToBottom,
  scrollToTop: scrollToTop
}
<|endoftext|>