define([

  'Backbone',

  'text!templates/project-page/playerTemplate.html'
], function(

  Backbone,

  playerTemplate
){

  var PlayersController = Backbone.View.extend({

    template: _.template(playerTemplate),

    initialize: function(){
      this.collection.off();
      this.options.message.off();

      this.collection.on('add remove', this.render, this);
      this.options.project.on('change', this.render, this);
      this.options.message.on('change', this.showMessage, this);
    },

    showMessage: function(){
      var message = this.options.message.get('message');
      this.$('.js-message').text(message);
    },

    render: function(){
      this.$('.list').html('');
      var template = this.template;
      var project  = this.options.project.get('name');
      var players  = this.collection.where({project: project});
      var $list    = this.$('.list');

      _.each(players, function(player){
        $list.append(template({player: player.get('email')}));
      });
    }
  });

  return PlayersController;
});

define([

  'Backbone',

  'text!templates/project-page/playerTemplate.html'
], function(

  Backbone,

  playerTemplate
){

  var PlayersController = Backbone.View.extend({

    template: _.template(playerTemplate),

    initialize: function(){
      this.collection.off();

      this.collection.on('add remove', this.render, this);
      this.options.project.on('change', this.render, this);
    },

    render: function(){
      this.$('.list').html('');
      var template = this.template;
      var project  = this.options.project.get('name');
      var players  = this.collection.where({project: project});
      var $list    = this.$('.list');

      _.each(players, function(player){
        $list.append(template({player: player.get('email')}));
      });

      //Display last message
      this.options.socket.message.trigger('change');
    }
  });

  return PlayersController;
});

Remove message logic from player controller


"use strict";

export function isTopicValid(topic){
  const MAX_TITLE_LENGTH = 20;

  let isValid = !(topic.title.length > MAX_TITLE_LENGTH || topic.author.isBlocked);
  return isValid;
}

export function isEmailAuthorized(email){
  const EMAIL_DOMAIN = "@codeschool.com";
  return email.indexOf(EMAIL_DOMAIN) > 0;
}

"use strict";

function isTopicValid(topic){
  const MAX_TITLE_LENGTH = 20;

  let isValid = !(topic.title.length > MAX_TITLE_LENGTH || topic.author.isBlocked);
  return isValid;
}

function isEmailAuthorized(email){
  const EMAIL_DOMAIN = "@codeschool.com";
  return email.indexOf(EMAIL_DOMAIN) > 0;
}

export {isTopicValid, isEmailAuthorized};

Refactor the code to use named exports


import React from 'react';
import './status-button.scss';

class StatusButton extends React.Component {
  getClass() {
    return `button -${this.props.type}`;
  }
  // Ideal setStatus would be defined on this component and would set some sort of global state via redux
  render() {
    return (
      <button className={this.getClass()} onClick={() => this.props.setStatus(this.props.type)}>{this.props.type}</button>
    )
  }
}

export default StatusButton;

import React from 'react';
import './status-button.scss';
import classnames from 'classnames';

class StatusButton extends React.Component {
  // Ideal setStatus would be defined on this component and would set some sort of global state via redux
  render() {
    return (
      <button className={classnames('button', `-${this.props.type}`)} onClick={() => this.props.setStatus(this.props.type)}>{this.props.type}</button>
    )
  }
}

export default StatusButton;

Use classnames helper to generate class string


function deleteApp(appName) {
    var result = window.confirm('Do you want to delete this app?' + "\n\n" + appName);
    if (result)
    {
        window.location = '/openmrs/module/owa/deleteApp.htm?appName=' + appName;
    }
}

function deleteApp(appName) {
    var result = window.confirm('Do you want to delete this app?' + "\n\n" + appName);
    if (result)
    {
        window.location = window.location.pathname.replace("manage.form","deleteApp.htm?appName=" + appName);
    }
}

Correct the delete app functionality


const expect = require('chai').expect

const workloadCapacityHelper = require('../helpers/data/aggregated-data-helper')

var workloadOwnerIds = []

describe('View overview', () => {
  before(function () {
    return workloadCapacityHelper.selectIdsForWorkloadOwner()
      .then(function (results) {
        workloadOwnerIds = results
      })
  })

  it('should navigate to the overview page', () => {
    return browser.url('/offender-manager/' + workloadOwnerIds.filter((item) => item.table === 'workload_owner')[0].id + 'overview')
      .waitForExist('.breadcrumbs')
      .waitForExist('.sln-subnav')
      .waitForExist('.sln-grade')
      .getText('.sln-grade')
      .then(function (text) {
        expect(text).to.equal(workloadOwnerIds.filter((item) => item.table === 'workload_owner')[0].grade)
      })
  })
})

const expect = require('chai').expect

const dataHelper = require('../helpers/data/aggregated-data-helper')

var workloadOwnerIds = []

describe('View overview', () => {
  before(function () {
    return dataHelper.selectIdsForWorkloadOwner()
      .then(function (results) {
        workloadOwnerIds = results
      })
  })

  it('should navigate to the overview page', () => {
    return browser.url('/offender-manager/' + workloadOwnerIds.filter((item) => item.table === 'workload_owner')[0].id + 'overview')
      .waitForExist('.breadcrumbs')
      .waitForExist('.sln-subnav')
      .waitForExist('.sln-grade')
      .getText('.sln-grade')
      .then(function (text) {
        expect(text).to.equal(workloadOwnerIds.filter((item) => item.table === 'workload_owner')[0].grade)
      })
  })
})

Update variable name based on review comment


'use strict'

const serilog = require('../src/core/structured-log.js');
const serilogCompactSink = require('../src/npm/serilog-compact-console-sink.js');
const assert = require('assert');
require('mocha-sinon');

describe('SerilogCompactConsoleSink', function() {
    const consoleInfoOutput = []
    const originalConsoleInfo = console.info

    beforeEach(function() {
        console.info = function() {
            consoleInfoOutput.push([].slice.call(arguments))
        }
    })

    afterEach(() => {
        console.info = originalConsoleInfo
    })

    it('should output logs to console as JSON', (done) => {
        const log = serilog.configure()
            .writeTo(serilogCompactSink())
            .create()

        const messageTemplate = 'Hello, {Name}!'
        const name = 'world'

        log.info(messageTemplate, name)

        assert(consoleInfoOutput[0][0]['@mt'] === messageTemplate)
        assert(consoleInfoOutput[0][0]['Name'] === name)

        done()
    })
})

var serilog = require('../src/core/structured-log.js');
var serilogCompactSink = require('../src/npm/serilog-compact-console-sink.js');
var assert = require('assert');

describe('SerilogCompactConsoleSink', function() {
    var consoleInfoOutput = []
    var originalConsoleInfo = console.info

    beforeEach(function() {
        console.info = function() {
            consoleInfoOutput.push([].slice.call(arguments))
        }
    })

    afterEach(function() {
        console.info = originalConsoleInfo
    })

    it('should output logs to console as JSON', function(done) {
        var log = serilog.configure()
            .writeTo(serilogCompactSink())
            .create()

        var messageTemplate = 'Hello, {Name}!'
        var name = 'world'

        log.info(messageTemplate, name)

        assert(consoleInfoOutput[0][0]['@mt'] === messageTemplate)
        assert(consoleInfoOutput[0][0]['Name'] === name)

        done()
    })
})

Update code to be compliant with node 0.10


/**
 * Created by Tomasz Gabrysiak on 2016-03-12.
 */

var gulp = require('gulp');
var webserver = require('gulp-webserver');

gulp.task('webserver', function () {
  gulp.src('./app/')
    .pipe(webserver({
      host: 'localhost',
      port: '8000',
      livereload: true,
      directoryListing: false
    }));
});

gulp.task('default', ['webserver']);

/**
 * Created by Tomasz Gabrysiak on 2016-03-12.
 */

var gulp = require('gulp');
var webserver = require('gulp-webserver');

gulp.task('webserver', function () {
  gulp.src('./app/')
    .pipe(webserver({
      host: 'localhost',
      port: '8080',
      livereload: true,
      directoryListing: false
    }));
});

gulp.task('default', ['webserver']);

Use different port in frontend


import {
  FILTER_BY_AREA_TYPE,
  FILTER_BY_AREA_IDENTIFIER,
  FILTER_BY_AREA_CUSTOM,
} from '../constants/action_types';

const defaultState = {
  area: 'Citywide',
  identifier: undefined,
  latLons: []
};

export default (state = defaultState, action) => {
  switch (action.type) {
    case FILTER_BY_AREA_TYPE:
      return {
        ...state,
        geo: action.geo,
      };

    case FILTER_BY_AREA_IDENTIFIER:
      return {
        ...state,
        identifier: action.identifier,
      };

    case FILTER_BY_AREA_CUSTOM:
      return {
        ...state,
        latLons: action.latLons
      };

    default:
      return state;
  }
};

import {
  FILTER_BY_AREA_TYPE,
  FILTER_BY_AREA_IDENTIFIER,
  FILTER_BY_AREA_CUSTOM,
} from '../constants/action_types';

const defaultState = {
  area: 'Citywide',
  identifier: undefined,
  latLons: []
};

export default (state = defaultState, action) => {
  switch (action.type) {
    case FILTER_BY_AREA_TYPE:
      return {
        ...state,
        geo: action.geo,
        identifier: undefined,
      };

    case FILTER_BY_AREA_IDENTIFIER:
      return {
        ...state,
        identifier: action.identifier,
      };

    case FILTER_BY_AREA_CUSTOM:
      return {
        ...state,
        latLons: action.latLons
      };

    default:
      return state;
  }
};

Set identifier to undefined when setting geo in area filter reducer


var P = require('bluebird');

var MINUTE = 60; // seconds
var MODIFIED_TTL = 1 * MINUTE;
var DEPENDENTS_TTL = 30 * MINUTE;

module.exports = function(request, reply) {
  var Package = require("../models/package").new(request);
  var Download = require("../models/download").new({
    request: request,
    cache: require("../lib/cache")
  });
  var context = {
    explicit: require("npm-explicit-installs")
  };

  var actions = {
    modified: Package.list({
      sort: "modified",
      count: 12
    }, MODIFIED_TTL),
    dependents: Package.list({
      sort: "dependents",
      count: 12
    }, DEPENDENTS_TTL),
    downloads: Download.getAll(),
    totalPackages: Package.count().catch(function(err) {
      request.logger.error(err);
      return null;
    }),
  };

  P.props(actions).then(function(results) {
    context.modified = results.modified;
    context.dependents = results.dependents;
    context.downloads = results.downloads;
    context.totalPackages = results.totalPackages;

    reply.view('homepage', context);
  }).catch(function(err) {
    request.logger.error(err);
    reply.view('errors/internal', err);
  });
};

var P = require('bluebird');

var feature = require('../lib/feature-flags.js');

var MINUTE = 60; // seconds
var MODIFIED_TTL = 1 * MINUTE;
var DEPENDENTS_TTL = 30 * MINUTE;

module.exports = function(request, reply) {
  var Package = require("../models/package").new(request);
  var Download = require("../models/download").new({
    request: request,
    cache: require("../lib/cache")
  });
  var context = {
    explicit: require("npm-explicit-installs")
  };

  var actions = {};

  actions.modified = Package.list({
    sort: "modified",
    count: 12
  }, MODIFIED_TTL);
  actions.dependents = Package.list({
    sort: "dependents",
    count: 12
  }, DEPENDENTS_TTL);

  if (!feature('npmo')) {
    actions.downloads = Download.getAll();
    actions.totalPackages = Package.count().catch(function(err) {
      request.logger.error(err);
      return null;
    });
  }

  P.props(actions).then(function(results) {
    context.modified = results.modified;
    context.dependents = results.dependents;
    context.downloads = results.downloads;
    context.totalPackages = results.totalPackages;

    reply.view('homepage', context);
  }).catch(function(err) {
    request.logger.error(err);
    reply.view('errors/internal', err);
  });
};

Put download stats behind !npmo feature flag


var $ = require('jquery');

var ActionsUpdater = function($changelist) {
    this.$changelist = $changelist;
};

ActionsUpdater.prototype = {
    removeLabel: function($actions) {
        var $input = $actions.find('[name="action"]').first();

        if ($input.length == 0) {
            return;
        }

        var $label = $($input[0].previousSibling);

        if ($label.get(0).nodeType == 3) {
            $label.remove();
        }
    },
    wrapLabels: function($actions) {
        var $wrapper = $('<div>').addClass('labels');
        $actions.find('span.all, span.action-counter, span.clear, span.question')
                .wrapAll($wrapper);
    },
    moveActions: function($actions) {
        var $paginator = this.$changelist.find('.paginator');
        var $wrapper = $('<div>').addClass('changelist-footer');

        $wrapper.insertAfter($paginator);

        $actions.detach();
        $paginator.detach();

        $wrapper.append($actions);
        $wrapper.append($paginator);
    },
    run: function() {
        var $actions = this.$changelist.find('.actions');

        try {
            this.removeLabel($actions);
            this.wrapLabels($actions);
            this.moveActions($actions);
        } catch (e) {
            console.error(e, e.stack);
        }

        $actions.addClass('initialized');
    }
};

$(document).ready(function() {
    $('#changelist').each(function() {
        new ActionsUpdater($(this)).run();
    });
});

var $ = require('jquery');

var ActionsUpdater = function($changelist) {
    this.$changelist = $changelist;
};

ActionsUpdater.prototype = {
    removeLabel: function($actions) {
        var $input = $actions.find('[name="action"]').first();

        if ($input.length == 0) {
            return;
        }

        var $label = $($input[0].previousSibling);

        if ($label.get(0).nodeType == 3) {
            $label.remove();
        }
    },
    wrapLabels: function($actions) {
        var $wrapper = $('<div>').addClass('labels');
        $actions.find('span.all, span.action-counter, span.clear, span.question')
                .wrapAll($wrapper);
    },
    moveActions: function($actions) {
        var $paginator = this.$changelist.find('.paginator');
        var $wrapper = $('<div>').addClass('changelist-footer');

        $wrapper.insertAfter($paginator);

        $actions.detach();
        $paginator.detach();

        $wrapper
            .append($actions)
            .append($paginator)
            .append($('<div>').addClass('cf'));
    },
    run: function() {
        var $actions = this.$changelist.find('.actions');

        try {
            this.removeLabel($actions);
            this.wrapLabels($actions);
            this.moveActions($actions);
        } catch (e) {
            console.error(e, e.stack);
        }

        $actions.addClass('initialized');
    }
};

$(document).ready(function() {
    $('#changelist').each(function() {
        new ActionsUpdater($(this)).run();
    });
});

Fix changelist footer fixed transition


//OpenWeatherMap-JQuery
(function( $ ) {

  var settings = {};
  var defer = $.Deferred();

  // Plugin definition.
  $.OpenWeatherMap = function( options ) {
    settings = options;
    getConditions(settings);
    return defer.promise();
  };

  function getConditions( options ){
    $.getJSON( "http://api.openweathermap.org/data/2.5/weather", settings )
    .done(function(data){
      $.extend(data, {
        isDay:( data.dt > data.sys.sunrise && data.dt < data.sys.sunset ) ? true : false
      });
      defer.resolve(data);
    })
    .fail(function(data){
      defer.reject(data);
    });
  }

})( jQuery );

//OpenWeatherMap-JQuery
(function( $ ) {

  var settings = {};
  var defer = null;

  // Plugin definition.
  $.OpenWeatherMap = function( options ) {
    settings = options;
    defer = $.Deferred();
    getConditions(settings);
    return defer.promise();
  };

  function getConditions( options ){
    $.getJSON( "http://api.openweathermap.org/data/2.5/weather", settings )
    .done(function(data){
      $.extend(data, {
        isDay:( data.dt > data.sys.sunrise && data.dt < data.sys.sunset ) ? true : false
      });
      defer.resolve(data);
    })
    .fail(function(data){
      defer.reject(data);
    });
  }

})( jQuery );

Update defer to work with multiple calls


import Ember from 'ember';
import startApp from '../helpers/start-app';

var App;

module('Acceptance: Aviary Editor', {
  setup: function() {
    App = startApp();
  },
  teardown: function() {
    Ember.run(App, 'destroy');
  }
});

test('renders component', function() {
  expect(1);
  visit('/');

  andThen(function() {
    equal(find('.edit-icon').length, 1, 'Renders aviary-editor component');
  });
});

import Ember from 'ember';
import startApp from '../helpers/start-app';

var App;

module('Acceptance: Aviary Editor', {
  setup: function() {
    App = startApp();
  },
  teardown: function() {
    Ember.run(App, 'destroy');
  }
});

test('renders component', function() {
  expect(2);
  visit('/');

  andThen(function() {
    equal(find('.edit-icon').length, 1, 'Aviary editor component was rendered.');
    click('.edit-icon');
  });

  andThen(function() {
    equal(find('.edit-icon.active').length, 1, 'Aviary editor launched.');
  });
});

Add test to look for class binding when Aviary Editor has launched


Comments = new Mongo.Collection('comments');

Meteor.methods({
  commentInsert: function(commentAttributes) {
    check(this.userId, String);
    check(commentAttributes, {
      postId: String,
      body: String
    });

    var user = Meteor.user();
    var post = Posts.findOne(commentAttributes.postId);

    if (!post) {
      throw new Meteor.Error('invalid-comment', 'You must comment on a post');
    }

    comment = _.extend(commentAttributes, {
      userId: user._id,
      author: user.username,
      submitted: new Date()
    });

    Posts.update(comment.postId, { $inc: { commentsCount: 1 }});

    return Comments.insert(comment);
  }
});

Comments = new Mongo.Collection('comments');

Meteor.methods({
  commentInsert: function(commentAttributes) {
    check(this.userId, String);
    check(commentAttributes, {
      postId: String,
      body: String
    });

    var user = Meteor.user();
    var post = Posts.findOne(commentAttributes.postId);

    if (!post) {
      throw new Meteor.Error('invalid-comment', 'You must comment on a post');
    }

    comment = _.extend(commentAttributes, {
      userId: user._id,
      author: user.username,
      submitted: new Date()
    });

    Posts.update(comment.postId, { $inc: { commentsCount: 1 }});
    comment._id = Comments.insert(comment);
    createCommentNotification(comment);

    return Comments._id;
  }
});

Modify commentInsert to add notifications after commenting


$(document).ready(function() {
    $('#dataTable').DataTable();
} );

$(document).ready(function() {
    $('#dataTable').dataTable( {
        language: {
            search: "_INPUT_",
            searchPlaceholder: "Search"
        }
    } );
} );

Add html placeholder option to search input


/*global angular, removeLocationFromFilter*/
/*jslint nomen: true, es5: true */
angular.module('slideshows').directive('slideshowSlidesHeightSetter', ['$window', '$timeout', '$rootScope', function ($window, $timeout, $rootScope) {
    'use strict';

    function link(scope, element, attrs) {
        var window = angular.element($window);
        var top = attrs.top;
        var footerHeight = attrs.footerHeight;
        var me = this;

        var update = function () {
            $timeout.cancel(lastTimeout);
            lastTimeout = $timeout(function () {
                var newHeight = window.height() - top - footerHeight,
                    overflowY = 'hidden',
                    slidesDivHeight = element.children().first().height();
                element.height(newHeight);
                if (slidesDivHeight > newHeight) {
                    overflowY = 'scroll';
                }
                if (!$rootScope.$$phase) {
                    $rootScope.$apply();
                }
                element.css('overflow-Y', overflowY);
            });
        };

        var lastTimeout;
        var onResize = function () {
            update();
        };

        window.on('resize', onResize);

        update();

        scope.$on('$destroy', function () {
            window.off('resize', onResize);
        });

        scope.$watch('slidesHeightSetterStrategy().getSlides().length', function () {
            update();
            scope.slidesHeightSetterStrategy().reselectCurrentSlideToFixChormeHeightCalculationBug();
        });
    }

    return {
        link: link,
        scope: {
            slidesHeightSetterStrategy: '&',
            slideshow: '&'
        }
    };
}]);

/*global angular, removeLocationFromFilter*/
/*jslint nomen: true, es5: true */
angular.module('slideshows').directive('slideshowSlidesHeightSetter', ['$window', '$timeout', '$rootScope', function ($window, $timeout, $rootScope) {
    'use strict';

    function link(scope, element, attrs) {
        var window = angular.element($window);
        var top = attrs.top;
        var footerHeight = attrs.footerHeight;
        var me = this;

        var update = function () {
            $timeout.cancel(lastTimeout);
            lastTimeout = $timeout(function () {
                var newHeight = window.height() - top - footerHeight,
                    overflowY = 'hidden',
                    slidesDivHeight = element.children().first().height();
                element.height(newHeight);
                if (slidesDivHeight > newHeight) {
                    overflowY = 'scroll';
                }
                if (!$rootScope.$$phase) {
                    $rootScope.$apply();
                }
                element.css('overflow-y', overflowY);
            });
        };

        var lastTimeout;
        var onResize = function () {
            update();
        };

        window.on('resize', onResize);

        update();

        scope.$on('$destroy', function () {
            window.off('resize', onResize);
        });

        scope.$watch('slidesHeightSetterStrategy().getSlides().length', function () {
            update();
            scope.slidesHeightSetterStrategy().reselectCurrentSlideToFixChormeHeightCalculationBug();
        });
    }

    return {
        link: link,
        scope: {
            slidesHeightSetterStrategy: '&',
            slideshow: '&'
        }
    };
}]);

Fix sidebar scrollbar not being displayed on slideshow edit on Chrome 53.


var OAuth2 = require("./oauth2")

function NinjaBlocks(options) {
  ["code", "token", "user"].forEach(function(name) {
    this[name] = Object.create(this[name])
  }, this)

  this.code.query = {
    client_id: options.id,
    response_type: "code",
    scope: options.scope || []
  }

  this.token.query = {
    client_id: options.id,
    client_secret: options.secret,
    grant_type: "authorization_code"
  }

  this.user.query = {}

  this.on("request", this.onRequest.bind(this))
}

NinjaBlocks.prototype = new OAuth2

NinjaBlocks.prototype.code = {
  protocol: "https",
  host: "api.ninja.is",
  pathname: "/oauth/authorize"
}

NinjaBlocks.prototype.token = {
  method: "POST",
  host: "api.ninja.is",
  path: "/oauth/access_token"
}

NinjaBlocks.prototype.user = {
  host: "api.ninja.is",
  path: "/rest/v0/user"
}

module.exports = NinjaBlocks

var OAuth2 = require("./oauth2")

function NinjaBlocks(options) {
  ["code", "token", "user"].forEach(function(name) {
    this[name] = Object.create(this[name])
  }, this)

  this.code.query = {
    client_id: options.id,
    response_type: "code",
    scope: options.scope || []
  }

  this.token.query = {
    client_id: options.id,
    client_secret: options.secret,
    grant_type: "authorization_code"
  }

  this.user.query = {}

  this.on("request", this.onRequest.bind(this))
}

NinjaBlocks.prototype = new OAuth2

NinjaBlocks.prototype.code = {
  protocol: "https",
  host: "api.ninja.is",
  pathname: "/oauth/authorize"
}

NinjaBlocks.prototype.token = {
  method: "POST",
  host: "api.ninja.is",
  path: "/oauth/access_token"
}

NinjaBlocks.prototype.user = {
  host: "api.ninja.is",
  path: "/rest/v1/user"
}

module.exports = NinjaBlocks

Update NinjaBlocks user endpoint to v1


var util = require('util');

var ValidaInvalidError = module.exports = function ValidaInvalidError (validationErrors) {
  Error.call(this);

  this.name = this.constructor.name;
  Error.captureStackTrace(this, this.constructor);

  this.validationErrors = validationErrors;
};

util.inherits(ValidaInvalidError, Error);

var util = require('util');

var ValidaInvalidError = module.exports = function ValidaInvalidError (validationErrors) {
  var message = 'validaInvalidError';

  Error.call(this);

  this.name = this.constructor.name;
  this.validationErrors = validationErrors;

  Object.defineProperty(
    this,
    'message',
    {
      enumerable: false,
      value: message,
      writable: true,
    }
  );

  if (Error.hasOwnProperty('captureStackTrace')) {
    Error.captureStackTrace(this, this.constructor);
    return;
  }

  Object.defineProperty(
    this,
    'stack',
    {
      enumerable: false,
      value: (new Error(message)).stack,
    }
  );
};

util.inherits(ValidaInvalidError, Error);

Fix captureStackTrace and message for ie


.import "../applicationShared.js" as Globals

function workerOnMessage(messageObject) {
    if(messageObject.apiMethod === 'channels.history') {
        addMessagesToModel(messageObject.data);
    } else if(messageObject.apiMethod === 'channels.info') {
        addChannelInfoToPage(messageObject.data);
    } else {
        console.log("Unknown api method");
    }
}

function loadChannelInfo() {
    var arguments = {
        channel: channelPage.channelID
    }
    slackWorker.sendMessage({'apiMethod': "channels.info", 'token': Globals.slackToken, 'arguments': arguments });
}

function loadChannelHistory() {
    slackWorker.sendMessage({'apiMethod': "channels.history", 'token': Globals.slackToken});
}

// private

function addMessagesToModel(data) {
    for(var i=0; i<data.messages.length; i++) {
        channelList.append(data.messages[i]);
    }
}

function addChannelInfoToPage(data) {
    channelPage.channelPurpose = data.channel.purpose.value;
}

.import "../applicationShared.js" as Globals

function workerOnMessage(messageObject) {
    if(messageObject.apiMethod === 'channels.history') {
        addMessagesToModel(messageObject.data);
    } else if(messageObject.apiMethod === 'channels.info') {
        addChannelInfoToPage(messageObject.data);
    } else {
        console.log("Unknown api method");
    }
}

function loadChannelInfo() {
    var arguments = {
        channel: channelPage.channelID
    }
    slackWorker.sendMessage({'apiMethod': "channels.info", 'token': Globals.slackToken, 'arguments': arguments });
}

function loadChannelHistory() {
    var arguments = {
        channel: channelPage.channelID,
        count: 20
    }

    slackWorker.sendMessage({'apiMethod': "channels.history", 'token': Globals.slackToken, 'arguments': arguments});
}

// private

function addMessagesToModel(data) {
    if(data.ok === false) {
        console.log("addMessagesToModel:", data.error)
    } else {
        for(var i=data.messages.length-1; i >= 0; i--) {
            messagesModel.append(data.messages[i]);
        }
    }
}

function addChannelInfoToPage(data) {
    channelPage.channelPurpose = data.channel.purpose.value;
}

Fix loading a channels's old messages


"use strict";

// This entry module is loaded in 'base.html' and is used to load JS that
// should run on every page on the site

require("../../../../assets/css/sass/main.scss");
require("autotrack");
require("treemap/lib/buttonEnabler.js").run();
require("treemap/lib/export.js").run();

// Polyfill for String.startsWith(), not supported in IE 11
if (!String.prototype.startsWith) {
    String.prototype.startsWith = function (searchString, position) {
        position = position || 0;
        return this.indexOf(searchString, position) === position;
    };
}

Fix search for IE 11


function addQueueButton() {
    let saveButtons = document.querySelectorAll('[value="Save Edits"]');
    saveButtons.forEach(button => {
        if (!button.parentNode.querySelector('.queue-edit-button')) {
            let queueButton = document.createElement('input');
            queueButton.type = 'button';
            queueButton.value = 'Queue Edits';
            queueButton.classList.add('queue-edit-button');
            button.parentNode.insertBefore(queueButton, button.nextSibling);
            button.parentNode.insertBefore(document.createTextNode(" "), queueButton);
        }
    });
}

let observer = new MutationObserver((mutations, observer) => {
    mutations.forEach(mutation => {
        mutation.addedNodes.forEach(addQueueButton);
    });
});

observer.observe(document, {
    subtree: true,
    childList: true
});

function shouldAddQueueButton(saveButton) {
    if (saveButton.parentElement.querySelector('.queue-edit-button')) {
        // already have one
        return false;
    }

    let node = saveButton.parentElement;
    while (node) {
        if (node.classList.contains('postcell')) {
            // exclude answers and comments, see https://github.com/RootAccessOrg/TrickleEdit/issues/2
            // answers are answercell while comments aren't in a cell
            return true;
        }
        node = node.parentElement;
    }

    return false;
}

function addQueueButton() {
    let saveButtons = document.querySelectorAll('[value="Save Edits"]');
    saveButtons.forEach(button => {
        if (shouldAddQueueButton(button)) {
            let queueButton = document.createElement('input');
            queueButton.type = 'button';
            queueButton.value = 'Queue Edits';
            queueButton.classList.add('queue-edit-button');
            button.parentNode.insertBefore(queueButton, button.nextSibling);
            button.parentNode.insertBefore(document.createTextNode(" "), queueButton);
        }
    });
}

let observer = new MutationObserver((mutations, observer) => {
    mutations.forEach(mutation => {
        mutation.addedNodes.forEach(addQueueButton);
    });
});

observer.observe(document, {
    subtree: true,
    childList: true
});

Disable on comments and answers


(function() {
VimeoVideo = function(domObject, url) {
  this.domObject = domObject;
  this.url = url;
};

VimeoVideo.vimeoRegEx = new RegExp('^http://(?:www\\.)?vimeo\\.com/.+clip_id=(\\d+)');
VimeoVideo.tryHandling = function(node, url) {
  if (VimeoVideo.vimeoRegEx.test(url)) {
    return new VimeoVideo(node, url);
  } else {
    return null;
  }
};

VimeoVideo.prototype.start = function() {
  var match = VimeoVideo.vimeoRegEx.exec(this.url);
  if (match) {
    this.clipId = match[1];
    this.watchUrl = 'http://vimeo.com/' + this.clipId;

    var videoUrl = 'http://vimeo.com/play_redirect?clip_id=' + this.clipId;

    VideoHandlers.replaceFlashObjectWithVideo(this.domObject,
      videoUrl,
      { watchURL: this.watchUrl });
  }
};

return VimeoVideo;
})

(function() {
VimeoVideo = function(domObject, url, flashVars) {
  this.domObject = domObject;
  this.url = url;
  this.clip_id = flashVars.clip_id;
};

VimeoVideo.vimeoRegEx = new RegExp('^http://(?:www\\.)?vimeo\\.com/.+clip_id=(\\d+)');
VimeoVideo.tryHandling = function(node, url) {
  var flashVars = FlashVarsParser.parse(node);
  if (VimeoVideo.vimeoRegEx.test(url) ||
      flashVars.clip_id !== undefined) {
    return new VimeoVideo(node, url, flashVars);
  } else {
    return null;
  }
};

VimeoVideo.prototype.start = function() {
  var match, clipId;
  if (this.clip_id !== undefined) {
    clipId = this.clip_id;
  } else if ((match = VimeoVideo.vimeoRegEx.exec(this.url)) !== null) {
    clipId = match[1];
  } else {
    return;
  }

  this.watchUrl = 'http://vimeo.com/' + clipId;

  var videoUrl = 'http://vimeo.com/play_redirect?clip_id=' + clipId;

  VideoHandlers.replaceFlashObjectWithVideo(this.domObject,
    videoUrl,
    { watchURL: this.watchUrl });
};

return VimeoVideo;
})

Handle Vimeo with Flash vars.


import { showMessageBarMessage } from 'containers/MessageBar/actions';

// Works with redux-api-middleware, taking action when requests fail
const apiErrorHandlingMidddlware = (store) => (next) => (action) => {
  const message = action.payload ? fetchError(action.payload) || false : false;
  if (message) {
    store.dispatch(showMessageBarMessage(message));
  }
  return next(action);
};

function fetchError(payload) {
  let response = '';

  if (payload.name === 'ApiError' || payload.name === 'SubmissionError') {
    const errorData = payload.errors ? payload.errors : payload.response;

    if (errorData.status === 403) {
      response = 'Invalid request - 403';
    } else if (errorData.status === 401) {
      response = 'Unauthorized, please try your request again - 401';
    } else if (errorData.status === 404) {
      response = 'Error reaching server - 404';
    } else if (errorData.status === 500) {
      response = errorData.message;
    }
  }

  return response;
}

export default apiErrorHandlingMidddlware;

import { showMessageBarMessage } from 'containers/MessageBar/actions';

// Works with redux-api-middleware, taking action when requests fail
const apiErrorHandlingMidddlware = (store) => (next) => (action) => {
  const message = action.payload ? fetchError(action.payload) || false : false;
  if (message) {
    store.dispatch(showMessageBarMessage(message));
  }
  return next(action);
};

function fetchError(payload) {
  let response = '';

  const errorData = payload.errors ? payload.errors : payload.response;

  if (errorData && errorData.status) {
    if (errorData.status === 403) {
      response = 'Invalid request - 403';
    } else if (errorData.status === 401) {
      response = 'Unauthorized, please try your request again - 401';
    } else if (errorData.status === 404) {
      response = 'Error reaching server - 404';
    } else if (errorData.status === 500) {
      response = errorData.message;
    }
  }

  return response;
}

export default apiErrorHandlingMidddlware;

Fix snackbar in production, where the error name was being minimized Squashed commit of the following:


// ==UserScript==
// @name        All Courses Sort
// @namespace   https://github.com/jamesjonesmath/canvancement
// @description Allows sorting on any column of the All Courses list
// @include     https://*.instructure.com/courses
// @require     https://cdn.jsdelivr.net/npm/tablesorter@2.31.1/dist/js/jquery.tablesorter.combined.min.js
// @version     1
// @grant       none
// ==/UserScript==
(function() {
  'use strict';
  if (/^\/courses\/?$/.test(window.location.pathname)) {
    const head = document.querySelector('head');
    if (head) {
      const link = document.createElement('link');
      link.rel = 'stylesheet';
      link.type = 'text/css';
      link.href = 'https://cdn.jsdelivr.net/npm/tablesorter@2.31.1/dist/css/theme.default.min.css';
      head.appendChild(link);
    }
    document.querySelectorAll('table.ic-Table th.course-list-star-column').forEach(function(e) {
      e.classList.add('filter-false');
    });
    $('table.ic-Table').tablesorter({
      'widgets' : [ 'filter' ]
    });
  }
})();

// ==UserScript==
// @name        All Courses Sort
// @namespace   https://github.com/jamesjonesmath/canvancement
// @description Allows sorting on any column of the All Courses list
// @include     https://*.instructure.com/courses
// @require     https://cdn.jsdelivr.net/npm/tablesorter@2.31.1/dist/js/jquery.tablesorter.combined.min.js
// @version     1
// @grant       none
// ==/UserScript==
(function() {
  'use strict';
  if (/^\/courses\/?$/.test(window.location.pathname)) {
    const head = document.querySelector('head');
    if (head) {
      const link = document.createElement('link');
      link.rel = 'stylesheet';
      link.type = 'text/css';
      link.href = 'https://cdn.jsdelivr.net/npm/tablesorter@2.31.1/dist/css/theme.default.min.css';
      head.appendChild(link);
    }
    document.querySelectorAll('table.ic-Table th.course-list-star-column').forEach(function(e) {
      e.classList.add('filter-false');
    });
    $('table.ic-Table').tablesorter({
      'widgets' : [ 'filter' ],
      // 'sortList' : [[3,1]]
    });
  }
})();

Add option to sort by term in descending order


#!/usr/bin/env node
const args = require(`args`);
const ncp = require(`ncp`);
const path = require(`path`);

const replaceRecursive = require(`../lib/replace-recursive.js`);

args.option(
  `project-name`,
  `Directory and package name for the new avalanche powered project.`,
  `New Project`
);

const options = args.parse(process.argv);
const projectName = options.projectName;

const projectNameCleaned = projectName
  .toLowerCase()
  .split(` `)
  .join(`-`);

const replacements = [
  {
    regex: `PROJECT-NAME-CLEANED`,
    replacement: projectNameCleaned,
  },
  {
    regex: `PROJECT-NAME`,
    replacement: projectName,
  },
];

const packagePath = path.resolve(__dirname, `../`);
const source = path.join(packagePath, `templates`, `default`);
const destination = path.join(process.cwd(), projectNameCleaned);

ncp(source, destination, (error) => {
  if (error) throw error;

  replaceRecursive(replacements, destination);

  // eslint-disable-next-line no-console
  console.info(`Created a new avalanche project "${projectName}" in ${destination}.`);
});

#!/usr/bin/env node
const args = require(`args`);
const ncp = require(`ncp`);
const path = require(`path`);

const replaceRecursive = require(`../lib/replace-recursive.js`);

args.option(
  `project-name`,
  `Directory and package name for the new avalanche powered project.`,
  `New Project`
);

const options = args.parse(process.argv);
let projectName = options.projectName;

if (process.argv[2].substring(0, 1) !== `-`) {
  projectName = process.argv[2];
}

const projectNameCleaned = projectName
  .toLowerCase()
  .split(` `)
  .join(`-`);

const replacements = [
  {
    regex: `PROJECT-NAME-CLEANED`,
    replacement: projectNameCleaned,
  },
  {
    regex: `PROJECT-NAME`,
    replacement: projectName,
  },
];

const packagePath = path.resolve(__dirname, `../`);
const source = path.join(packagePath, `templates`, `default`);
const destination = path.join(process.cwd(), projectNameCleaned);

ncp(source, destination, (error) => {
  if (error) throw error;

  replaceRecursive(replacements, destination);

  // eslint-disable-next-line no-console
  console.info(`Created a new avalanche project "${projectName}" in ${destination}.`);
});

Use first cli input es project name without specifying it explicitly


const name = 'Bugsnag Expo'
const { version } = require('../package.json')
const url = 'https://github.com/bugsnag/bugsnag-js'

const Client = require('@bugsnag/core/client')

const schema = { ...require('@bugsnag/core/config').schema, ...require('./config') }

const plugins = [
  require('@bugsnag/plugin-react-native-global-error-handler'),
  require('@bugsnag/plugin-react-native-unhandled-rejection')
]

module.exports = (opts) => {
  // handle very simple use case where user supplies just the api key as a string
  if (typeof opts === 'string') opts = { apiKey: opts }

  const bugsnag = new Client({ name, version, url })

  bugsnag.setOptions(opts)
  bugsnag.configure(schema)

  plugins.forEach(pl => bugsnag.use(pl))

  bugsnag._logger.debug(`Loaded!`)

  return bugsnag
}

module.exports['default'] = module.exports

const name = 'Bugsnag Expo'
const { version } = require('../package.json')
const url = 'https://github.com/bugsnag/bugsnag-js'
const { Constants } = require('expo')

const Client = require('@bugsnag/core/client')

const schema = { ...require('@bugsnag/core/config').schema, ...require('./config') }

const plugins = [
  require('@bugsnag/plugin-react-native-global-error-handler'),
  require('@bugsnag/plugin-react-native-unhandled-rejection')
]

module.exports = (opts) => {
  // handle very simple use case where user supplies just the api key as a string
  if (typeof opts === 'string') opts = { apiKey: opts }

  // ensure opts is actually an object (at this point it
  // could be null, undefined, a number, a boolean etc.)
  opts = { ...opts }

  // attempt to fetch apiKey from app.json if we didn't get one explicitly passed
  if (!opts.apiKey &&
    Constants.manifest.extra &&
    Constants.manifest.extra.bugsnag &&
    Constants.manifest.extra.bugsnag.apiKey) {
    opts.apiKey = Constants.manifest.extra.bugsnag.apiKey
  }

  const bugsnag = new Client({ name, version, url })

  bugsnag.setOptions(opts)
  bugsnag.configure(schema)

  plugins.forEach(pl => bugsnag.use(pl))

  bugsnag._logger.debug(`Loaded!`)

  return bugsnag
}

module.exports['default'] = module.exports

Allow apiKey to be set in app.json rather than in code


const visit = require('unist-util-visit')

const inline = ['p', 'kbd', 'del', 'sup']

function plugin () {
  return transformer
}

function transformer (tree) {
  visit(tree, 'raw', visitor)
}

function visitor (node, index, parent) {
  let replacement
  if (!parent) return

  if (!inline.includes(parent.tagName)) {
    replacement = {
      type: 'element',
      tagName: 'p',
      properties: {},
      children: [{
        type: 'text',
        value: node.value
      }]
    }
  } else {
    replacement = {
      type: 'text',
      value: node.value
    }
  }

  parent.children[index] = replacement
}

module.exports = plugin

const visit = require('unist-util-visit')

const inline = [
  'a',
  'b',
  'big',
  'i',
  'small',
  'tt',
  'abbr',
  'acronym',
  'cite',
  'code',
  'dfn',
  'em',
  'kbd',
  'strong',
  'samp',
  'time',
  'var',
  'bdo',
  'br',
  'img',
  'map',
  'object',
  'p',
  'q',
  'script',
  'span',
  'sub',
  'sup',
  'button',
  'input',
  'label',
  'select',
  'textarea'
]

function plugin () {
  return transformer
}

function transformer (tree) {
  visit(tree, 'raw', visitor)
}

function visitor (node, index, parent) {
  let replacement
  if (!parent) return

  if (!inline.includes(parent.tagName)) {
    replacement = {
      type: 'element',
      tagName: 'p',
      properties: {},
      children: [{
        type: 'text',
        value: node.value
      }]
    }
  } else {
    replacement = {
      type: 'text',
      value: node.value
    }
  }

  parent.children[index] = replacement
}

module.exports = plugin

Expand elements inside of which we don't wrap stuff in <p>s


/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

CLASS({
  package: 'com.chrome.apis',
  name: 'Origin',
  properties: [
    {
      name: 'id',
      hidden: true
    },
    {
      name: 'origin'
    },
    {
      name: 'owner',
      type: 'Reference',
      subType: 'com.chrome.apis.User'
    }
  ],
  relationships: [
    {
      name: 'activeExperiments',
      relatedModel: 'com.chrome.apis.ExperimentActivation'
    }
  ]
});

/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

CLASS({
  package: 'com.chrome.apis',
  name: 'Origin',
  properties: [
    {
      name: 'id',
      hidden: true
    },
    {
      name: 'origin'
    },
    {
      name: 'owner',
      type: 'Reference',
      subType: 'com.chrome.apis.User'
    }
  ],
  relationships: [
    {
      name: 'activeExperiments',
      relatedModel: 'com.chrome.apis.ExperimentActivation',
      relatedProperty: 'origin',
      destinationModel: 'com.chrome.apis.Experiment',
      destinationProperty: 'experiment'
    }
  ]
});

Update origin relationship to be many to many.


'use strict';

const _ = require('lodash');
const fs = require('fs');
const path = require('path');

class Greshunkel {

    constructor(options) {
        this.options = {
            directory: process.cwd()
        };

        _.extend(this.options, options);
    }

    compile(template) {
        return new Promise((resolve, reject) => {
            this._loadTemplate(template).then((content) => {
                return resolve(content);
            }).catch((err) => {
                return reject(err);
            });
        });
    }

    _loadTemplate(template) {
        return new Promise((resolve, reject) => {
            template = template.indexOf('.gshkl') !== -1 ? template : template + '.gshkl';

            let templatePath = path.join(this.options.directory, template);
            let reader = fs.createReadStream(templatePath, 'utf-8');
            let content = '';

            reader.on('data', (data) => {
                content += data;
            });

            reader.on('close', () => {
                return resolve(content);
            });

            reader.on('error', (err) => {
                return reject(err);
            });
        });
    }

}

let instance = new Greshunkel();

module.exports = {
    Greshunkel: Greshunkel,
    compile: instance.compile
};

'use strict';

const _ = require('lodash');
const fs = require('fs');
const path = require('path');

class Greshunkel {

    constructor(options) {
        this.options = {
            directory: process.cwd()
        };

        _.extend(this.options, options);
    }

    compile(template) {
        return new Promise((resolve, reject) => {
            this._loadTemplate(template).then((content) => {
                return resolve(content);
            }).catch((err) => {
                return reject(err);
            });
        });
    }

    _loadTemplate(template) {
        return new Promise((resolve, reject) => {
            template = template.endsWith('.gshkl') ? template : template + '.gshkl';

            let templatePath = path.join(this.options.directory, template);
            let reader = fs.createReadStream(templatePath, 'utf-8');
            let content = '';

            reader.on('data', (data) => {
                content += data;
            });

            reader.on('close', () => {
                return resolve(content);
            });

            reader.on('error', (err) => {
                return reject(err);
            });
        });
    }

}

let instance = new Greshunkel();

module.exports = {
    Greshunkel: Greshunkel,
    compile: instance.compile
};

Use cleaner method for checking if template ends with .gshkl


'use strict';
/* eslint camelcase: 0 */

module.exports = function(keyword, name, description, line) {
  return {
    token: 'feature',
    keyword: keyword,
    name: name,
    description: description,
    line: line,
    scenarios: []
  };
}

'use strict';
/* eslint camelcase: 0 */

function Feature(keyword, name, description, line) {
  this.token = 'feature';
  this.keyword = keyword;
  this.name = name;
  this.description = description;
  this.line = line;
  this.scenarios = [];
  this.tags = [];
};

module.exports = function(keyword, name, description, line) {
  return new Feature(keyword, name, description, line);
}

Use a contructor for feature objects to allow ease of expanding functionality.


// IPython mode is just a slightly altered Python Mode with `?` beeing a extra
// single operator. Here we define `ipython` mode in the require `python`
// callback to auto-load python mode, which is more likely not the best things
// to do, but at least the simple one for now.

CodeMirror.requireMode('python',function(){
    "use strict";

    CodeMirror.defineMode("ipython", function(conf, parserConf) {

        parserConf.singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!\\?]");
        parserConf.name = 'python'
        return CodeMirror.getMode(conf, parserConf);
    }, 'python');

    CodeMirror.defineMIME("text/x-ipython", "ipython");
})

// IPython mode is just a slightly altered Python Mode with `?` beeing a extra
// single operator. Here we define `ipython` mode in the require `python`
// callback to auto-load python mode, which is more likely not the best things
// to do, but at least the simple one for now.

CodeMirror.requireMode('python',function(){
    "use strict";

    CodeMirror.defineMode("ipython", function(conf, parserConf) {
        var pythonConf = {};
        for (var prop in parserConf) {
            if (parserConf.hasOwnProperty(prop)) {
                pythonConf[prop] = parserConf[prop];
            }
        }
        pythonConf.name = 'python';
        pythonConf.singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!\\?]");
        return CodeMirror.getMode(conf, pythonConf);
    }, 'python');

    CodeMirror.defineMIME("text/x-ipython", "ipython");
})

Copy codemirror mode configuration instead of changing it


jQuery(function ($) {
  var localise_timestamp = function(timestamp){
    if (timestamp === "None"){
      return '';
    };
    d = moment.utc(timestamp);
    d.local();
    return d;
  };

  $("[rel=utctimestamp]").each(
    function(index, timestamp){
      var tstamp = $(timestamp);
      var tstring = tstamp.text().trim();
      var result = localise_timestamp(tstring);
      if (result == '') {
        tstamp.text('Unknown');
      } else {
        tstamp.text(localise_timestamp(tstring).format('LLLL'));
      };
  });

});

jQuery(function ($) {
  var localise_timestamp = function(timestamp){
    if (timestamp === "None"){
      return '';
    };
    d = moment.utc(timestamp);
    d.local();
    return d;
  };

  $("[rel=utctimestamp]").each(
    function(index, timestamp){
      var tstamp = $(timestamp);
      var tstring = tstamp.text().trim();
      var result = localise_timestamp(tstring);
      if (result == '') {
        tstamp.text('Unknown');
      } else {
        tstamp.text(localise_timestamp(tstring).format('MMM DD YYYY - HH:mm:ss'));
      };
  });

});

Swap timestamp format to ISO and 24h clock.


define([
  'underscore',
  'backbone'
], function(_, Backbone) {

  var UserModel = Backbone.Model.extend({

  	defaults: {
  		id: 0,
  		name: '',
      email: '',
      password: ''
  	},

  	initialize: function (options) {
      //_.bindAll(this);
  	},

    url: function() {
      return '/api/users';
    }

  });

  return UserModel;

});

define([
  'underscore',
  'backbone'
], function(_, Backbone) {

  var UserModel = Backbone.Model.extend({

  	defaults: {
  		name: '',
      email: '',
      password: ''
  	},

  	url: '/api/users'

  });

  return UserModel;

});

Remove commented code, and simplify class


function startMixer(){
  return new Mixer
}

// function loadSound(mix, trackId, divId) {
//   //var tracktoAdd =  eventually this will look up track from server based on id

//   var trackSource = _.find(sounds, function(sound) {
//     return sound.id === trackId
//   });

//   var trackToAdd = new Wad({
//     source: trackSource.src,
//     env: {hold: 9001}
//   });

//   mix.add(trackToAdd);
//   mostRecent = _.last(mix.wads);
//   mostRecent.label = divId;

//   var targetDiv = _.find($("div"), function(div) {
//     return Number($(div).attr("id")) === divId;
//   });

//   $(targetDiv).append("<p>" + trackSource.name + "</p>")
  $(targetDiv).attr("track-id", trackId)
}

// var sounds = [];

function startMixer(){
  return new Mixer
}

// function loadSound(mix, trackId, divId) {
//   //var tracktoAdd =  eventually this will look up track from server based on id

//   var trackSource = _.find(sounds, function(sound) {
//     return sound.id === trackId
//   });

//   var trackToAdd = new Wad({
//     source: trackSource.src,
//     env: {hold: 9001}
//   });

//   mix.add(trackToAdd);
//   mostRecent = _.last(mix.wads);
//   mostRecent.label = divId;

//   var targetDiv = _.find($("div"), function(div) {
//     return Number($(div).attr("id")) === divId;
//   });

//   $(targetDiv).append("<p>" + trackSource.name + "</p>")
// $(targetDiv).attr("track-id", trackId)
// }

// var sounds = [];

Remove adding track-id class in loadSound


const Config = require('../config.json');
const fetch = require('node-fetch');

module.exports = class Fetcher {
  get(url) {
    const options = { method: 'GET' };
    return this.fetchJSON(url, options);
  }

  post(url, params) {
    const body = [];
    for (const key in params) {
      body.push(key + '=' + encodeURIComponent(params[key]));
    }
    const options = { method: 'POST', body: body.join('&') };
    return this.fetchJSON(url, options);
  }

  fetchJSON(url, options) {
    if (typeof options.headers === 'undefined') {
      options.headers = {};
    }
    options.headers['User-Agent'] = Config.user_agent;
    return new Promise((resolve, reject) => {
      fetch(url, options).then((response) => {
        response.json().then((json) => {
          if (response.ok) {
            resolve(json);
          } else {
            json.url = url;
            json.status = response.status + ' ' + response.statusText;
            reject(json);
          }
        });
      });
    });
  }
}

const Config = require('../config.json');
const fetch = require('node-fetch');

module.exports = class Fetcher {
  get(url) {
    const options = { method: 'GET' };
    return this.makeRequest(url, options);
  }

  post(url, params) {
    const body = [];
    for (const key in params) {
      body.push(key + '=' + encodeURIComponent(params[key]));
    }
    const options = { method: 'POST', body: body.join('&') };
    return this.makeRequest(url, options);
  }

  makeRequest(url, options) {
    if (typeof options.headers === 'undefined') {
      options.headers = {};
    }
    options.headers['User-Agent'] = Config.user_agent;
    return new Promise((resolve, reject) => {
      fetch(url, options).then((response) => {
        const respType = response.headers.get('Content-Type');
        const status = response.status + ' ' + response.statusText;
        if (respType === 'text/html') {
          response.text().then((body) => {
            if (response.ok) {
              resolve({ body: body });
            } else {
              reject({ url: url, status: status, body: body });
            }
          }).catch((error) => {
            console.error('failed to parse html response', error);
            reject({ error: error });
          });
        } else {
          response.json().then((json) => {
            if (response.ok) {
              resolve(json);
            } else {
              json.url = url;
              json.status = status;
              reject(json);
            }
          }).catch((error) => {
            console.error('failed to parse json response', error);
            reject({ error: error });
          });
        }
      });
    });
  }
}

Handle non-JSON responses in Fetcher


/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */

"use strict";

export default function(sandbox, sid, iid) {
    const iid_string = sandbox.iidToLocation(sid, iid);

    if (!iid_string) {
        return undefined;
    }

    const elems = iid_string.substr(1, iid_string.length - 2).split(':'); //Comes in the form (Filename:1:2:3:4)

    return {
        line_start: elems[1],
        char_start: elems[2],
        line_end: elems[3],
        char_end: elems[4]
    };
}

/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */

"use strict";

export default function(sandbox, sid, iid) {
    const iid_string = sandbox.iidToLocation(sid, iid);

    if (!iid_string) {
        return undefined;
    }

    return iid_string.substr(1, iid_string.length - 2).split(':').slice(1); //Comes in the form (Filename:1:2:3:4)
}

Reduce size of smaps with array notation


import { shallow } from 'enzyme';
import assert from 'assert';
import React from 'react';
import Summary from './summary';
import { workflow } from '../../../pages/dev-classifier/mock-data';

const annotation = {
  value: [{
    "choice": "ar",
    "answers": {
      "ho": "two",
      "be": [
        "mo",
        "ea"
      ]
    },
    "filters": {}
  }]
};
const task = workflow.tasks.survey
const expectedSummary = 'Armadillo: 2; Moving, Eating';

describe('Survey task summary, not expanded', function() {
  const wrapper = shallow(<Summary annotation={annotation} task={task} />);
  const answers = wrapper.find('.answers .answer');

  it('should show one answer node', function(){
    assert.equal(answers.length, 1);
  });

  it('should summarise the number of identifications', function() {
    assert.equal(answers.text(), '1 identifications');
  });
});

describe('Survey task summary, expanded', function() {
  const wrapper = shallow(<Summary annotation={annotation} task={task} expanded={true} />);
  const answers = wrapper.find('.answers .answer');

  it('should show two answer nodes', function(){
    assert.equal(answers.length, 2);
  });

  it('should summarise the identification and questions', function() {
    assert.equal(answers.last().text(), expectedSummary);
  });
})

import { mount } from 'enzyme';
import assert from 'assert';
import counterpart from 'counterpart';
import React from 'react';
import Summary from './summary';
import { workflow } from '../../../pages/dev-classifier/mock-data';
import enLocale from '../../../locales/en';

counterpart.registerTranslations('en', enLocale);

const annotation = {
  value: [{
    "choice": "ar",
    "answers": {
      "ho": "two",
      "be": [
        "mo",
        "ea"
      ]
    },
    "filters": {}
  }]
};
const task = workflow.tasks.survey
const expectedSummary = 'Armadillo: 2; Moving, Eating';

describe('Survey task summary, not expanded', function() {
  const wrapper = mount(<Summary annotation={annotation} task={task} />);
  const answers = wrapper.find('.answers .answer');

  it('should show one answer node', function(){
    assert.equal(answers.length, 1);
  });

  it('should summarise the number of identifications', function() {
    assert.equal(answers.text(), '1 identification');
  });
});

describe('Survey task summary, expanded', function() {
  const wrapper = mount(<Summary annotation={annotation} task={task} expanded={true} />);
  const answers = wrapper.find('.answers .answer');

  it('should show two answer nodes', function(){
    assert.equal(answers.length, 2);
  });

  it('should summarise the identification and questions', function() {
    assert.equal(answers.last().text(), expectedSummary);
  });
})

Update tests to use Translate


import React, { Component } from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';

import Box from 'react/components/UI/Box';
import LoadableImage from 'react/components/UI/LoadableImage';

const Container = styled(Box).attrs({
  display: 'flex',
  justifyContent: 'center',
  alignItems: 'center',
})`
  position: relative;
  width: 100%;
  height: 100%;

  > img {
    max-width: 100%;
    max-height: 100%;
  }

  &:before {
    content: '';
    display: block;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 1em;
    height: 1em;
    border-radius: 50%;
    background-color: ${x => x.theme.colors.gray.light};
    z-index: -1;
  }
`;

class Img extends Component {
  static propTypes = {
    src: PropTypes.string.isRequired,
    alt: PropTypes.string,
  }

  static defaultProps = {
    alt: null,
  }

  render() {
    const { src, alt } = this.props;

    return (
      <Container>
        <LoadableImage src={src} alt={alt} />
      </Container>
    );
  }
}

export default Img;

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';

import Box from 'react/components/UI/Box';
import LoadableImage from 'react/components/UI/LoadableImage';

const Container = styled(Box).attrs({
  display: 'flex',
  justifyContent: 'center',
  alignItems: 'center',
})`
  position: relative;
  width: 100%;
  height: 100%;

  > img {
    max-width: 100%;
    max-height: 100%;
  }

  ${props => !props.done && `
    &:before {
      content: '';
      display: block;
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      width: 1em;
      height: 1em;
      border-radius: 50%;
      background-color: ${props.theme.colors.gray.light};
      z-index: -1;
    }
  `}
`;

class Img extends Component {
  static propTypes = {
    src: PropTypes.string.isRequired,
    alt: PropTypes.string,
  }

  static defaultProps = {
    alt: null,
  }

  state = {
    done: false,
  }

  setDone = () => {
    this.setState({ done: true });
  }

  render() {
    const { done } = this.state;
    const { src, alt } = this.props;

    return (
      <Container done={done}>
        <LoadableImage src={src} alt={alt} onLoad={this.setDone} />
      </Container>
    );
  }
}

export default Img;

Remove the dot once the images load


/**
 * Home controller definition
 * @scope Controllers
 */
define(['./module', 'jquery'], function (controllers, $) {
  'use strict';

  controllers.controller('HomeController', ['$scope', 'fireRef',
    function HomeController($scope, fireRef) {

      $scope.newLink = '';
      var numberOfLinks = 0;

      $scope.$watch('links', function () {
        var length = numberOfLinks;
        var totalLength = length * 9;
        $('ul').css('width', totalLength+"px");

        numberOfLinks += 1;
      }, true);

      $scope.addLink = function () {
        var newLink = $scope.newLink.trim();
        if (!newLink.length) {
          return;
        }
        $scope.links.$add({
          url: newLink,
          nbClick: 0
        });
        $scope.newLink = '';
      };

      $scope.incrementClick = function (id) {
        $scope.links[id].nbClick++;
        $scope.links.$save();
      };

      $scope.links = fireRef.links();
    }
  ]);
});

/**
 * Home controller definition
 * @scope Controllers
 */
define(['./module', 'jquery'], function (controllers, $) {
  'use strict';

  controllers.controller('HomeController', ['$scope', 'fireRef',
    function HomeController($scope, fireRef) {

      $scope.newLink = '';
      var numberOfLinks = 0;

      $scope.$watch('links', function () {
        // Resize the list width, fits to the content size.
        var length = numberOfLinks;
        var totalLength = length * 9;
        $('ul').css('width', totalLength+"px");

        numberOfLinks += 1;
      }, true);

      $scope.addLink = function () {
        var newLink = $scope.newLink.trim();
        if (!newLink.length) {
          return;
        }
        $scope.links.$add({
          url: newLink,
          nbClick: 0
        });
        $scope.newLink = '';
      };

      $scope.incrementClick = function (id) {
        // Check if the attribute exists. Default value is 0.
        if ($scope.links[id].nbClick === undefined) {
            $scope.links[id].nbClick = 0;
        } else {
            $scope.links[id].nbClick++;
        }

        $scope.links.$save();
      };

      $scope.links = fireRef.links();
    }
  ]);
});

Update - Add simple variable colour pattern


//test-name.spec.js
/*
Acceptance Criteria:
I can:
-Navigate to this page according to the site map url
-See that the page looks like the mockup
-View the page at different resolutions and the content is laid out logically
*/

/* global casper */

casper.test.begin('Contact page navigates to home page ', 2,
  function suite(test) {
    casper.start('http://localhost:63342/fullstack-project-01/neukinnis/client/contactus.html', function () {
      test.assertTitle('Contact Us', 'contact us page title good');
      test.assertExists('a[href="index.html"]', 'link found');
    });

    //casper.start('http://localhost:3000/contact.html', function(){

    casper.then(function () {
      this.click('a[href="index.html"]');
    });
    casper.run(function () {
      test.done();
    });
  });

//test-name.spec.js
/*
Acceptance Criteria:
I can:
-Navigate to this page according to the site map url
-See that the page looks like the mockup
-View the page at different resolutions and the content is laid out logically
*/

/* global casper */

casper.test.begin('Contact page navigates to home page ', 2,
  function suite(test) {
    casper.start('http://localhost:3000/contact.html', function () {
      test.assertTitle('Contact Us', 'contact us page title good');
      test.assertExists('a[href="index.html"]', 'link found');
    });

    //casper.start('http://localhost:3000/contact.html', function(){

    casper.then(function () {
      this.click('a[href="index.html"]');
    });
    casper.run(function () {
      test.done();
    });
  });

Add correct localhost port to test


import React, { PropTypes } from 'react';
import Carousel from 'react-bootstrap/lib/Carousel';

import { getCaption } from 'utils/imageUtils';

const IMAGE_WIDTH = 700;
const IMAGE_HEIGHT = 420;

function renderCarouselItem(image, altText) {
  const imageAlt = getCaption(image) || altText;
  const imageSrc = `${image.url}?dim=${IMAGE_WIDTH}x${IMAGE_HEIGHT}`;
  return (
    <Carousel.Item key={image.url}>
      <img alt={imageAlt} src={imageSrc} />
    </Carousel.Item>
  );
}

function ImageCarousel({ images, altText }) {
  return (
    <Carousel
      className="image-carousel"
      controls={images.length > 1}
      indicators={images.length > 1}
      interval={7000}
      style={{ maxWidth: IMAGE_WIDTH }}
    >
      {images.map(image => renderCarouselItem(image, altText))}
    </Carousel>
  );
}

ImageCarousel.propTypes = {
  altText: PropTypes.string.isRequired,
  images: PropTypes.array.isRequired,
};

export default ImageCarousel;

import React, { PropTypes } from 'react';
import Carousel from 'react-bootstrap/lib/Carousel';

import { getCaption } from 'utils/imageUtils';

const IMAGE_WIDTH = 700;
const IMAGE_HEIGHT = 420;

function renderCarouselItem(image, altText) {
  const imageAlt = getCaption(image) || altText;
  const imageSrc = `${image.url}?dim=${IMAGE_WIDTH}x${IMAGE_HEIGHT}`;
  return (
    <Carousel.Item key={image.url}>
      <img
        alt={imageAlt}
        src={imageSrc}
        style={{ backgroundColor: '#ccc', height: IMAGE_HEIGHT, width: IMAGE_WIDTH }}
      />
    </Carousel.Item>
  );
}

function ImageCarousel({ images, altText }) {
  return (
    <Carousel
      className="image-carousel"
      controls={images.length > 1}
      indicators={images.length > 1}
      interval={7000}
      style={{ maxWidth: IMAGE_WIDTH }}
    >
      {images.map(image => renderCarouselItem(image, altText))}
    </Carousel>
  );
}

ImageCarousel.propTypes = {
  altText: PropTypes.string.isRequired,
  images: PropTypes.array.isRequired,
};

export default ImageCarousel;

Make sure small images look good in carousel


'use strict';
(function(){
  angular.module('cla.directives')
    .directive('fullHeight', ['$window', function($window) {
      return {
        restrict: 'A',
        link: function(scope, element) {
          var headerAndFooter = 48;
          scope.initializeWindowSize = function () {
            $(element).css('min-height', $window.innerHeight - headerAndFooter);
          };
          scope.initializeWindowSize();
          angular.element($window).bind('resize', function () {
            scope.initializeWindowSize();
          });
        }
      };
    }]);
})();

'use strict';
(function(){
  angular.module('cla.directives')
    .directive('fullHeight', ['$window', function($window) {
      return {
        restrict: 'A',
        link: function(scope, element) {
          var headerHeight = angular.element('#global-header').height();
          var caseBarHeight = angular.element('.CaseBar').height();
          var headerFooterPadding = headerHeight + caseBarHeight;

          scope.initializeWindowSize = function () {
            $(element).css('min-height', $window.innerHeight - headerFooterPadding);
          };
          scope.initializeWindowSize();
          angular.element($window).bind('resize', function () {
            scope.initializeWindowSize();
          });
        }
      };
    }]);
})();

Correct column height header calculation


'use strict'

let Gpio = require('onoff').Gpio
let EventEmitter = require('events').EventEmitter

class Door extends EventEmitter {
  constructor(options) {
    super()
    EventEmitter.call(this)
    this.options = options
    this.isOpen = false
    let pins = options.pins

    let sensor = new Gpio(pins.sensor, 'in', 'both')
    let statusOpen = new Gpio(pins.statusOpen, 'out')
    let statusClosed = new Gpio(pins.statusClosed, 'out')

    let update = (err, value) => {
      statusOpen.writeSync(value^1)
      statusClosed.writeSync(value)
      this.isOpen = value
      this.emit(this.status())
    }

    sensor.read(update)
    sensor.watch(update)
  }
  status() {
    return this.isOpen ? 'open' : 'closed'
  }
  toggle() {
    console.log('Door toggled')
    var doorSwitch = new Gpio(this.options.pins.doorSwitch, 'out')
    doorSwitch.writeSync(1)
    setTimeout(function() {
      doorSwitch.writeSync(0)
    }, 100)
  }
  open() {
    if (!this.isOpen) this.toggle()
  }
  close() {
    if (this.isOpen) this.toggle()
  }
}

module.exports = Door

'use strict'

let Gpio = require('onoff').Gpio
let EventEmitter = require('events').EventEmitter

class Door extends EventEmitter {
  constructor(options) {
    super()
    EventEmitter.call(this)
    this.options = options
    this.isOpen = false
    let pins = options.pins

    let sensor = new Gpio(pins.sensor, 'in', 'both')
    let statusOpen = new Gpio(pins.statusOpen, 'out')
    let statusClosed = new Gpio(pins.statusClosed, 'out')

    let update = (err, value) => {
      if (this.isOpen !== value) {
        statusOpen.writeSync(value^1)
        statusClosed.writeSync(value)
        this.isOpen = value
        this.emit(this.status())
      }
    }

    sensor.read(update)
    sensor.watch(update)
  }
  status() {
    return this.isOpen ? 'open' : 'closed'
  }
  toggle() {
    console.log('Door toggled')
    var doorSwitch = new Gpio(this.options.pins.doorSwitch, 'out')
    doorSwitch.writeSync(1)
    setTimeout(function() {
      doorSwitch.writeSync(0)
    }, 100)
  }
  open() {
    if (!this.isOpen) this.toggle()
  }
  close() {
    if (this.isOpen) this.toggle()
  }
}

module.exports = Door

Make sure state is different


function withBoxUnlocked(body) {
    var relock = false;
    if (box.locked) {
        box.unlock();
        relock = true;
    }

    try {
        return body();
    }
    finally {
        if (relock)
            box.lock();
    }
}

function withBoxUnlocked(body) {
    let wasLocked = box.locked;
    if (wasLocked) {
        box.unlock();
    }
    try {
        return body();
    } finally {
        if (wasLocked) {
            box.lock();
        }
    }
}

Add Chapter 08, exercise 2


function initialize()
{
}

function start()
{
}

function update(time)
{
}

function draw(timeLapse)
{
}

function stop()
{
}

function cleanup()
{
}

self.initialize = initialize;
self.start = start;
self.update = update;
self.draw = draw;
self.stop = stop;
self.cleanup = cleanup;

/*
 * Called when the scene any GameObject an instance of this class is
 * attached to is loaded by the engine. This will happen exactly once
 * during the instance's lifetime.
 */
function initialize()
{
}

/*
 * Called when the scene any GameObject an instance of this class is
 * attached to begins to play. This can happen multiple times over
 * the instance's lifetime.
 */
function start()
{
}

/*
 * Called a set number of times every second while the scene is active. This
 * happend after start and before stop.
 * The parameter time describes the number of milliseconds since the previous
 * update call.
 */
function update(time)
{
}

/*
 * Called any number of times every update, from 0 to many, depending on how
 * powerful the machine the game is being run on is. If you are drawing anything
 * in the game, put it in this function.
 * The parameter timeLapse contains the number of milliseconds which have passed
 * since the most recent update call.
 */
function draw(timeLapse)
{
}

/*
 * Called when the scene any GameObject an instance of this class is
 * attached to stops playing. This can happen multiple times during the
 * instance's lifetime.
 */
function stop()
{
}

/*
 * Called when the scene is unloaded by the engine. Use this to clean up
 * your object's impact on the game (such as MessageHandler subscriptions).
 * This will happen exactly once during the instance's lifetime.
 */
function cleanup()
{
}

/*
 * Here you describe which functions and other variables should be available
 * outside the the object instance. The six functions defined here are the
 * ones the engine will call, but you can also call functions on other objects
 * by defining them here. The self object is given to you by the engine. You
 * can view this as a way of creating public and private sections of the object.
 */
self.initialize = initialize;
self.start = start;
self.update = update;
self.draw = draw;
self.stop = stop;
self.cleanup = cleanup;

Add some documentation to the scripted logic template. First help++


const {Initializer, api} = require('actionhero');
const r = require('../../shared/r');
const access = require('../lib/dal/access');
const projectAccessCache = require('../lib/project-access-cache')(access);

module.exports = class APIKeyInitializer extends Initializer {
    constructor() {
        super();
        this.name = 'project-access';
        this.startPriority = 1010;
    }

    initialize() {
        const middleware = {
            name: this.name,
            global: true,
            preProcessor: async (data) => {
                if (data.actionTemplate.do_not_authenticate) {
                    return;
                }

                if (!data.params.project_id) {
                    return;
                }

                const project = await projectAccessCache.find(data.params.project_id);
                if (!project) {
                    throw new Error(`No such project ${data.params.project_id}`);
                }

                if (!projectAccessCache.validateAccess(data.params.project_id, data.user)) {
                    throw new Error(`No access to project ${data.params.project_id}`);
                }
            }
        };

        r.table('access').changes().toStream().on('data', function() {
            projectAccessCache.clear();
        });

        r.table('projects').changes().toStream().on('data', function() {
            projectAccessCache.clear();
        });

        api.actions.addMiddleware(middleware);
    }
};

const {Initializer, api} = require('actionhero');
const r = require('../../shared/r');
const access = require('../lib/dal/access');
const projectAccessCache = require('../lib/project-access-cache')(access);

module.exports = class APIKeyInitializer extends Initializer {
    constructor() {
        super();
        this.name = 'project-access';
        this.startPriority = 1010;
    }

    initialize() {
        const middleware = {
            name: this.name,
            global: true,
            preProcessor: async (data) => {
                if (data.actionTemplate.do_not_authenticate) {
                    return;
                }

                if (!data.params.project_id) {
                    return;
                }

                const project = await projectAccessCache.find(data.params.project_id);
                if (!project) {
                    throw new Error(`No such project ${data.params.project_id}`);
                }

                if (!projectAccessCache.validateAccess(data.params.project_id, data.user)) {
                    throw new Error(`No access to project ${data.params.project_id}`);
                }

                data.projectAccess = project;
            }
        };

        r.table('access').changes().toStream().on('data', function() {
            projectAccessCache.clear();
        });

        r.table('projects').changes().toStream().on('data', function() {
            projectAccessCache.clear();
        });

        api.actions.addMiddleware(middleware);
    }
};

Save projectAccess into data space


'use strict';

// TODO: clean-up

var abs = require( '@stdlib/math/base/special/abs' );
var divide = require( 'compute-divide' );
var mean = require( 'compute-mean' );
var subtract = require( 'compute-subtract' );
var log10 = require( './../lib' );

var data = require( './fixtures/julia/data.json' );

var x = data.x;
var yexpected = data.expected;
var ycustom = new Array( x.length );
var ynative = new Array( x.length );
for ( var i = 0; i < x.length; i++ ) {
	if ( yexpected[ i ] === 0.0 ) {
		yexpected[ i ] += 1e-16;
	}
	ycustom[ i ] = log10( x[ i ] );
	ynative[ i ] = Math.log10( x[ i ] );
}

var customErrs = abs( divide( subtract( ycustom, yexpected ), yexpected ) );
var nativeErrs = abs( divide( subtract( ynative, yexpected ), yexpected ) );

console.log( 'The mean relative error of Math.log10 compared to Julia is %d', mean( nativeErrs ) );
console.log( 'The mean relative error of this module compared to Julia is %d', mean( customErrs ) );

'use strict';

// TODO: clean-up

// MODULES //

var abs = require( '@stdlib/math/base/special/abs' );
var divide = require( 'compute-divide' );
var mean = require( 'compute-mean' );
var subtract = require( 'compute-subtract' );
var log10 = require( './../lib' );

// FIXTURES //

var data = require( './fixtures/julia/data.json' );

// MAIN //

var customErrs;
var nativeErrs;
var yexpected;
var ycustom;
var ynative;
var x;
var i;

x = data.x;
yexpected = data.expected;
ycustom = new Array( x.length );
ynative = new Array( x.length );
for ( i = 0; i < x.length; i++ ) {
	if ( yexpected[ i ] === 0.0 ) {
		yexpected[ i ] += 1e-16;
	}
	ycustom[ i ] = log10( x[ i ] );
	ynative[ i ] = Math.log10( x[ i ] );
}

customErrs = abs( divide( subtract( ycustom, yexpected ), yexpected ) );
nativeErrs = abs( divide( subtract( ynative, yexpected ), yexpected ) );

console.log( 'The mean relative error of Math.log10 compared to Julia is %d', mean( nativeErrs ) );
console.log( 'The mean relative error of this module compared to Julia is %d', mean( customErrs ) );

Move variable declaration and add section headers


export class Attribute {

    constructor(data) {
        this.name = (data && data.name) || "default"
        this.globalUnique = (data && data.globalUnique) || false
        this.localUnique = (data && data.localUnique) || false
        this.type = (data && data.type) || "String"
        this.default = (data && data.default) || {type: "String", value: ""}
        this.constant = (data && data.constant) || false
        this.singleAssignment = (data && data.singleAssignment) || false
        this.expression = (data && data.expression) || ""
        this.ordered = (data && data.ordered) || false
        this.transient = (data && data.transient) || false
        this.value = (data && data.value) || {type: "String", value: ""}
    }

    clone() {
        return new Attribute({
            name: this.name,
            globalUnique: this.globalUnique,
            localUnique: this.localUnique,
            type: this.type,
            default: this.default,
            constant: this.constant,
            singleAssignment: this.singleAssignment,
            expression: this.expression,
            ordered: this.ordered,
            transient: this.transient,
            value: this.value
        });
    }
}

export class Attribute {

    constructor(data) {
        this.name = (data && data.name) || "default"
        this.globalUnique = (data && data.globalUnique) || false
        this.localUnique = (data && data.localUnique) || false
        this.type = (data && data.type) || "String"
        this.default = (data && data.default) || ""
        this.constant = (data && data.constant) || false
        this.singleAssignment = (data && data.singleAssignment) || false
        this.expression = (data && data.expression) || ""
        this.ordered = (data && data.ordered) || false
        this.transient = (data && data.transient) || false
        this.value = (data && data.value) || ""
    }

    clone() {
        return new Attribute({
            name: this.name,
            globalUnique: this.globalUnique,
            localUnique: this.localUnique,
            type: this.type,
            default: this.default,
            constant: this.constant,
            singleAssignment: this.singleAssignment,
            expression: this.expression,
            ordered: this.ordered,
            transient: this.transient,
            value: this.value
        });
    }
}

Change attribute values from object type to string type


import Ember from 'ember';
import trapTabKey from '../mixins/trap-tab-key';
import $ from 'jquery';

export default Ember.Component.extend(trapTabKey, {

  didInsertElement () {
    const _this = this;

    this.trapTabKey({elementId: 'user-settings-cont'}, () => {
      this.sendAction('action');
      Ember.run.later(() => {
        $('.st--main-nav').children().last().focus();
      }, 100);
    });

    $('.settings-item').click(function () {
      Ember.run.later(() => {
        _this.updateRegisteredKeys();
        $(this).find('input').focus();
      }, 100);
    });
  }
});

import Ember from 'ember';
import trapTabKey from '../mixins/trap-tab-key';
import $ from 'jquery';

export default Ember.Component.extend(trapTabKey, {

  didInsertElement () {
    const _this = this;

    this.trapTabKey({elementId: 'user-settings-cont'}, () => {
      this.sendAction('action');
      Ember.run.later(() => {
        $('.st--main-nav').children().last().focus();
      }, 100);
    });

    $('.settings-item').click(function () {
      Ember.run.later(() => {
        _this.updateRegisteredKeys();
        $(this).find('input, select').focus();
      }, 100);
    });
  }
});

Add select when finding closest focusable element, when in edit mode


import React, { Component, PropTypes } from 'react';

class Counter extends Component {
  render() {
    const { increment, incrementIfOdd, incrementAsync, bg, decrement, state } = this.props;
    return (

        Clicked: {state.counter.count} times

        <button className="btn" onClick={increment}>+</button>
        {' '}
        <button className="btn" onClick={decrement}>-</button>

        <button className="btn" onClick={incrementIfOdd}>Increment if odd</button>

        <button className="btn" onClick={() => incrementAsync()}>Increment async</button>

        <button className="btn" onClick={() => bg('increment')}>Increment in background</button>

    );
  }
}

Counter.propTypes = {
  increment: PropTypes.func.isRequired,
  incrementIfOdd: PropTypes.func.isRequired,
  incrementAsync: PropTypes.func.isRequired,
  bg: PropTypes.func.isRequired,
  decrement: PropTypes.func.isRequired,
  state: PropTypes.object.isRequired
};

export default Counter;

import React, { Component, PropTypes } from 'react';

class Counter extends Component {
  render() {
    const { increment, incrementIfOdd, incrementAsync, decrement, state } = this.props;
    return (

        Clicked: {state.counter.count} times

        <button className="btn" onClick={increment}>+</button>
        {' '}
        <button className="btn" onClick={decrement}>-</button>

        <button className="btn" onClick={incrementIfOdd}>Increment if odd</button>

        <button className="btn" onClick={() => incrementAsync()}>Increment async</button>

    );
  }
}

Counter.propTypes = {
  increment: PropTypes.func.isRequired,
  incrementIfOdd: PropTypes.func.isRequired,
  incrementAsync: PropTypes.func.isRequired,
  decrement: PropTypes.func.isRequired,
  state: PropTypes.object.isRequired
};

export default Counter;

Remove 'Increment in background' action as all the actions should be dispatched in background


import { app, BrowserWindow, nativeTheme } from 'electron'
import path from 'path'

try {
  if (process.platform === 'win32' && nativeTheme.shouldUseDarkColors === true) {
    require('fs').unlinkSync(require('path').join(app.getPath('userData'), 'DevTools Extensions'))
  }
} catch (_) { }

let mainWindow

function createWindow () {
  /**
   * Initial window options
   */
  mainWindow = new BrowserWindow({
    width: 1000,
    height: 600,
    useContentSize: true,
    webPreferences: {
      contextIsolation: true,
      // More info: /quasar-cli/developing-electron-apps/electron-preload-script
      preload: path.resolve(__dirname, process.env.QUASAR_ELECTRON_PRELOAD)
    }
  })

  mainWindow.loadURL(process.env.APP_URL)

  mainWindow.on('closed', () => {
    mainWindow = null
  })
}

app.on('ready', createWindow)

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('activate', () => {
  if (mainWindow === null) {
    createWindow()
  }
})

import { app, BrowserWindow, nativeTheme } from 'electron'
import path from 'path'

try {
  if (process.platform === 'win32' && nativeTheme.shouldUseDarkColors === true) {
    require('fs').unlinkSync(require('path').join(app.getPath('userData'), 'DevTools Extensions'))
  }
} catch (_) { }

let mainWindow

function createWindow () {
  /**
   * Initial window options
   */
  mainWindow = new BrowserWindow({
    width: 1000,
    height: 600,
    useContentSize: true,
    webPreferences: {
      contextIsolation: true,
      // More info: /quasar-cli/developing-electron-apps/electron-preload-script
      preload: path.resolve(__dirname, process.env.QUASAR_ELECTRON_PRELOAD)
    }
  })

  mainWindow.loadURL(process.env.APP_URL)

  if (process.env.DEBUGGING) {
    // if on DEV or Production with debug enabled
    mainWindow.webContents.openDevTools()
  } else {
    // we're on production; no access to devtools pls
    mainWindow.webContents.on('devtools-opened', () => {
      mainWindow.webContents.closeDevTools()
    })
  }

  mainWindow.on('closed', () => {
    mainWindow = null
  })
}

app.on('ready', createWindow)

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('activate', () => {
  if (mainWindow === null) {
    createWindow()
  }
})

Improve electron template on devtools usage


#!/usr/bin/env node

var fs = require('fs'),
  compressor = require('node-minify');

new compressor.minify({
  type: 'gcc',
  fileIn: '../intro.js',
  fileOut: '../minified/intro.min.js',
  callback: function (err) {
    if (err) {
      console.log(err);
    } else {
      console.log("JS minified");
    }
  }
});

new compressor.minify({
  type: 'yui-css',
  fileIn: '../introjs.css',
  fileOut: '../minified/introjs.min.css',
  callback: function (err) {
    if (err) {
      console.log(err);
    } else {
      console.log("CSS minified");
    }
  }
});

#!/usr/bin/env node

var fs = require('fs'),
  compressor = require('node-minify');

new compressor.minify({
  type: 'gcc',
  fileIn: '../intro.js',
  fileOut: '../minified/intro.min.js',
  callback: function (err) {
    if (err) {
      console.log(err);
    } else {
      console.log("JS minified successfully.");
    }
  }
});

new compressor.minify({
  type: 'yui-css',
  fileIn: '../introjs.css',
  fileOut: '../minified/introjs.min.css',
  callback: function (err) {
    if (err) {
      console.log(err);
    } else {
      console.log("CSS minified successfully.");
    }
  }
});

Change success message of builds


import _ from 'lodash';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchPosts } from  '../actions';

class PostsIndex extends Component {
  componentDidMount() {
    this.props.fetchPosts();
  }

  renderPosts() {
    return _.map(this.props.posts, post => {
      return (

          {post.title}

      );
    })
  }

  render() {
    return (

Posts

          {this.renderPosts()}

    );
  }
}

function mapStateToProps(state) {
  return { posts: state.posts }
}

export default connect(mapStateToProps, { fetchPosts })(PostsIndex);

import _ from 'lodash';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { fetchPosts } from  '../actions';

class PostsIndex extends Component {
  componentDidMount() {
    this.props.fetchPosts();
  }

  renderPosts() {
    return _.map(this.props.posts, post => {
      return (

          {post.title}

      );
    })
  }

  render() {
    return (

          <Link className="btn btn-primary" to="/posts/new">
            Add a Post
          </Link>

Posts

          {this.renderPosts()}

    );
  }
}

function mapStateToProps(state) {
  return { posts: state.posts }
}

export default connect(mapStateToProps, { fetchPosts })(PostsIndex);

Add a link for a new post


$(document).ready(function() {
	$('#reservation-table').DataTable({});
});

$('.btn-cancel').on('click', function() {
	var button = $(this);
	var id = button.data('rev-id');
	if (confirm('Are you sure to cancel this reservation?')) {
		$.post(reservationCancelUrl, {id:id}, function(data) {
			if (data['result']) {
				button.hide();
				$('[data-reservation="'+id+'"]').find('.status').text('cancel');
				successMessage(data['message']);
			} else {
				errorMessage(data['message']);
			}
		}, 'JSON');
	}
});

$('.btn-view').on('click', function() {
	var id = $(this).data('rev-id');
	$.post(reservationDetailUrl, {id:id}, function(data) {
		if (data['result']) {
			$('#reservation-modal').find('.modal-body').html(data['view']);
			$('#reservation-modal').modal('show');
		} else {
			errorMessage(data['message']);
		}
	}, 'JSON');
});

$('.btn-rent').on('click', function() {
	var id = $(this).data('rev-id');
	successMessage('Please wait while connecting to paypal');
	$.post(rentUrl, {id:id}, function(data) {
		if (data['result']) {
			if (data['paypal']['result']) {
				$('#paykey').val(data['paypal']['packet']['payKey']);
				$('#btn-pay').click();
			} else {
				errorMessage(data['paypal']['message']);
			}
		} else {
			errorMessage(data['message']);
		}
	}, 'JSON');
});

function closePaypal()  {
	dgFlow.closeFlow();
	top.close();
	window.reload
}

Update reservation script for lessees reservation


'use strict';

CKEDITOR.editorConfig = function(config) {
  config.toolbar =
  [
    {
      name: 'styles',
      items : [ 'Format' ]
    },
    {
      name: 'basicstyles',
      items : [ 'Bold','Italic','Strike','-','RemoveFormat' ]
    },
    {
      name: 'paragraph',
      items : [ 'NumberedList','BulletedList','-','Outdent','Indent','-' ]
    }
  ];
};

'use strict';

CKEDITOR.editorConfig = function(config) {
  config.toolbar =
  [
    {
      name: 'styles',
      items : [ 'Format' ]
    },
    {
      name: 'basicstyles',
      items : [ 'Bold','Italic','Strike','-','RemoveFormat' ]
    },
    {
      name: 'paragraph',
      items : [ 'NumberedList','BulletedList','-','Outdent','Indent','-' ]
    },
    {
      name: 'links',
      items : [ 'Link','Unlink' ]
    }
  ];
};

Allow insert liks in the wysiwyg editor


// @flow

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { autocomplete } from 'app/actions/SearchActions';
import { debounce } from 'lodash';

type Props = {
  filter: Array<string>
};

function withAutocomplete(WrappedComponent: any) {
  return class extends Component {
    state = {
      searching: false,
      result: []
    };

    handleSearch = (query: string, filter): void => {
      this.setState({
        searching: true
      });
      this.props
        .autocomplete(query, filter)
        .then(result =>
          this.setState({
            result,
            searching: false
          })
        )
        .catch(() => this.setState({ searching: false }));
    };

    render() {
      const { filter, ...restProps }: Props = this.props;
      return (
        <WrappedComponent
          {...restProps}
          options={this.state.result}
          onSearch={debounce(query => this.handleSearch(query, filter), 300)}
          fetching={this.state.searching}
        />
      );
    }
  };
}

const mapDispatchToProps = {
  autocomplete
};

export default (WrappedComponent: any) =>
  connect(null, mapDispatchToProps)(withAutocomplete(WrappedComponent));

// @flow

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { autocomplete } from 'app/actions/SearchActions';
import { debounce } from 'lodash';

type Props = {
  filter: Array<string>
};

function withAutocomplete(WrappedComponent: any) {
  return class extends Component {
    state = {
      searching: false,
      result: [],
      _isMounted: false
    };

    componentDidMount() {
      this.setState({
        _isMounted: true
      });
    }

    componentWillUnmount() {
      this.setState({
        _isMounted: false
      });
    }

    handleSearch = (query: string, filter): void => {
      this.setState({
        searching: true
      });
      this.props
        .autocomplete(query, filter)
        .then(result => {
          if (this.state._isMounted) {
            this.setState({
              result,
              searching: false
            });
          }
        })
        .catch(() => {
          if (this.state._isMounted) {
            this.setState({ searching: false });
          }
        });
    };

    render() {
      const { filter, ...restProps }: Props = this.props;
      return (
        <WrappedComponent
          {...restProps}
          options={this.state.result}
          onSearch={debounce(query => this.handleSearch(query, filter), 300)}
          fetching={this.state.searching}
        />
      );
    }
  };
}

const mapDispatchToProps = {
  autocomplete
};

export default (WrappedComponent: any) =>
  connect(null, mapDispatchToProps)(withAutocomplete(WrappedComponent));

Add _isMounted check for setState


// @flow

import { compose } from 'redux';
import { connect } from 'react-redux';
import EmailLists from './components/EmailLists';
import { fetch } from 'app/actions/EmailListActions';
import { selectEmailLists } from 'app/reducers/emailLists';
import prepare from 'app/utils/prepare';
import loadingIndicator from 'app/utils/loadingIndicator';

const mapStateToProps = state => ({
  emailLists: selectEmailLists(state),
  fetching: state.emailLists.fetching,
  hasMore: state.emailLists.hasMore
});

const mapDispatchToProps = { fetch };

export default compose(
  prepare((props, dispatch) => dispatch(fetch())),
  connect(
    mapStateToProps,
    mapDispatchToProps
  ),
  loadingIndicator(['emailLists.length'])
)(EmailLists);

// @flow

import { compose } from 'redux';
import { connect } from 'react-redux';
import EmailLists from './components/EmailLists';
import { fetch } from 'app/actions/EmailListActions';
import { selectEmailLists } from 'app/reducers/emailLists';
import prepare from 'app/utils/prepare';
import loadingIndicator from 'app/utils/loadingIndicator';

const mapStateToProps = state => ({
  emailLists: selectEmailLists(state),
  fetching: state.emailLists.fetching,
  hasMore: state.emailLists.hasMore,
  notFetching: !state.emailLists.fetching
});

const mapDispatchToProps = { fetch };

export default compose(
  prepare((props, dispatch) => dispatch(fetch())),
  connect(
    mapStateToProps,
    mapDispatchToProps
  ),
  loadingIndicator(['notFetching'])
)(EmailLists);

Remove loading indicator when emaillists empty


/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.u2.view',
  name: 'ValueView',
  extends: 'foam.u2.View',

  documentation: 'Just shows the value of data as a string.',

  methods: [
    function initE() {
      this.SUPER();
      return this.add(this.data);
    }
  ],
});

/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.u2.view',
  name: 'ValueView',
  extends: 'foam.u2.View',

  documentation: 'Just shows the value of data as a string.',

  methods: [
    function initE() {
      this.SUPER();
      return this.add(this.data$);
    }
  ],
});

Fix bug where output wouldn't refresh when running script through GUI