export class PlayerModelService {
	constructor($log, $http, $localStorage, $q, _, playersApi) {
		'ngInject';

		this.$log = $log;
		this.$http = $http;
		this.$localStorage = $localStorage;
		this.$q = $q;

		this._ = _;
		this.playersApi = playersApi;

		this.model = {
			player: null
		};

		this.numDrawCards = 7;
	}

	insert(data) {
		let pl = Object.assign({}, {
					isCurrent: true,
					cardsInHand: []
				}, data
			);

		this.$log.info('insert()', pl, this);

		this.model.player = pl;
		this.$localStorage.player = this.model.player;
	}

	discard(id) {
		let cards = this.model.player.cardsInHand,
			defer = this.$q.defer(),
			onSuccess = (res => {
				defer.resolve(res);
			}),
			onError = (err => {
				this.$log.error(err);
				defer.reject(err);
			});

		this.$log.info('discard()', id, cards, this);

		// Remove card from player
		this._.pull(cards, id);

		this.$log.info('playerModel:cards -> ', cards);

		this.playersApi
			.update({ cardsInHand: cards, totalCards: cards.length }, this.model.player.id)
			.then(onSuccess, onError);

		return defer.promise;
	}

	update(data) {
		Object.assign(this.model.player, data);
	}
}

export class PlayerModelService {
	constructor($log, $http, $localStorage, $q, _, playersApi) {
		'ngInject';

		this.$log = $log;
		this.$http = $http;
		this.$localStorage = $localStorage;
		this.$q = $q;

		this._ = _;
		this.playersApi = playersApi;

		this.model = {
			player: null
		};

		this.numDrawCards = 7;
	}

	insert(data) {
		let pl = Object.assign({}, {
					isCurrent: true,
					cardsInHand: []
				}, data
			);

		this.$log.info('insert()', pl, this);

		this.model.player = pl;
		this.$localStorage.player = this.model.player;
	}

	discard(id) {
		let cards = this.model.player.cardsInHand;

		this.$log.info('discard()', id, cards, this);

		// Remove card from player
		this._.pull(cards, id);

		let plData = {
			cardsInHand: cards,
			totalCards: cards.length
		};

		this.$log.info('playerModel:cards -> ', plData);

		return this.playersApi.update(plData, this.model.player.id);
	}

	update(data) {
		Object.assign(this.model.player, data);
	}
}

Return $http request from playerModel instead of deferred


(function() {
  'use strict';

  angular
    .module('semantic.ui.elements.checkbox', [])

    .directive('smCheckbox', smCheckbox);

  function smCheckbox() {
    return {
      restrict: 'E',
      require: '?ngModel',
      transclude: true,
      replace: true,
      scope: {
        ngDisabled: '='
      },
      template: '
' +
                  '<input type="checkbox" ng-disabled="ngDisabled" ng-checked="isChecked">' +
                  '' +
                '
',

      link: function(scope, element, attrs, ngModel) {

        element.bind('click', toggleFn);

        if (ngModel) {
          ngModel.$render = function() {
            scope.isChecked = ngModel.$viewValue;
          };
        }

        if (attrs.toggle !== void 0) {
          angular.element(element).addClass('toggle');
        }
        else if (attrs.slider !== void 0) {
          angular.element(element).addClass('slider');
        }

        if (attrs.ariaLabel === void 0) {
          element.attr('aria-label', element[0].textContent.trim());
        }

        function toggleFn() {

          if (scope.ngDisabled) { return; }

          scope.isChecked = !scope.isChecked;
          if (ngModel) {
            ngModel.$setViewValue(scope.isChecked);
          }
          scope.$apply();
        }

      }
    };

  }
})();

(function() {
  'use strict';

  angular
    .module('semantic.ui.elements.checkbox', [])

    .directive('smCheckbox', smCheckbox);

  function smCheckbox() {
    return {
      restrict: 'E',
      require: '?ngModel',
      transclude: true,
      replace: true,
      scope: {
        ngDisabled: '='
      },
      template: '
' +
                  '<input type="checkbox" ng-disabled="ngDisabled" ng-checked="isChecked">' +
                  '' +
                '
',

      link: function(scope, element, attrs, ngModel) {

        element.on('click', toggleFn);

        if (ngModel) {
          ngModel.$render = function() {
            scope.isChecked = ngModel.$viewValue;
          };
        }

        if (attrs.toggle !== void 0) {
          angular.element(element).addClass('toggle');
        }
        else if (attrs.slider !== void 0) {
          angular.element(element).addClass('slider');
        }

        if (attrs.ariaLabel === void 0) {
          element.attr('aria-label', element[0].textContent.trim());
        }

        function toggleFn() {

          if (scope.ngDisabled) { return; }

          scope.isChecked = !scope.isChecked;
          if (ngModel) {
            ngModel.$setViewValue(scope.isChecked);
          }
          scope.$apply();
        }

      }
    };

  }
})();

Change `bind('click', ...` to `on('click', ...`


import React, { useState } from 'react';
import { IconExternalLinkSmallOutline } from '@teamleader/ui-icons';

import Box from '../../box';
import Link from '../../link';

import theme from './theme.css';

const findLinkEntities = (contentBlock, callback, contentState) => {
  contentBlock.findEntityRanges((character) => {
    const entityKey = character.getEntity();
    return entityKey !== null && contentState.getEntity(entityKey).getType() === 'LINK';
  }, callback);
};

const LinkEntity = ({ entityKey, contentState, children }) => {
  const [showOpenLinkIcon, setShowOpenLinkIcon] = useState();
  const { url } = contentState.getEntity(entityKey).getData();

  const openLink = () => {
    window.open(url, 'blank');
  };

  const toggleShowOpenLinkIcon = () => {
    setShowOpenLinkIcon(!showOpenLinkIcon);
  };

  return (
    <Box display="inline-block" onMouseEnter={toggleShowOpenLinkIcon} onMouseLeave={toggleShowOpenLinkIcon}>
      <Link className={theme['link']} href="" inherit={false} onClick={(event) => event.preventDefault()}>
        {children}
      </Link>
      {showOpenLinkIcon && <IconExternalLinkSmallOutline onClick={openLink} className={theme['icon']} />}
    </Box>
  );
};

export default {
  strategy: findLinkEntities,
  component: LinkEntity,
};

import React, { useState } from 'react';
import { IconExternalLinkSmallOutline } from '@teamleader/ui-icons';

import Box from '../../box';
import Link from '../../link';

import theme from './theme.css';

const findLinkEntities = (contentBlock, callback, contentState) => {
  contentBlock.findEntityRanges((character) => {
    const entityKey = character.getEntity();
    return entityKey !== null && contentState.getEntity(entityKey).getType() === 'LINK';
  }, callback);
};

const LinkEntity = ({ entityKey, contentState, children }) => {
  const [showOpenLinkIcon, setShowOpenLinkIcon] = useState();
  const { url } = contentState.getEntity(entityKey).getData();

  const openLink = () => {
    let prefixedUrl = url;
    if (!url.includes('//')) {
      prefixedUrl = '//' + url;
    }
    window.open(prefixedUrl, '_blank');
  };

  const toggleShowOpenLinkIcon = () => {
    setShowOpenLinkIcon(!showOpenLinkIcon);
  };

  return (
    <Box display="inline-block" onMouseEnter={toggleShowOpenLinkIcon} onMouseLeave={toggleShowOpenLinkIcon}>
      <Link className={theme['link']} href="" inherit={false} onClick={(event) => event.preventDefault()}>
        {children}
      </Link>
      {showOpenLinkIcon && <IconExternalLinkSmallOutline onClick={openLink} className={theme['icon']} />}
    </Box>
  );
};

export default {
  strategy: findLinkEntities,
  component: LinkEntity,
};

Fix certain links not opening correctly


const add = (URL, originalURL, shortURL) => {
  const tempURL = new URL({originalURL, shortURL});
  tempURL.save((err, permURL) => {
    if(err) {
      console.err(err);
    } else {
      console.log('URL added: ', permURL);
    }
  });
};

const fetchShortURLs = (URL, callback) => {
  URL.find({}, (err, data) => {
    if (err) {
      console.err(err);
    } else {
      callback(data.map((el) => el.shortURL));
    }
  });
};

module.exports = { add, fetchShortURLs };

const add = (URL, originalURL, shortURL) => {
  const tempURL = new URL({originalURL, shortURL});
  tempURL.save((err, permURL) => {
    if(err) {
      console.err(err);
    } else {
      console.log('URL added: ', permURL);
    }
  });
};

const fetchShortURLs = (URL, callback) => {
  URL.find({}, (err, data) => {
    if (err) {
      console.err(err);
    } else {
      callback(data.map((el) => el.shortURL));
    }
  });
};

const findOriginalURL = (URL, shortURL, callback) => {
  URL.find({ shortURL }, (err, data) => {
    if (err) {
      console.err(err);
    } else {
      callback(data);
    }
  })
}

module.exports = { add, fetchShortURLs, findOriginalURL };

Implement database original url lookup


/**
 * Controller responsible for editing a specific frame in a specific game.
 */
var EditFrameController = function($state, $stateParams, MatchService, league, week, match, scoreSheet) {

    var controller = this;

    controller.scoreSheet = scoreSheet;
    controller.frameNumber = $stateParams.frameId;
    controller.gameNumber = $stateParams.gameId;

    controller.submit = function() {

        MatchService.cleanUpScoresheet(scoreSheet);

        match.one('scoresheet').customPUT(scoreSheet).then(function() {
            $state.go('^');
        });
    };

};

angular.module('bowling.entry.core')
    .controller('EditFrameController', ['$state', '$stateParams', 'MatchService', 'league', 'week', 'match', 'scoreSheet', EditFrameController])
    .config(['$stateProvider', function($stateProvider) {

        $stateProvider.state('bowling.league.week.match.game.frame', {
            url: '/:frameId/',
            templateUrl: 'partials/entry/leagues/matches/games/frames/frame.html',
            title: 'Frame Details',
            controller: 'EditFrameController',
            controllerAs: 'frameController'
        });

    }]);

/**
 * Controller responsible for editing a specific frame in a specific game.
 */
var EditFrameController = function($state, $stateParams, MatchService, league, week, match, scoreSheet) {

    var controller = this;

    controller.scoreSheet = scoreSheet;
    controller.frameNumber = $stateParams.frameId;
    controller.gameNumber = $stateParams.gameId;

    controller.submit = function() {

        MatchService.cleanUpScoresheet(scoreSheet);

        match.one('scoresheet').customPUT(scoreSheet).then(function() {
            $state.go('^.detail');
        });
    };

};

angular.module('bowling.entry.core')
    .controller('EditFrameController', ['$state', '$stateParams', 'MatchService', 'league', 'week', 'match', 'scoreSheet', EditFrameController])
    .config(['$stateProvider', function($stateProvider) {

        $stateProvider.state('bowling.league.week.match.game.frame', {
            url: '/:frameId/',
            templateUrl: 'partials/entry/leagues/matches/games/frames/frame.html',
            title: 'Frame Details',
            controller: 'EditFrameController',
            controllerAs: 'frameController'
        });

    }]);

Fix state editFramecontroller goes to after save is successful.


/**
 * Adapter for handling of parameter together with its default value
 */
export default class Parameter {
  constructor(fn, param) {
    this.fn = fn;
    this.param = param;
    this.index = fn.params.indexOf(param);
  }

  name() {
    return this.param.name;
  }

  isIdentifier() {
    return this.param.type === 'Identifier';
  }

  setDefault(value) {
    this.fn.defaults = this.fn.defaults || [];
    this.fn.defaults[this.index] = value;
  }

  remainingParams() {
    return this.fn.params.slice(this.index);
  }
}

/**
 * Adapter for handling of parameter together with its default value
 */
export default class Parameter {
  constructor(fn, param) {
    this.fn = fn;
    this.param = param;
    this.index = fn.params.indexOf(param);
  }

  name() {
    return this.param.name;
  }

  isIdentifier() {
    return this.param.type === 'Identifier';
  }

  setDefault(value) {
    this.fn.params[this.index] = {
      type: 'AssignmentPattern',
      left: this.param,
      right: value,
    };
  }

  remainingParams() {
    return this.fn.params.slice(this.index);
  }
}

Create AssignmentPattern instead of using .defaults field


$(".spinner").addClass("hidden");
$(".status").removeClass("hidden");

$.post("https://209.235.214.90/setistatus/checkstatus.asp", function(data) {
  console.log(data);
});

var params = location.hash.slice(1).split("/"),
    url = "http://localhost:5000",
    email = params[0],
    ids = params[1].split(",");

function showError() {
  $(".alert").html(
    "Sorry. There was an error. Are your order numbers correct?"
  ).removeClass("hidden");
};

function showStatus(data) {
  $.each(data.orders, function(_, order) {
    $(".js-status-rows").append(
      [ '
',
          '
',
            '
',
              '',
                order.number,
              '',
              order.progress + " " + order.message,
            '
',
          '
',
          '
',
            order.description,
          '
',
        '
'
      ].join("")
    );
  });
};

$.get(
  [url, email, ids.join(",")].join("/")
).done(function(data) {
  $(".status").removeClass("hidden");
  showStatus(data);
}).fail(function() {
  showError();
}).always(function() {
  $(".spinner").addClass("hidden");
});

Write some horrible js to show status of orders


$(function(){
  $('.more-or-less').each(function(i, el) {
  console.log(el);
    $(el).find("li a").click(function() {
    $(el).find("button").html($(this).text() + ' ');
    $(el).find("input.more-or-less-type").val($(this).text());
    });
  });

  $(".formify").each(function (_, element) {
    var value = $(element).find("[data-selected='true']").first().addClass("active").data("value");
    $(element).append('<input class="formify-value" type="hidden" name="' + $(this).data('name') + '" value="' + value + '" >');
  });

  $(".formify .btn").click(function () {
    $(this).addClass("active");
    var val = $(this).data("value");
    var input = $(this).parent().find(".formify-value")
    console.log(val)
    $(input).val(val)
  });

  $(".incident").click(function() {
    $(this).toggleClass("selected");
  });

  $(".select-all").click(function(event) {
    $(event.target).parent().parent().find("input").prop("checked", true);
    event.preventDefault();
  });

  $(".clear-all").click(function(event) {
    $(event.target).parent().parent().find("input").prop("checked", false);
    event.preventDefault();
  });
})

$(function(){
  $('.more-or-less').each(function(i, el) {
  console.log(el);
    $(el).find("li a").click(function() {
    $(el).find("button").html($(this).text() + ' ');
    $(el).find("input.more-or-less-type").val($(this).text());
    });
  });

  $(".formify").each(function (_, element) {
    var value = $(element).find("[data-selected='true']").first().addClass("active").data("value");
    $(element).append('<input class="formify-value" type="hidden" name="filter[' + $(this).data('name') + ']" value="' + value + '" >');
  });

  $(".formify .btn").click(function () {
    $(this).addClass("active");
    var val = $(this).data("value");
    var input = $(this).parent().find(".formify-value")
    console.log(val)
    $(input).val(val)
  });

  $(".incident").click(function() {
    $(this).toggleClass("selected");
  });

  $(".select-all").click(function(event) {
    $(event.target).parent().parent().find("input").prop("checked", true);
    event.preventDefault();
  });

  $(".clear-all").click(function(event) {
    $(event.target).parent().parent().find("input").prop("checked", false);
    event.preventDefault();
  });
})

Bring formified inputs into the world of the filter.


import React, { Pureomponent } from "react";
import PropTypes from "prop-types";
import styled from "styled-components";
import { NonIdealState } from "@blueprintjs/core";
import Entries from "../../shared/components/Entries.js";

const BUTTERCUP_LOGO = require("../../../resources/buttercup-standalone.png");

const Container = styled.div`
    flex: 1;
`;

const EntryShape = PropTypes.shape({
    id: PropTypes.string.isRequired,
    title: PropTypes.string.isRequired,
    url: PropTypes.string
});

class SearchResults extends Pureomponent {
    static propTypes = {
        entries: PropTypes.arrayOf(EntryShape),
        sourcesUnlocked: PropTypes.number.isRequired,
        onSelectEntry: PropTypes.func.isRequired
    };

    render() {
        return (
            <Container>
                <Choose>
                    <When condition={this.props.entries.length > 0}>
                        <Entries
                            autoLoginEnabled={false}
                            entries={this.props.entries}
                            onSelectEntry={this.props.onSelectEntry}
                            sourcesUnlocked={this.props.sourcesUnlocked}
                        />
                    </When>
                    <Otherwise>
                        <NonIdealState
                            title="Welcome to Buttercup"
                            description="Use the search bar to find entries in your unlocked vaults."
                            icon={<img src={BUTTERCUP_LOGO} width="64" />}
                        />
                    </Otherwise>
                </Choose>
            </Container>
        );
    }
}

export default SearchResults;

import React, { PureComponent } from "react";
import PropTypes from "prop-types";
import styled from "styled-components";
import { NonIdealState } from "@blueprintjs/core";
import Entries from "../../shared/components/Entries.js";

const BUTTERCUP_LOGO = require("../../../resources/buttercup-standalone.png");

const Container = styled.div`
    flex: 1;
`;

const EntryShape = PropTypes.shape({
    id: PropTypes.string.isRequired,
    title: PropTypes.string.isRequired,
    url: PropTypes.string
});

class SearchResults extends PureComponent {
    static propTypes = {
        entries: PropTypes.arrayOf(EntryShape),
        sourcesUnlocked: PropTypes.number.isRequired,
        onSelectEntry: PropTypes.func.isRequired
    };

    render() {
        return (
            <Container>
                <Choose>
                    <When condition={this.props.entries.length > 0}>
                        <Entries
                            autoLoginEnabled={false}
                            entries={this.props.entries}
                            onSelectEntry={this.props.onSelectEntry}
                            sourcesUnlocked={this.props.sourcesUnlocked}
                        />
                    </When>
                    <Otherwise>
                        <NonIdealState
                            title="Welcome to Buttercup"
                            description="Use the search bar to find entries in your unlocked vaults."
                            icon={<img src={BUTTERCUP_LOGO} width="64" />}
                        />
                    </Otherwise>
                </Choose>
            </Container>
        );
    }
}

export default SearchResults;

Fix typo in class declaration


var CourseCardContentPanel = {

    render: function (c_section, fetched_eval) {

        var eval_data = (fetched_eval? WSData.iasystem_data(): null);
        var index = c_section.index;

        // Determine if we have valid eval data for the section
        var has_valid_eval = (eval_data && eval_data.sections && eval_data.sections.length > 0 && eval_data.sections[index] && eval_data.sections[index].evaluation_data && eval_data.sections[index].evaluation_data.length > 0 ? true : false);
        c_section.has_eval = has_valid_eval;
        c_section.evals = (has_valid_eval ? eval_data.sections[index].evaluation_data : null);

        // Determine if there was an err when fetching the section eval data
        var eval_data_err = (fetched_eval && !has_valid_eval && eval_data && eval_data.sections[index].evaluation_data === null ? true :false);
        c_section.eval_data_err = eval_data_err;

        var source = $("#course_card_content_panel").html();
        var template = Handlebars.compile(source);
        var raw = template(c_section);
        $('#course_card_content' + index).html(raw);

        if (has_valid_eval) {
            CourseEvalPanel.render(c_section);
        }

        CourseSchePanel.render(c_section);

        CourseResourcePanel.render(c_section);

        CourseInstructorPanel.render(c_section);
    }
};

var CourseCardContentPanel = {

    render: function (c_section, fetched_eval) {

        var eval_data = (fetched_eval? WSData.iasystem_data(): null);
        var index = c_section.index;

        // Determine if we have valid eval data for the section
        var has_valid_eval = (eval_data && eval_data.sections && eval_data.sections.length > 0 && eval_data.sections[index] && eval_data.sections[index].evaluation_data && eval_data.sections[index].evaluation_data.length > 0 ? true : false);
        c_section.has_eval = has_valid_eval;
        c_section.evals = (has_valid_eval ? eval_data.sections[index].evaluation_data : null);

        // Determine if there was an err when fetching the section eval data
        var eval_data_err = (fetched_eval && !has_valid_eval && eval_data && eval_data.sections[index].evaluation_data === null ? true :false);
        c_section.eval_data_err = eval_data_err;

        var source = $("#course_card_content_panel").html();
        var template = Handlebars.compile(source);
        var raw = template(c_section);
        $('#course_card_content' + index).html(raw);

        if (has_valid_eval) {
            CourseEvalPanel.render(c_section);
        }

        CourseSchePanel.render(c_section);

        CourseResourcePanel.render(c_section);
        if (c_section.instructors) {
            CourseInstructorPanel.render(c_section);
        }
    }
};

Call CourseInstructorPanel only when having instructors


import React from 'react'

import BlankSlate from './BlankSlate'
import EventCard from '../EventCard'
import StyledEvents, {
  StyledLink,
  StyledList,
  StyledListItem,
} from './Events.css'

export default ({ events = [] }) => {
  if (!events.length) {
    return <BlankSlate>No events yet. Check back soon.</BlankSlate>
  }

  return (
    <StyledEvents>
      <StyledList>
        {events.map((event, index) => (
          <StyledListItem>
            <StyledLink key={index} to={`/events/${event.slug}`}>
              <EventCard {...event }/>
            </StyledLink>
          </StyledListItem>
        ))}
      </StyledList>
    </StyledEvents>
  )
}

import React from 'react'

import BlankSlate from './BlankSlate'
import EventCard from '../EventCard'
import StyledEvents, {
  StyledLink,
  StyledList,
  StyledListItem,
} from './Events.css'

export default ({ events = [] }) => {
  if (!events.length) {
    return <BlankSlate>No events yet. Check back soon.</BlankSlate>
  }

  return (
    <StyledEvents>
      <StyledList>
        {events.map((event, index) => (
          <StyledListItem key={event.slug}>
            <StyledLink to={`/events/${event.slug}`}>
              <EventCard {...event }/>
            </StyledLink>
          </StyledListItem>
        ))}
      </StyledList>
    </StyledEvents>
  )
}

Move key to correct element


let ServiceConfig = {
  BLACKLIST: [
    'uris',
    'ports',
    'version',
    'versions',
    'versionInfo',
    'deployments',
    'queue',
    'tasks',
    'taskStats',
    'tasksHealthy',
    'tasksRunning',
    'tasksStaged',
    'tasksUnhealthy'
  ]
};

module.exports = ServiceConfig;

let ServiceConfig = {
  BLACKLIST: [
    'uris',
    'ports',
    'version',
    'versions',
    'versionInfo',
    'deployments',
    'queue',
    'lastTaskFailure',
    'tasks',
    'taskStats',
    'tasksHealthy',
    'tasksRunning',
    'tasksStaged',
    'tasksUnhealthy'
  ]
};

module.exports = ServiceConfig;

Add lastTaskFailure to service config black list


var v = {
  origin: {
    box: 'https://box.outofindex.com',
    org: 'https://json.varnalab.org'
    // box: 'http://192.168.1.101:3000',
    // org: 'http://192.168.1.101:3000/varnalab'
  },
  prefix: '',
  layout: {},
  module: {},
  route: {},
  view: {}
}

window.addEventListener('DOMContentLoaded', () => {

  if (location.host === 'box.outofindex.com') {
    v.prefix = '/varnalab/app'
    m.route.prefix(v.prefix)
  }

  m.route(document.querySelector('body'), '/', {

    '/': v.route.index(
      v.module.team(v)
    ),

    '/team': v.route.team(
      v.module.team(v)
    ),

    '/team/:filter': v.route.team(
      v.module.team(v)
    ),

    '/member/:id': v.route.member(
      v.module.team(v),
      v.module.member(v)
    )
  })
})

var v = {
  origin: {
    box: 'https://box.outofindex.com',
    org: 'https://json.varnalab.org'
    // box: 'http://192.168.1.101:3000',
    // org: 'http://192.168.1.101:3000/varnalab'
  },
  prefix: '',
  layout: {},
  module: {},
  route: {},
  view: {}
}

window.addEventListener('DOMContentLoaded', () => {

  if (location.host === 'box.outofindex.com') {
    v.prefix = '/varnalab/app'
    m.route.prefix(v.prefix)
  }
  else if (location.protocol === 'file:') {
    v.prefix = '/android_asset/www'
  }

  m.route(document.querySelector('body'), '/', {

    '/': v.route.index(
      v.module.team(v)
    ),

    '/team': v.route.team(
      v.module.team(v)
    ),

    '/team/:filter': v.route.team(
      v.module.team(v)
    ),

    '/member/:id': v.route.member(
      v.module.team(v),
      v.module.member(v)
    )
  })
})

Add asset prefix when served as cordova app in android


export
default class PlayerController {
	constructor($rootScope, $scope, $localStorage, $log, playersStore, playerModel, websocket) {
		'ngInject';

		this.$rootScope = $rootScope;
		this.$scope = $scope;
		this.$log = $log;

		this.playerModel = playerModel;
		this.playersStore = playersStore;
		this.ws = websocket;

		this.$log.info('constructor()', this);
	}

	$onInit() {
		this.$rootScope.$on('websocket:player:active', (event, data) => {
			this.$log.info('$on -> websocket:player:active', data);

			this.playerModel.update('isActive', data.isActive);
		});

		this.$log.info('$onInit()', this);
	}

	$onDestroy() {
		return () => {
			this.$log.info('destroy', this);
		};
	}
};

export
default class PlayerController {
	constructor($rootScope, $scope, $localStorage, $log, playersStore, playerModel, websocket) {
		'ngInject';

		this.$rootScope = $rootScope;
		this.$scope = $scope;
		this.$log = $log;

		this.playerModel = playerModel;
		this.playersStore = playersStore;
		this.ws = websocket;

		this.$log.info('constructor()', this);
	}

	$onInit() {
		this.$log.info('$onInit()', this);
	}

	$onDestroy() {
		return () => {
			this.$log.info('destroy', this);
		};
	}
};

Remove unused event from player Controller


'use strict';

var conf = require('./conf');
var pjson = require('../package.json');

// variables for views, this must be before the views are rendered and after any necessary request variables are set
function locals (req, res, next) {
  res.locals.user = req.user;
  if (req.csrfToken) { // not every request has CSRF token
    res.locals.csrfToken = req.csrfToken();
  }
  res.locals.lang = req.locale;
  res.locals.persona = conf.persona.enabled;
  res.locals.baseHref = conf.url + '/' + req.locale + '/'; // use proxy URL (if applicable), not req.url
  res.locals.environment = conf.env;
  res.locals.version = pjson.version;
  res.locals.commit = process.env.COMMIT_HASH;

  // DEPLOY_DATE is in seconds since that's what date +"%s" returns
  res.locals.deployDate = parseInt(process.env.DEPLOY_DATE, 10) * 1000;

  next();
}
module.exports = locals;

'use strict';

var conf = require('./conf');
var pjson = require('../package.json');

// variables for views, this must be before the views are rendered and after any necessary request variables are set
function locals (req, res, next) {
  res.locals.user = req.user;
  if (req.csrfToken) { // not every request has CSRF token
    res.locals.csrfToken = req.csrfToken();
  }
  res.locals.lang = req.locale;
  res.locals.persona = conf.persona.enabled;
  res.locals.baseHref = conf.url + '/' + req.locale + '/'; // use proxy URL (if applicable), not req.url
  res.locals.environment = conf.env;
  res.locals.version = pjson.version;
  res.locals.commit = process.env.COMMIT_HASH || '';

  if (process.env.DEPLOY_DATE) {
    // DEPLOY_DATE is in seconds since that's what date +"%s" returns
    res.locals.deployDate = parseInt(process.env.DEPLOY_DATE, 10) * 1000;
  } else {
    res.locals.deployDate = '';
  }

  next();
}
module.exports = locals;

Fix bad version description if no env vars


function MentoringWithStepsEdit(runtime, element) {
    "use strict";
    // Disable "add" buttons when a message of that type already exists:
    var $buttons = $('.add-xblock-component-button[data-category=pb-message]', element);
    var updateButtons = function() {
        $buttons.each(function() {
            var msg_type = $(this).data('boilerplate');
            $(this).toggleClass('disabled', $('.xblock .submission-message.'+msg_type).length > 0);
        });
    };
    updateButtons();
    $buttons.click(function(ev) {
        if ($(this).is('.disabled')) {
            ev.preventDefault();
            ev.stopPropagation();
        } else {
            $(this).addClass('disabled');
        }
    });

    ProblemBuilderUtil.transformClarifications(element);
    StudioEditableXBlockMixin(runtime, element);
}

function MentoringWithStepsEdit(runtime, element) {
    "use strict";

    var blockIsPresent = function(klass) {
        return $('.xblock ' + klass).length > 0;
    };

    var updateButton = function(button, condition) {
        button.toggleClass('disabled', condition);
    };

    var disableButton = function(ev) {
        if ($(this).is('.disabled')) {
            ev.preventDefault();
            ev.stopPropagation();
        } else {
            $(this).addClass('disabled');
        }
    };

    var initButtons = function(dataCategory) {
        var $buttons = $('.add-xblock-component-button[data-category='+dataCategory+']', element);
        $buttons.each(function() {
            if (dataCategory === 'pb-message') {
                var msg_type = $(this).data('boilerplate');
                updateButton($(this), blockIsPresent('.submission-message.'+msg_type));
            } else {
                updateButton($(this), blockIsPresent('.xblock-header-sb-review-step'));
            }
        });
        $buttons.on('click', disableButton);
    };

    initButtons('pb-message');
    initButtons('sb-review-step');

    ProblemBuilderUtil.transformClarifications(element);
    StudioEditableXBlockMixin(runtime, element);
}

Allow only one review step per mentoring block.


var pkg = require("./package.json");

module.exports = {
  build: require("./lib/build"),
  create: require("./lib/create"),
  config: require("./lib/config"),
  console: require("./lib/repl"),
  contracts: require("truffle-workflow-compile"),
  init: require("./lib/init"),
  package: require("./lib/package"),
  test: require("./lib/test"),
  version: pkg.version
};

var pkg = require("./package.json");

module.exports = {
  build: require("./lib/build"),
  create: require("./lib/create"),
  console: require("./lib/repl"),
  contracts: require("truffle-workflow-compile"),
  package: require("./lib/package"),
  test: require("./lib/test"),
  version: pkg.version
};

Remove some references to files that don't exist


var jsdiff = require('diff');

// Taken from diff.js
function removeEmpty(array) {
    var ret = [];
    for (var i = 0; i < array.length; i++) {
        if (array[i]) {
            ret.push(array[i]);
        }
    }
    return ret;
}

// Custom differ that includes newlines and sentence breaks
var wikiDiff =  new jsdiff.Diff();
wikiDiff.tokenize = function (value) {
    return removeEmpty(value.split(/(\S.+?[.!?\n])(?=\s+|$)/));
};

var diff = function(beforeText, afterText) {
    beforeText = beforeText.replace(/(?:\r\n|\r|\n)/g, '\n');
    afterText = afterText.replace(/(?:\r\n|\r|\n)/g, '\n');
    var diffList = wikiDiff.diff(beforeText, afterText);
    var fragment = document.createDocumentFragment();

    diffList.forEach(function(part) {
        var color = part.added ? 'text-success' : part.removed ? 'text-danger' : '';
        var span = part.removed ? document.createElement('s') : document.createElement('span');
        span.className = color;
        span.appendChild(document.createTextNode(part.value));
        fragment.appendChild(span);
    });

    var output = $('<div>').append(fragment).html();
    return output;
};

module.exports = {
    diff: diff
};

var jsdiff = require('diff');

// Taken from diff.js
function removeEmpty(array) {
    var ret = [];
    for (var i = 0; i < array.length; i++) {
        if (array[i]) {
            ret.push(array[i]);
        }
    }
    return ret;
}

// Custom differ that includes newlines and sentence breaks
var wikiDiff =  new jsdiff.Diff();
wikiDiff.tokenize = function (value) {
    return removeEmpty(value.split(/(\S.+?[.!?\n])(?=\s+|$)/));
};

var diff = function(beforeText, afterText) {
    beforeText = beforeText.replace(/(?:\r\n|\r|\n)/g, '\n');
    afterText = afterText.replace(/(?:\r\n|\r|\n)/g, '\n');
    var diffList = wikiDiff.diff(beforeText, afterText);
    var fragment = document.createDocumentFragment();

    diffList.forEach(function(part) {
        var color = part.added ? 'bg-success' : part.removed ? 'text-danger' : '';
        var span = part.removed ? document.createElement('s') : document.createElement('span');
        span.className = color;
        span.appendChild(document.createTextNode(part.value));
        fragment.appendChild(span);
    });

    var output = $('<div>').append(fragment).html();
    return output;
};

module.exports = {
    diff: diff
};

Make colors more obvious for diff


"use strict";

var UTA = require('underscore-template-additions');
var engines = require('consolidate');

var templates = new UTA();

module.exports = function(app, viewDirectory) {
  if (app.get('env') === 'production') {
    templates.cacheTemplates = true;
  }
  app.set('views', viewDirectory);
  app.engine('html', templates.forExpress());
  app.engine('txt', engines.hogan);
};

"use strict";

var UTA = require('underscore-template-additions');
var engines = require('consolidate');

module.exports = function(app, viewDirectory) {
  var templates = new UTA();

  if (app.get('env') === 'production') {
    templates.cacheTemplates = true;
  }
  app.set('views', viewDirectory);
  app.engine('html', templates.forExpress());
  app.engine('txt', engines.hogan);
};

Make sure each app has its own template instance


/**
 * External dependencies
 */
import amphtmlValidator from 'amphtml-validator';
import fetch from 'node-fetch';

export async function toHaveValidAMP( path = '/' ) {
	const result = {};
	try {
		const html = await fetch( 'http://localhost:9002' + path, { credentials: 'omit' } ).then( ( res ) => res.text() );
		const scanStatus = '';
		await amphtmlValidator.getInstance().then( ( validator ) => {
			const { status } = validator.validateString( html );
			result.pass = ( 'PASS' === status );
			result.message = `AMP Status: ${ status }`;
		} );
		result.message = () => `AMP Status: ${ scanStatus }`;
	} catch ( error ) {
		result.pass = false;
		result.message = () => error.message;
	}

	return result;
}

/**
 * External dependencies
 */
import amphtmlValidator from 'amphtml-validator';
import fetch from 'node-fetch';

/**
 * WordPress dependencies
 */
import {
	createURL,
} from '@wordpress/e2e-test-utils';

/**
 * Jest matcher for asserting the URL at the given path validates with AMP.
 *
 * @param {string} path The URL path of the current site to check.
 */
export async function toHaveValidAMP( path ) {
	const result = {};
	try {
		const url = createURL( path );
		const response = await fetch( url, { credentials: 'omit' } );
		if ( 200 !== response.status ) {
			result.pass = false;
			result.message = () => `fetch() error: ${ url } returned a status of ${ response.statusText }`;
		} else {
			const html = await response.text();
			await amphtmlValidator.getInstance().then( ( validator ) => {
				const { status } = validator.validateString( html );
				result.pass = ( 'PASS' === status );
				result.message = () => `AMP Status: ${ status }`;
			} );
		}
	} catch ( error ) {
		result.pass = false;
		result.message = () => error.message;
	}

	return result;
}

Add handling for URL fetch issues.


$(function() {
    $('.js-barchart-table').each(function() {
        $.magnaCharta($(this));
    })
});

$(function() {
    $('.js-barchart-table').each(function() {
        $.magnaCharta($(this), {
            toggleText: "Change between chart and table"
        });
    })
});

Change 'toggle' to 'change' to read less oddly.


//= require jquery
//= require jquery_ujs
//= require bootstrap
//= require grayscale/grayscale
//= require grayscale/jquery.easing.min

//= require jquery
//= require jquery_ujs
//= require bootstrap
//= require grayscale/jquery.easing.min
//= require grayscale/grayscale

Reorder js libraries for grayscale theme in order to work on heroku


const path = require('path');
const webpack = require('webpack');

module.exports = {
    devtool: 'eval',
    context: path.join(__dirname, '../dist'),
    entry: {
        index: [
            'webpack/hot/dev-server',
            'webpack-hot-middleware/client?reload=true',
            path.join(__dirname, '../dist/client/index.js')
        ]
    },
    output: {
        path: path.join(__dirname, '../dist/public/js'),
        filename: '[name].bundle.js',
        publicPath: '/js'
    }/*,
    module: {
        loaders: [
            {
                test: /\.js$/,
                exclude: /(node_modules)/,
                loader: 'babel',
                query: {
                    plugins: [ 'transform-object-assign' ],
                    presets: [ 'react', 'es2015', 'stage-0' ]
                }
            }
        ]
    }*/,
    plugins: [
        new webpack.HotModuleReplacementPlugin()
    ]
};

const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
    devtool: 'eval',
    context: path.join(__dirname, '../dist'),
    entry: {
        index: [
            'webpack/hot/dev-server',
            'webpack-hot-middleware/client?reload=true',
            path.join(__dirname, '../dist/client/index.js')
        ]
    },
    output: {
        path: path.join(__dirname, '../dist/public/js'),
        filename: 'js/[name].bundle.js',
        publicPath: '/'
    },
    module: {
        loaders: [
            {
                test: /\.scss$/,
                exclude: /node_modules/,
                loader: ExtractTextPlugin.extract('style', 'css!sass')
            }
        ]
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin(),
        new webpack.DefinePlugin({
            'process.env': {
                'BROWSER': JSON.stringify(true)
            }
        }),
        new ExtractTextPlugin('css/[name].bundle.css', {
            disable: false
        })
    ]
};

Update webpack for CSS and JS


module.exports.isValidUrl = function(_url) {
  if (_url && typeof _url === 'string' && (_url.indexOf('amqp://') === 0 || _url.indexOf('amqps://') === 0)) {
    return true;
  }

  return false;
};

module.exports.parseMsg  = function(_msg) {
  if (_msg.properties.contentType === 'application/json') {
    return JSON.parse(_msg.content.toString());
  }

  return _msg.content.toString();
};

module.exports.getValidUrl = function(_urls) {
  for (var i = 0, l = _urls.length; i < l; ++i) {
    if (_urls[i] && typeof _urls[i] === 'string' && (_urls[i].indexOf('amqp://') === 0 || _urls[i].indexOf('amqps://') === 0)) {
      return _urls[i];
    }
  }

  return 'amqp://localhost';
};

module.exports.parseMsg  = function(_msg) {
  if (_msg.properties.contentType === 'application/json') {
    return JSON.parse(_msg.content.toString());
  }

  return _msg.content.toString();
};

Remove isValidUrl function and add getValidUrl


var Reflux      = require('reflux');
var ApiActions  = require('./../actions/ApiActions');
var ConfigStore = require('./ConfigStore');

var buffer = [];
var ws     = null;

var ApiStore = Reflux.createStore({
    init() {
        this.listenTo(ConfigStore, this.initWs);
    },

    initWs(config) {
        var proto = 'ws';
        if (config.useWssConnection === true) {
            proto = 'wss';
        }

        ws = new WebSocket(`${ proto }://${ window.document.location.host }`);
        ws.onmessage = event => {
            ApiStore.trigger(JSON.parse(event.data));
        };

        ws.onopen = () => {
            buffer.forEach(request => {
                ws.send(JSON.stringify(request));
            });
        };
        this.listenTo(ApiActions.get, this.get);
    },

    get(id, params) {
        if (ws === null || ws.readyState !== WebSocket.OPEN) {
            buffer.push({
                id:     id,
                params: params || {}
            });

            return;
        }

        ws.send(JSON.stringify({
            id:     id,
            params: params || {}
        }));
    }
});

module.exports = ApiStore;

var Reflux      = require('reflux');
var ApiActions  = require('./../actions/ApiActions');
var ConfigStore = require('./ConfigStore');

var buffer = [];
var ws     = null;

var ApiStore = Reflux.createStore({
    init() {
        this.listenTo(ConfigStore, this.initWs);
    },

    initWs(config) {
        var proto = 'ws';
        if (config.useWssConnection === true) {
            proto = 'wss';
        }

        var port = 80;
        if (config.wsPort > 0) {
          port = config.wsPort;
        }

        ws = new WebSocket(`${ proto }://${ window.document.location.host }:${ port }`);
        ws.onmessage = event => {
            ApiStore.trigger(JSON.parse(event.data));
        };

        ws.onopen = () => {
            buffer.forEach(request => {
                ws.send(JSON.stringify(request));
            });
        };
        this.listenTo(ApiActions.get, this.get);
    },

    get(id, params) {
        if (ws === null || ws.readyState !== WebSocket.OPEN) {
            buffer.push({
                id:     id,
                params: params || {}
            });

            return;
        }

        ws.send(JSON.stringify({
            id:     id,
            params: params || {}
        }));
    }
});

module.exports = ApiStore;

Add config option for WebSockets port


var validate = require('validate');

/**
 * Simple validator plugin.
 *
 * @param {Object} cls The rhom-ified class to extend with validation.
 * @param {Object} schema The expected schema. See https://github.com/eivindfjeldstad/validate
 */
function Validation(cls, schema) {
	var mdl = cls._mdl.plugin("Validator", "Basic object validation");

	var validator = validate(schema);

	mdl.on("beforeSave", function(event) {
    var result = validator.validate(event.target);
    if (result.errors.length) {
      event.failure("Validation error(s): " + result.errors.join(", "));
    }
  });
}

module.exports = Validation;

var validate = require('validate');

/**
 * Simple validator plugin.
 *
 * @param {Object} cls The rhom-ified class to extend with validation.
 * @param {Object} schema The expected schema. See https://github.com/eivindfjeldstad/validate
 */
function Validation(cls, schema) {
	var mdl = cls._mdl.plugin("Validator", "Basic object validation");

	var validator = validate(schema);

	mdl.on("beforeSave", function(event) {
    var errors = validator.validate(event.target);
    if (errors.length) {
      event.failure("Validation error(s): " + errors.join(", "));
    }
  });
}

module.exports = Validation;

Adjust validation for API change in validation dependency.


// Copyright (c) 2017 The Regents of the University of Michigan.
// All Rights Reserved. Licensed according to the terms of the Revised
// BSD License. See LICENSE.txt for details.

module.exports = function(inputError) {
  const errorTree = {};

  errorTree.getLines = function() {
    return [
      inputError.description + ":",
      "  " + inputError.messages[0]
    ];
  };

  errorTree.getJSON = function() {
    return JSON.stringify({
      "description": "one node",
      "messages": ["Error: uh oh"],
      "children": []
    });
  };

  return errorTree;
};

// Copyright (c) 2017 The Regents of the University of Michigan.
// All Rights Reserved. Licensed according to the terms of the Revised
// BSD License. See LICENSE.txt for details.

module.exports = function(inputError) {
  const errorTree = {};

  errorTree.getLines = function() {
    return [
      inputError.description + ":",
      "  " + inputError.messages[0]
    ];
  };

  errorTree.getJSON = function() {
    return JSON.stringify({
      "description": inputError.description,
      "messages": inputError.messages,
      "children": []
    }, fillInErrors, 0);
  };

  return errorTree;
};

const fillInErrors = function(key, value) {
  if (value instanceof Error && JSON.stringify(value) === "{}")
    return value.toString();

  else
    return value;
};

Use inputError for json output


"use strict";

var routington = require("routington");

/**
 * Generates the checking router from the swagger def
 * @param def {Swagger} The swagger complete definition
 * @return {routington} A router
 */
module.exports = function genRouter(def) {
  var paths = def.paths;
  var base = def.basePath || "";

  var router = routington();
  Object.keys(paths).forEach(function eachRoute(route) {
    var node = router.define(base + route)[0];
    node.def = paths[route];
  });
  return router;
};

"use strict";

var routington = require("routington");

/**
 * Generates the checking router from the swagger def
 * @param def {Swagger} The swagger complete definition
 * @return {routington} A router
 */
module.exports = function genRouter(def) {
  var paths = def.paths;
  var base = def.basePath || "";

  var router = routington();
  Object.keys(paths).forEach(function eachRoute(route) {
    var node = router.define(base + route.replace(/{([^}]*)}/, ":$1"))[0];
    node.def = paths[route];
  });
  return router;
};

Support {parameter} styles in swagger routes


'use strict';

var deepEqual = require('deep-equal');
var qs = require('querystring');

module.exports =
function matchBody(spec, body) {
  if (typeof spec === 'undefined') {
    return true;
  }
  var options = this || {};

  if (Buffer.isBuffer(body)) {
    body = body.toString();
  }

  //strip line endings from both so that we get a match no matter what OS we are running on
  body = body.replace(/\r?\n|\r/g, '');

  if (spec instanceof RegExp) {
    return body.match(spec);
  }

  if (typeof spec === "string") {
    spec = spec.replace(/\r?\n|\r/g, '');
  }

  // try to transform body to json
  var json;
  if (typeof spec === 'object' || typeof spec === 'function') {
    try { json = JSON.parse(body);} catch(err) {}
    if (json !== undefined) {
      body = json;
    }
    else
      if (options.headers) {
        var contentType = options.headers['Content-Type'] ||
                          options.headers['content-type'];

        if (contentType.match(/application\/x-www-form-urlencoded/)) {
          body = qs.parse(body);
        }
      }
  }

  if (typeof spec === "function") {
    return spec.call(this, body);
  }

  return deepEqual(spec, body, { strict: true });
};

'use strict';

var deepEqual = require('deep-equal');
var qs = require('querystring');

module.exports =
function matchBody(spec, body) {
  if (typeof spec === 'undefined') {
    return true;
  }
  var options = this || {};

  if (Buffer.isBuffer(body)) {
    body = body.toString();
  }

  //strip line endings from both so that we get a match no matter what OS we are running on
  body = body.replace(/\r?\n|\r/g, '');

  if (spec instanceof RegExp) {
    return body.match(spec);
  }

  if (typeof spec === "string") {
    spec = spec.replace(/\r?\n|\r/g, '');
  }

  // try to transform body to json
  var json;
  if (typeof spec === 'object' || typeof spec === 'function') {
    try { json = JSON.parse(body);} catch(err) {}
    if (json !== undefined) {
      body = json;
    }
    else
      if (options.headers) {
        var contentType = options.headers['Content-Type'] ||
                          options.headers['content-type'];

        if (contentType && contentType.match(/application\/x-www-form-urlencoded/)) {
          body = qs.parse(body);
        }
      }
  }

  if (typeof spec === "function") {
    return spec.call(this, body);
  }

  return deepEqual(spec, body, { strict: true });
};

Fix for Uncaught TypeError: Cannot read property 'match' of undefined


$(document).ready(function () {
  var contents_container = $('.js-fixed-content-top')
  if (contents_container.length > 0) {
    var top = $('.js-fixed-content-top').offset().top - parseFloat($('.js-fixed-content-top').css('marginTop').replace(/auto/, 0));
    $(window).scroll(function (event) {
      // what the y position of the scroll is
      var y = $(this).scrollTop();

      // whether that's below
      if (y >= top) {
        // if so, ad the fixed class
        $('.js-fixed-content-top').addClass('content-fixed');
      } else {
        // otherwise remove it
        $('.js-fixed-content-top').removeClass('content-fixed');
      }
    });
  };
});

$(document).ready(function () {
  var contentContainer = $('.js-fixed-content-top')
  if (contentContainer.length > 0) {
    var top = $('.js-fixed-content-top').offset().top - parseFloat($('.js-fixed-content-top').css('marginTop').replace(/auto/, 0));
    $(window).scroll(function (event) {
      // what the y position of the scroll is
      var y = $(this).scrollTop();

      // whether that's below
      if (y >= top) {
        // if so, ad the fixed class
        $('.js-fixed-content-top').addClass('content-fixed');
      } else {
        // otherwise remove it
        $('.js-fixed-content-top').removeClass('content-fixed');
      }
    });
  };
});

Use camelCase for javascript variable names.


const Duplex = require('stream').Duplex;
const Transform = require('stream').Transform;
const JlTransform = require('./JlTransform');

class JlTransformsChain extends Transform
{
	constructor(streams)
	{
		super({
			objectMode: true
		});

		this.inputType = JlTransform.ANY;
		this.outputType = JlTransform.ANY;

		this.firstStream = streams[0];
		this.lastStream = streams[streams.length - 1];

		for (let i = 0; i < streams.length - 1; i++) {
			streams[i].pipe(streams[i + 1]);
		}

		this.lastStream.on('end', () => {
			this.emit('end');
		});

		this.lastStream.on('data', (d) => {
			if (!this.push(d)) {
				this.lastStream.pause();
			}
		});

		this.lastStream.on('error', e => {
			this.emit('error', e);
		});
	}

	_transform(chunk, encoding, callback)
	{
		this.firstStream.write(chunk, encoding, callback);
	}

	_flush(cb)
	{
		this.lastStream.once('end', cb);
		this.firstStream.end();
	}

	_read(...args)
	{
		super._read(...args);

		if (this.lastStream.isPaused()) {
			this.lastStream.resume();
		}
	}
}

module.exports = JlTransformsChain;

const Duplex = require('stream').Duplex;
const Transform = require('stream').Transform;
const JlTransform = require('./JlTransform');

class JlTransformsChain extends Transform
{
	constructor(streams)
	{
		super({
			objectMode: true
		});

		this.inputType = JlTransform.ANY;
		this.outputType = JlTransform.ANY;

		this.firstStream = streams[0];
		this.lastStream = streams[streams.length - 1];

		for (let i = 0; i < streams.length - 1; i++) {
			streams[i].pipe(streams[i + 1]);
		}

		this.lastStream.on('end', () => {
			this.push(null);
		});

		this.lastStream.on('data', (d) => {
			if (!this.push(d)) {
				this.lastStream.pause();
			}
		});

		this.lastStream.on('error', e => {
			this.emit('error', e);
		});
	}

	_transform(chunk, encoding, callback)
	{
		this.firstStream.write(chunk, encoding, callback);
	}

	_flush(cb)
	{
		this.lastStream.once('end', cb);
		this.firstStream.end();
	}

	_read(...args)
	{
		super._read(...args);

		if (this.lastStream.isPaused()) {
			this.lastStream.resume();
		}
	}
}

module.exports = JlTransformsChain;

Handle 'end' event in the right way


angular
	.module('ngSharepoint.Lists')
	.factory('Query', function() {
		var Query = function() {
			//this.__queries = {};
		};
        Query.prototype.unpackItem = function(item) {
            var query = this;
            var obj = {};
            if (Array.isArray(query.__values)) {
                query.__values.forEach(function(key) {
                    var value = item.get_item(key);
                    obj[key] = query.__trim(value);
                });
            }else {
                Object.getOwnPropertyNames(query.__values).forEach(function(key) {
                    var value = item.get_item(key);
                    obj[key] = query.__trim(value);
                });
            }
            return obj;
        };
        Query.prototype.packItem = function(item) {
            var query = this;
            Object.getOwnPropertyNames(query.__values).forEach(function(key) {
                item.set_item(key, query.__trim(query.__values[key]));
            });
        };
        Query.prototype.then = function(success, reject) {
        	return this.__execute().then(success, reject);
        };
        Query.prototype.__trim = function(value) {
            if (value !== null && value !== undefined && typeof value === 'string') {
                return value.trim();
            }else {
                return value;
            }
        };
		return (Query);
	});

Use spaces instead of tabs


app.controller('SearchController', ['$http', function($http) {
  console.log('SearchController running');
  var self = this;
  self.mentors = [];
  self.newSearch = {
    first_name: null,
    last_name: null,
    email: null,
    company: null,
    job_title: null,
    zip: null,
    race: null,
    sex: null,
    orientation: null,
    birthday: null,
    school: null,
    degree: null,
    major: null,
    language: null
  };

  // .config(function($mdThemingProvider) {
  //   $mdThemingProvider.theme('docs-dark', 'default')
  //     .primaryPalette('yellow')
  //     .dark();
  //
  // });

  self.test = function() {
    console.log(self.newSearch);
  };

  self.getMentors = function() {
    console.log(self.newSearch);
    var newSearchString = JSON.stringify(self.newSearch);
    return $http({
          method: 'GET',
          url: '/mentor-search',
          headers: {
            newSearchString: newSearchString
          }
        })
        .then(function (response) {
          self.mentors = response.data;
          console.log("Mentors list:", self.mentors);
        }),
        function(err){
          console.log("Error with search get request ", err);
        };

  }

}]);
/**
Copyright 2016 Google Inc. All Rights Reserved.
Use of this source code is governed by an MIT-style license that can be foundin the LICENSE file at http://material.angularjs.org/HEAD/license.
**/

app.controller('SearchController', ['$http', function($http) {
  console.log('SearchController running');
  var self = this;
  self.mentors = [];
  self.newSearch = {
    first_name: null,
    last_name: null,
    email: null,
    company: null,
    job_title: null,
    zip: null,
    race: null,
    sex: null,
    orientation: null,
    birthday: null,
    school: null,
    degree: null,
    major: null,
    language: null
  };

  // .config(function($mdThemingProvider) {
  //   $mdThemingProvider.theme('docs-dark', 'default')
  //     .primaryPalette('yellow')
  //     .dark();
  //
  // });

  self.test = function() {
    console.log(self.newSearch);
  };

  self.getMentors = function() {
    console.log(self.newSearch);
    var newSearchString = JSON.stringify(self.newSearch);
    return $http({
          method: 'GET',
          url: '/mentor-search/search',
          headers: {
            newSearchString: newSearchString
          }
        })
        .then(function (response) {
          self.mentors = response.data;
          console.log("Mentors list:", self.mentors);
        }),
        function(err){
          console.log("Error with search get request ", err);
        };

  }

}]);
/**
Copyright 2016 Google Inc. All Rights Reserved.
Use of this source code is governed by an MIT-style license that can be foundin the LICENSE file at http://material.angularjs.org/HEAD/license.
**/

Modify url for mentor search get request


/*
Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.
For licensing, see LICENSE.html or http://ckeditor.com/license
*/

CKEDITOR.editorConfig = function( config )
{
  config.PreserveSessionOnFileBrowser = true;
  // Define changes to default configuration here. For example:
 	//config.language = '';
  config.uiColor = '#E0ECFF';
	config.toolbar = 'Basic';
	config.entities_greek = false;
	config.entities_latin = false;
	config.entities_processNumerical = false;
	config.toolbar_Basic =
	[
	        ['Format', '-', 'Bold', 'Italic'],
	        ['NumberedList','BulletedList'],
	        ['Outdent', 'Indent'],
	        ['Image', 'Flash', '-', 'Link'],
	        ['Maximize'],
	        ['Table'],
	        ['PageBreak'],
	        ['Source']
	];
}

/*
Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.
For licensing, see LICENSE.html or http://ckeditor.com/license
*/

CKEDITOR.editorConfig = function( config )
{
  config.PreserveSessionOnFileBrowser = true;
  // Define changes to default configuration here. For example:
 	//config.language = '';
  config.uiColor = '#E0ECFF';
	config.toolbar = 'Basic';
	config.entities_greek = false;
	config.entities_latin = false;
	config.entities_processNumerical = false;
	config.toolbar = 'Full';
}

Make sure each time the ckeditor config is not overwrite by server restart


var constants = require('../constants');

exports.NAVDATA_NUM_TAGS         = Object.keys(constants.options).length;
exports.NAVDATA_OPTION_FULL_MASK = (1<<exports.NAVDATA_NUM_TAGS) - 1;

exports.TAG_TYPE_MASK = function (tagtype) {
  return (tagtype == 0) ? 0 : 1 << (tagtype - 1);
};

exports.NAVDATA_OPTION_MASK = function (option) {
  return 1 << option;
};

exports.maskFromNavdataOptions = function (options) {
  if (!Array.isArray(options)) {
    options = Array.prototype.slice.call(arguments);
  }

  var masks = options.map(exports.NAVDATA_OPTION_MASK);
  var mask  = masks.reduce(function (prev, curr) {
    return prev | curr;
  });

  return mask;
};

Add two properties for navdata option masks.


const blueprintHelper = require('ember-frost-core/blueprint-helper')

module.exports = {
  afterInstall: function (options) {
    const addonsToAdd = [
      {name: 'ember-frost-core', target: '^1.14.3'},
      {name: 'ember-prop-types', target: '^3.0.0'}
    ]

    // Get the packages installed in the consumer app/addon. Packages that are already installed in the consumer within
    // the required semver range will not be re-installed or have blueprints re-run.
    const consumerPackages = blueprintHelper.consumer.getPackages(options)

    // Get the packages to install (not already installed) from a list of potential packages
    return blueprintHelper.packageHandler.getPkgsToInstall(addonsToAdd, consumerPackages).then((pkgsToInstall) => {
      if (pkgsToInstall.length !== 0) {
        // Call the blueprint hook
        return this.addAddonsToProject({
          packages: pkgsToInstall
        })
      }
    })
  },

  normalizeEntityName: function () {
    // this prevents an error when the entityName is
    // not specified (since that doesn't actually matter to us)
  }
}

const blueprintHelper = require('ember-frost-core/blueprint-helper')

module.exports = {
  afterInstall: function (options) {
    const addonsToAdd = [
      {name: 'ember-frost-core', target: '^1.14.3'}
    ]

    // Get the packages installed in the consumer app/addon. Packages that are already installed in the consumer within
    // the required semver range will not be re-installed or have blueprints re-run.
    const consumerPackages = blueprintHelper.consumer.getPackages(options)

    // Get the packages to install (not already installed) from a list of potential packages
    return blueprintHelper.packageHandler.getPkgsToInstall(addonsToAdd, consumerPackages).then((pkgsToInstall) => {
      if (pkgsToInstall.length !== 0) {
        // Call the blueprint hook
        return this.addAddonsToProject({
          packages: pkgsToInstall
        })
      }
    })
  },

  normalizeEntityName: function () {
    // this prevents an error when the entityName is
    // not specified (since that doesn't actually matter to us)
  }
}

Remove unecessary dependencies in blueprint


const React = require('react');
import { PAGES } from '../constants';
const PACKAGE = require('../../../package.json');

module.exports = class PageAbout extends React.Component {

  goToFeedback (e) {
    e.preventDefault();
    this.props.navigateTo(PAGES.FEEDBACK);
  }

  render() {
    return (

HotS Talent Picks

Hi! This is a simple app developed to bring your the invaluable data from Hotslogs.com (which by the way an awesome site, please consider supporting them!).

You can contact me via the Overwolf Forums, email, or via the Feedback page.

App version: v{PACKAGE.version} - changelog

    );
  }
};

const React = require('react');
import { PAGES } from '../constants';
const PACKAGE = require('../../../package.json');

module.exports = class PageAbout extends React.Component {

  goToFeedback (e) {
    e.preventDefault();
    this.props.navigateTo(PAGES.FEEDBACK);
  }

  render() {
    return (

HotS Talent Picks

Hi! This is a simple app developed to bring you the invaluable data from Hotslogs.com (which by the way an awesome site, please consider supporting them!).

You can contact me via the Overwolf Forums, email, or via the Feedback page.

App version: v{PACKAGE.version} - changelog

    );
  }
};

Fix a typo on about page


'use strict'

export const FETCH_EVENTS = 'FETCH_EVENTS'
export const EVENTS_SUCCESS = 'EVENTS_SUCCESS'
export const EVENTS_FAILED = 'EVENTS_FAILED'

export { fetchEvents } from './events'

'use strict'

export const FETCH_EVENTS = 'FETCH_EVENTS'
export const EVENTS_SUCCESS = 'EVENTS_SUCCESS'
export const EVENTS_FAILED = 'EVENTS_FAILED'

export const FILL_FILTER_SELECTS = 'FILL_FILTER_SELECTS'

export {
  fetchEvents,
  eventsSuccess,
  eventsFailed
} from './events'

export {
  prepareFilterSelects
} from './filter'

Add new actions to filter


function onKeyPress(key) {
	if (this.jwplayer) {
		location.href = "javascript:window.jwplayer().play()";
	}
}

function onKeyPress(key) {
	if (this.jwplayer) {
		if (key === NEXT) {
			var playPauseButton = document.querySelector('img[src="icons/default_dark/forward.png"]');
			simulateClick(playPauseButton);
		} else if (key === PLAY) {
      location.href = "javascript:window.jwplayer().play()";
		} else if (key === PREV) {
			var backButton = document.querySelector('img[src="icons/default_dark/back.png"]');
			simulateClick(backButton);
		}
	}
}

Add back and forward shortcut


import Ember from 'ember';

const get = Ember.get;
const { guidFor } = Ember

function updateTitle(tokens) {
  document.title = tokens.toString();
}

export default Ember.Helper.extend({
  pageTitleList: Ember.inject.service(),

  init() {
    this._super();
    let tokens = get(this, 'pageTitleList');
    tokens.push({ id: guidFor(this) });
  },

  compute(params, hash) {
    let tokens = get(this, 'pageTitleList');
    hash.id = guidFor(this);
    hash.title = params.join('');
    tokens.push(hash);
    Ember.run.scheduleOnce('afterRender', null, updateTitle, tokens);
    return '';
  },

  destroy() {
    let tokens = get(this, 'pageTitleList');
    let id = guidFor(this);
    tokens.remove(id);
    Ember.run.scheduleOnce('afterRender', null, updateTitle, tokens);
  }
});

import Ember from 'ember';

const get = Ember.get;
const { guidFor } = Ember;

function updateTitle(tokens) {
  document.title = tokens.toString();
}

export default Ember.Helper.extend({
  pageTitleList: Ember.inject.service(),

  init() {
    this._super();
    let tokens = get(this, 'pageTitleList');
    tokens.push({ id: guidFor(this) });
  },

  compute(params, hash) {
    let tokens = get(this, 'pageTitleList');
    hash.id = guidFor(this);
    hash.title = params.join('');
    tokens.push(hash);
    Ember.run.scheduleOnce('afterRender', null, updateTitle, tokens);
    return '';
  },

  destroy() {
    let tokens = get(this, 'pageTitleList');
    let id = guidFor(this);
    tokens.remove(id);
    Ember.run.scheduleOnce('afterRender', null, updateTitle, tokens);
  }
});

Revert "force a build failure through jshint"


if (Meteor.isServer) {
  Meteor.methods({
    // send user an email when they get a notifcation
    createNotification: function ( giblet, url, notificationKeys ) {
      Notifications.insert({
        createdAt: new Date(),
        owner: giblet.owner,
        gibletID: giblet._id,
        keywords: notificationKeys,
        url: url
      });
      if (giblet.email) {
        Meteor.call('sendEmail', giblet, url, notificationKeys);
      }
      if (giblet.SMS) {
        //Meteor.call('sendSMS', giblet, url, notificationKeys);
      }
    },

    sendEmail: function( giblet, url, notificationKeys ) {
      var user = Meteor.users.findOne({_id: giblet.owner});
      var subject = 'Gobbler alert: Found keywords from ' + giblet.taskname;
      var text = 'Found keywords ' + notificationKeys.join(', ') + ' at ' + url;
      var email;
      if (user.services.facebook) {
        email = user.services.facebook.email;
      }
      if (user.services.google) {
        email = user.services.google.email;
      }
      if (user.chosenEmail) {
        email = user.chosenEmail;
      }
      Email.send({
        to: email,
        from: 'GobblerGonnaGobble@gmail.com',
        subject: subject,
        text: text
      });
    }
  });
}

Add new line to bottom of file


module.exports = function ClickJackingConstructor(opts) {

    opts = opts || {};
    opts.deny = typeof opts.deny != "undefined" ? opts.deny : false;
    opts.sameOrigin = opts.sameOrigin || false;
    opts.allowFrom = opts.allowFrom || false;
    opts.jsUrl = opts.jsUrl || "clickjacking_protection.js";

    return function clickJacking(req, res, next) {

        // self-awareness
        if (req._esecurity_clickjacking)
            return next();

        req._esecurity_clickjacking = true;

        if (opts.jsUrl && opts.jsUrl.charAt(0) !== "/")
            opts.jsUrl = "/" + opts.jsUrl;

        if (opts.jsUrl && opts.jsUrl === req.url)
            return res.sendfile("utils/clickjackingProtection.js", {
                root: __dirname,
                maxAge: opts.maxAge
            });

        var frameOptions = "DENY";
        if (opts.deny) frameOptions = "DENY";
        else if (opts.sameOrigin) frameOptions = "SAMEORIGIN";
        else if (opts.allowFrom) frameOptions = "ALLOW-FROM " + opts.allowFrom;

        res.set("X-Frame-Options", frameOptions);

        return next();
    };
};

module.exports = function ClickJackingConstructor(opts) {

    opts = opts || {};
    opts.deny = typeof opts.deny != "undefined" ? opts.deny : false;
    opts.sameOrigin = opts.sameOrigin || false;
    opts.allowFrom = opts.allowFrom || false;
    opts.jsUrl = opts.jsUrl || "clickjacking_protection.js";

    return function clickJacking(req, res, next) {

        // self-awareness
        if (req._esecurity_clickjacking)
            return next();

        req._esecurity_clickjacking = true;

        if (opts.jsUrl && opts.jsUrl.charAt(0) !== "/")
            opts.jsUrl = "/" + opts.jsUrl;

        if (opts.jsUrl && opts.jsUrl === req.url) {

            var sendFileOpts = {
                root: __dirname,
                maxAge: opts.maxAge
            };

            // res.sendfile is deprecated
            if (res.sendFile) {
                return res.sendFile("utils/clickjackingProtection.js", sendFileOpts);
            }

            return res.sendfile("utils/clickjackingProtection.js", sendFileOpts);
        }

        var frameOptions = "DENY";
        if (opts.deny) frameOptions = "DENY";
        else if (opts.sameOrigin) frameOptions = "SAMEORIGIN";
        else if (opts.allowFrom) frameOptions = "ALLOW-FROM " + opts.allowFrom;

        res.set("X-Frame-Options", frameOptions);

        return next();
    };
};

Use sendFile method instead of deprecated sendfile method.


import 'Utils/TestSetup';
import React from 'react';
import sinon from 'sinon';
import { shallow } from 'enzyme';

import { LikeButton } from 'Components/LikeButton/LikeButton';

const onClickStub = sinon.spy();
const defaultProps = { storyId: 1, active: false, onClick: onClickStub };
const defaultComponent = <LikeButton {...defaultProps} />;

let component;

describe('Like button component', () => {
	beforeEach(() => {
		component = shallow(defaultComponent);
	});

	it('should show a button', () => {
		component.should.have.a.tagName('button');
	});

	it('should trigger callback on click', () => {
		const button = component.find('button');
		button.simulate('click');
		onClickStub.should.have.been.called();
	});

	it('should have an `is-active` class name when the user has liked the story', () => {
		component = shallow(<LikeButton {...defaultProps} active />);
		component.should.have.className('is_active');
	});
});

import 'Library/TestSetup';
import React from 'react';
import sinon from 'sinon';
import { shallow } from 'enzyme';

import { LikeButton } from 'Components/LikeButton/LikeButton';

const onClickStub = sinon.spy();
const defaultProps = { storyId: 1, active: false, onClick: onClickStub };
const defaultComponent = <LikeButton {...defaultProps} />;

let component;

describe('Like button component', () => {
	beforeEach(() => {
		component = shallow(defaultComponent);
	});

	it('should show a button', () => {
		component.should.have.a.tagName('button');
	});

	it('should trigger callback on click', () => {
		const button = component.find('button');
		button.simulate('click');
		onClickStub.should.have.been.called();
	});

	it('should have an `is-active` class name when the user has liked the story', () => {
		component = shallow(<LikeButton {...defaultProps} active />);
		component.should.have.className('is_active');
	});
});

Fix wrong import for the test setup.


$(function() {

function updateSubmission(url, spam, callback) {
  $.ajax({
    type: 'PUT',
    url: url,
    data: { spam: spam },
    dataType: 'json',
    success: function() {
      Wheelhouse.Flash.message("Submission updated.");
      callback();
    }
  });

  Wheelhouse.Flash.loading("Marking submission as spam.");
}

function clickHandler(spam) {
  return function() {
    var url = $(this).attr('href');
    var row = $(this).closest('tr');

    updateSubmission(url, spam, function() {
      row.remove();
    });

    return false;
  }
}

$('#submissions').on('click', 'a.mark-spam', clickHandler(true));
$('#submissions').on('click', 'a.not-spam', clickHandler(false));

$('button.mark-spam').click(function() {
  var url = $(this).closest('form').attr('action');

  updateSubmission(url, true, function() {
    $('button.mark-spam').hide();
    $('button.not-spam').show();
  });

  return false;
});

$('button.not-spam').click(function() {
  var url = $(this).closest('form').attr('action');

  updateSubmission(url, false, function() {
    $('button.not-spam').hide();
    $('button.mark-spam').show();
  });

  return false;
});

});

$(function() {

function updateSubmission(url, spam, callback) {
  $.ajax({
    type: 'PUT',
    url: url,
    data: { spam: spam },
    dataType: 'json',
    success: function() {
      Wheelhouse.Flash.message("Submission updated.");
      callback();
    }
  });

  Wheelhouse.Flash.loading("Updating submission.");
}

function clickHandler(spam) {
  return function() {
    var url = $(this).attr('href');
    var row = $(this).closest('tr');

    updateSubmission(url, spam, function() {
      row.remove();
    });

    return false;
  }
}

$('#submissions').on('click', 'a.mark-spam', clickHandler(true));
$('#submissions').on('click', 'a.not-spam', clickHandler(false));

$('button.mark-spam').click(function() {
  var url = $(this).closest('form').attr('action');

  updateSubmission(url, true, function() {
    $('button.mark-spam').hide();
    $('button.not-spam').show();
  });

  return false;
});

$('button.not-spam').click(function() {
  var url = $(this).closest('form').attr('action');

  updateSubmission(url, false, function() {
    $('button.not-spam').hide();
    $('button.mark-spam').show();
  });

  return false;
});

});

Fix loading message when updating submission


isic.views.LayoutHeaderView = isic.View.extend({
    events: {
        'mouseenter .dropdown': function (event) {
            $(event.currentTarget).addClass('open');
        },
        'mouseleave .dropdown': function (event) {
            $(event.currentTarget).removeClass('open');
        },
        'click .dropdown': function (event) {
            $(event.currentTarget).removeClass('open');
        }
    },

    initialize: function (settings) {
        this.render();

        girder.events.on('g:login', this.render, this);
    },

    render: function () {
        this.$el.html(isic.templates.layoutHeader({
            currentUser: girder.currentUser
        }));

        // Specify trigger for tooltip to ensure that tooltip hides when button
        // is clicked. See http://stackoverflow.com/a/33585981/2522042.
        this.$('a[title]').tooltip({
            placement: 'bottom',
            trigger: 'hover',
            delay: {show: 300}
        });

        new isic.views.LayoutHeaderUserView({
            el: this.$('.isic-current-user-wrapper'),
            parentView: this
        }).render();
    }
});

isic.views.LayoutHeaderView = isic.View.extend({
    events: {
        'mouseenter .dropdown': function (event) {
            $(event.currentTarget).addClass('open');
        },
        'mouseleave .dropdown': function (event) {
            $(event.currentTarget).removeClass('open');
        },
        'click .dropdown': function (event) {
            $(event.currentTarget).removeClass('open');
        }
    },

    initialize: function (settings) {
        this.render();

        girder.events.on('g:login', this.render, this);
        girder.events.on('g:login-changed', this.render, this);
    },

    render: function () {
        this.$el.html(isic.templates.layoutHeader({
            currentUser: girder.currentUser
        }));

        // Specify trigger for tooltip to ensure that tooltip hides when button
        // is clicked. See http://stackoverflow.com/a/33585981/2522042.
        this.$('a[title]').tooltip({
            placement: 'bottom',
            trigger: 'hover',
            delay: {show: 300}
        });

        new isic.views.LayoutHeaderUserView({
            el: this.$('.isic-current-user-wrapper'),
            parentView: this
        }).render();
    }
});

Refresh layout header on login-changed event


/**
 * Interactive features for the external menus functionality
 *
 * This piece of JS is loaded on both the list view app for the
 * epfl-external-menu custom post type, and in the Appearance -> Menus
 * screen (although at the moment it does nothing on the latter).
 */

/**
 * Activate the app on the epfl-external-menu list view screen
 */
function initExternalMenuList ($) {
    $('a.page-title-action').remove();
    $('h1.wp-heading-inline').after('<button class="page-title-action">' + wp.translations.refresh_button + '</button>');
    var $button = $('h1.wp-heading-inline').next();
    var spinning = false;
    $button.click(function() {
        if (spinning) return;
        var $form = window.EPFLMenus.asWPAdminPostForm('refresh');
        $form.submit();
        var $spinner = $('');
        $button.append($spinner);
        spinning = true;
    });
}

jQuery( document ).ready(function($) {
    if (window.wp.screen.base === 'edit' && window.wp.screen.post_type === 'epfl-external-menu' ) {
        initExternalMenuList($);
    }

    // If you see this, nothing threw or crashed (yet).
    console.log('epfl-menus-admin.js is on duty.');
});

/**
 * Interactive features for the external menus functionality
 *
 * This piece of JS is loaded on both the list view app for the
 * epfl-external-menu custom post type, and in the Appearance -> Menus
 * screen (although at the moment it does nothing on the latter).
 */

/**
 * Activate the app on the epfl-external-menu list view screen
 */
function initExternalMenuList ($) {
    $('a.page-title-action').remove();
    $('h1.wp-heading-inline').after('<button class="page-title-action">' + wp.translations.refresh_button + '</button>');
    var $button = $('h1.wp-heading-inline').next();
    var spinning = false;
    $button.click(function() {
        if (spinning) return;
        var $form = window.EPFLMenus.asWPAdminPostForm('refresh');
        $form.submit();
        var $spinner = $('');
        $button.append($spinner);
        spinning = true;
    });
}

jQuery(document).ready(function($) {
    if (window.wp.screen.base === 'edit' && window.wp.screen.post_type === 'epfl-external-menu' ) {
        initExternalMenuList($);
    }

    // If you see this, nothing threw or crashed (yet).
    console.log('epfl-menus-admin.js is on duty.');
});

Remove silly stylism for "( document )"


var mongoose = require('mongoose'),
    fs = require('fs');

function rm (file) {
  if (fs.existsSync(file)) {
    fs.unlinkSync(file);
  }
}

var secrets = require('../config/secrets'),
    config = require('../config/config');
mongoose.connect(secrets.db);

var Project = require('../models/project');
var SignedPackage = require('../models/signed_package');
var User = require('../models/user');
var Version = require('../models/version');

console.log('Nuking everything... Good luck with that');

mongoose.connection.db.dropDatabase();
mongoose.disconnect();

rm(config.keystorePath);
rm(config.derFilePath);

var async = require('async');
var mongoose = require('mongoose'),
    fs = require('fs');

function rm (file) {
  if (fs.existsSync(file)) {
    fs.unlinkSync(file);
  }
}

var secrets = require('../config/secrets'),
    config = require('../config/config');
mongoose.connect(secrets.db);

var Project = require('../models/project');
var SignedPackage = require('../models/signed_package');
var User = require('../models/user');
var Version = require('../models/version');

console.log('Nuking everything... Good luck with that');

function removeCb(err) {
        if (err) console.error(err);
}

async.series([
	function(done) {
		async.parallel([
			function(cb) { Project.remove(cb) },
			function(cb) { SignedPackage.remove(cb) },
			function(cb) { User.remove(cb) },
			function(cb) { Version.remove(cb) }
		], function(err) {
                        if (err) console.error(err);
                        done(err);
                });
	},
	function(err) {
		mongoose.disconnect();
	}
]);

rm(config.keystorePath);
rm(config.derFilePath);

Switch back to clearing individual collections


import React from 'react'
import PropTypes from 'prop-types'
import Popover from '@material-ui/core/Popover'
import { withTheme } from '@material-ui/core/styles'

const DashboardPopover = props => {
  const { anchorEl, children, onClose, open, theme, ...otherProps } = props
  return (
    <Popover
      open={open}
      anchorEl={anchorEl}
      onClose={onClose}
      anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
      transformOrigin={{ horizontal: 'right', vertical: 'top' }}
      {...otherProps}
    >
      <div
        style={{
          width: '100%',
          height: 3,
          // TODO: add tests that we're using this MUI theme color
          backgroundColor: theme.palette.primary.main,
        }}
      />
      {children}
    </Popover>
  )
}

DashboardPopover.propTypes = {
  anchorEl: PropTypes.object,
  children: PropTypes.oneOfType([
    PropTypes.arrayOf(PropTypes.node),
    PropTypes.node,
  ]),
  onClose: PropTypes.func,
  open: PropTypes.bool,
  theme: PropTypes.object.isRequired,
}

DashboardPopover.defaultProps = {
  open: false,
  anchorEl: null,
  onClose: () => {},
}

export default withTheme()(DashboardPopover)

import React from 'react'
import PropTypes from 'prop-types'
import Popover from '@material-ui/core/Popover'
import { withTheme } from '@material-ui/core/styles'

const DashboardPopover = props => {
  const { anchorEl, children, onClose, open, theme, ...otherProps } = props
  return (
    <Popover
      open={open}
      anchorEl={anchorEl}
      onClose={onClose}
      anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
      transformOrigin={{ horizontal: 'right', vertical: 'top' }}
      {...otherProps}
    >
      <div
        style={{
          width: '100%',
          height: 3,
          // We should test that we're using this MUI theme color
          // (e.g., see them tests in MoneyRaisedComponent). Skipping
          // for now because testing portal components with Enzyme
          // appears to still be a pain:
          // https://github.com/airbnb/enzyme/issues/252#issuecomment-266125422
          // https://github.com/mui-org/material-ui/issues/14342
          backgroundColor: theme.palette.primary.main,
        }}
      />
      {children}
    </Popover>
  )
}

DashboardPopover.propTypes = {
  anchorEl: PropTypes.object,
  children: PropTypes.oneOfType([
    PropTypes.arrayOf(PropTypes.node),
    PropTypes.node,
  ]),
  onClose: PropTypes.func,
  open: PropTypes.bool,
  theme: PropTypes.object.isRequired,
}

DashboardPopover.defaultProps = {
  open: false,
  anchorEl: null,
  onClose: () => {},
}

export default withTheme()(DashboardPopover)

Add note about a test to implement later


import window from 'global';
import React, { Component } from 'react';
import PropTypes from 'prop-types';

// this component renders an iframe, which gets updates via post-messages
export class IFrame extends Component {
  iframe = null;

  componentDidMount() {
    const { id } = this.props;
    this.iframe = window.document.getElementById(id);
  }

  shouldComponentUpdate(nextProps) {
    const { scale } = this.props;
    return scale !== nextProps.scale;
  }

  componentDidUpdate(prevProps) {
    const { scale } = this.props;
    if (scale !== prevProps.scale) {
      this.setIframeBodyStyle({
        width: `${scale * 100}%`,
        height: `${scale * 100}%`,
        transform: `scale(${1 / scale})`,
        transition: 'transform .2s ease-out, height .2s ease-out, width .2s ease-out',
        transformOrigin: 'top left',
      });
    }
  }

  setIframeBodyStyle(style) {
    return Object.assign(this.iframe.contentDocument.body.style, style);
  }

  render() {
    const { id, title, src, allowFullScreen, scale, ...rest } = this.props;
    return (
      <iframe
        scrolling="yes"
        id={id}
        title={title}
        src={src}
        allowFullScreen={allowFullScreen}
        {...rest}
      />
    );
  }
}
IFrame.propTypes = {
  id: PropTypes.string.isRequired,
  title: PropTypes.string.isRequired,
  src: PropTypes.string.isRequired,
  allowFullScreen: PropTypes.bool.isRequired,
  scale: PropTypes.number.isRequired,
};

import window from 'global';
import React, { Component } from 'react';
import PropTypes from 'prop-types';

// this component renders an iframe, which gets updates via post-messages
export class IFrame extends Component {
  iframe = null;

  componentDidMount() {
    const { id } = this.props;
    this.iframe = window.document.getElementById(id);
  }

  shouldComponentUpdate(nextProps) {
    const { scale } = this.props;
    return scale !== nextProps.scale;
  }

  componentDidUpdate(prevProps) {
    const { scale } = this.props;
    if (scale !== prevProps.scale) {
      this.setIframeBodyStyle({
        width: `${scale * 100}%`,
        height: `${scale * 100}%`,
        transform: `scale(${1 / scale})`,
        transformOrigin: 'top left',
      });
    }
  }

  setIframeBodyStyle(style) {
    return Object.assign(this.iframe.contentDocument.body.style, style);
  }

  render() {
    const { id, title, src, allowFullScreen, scale, ...rest } = this.props;
    return (
      <iframe
        scrolling="yes"
        id={id}
        title={title}
        src={src}
        allowFullScreen={allowFullScreen}
        {...rest}
      />
    );
  }
}
IFrame.propTypes = {
  id: PropTypes.string.isRequired,
  title: PropTypes.string.isRequired,
  src: PropTypes.string.isRequired,
  allowFullScreen: PropTypes.bool.isRequired,
  scale: PropTypes.number.isRequired,
};

REMOVE the animation, as it's very awkward


'use strict';

const { toSentence } = require('underscore.string');

// Turn ['a', 'b', 'c'] into 'a, b or c'
const getWordsList = function (
  words,
  {
    op = 'or',
    quotes = false,
    json = false,
  } = {},
) {
  if (words.length === 0) { return ''; }

  const wordsA = jsonStringify(words, { json });
  const wordsB = quoteWords(wordsA, { quotes });
  const wordsC = toSentence(wordsB, ', ', `, ${op} `);
  return wordsC;
};

const jsonStringify = function (words, { json }) {
  if (!json) { return words; }

  return words.map(JSON.stringify);
};

const quoteWords = function (words, { quotes }) {
  if (!quotes) { return words; }

  return words.map(word => `'${word}'`);
};

module.exports = {
  getWordsList,
};

'use strict';

const { toSentence } = require('underscore.string');

// Turn ['a', 'b', 'c'] into 'a, b or c'
const getWordsList = function (
  words,
  {
    op = 'or',
    quotes = false,
    json = false,
  } = {},
) {
  if (words.length === 0) { return ''; }

  const wordsA = jsonStringify(words, { json });
  const wordsB = quoteWords(wordsA, { quotes });
  const wordsC = toSentence(wordsB, ', ', ` ${op} `);
  return wordsC;
};

const jsonStringify = function (words, { json }) {
  if (!json) { return words; }

  return words.map(JSON.stringify);
};

const quoteWords = function (words, { quotes }) {
  if (!quotes) { return words; }

  return words.map(word => `'${word}'`);
};

module.exports = {
  getWordsList,
};

Remove comma before 'and' in error messages


import { parse } from 'papaparse'
import {
    compose, filter, find, flatten, identity,
    join, map, match, not, drop, takeWhile
} from 'ramda'

const clean = compose(filter(identity), flatten)

const dataRegexp = match(RegExp("^(\\d{4}|Year)$"))

function isData (row) {
    return !!find(dataRegexp, row)
}

function getRows (csv) {
    const parsed = parse(csv, {
        dynamicTyping: false,
        header: false,
    })

    return parsed.data
}

function splitHeaders (rows) {
    const header = takeWhile(not(isData), rows)
    const content = takeWhile(isData, drop(header.length, rows))
    const footer = drop(header.length + content.length, rows)

    return {
        content,
        header: clean(header),
        footer: clean(footer),
    }
}

export function cleanup (rawData) {
    let {header, content, footer} = splitHeaders(getRows(rawData))
    const text = join('\n', map(join('\t'), content))

    // Reparse data rows with dynamic typing
    const reparsed = parse(text, {
        header: true,
        dynamicTyping: true,
    })

    if (reparsed.errors.length > 0) {
        console.error("Errors while parsing raw data:", reparsed.errors)
    }

    return Object.assign(reparsed, {header, footer})
}

import { parse } from 'papaparse'
import {
    compose, filter, find, flatten, identity,
    join, map, complement, drop, takeWhile, test
} from 'ramda'

const clean = compose(filter(identity), flatten)

const dataRegexp = test(RegExp("^(\\d{4}|Year)$"))

function isData (row) {
    return !!find(dataRegexp, row)
}

function getRows (csv) {
    const parsed = parse(csv, {
        dynamicTyping: false,
        header: false,
    })

    return parsed.data
}

function splitHeaders (rows) {
    const header = takeWhile(complement(isData), rows)
    const content = takeWhile(isData, drop(header.length, rows))
    const footer = drop(header.length + content.length, rows)

    return {
        content,
        header: clean(header),
        footer: clean(footer),
    }
}

export function cleanup (rawData) {
    let {header, content, footer} = splitHeaders(getRows(rawData))
    const text = join('\n', map(join('\t'), content))

    // Reparse data rows with dynamic typing
    const reparsed = parse(text, {
        header: true,
        dynamicTyping: true,
    })

    if (reparsed.errors.length > 0) {
        console.error("Errors while parsing raw data:", reparsed.errors)
    }

    return Object.assign(reparsed, {header, footer})
}

Fix errors caused by updating Ramda


import {graphql} from 'graphql';

import todoSchema from './graphql/schema';

function handlePost(req, res) {
  graphql(todoSchema, req.body).then((result) => {
    res.send(result);
  });
}

export default (app) => {
  app.post('/api', handlePost)
};

import {graphql} from 'graphql';

import todoSchema from './graphql/schema';

function ensureAuthTokenPresent(req, res, next) {
	const authToken = req.get('Auth');
	if(authToken && authToken.length) {
		req.authToken = authToken;
		return next();
	}
	return res.status(403).json({ error: 'Auth token not found in request' });
}

function handlePost(req, res) {
	const context = { authToken: req.authToken }
	const query = req.body;
  graphql(todoSchema, query, context).then((result) => {
    res.send(result);
  });
}

export default (app) => {
  app.post('/api', ensureAuthTokenPresent, handlePost)
};

Add basic Auth token presence check


// npm dependencies
const express = require('express');
const hbs = require('hbs');

// local packages
let {mongoose} = require('./db/mongoose');
let {Product} = require('./db/models/products');

// instantiate Express.js
const app = express();

// Tell Handlebars where to look for partials
hbs.registerPartials(__dirname +  '../../views/partials');

// Set Handlebars as default templating engine
app.set('view engine', 'hbs');

// Point app towards stylesheets
app.use(express.static(__dirname + '/public'));

// root route
app.get('/', (req, res) => {
  res.render('home.hbs', {
    pageTitle: 'Home Page'
  });
});

// route for e-commerce site
app.get('/shop', (req, res) => {
  res.render('shop.hbs', {
    pageTitle: 'E-Commerce Shop'
  });
});

// Specify port and run local server
let port = 3000;

app.listen(port, () => {
  console.log(`listening on ${port}`);
});

module.exports = {
  app
}

// npm dependencies
const express = require('express');
const hbs = require('hbs');
const bodyParser = require('body-parser');

// local packages
let {mongoose} = require('./db/mongoose');
let {Product} = require('./models/products');

// instantiate Express.js
const app = express();

// Tell Handlebars where to look for partials
hbs.registerPartials(__dirname +  '../../views/partials');

// Set Handlebars as default templating engine
app.set('view engine', 'hbs');

// Point app towards stylesheets
app.use(express.static(__dirname + '/public'));

// Allows for JSON-formatted POST requests
app.use(bodyParser.json());

// root route
app.get('/', (req, res) => {
  res.render('home.hbs', {
    pageTitle: 'Home Page'
  });
});

// route for e-commerce site
app.get('/shop', (req, res) => {
  res.render('shop.hbs', {
    pageTitle: 'E-Commerce Shop'
  });
});

// API endpoint for seeding product data
app.post('/shop/products', (req, res) => {
  let product = new Product({
    title: req.body.title,
    description: req.body.description,
    price: req.body.price
  });

  product.save()
    .then((doc) => {
      res.send(doc);
    }, (e) => {
      res.send('Could not add product');
    });
})

app.get('/shop/products', (req, res) => {
  Product.find()
    .then((products) => {
      res.send(products);
    }, (e) => {
      rest.send('Could not retrieve products');
    });
});

// Specify port and run local server
let port = 3000;

app.listen(port, () => {
  console.log(`listening on ${port}`);
});

module.exports = {
  app
}

Add routes for posting new products and retrieving list of products


import Ember from 'ember';

export default function() {
  this.get('/slack/users.list', function(db, request) {
    if (Ember.isNone(request.params.token)) {
      return { ok: false, error: 'not_authed' };
    }

    if (request.params.token === 'invalid') {
      return { ok: false, error: 'invalid_auth' };
    }

    return { ok: true, members: db.users };
  });
}

import Ember from 'ember';

function invalidTokenResponse(token) {
  if (Ember.isNone(token)) {
    return { ok: false, error: 'not_authed' };
  }

  if (token === 'invalid') {
    return { ok: false, error: 'invalid_auth' };
  }
}

export default function() {
  this.get('/slack/users.list', function(db, request) {
    let response = invalidTokenResponse(request.params.token);
    if (!Ember.isNone(response)) { return response; }

    return { ok: true, members: db.users };
  });

  this.get('/slack/users.info', function(db, request) {
    let response = invalidTokenResponse(request.params.token);
    if (!Ember.isNone(response)) { return response; }

    let user = db.users.find(request.params.user);
    if (Ember.isNone(user)) {
      return { ok: false, error: 'user_not_found' };
    }

    // FIXME: no support for errors 'user_not_visible' and 'account_inactive'

    return { ok: true, user: user };
  });
}

Add mirage route for Slack user.info endpoint


// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

var fs = require('fs');
var files = ['base.js', 'controls.js', 'index.js', 'libembed-amd.js', 'embed-amd-render.js'];
var output = files.map((f)=>{
    return fs.readFileSync('./dist/amd/'+f).toString();
  }).join(';\n\n');
fs.writeFileSync('./dist/embed-amd.js', output)

// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

var fs = require('fs');

// Make a script file that defines all of the relevant AMD modules
var files = ['base.js', 'controls.js', 'index.js', 'libembed-amd.js'];
var output = files.map((f)=>{
    return fs.readFileSync('./dist/amd/'+f).toString();
  }).join(';\n\n');
fs.writeFileSync('./dist/libembed-amd.js', output)

// Make a script that has all of the above AMD modules and runs a function which
// renders all of the widgets on page load automatically.
files = ['base.js', 'controls.js', 'index.js', 'libembed-amd.js', 'embed-amd-render.js'];
var output = files.map((f)=>{
    return fs.readFileSync('./dist/amd/'+f).toString();
  }).join(';\n\n');
fs.writeFileSync('./dist/embed-amd.js', output)

Reorganize the amd build script.


module.exports = function(data){

	// If this is not a CanJS project
	if(typeof can === "undefined" || !can.view) {
		return {};
	}

	var oldCanImport;

	var canImport = function(el, tagData){
		var moduleName = el.getAttribute("from");
		var templateModule = tagData.options.attr("helpers.module");
		var parentName = templateModule ? templateModule.id : undefined;

		var isAPage = !!tagData.subtemplate;
		var loader = doneSsr.loader;
		loader.normalize(moduleName, parentName).then(function(name){
			if(isAPage) {
				doneSsr.loader.__ssrParentMap[name] = false;
			}
			var pages = data.pages;
			if(!pages) {
				pages = data.pages = [];
			}
			pages.push(name);
		});

		oldCanImport.apply(this, arguments);
	};

	return {
		beforeTask: function(){
			oldCanImport = can.view.callbacks._tags["can-import"];
			can.view.callbacks._tags["can-import"] = canImport;
		},
		afterTask: function(){
			can.view.callbacks._tags["can-import"] = oldCanImport;
		}
	};
};

module.exports = function(data){

	// If this is not a CanJS project
	if(typeof can === "undefined" || !can.view) {
		return {};
	}

	var oldCanImport;

	var canImport = function(el, tagData){
		var moduleName = el.getAttribute("from");
		var templateModule = tagData.options.attr("helpers.module");
		var parentName = templateModule ? templateModule.id : undefined;

		var isAPage = !!tagData.subtemplate;
		var loader = doneSsr.loader;
		loader.normalize(moduleName, parentName).then(function(name){
			if(isAPage) {
				loader.__ssrParentMap[name] = false;
			}
			var pages = data.pages;
			if(!pages) {
				pages = data.pages = [];
			}
			pages.push(name);
		});

		oldCanImport.apply(this, arguments);
	};

	return {
		beforeTask: function(){
			oldCanImport = can.view.callbacks._tags["can-import"];
			can.view.callbacks._tags["can-import"] = canImport;
		},
		afterTask: function(){
			can.view.callbacks._tags["can-import"] = oldCanImport;
		}
	};
};

Use the loader from the scope


var tokenizer = require('./tokens'),
	parser = require('./parser'),
	sourceCode = require('fs').readFileSync('./example_code.fun').toString()

require('./underscore')

var tokens = tokenizer.tokenize(sourceCode, ['let','for','if','in'], '=<>', '=')
console.log('\nTokens:')
console.log(_.map(tokens, function(token){ return token.type+' '+token.value }))

var ast = parser.parse(tokens)
console.log('\nAST:')
console.log(JSON.stringify(ast))

// TODO import compiler and compile the ast to js

Add TODO for compiling the AST into JS output


const replaceDescendantNode = require('../replaceDescendantNode');
const Relation = require('./Relation');

class JavaScriptPaintWorklet extends Relation {
  get href() {
    return this.node.value;
  }

  set href(href) {
    this.node.value = href;
  }

  inline() {
    super.inline();
    const newNode = { type: 'Literal', value: this.to.dataUrl };
    replaceDescendantNode(this.parentNode, this.node, newNode);
    this.node = newNode;
    this.from.markDirty();
    return this;
  }

  attach() {
    throw new Error('JavaScriptPaintWorklet.attach(): Not implemented');
  }

  detach() {
    throw new Error('JavaScriptPaintWorklet.detach(): Not implemented');
  }
}

module.exports = JavaScriptPaintWorklet;

const replaceDescendantNode = require('../replaceDescendantNode');
const Relation = require('./Relation');

class JavaScriptPaintWorklet extends Relation {
  get href() {
    return this.node.value;
  }

  set href(href) {
    this.node.value = href;
  }

  inline() {
    super.inline();
    const newNode = { type: 'Literal', value: this.to.dataUrl };
    replaceDescendantNode(this.parentNode, this.node, newNode);
    this.node = newNode;
    this.from.markDirty();
    return this;
  }

  attach() {
    throw new Error('JavaScriptPaintWorklet.attach(): Not implemented');
  }

  detach() {
    throw new Error('JavaScriptPaintWorklet.detach(): Not implemented');
  }
}

JavaScriptPaintWorklet.prototype.targetType = 'JavaScript';

module.exports = JavaScriptPaintWorklet;

Add targetType to JavaScripPaintWorklet prototype