import uuidv4 from 'uuid/v4';
import { oauthClientId } from '../bungie-api/bungie-api-utils';
import template from './login.html';
import './login.scss';

export const LoginComponent = {
  template,
  controller: LoginCtrl,
  controllerAs: 'vm'
};

function LoginCtrl($stateParams) {
  'ngInject';

  const vm = this;

  localStorage.authorizationState = uuidv4();
  const clientId = oauthClientId();

  const reauth = $stateParams.reauth;
  const loginUrl = `/en/OAuth/Authorize?client_id=${clientId}&response_type=code&state=${localStorage.authorizationState}`;

  if (reauth) {
    // TEMPORARY: fully log out from Bungie.net by redirecting to a special logout/relogin page
    // Soon, Bungie.net will respect the reauth parameter and we won't have to do this
    const logoutUrl = `https://www.bungie.net/en/User/SignOut?bru=${encodeURIComponent(loginUrl)}`;
    vm.authorizationURL = logoutUrl;
  } else {
    vm.authorizationURL = `https://www.bungie.net${loginUrl}${reauth ? '&reauth=true' : ''}`;
  }
}

import uuidv4 from 'uuid/v4';
import { oauthClientId } from '../bungie-api/bungie-api-utils';
import template from './login.html';
import './login.scss';

export const LoginComponent = {
  template,
  controller: LoginCtrl,
  controllerAs: 'vm'
};

function LoginCtrl($stateParams) {
  'ngInject';

  const vm = this;

  localStorage.authorizationState = uuidv4();
  const clientId = oauthClientId();

  const reauth = $stateParams.reauth;

  vm.authorizationURL = `https://www.bungie.net/en/OAuth/Authorize?client_id=${clientId}&response_type=code&state=${localStorage.authorizationState}${reauth ? '&reauth=true' : ''}`;
}

Remove our explicit logout since reauth works now


/*
 * statici18n
 * https://github.com/beck/grunt-static-i18n
 *
 * Copyright (c) 2014 Douglas Beck
 * Licensed under the MIT license.
 */

'use strict';

module.exports = function(grunt) {

  var statici18n = function() {

    // Merge task-specific and/or target-specific options with these defaults.
    var options = this.options({
      punctuation: '.',
      separator: ', '
    });

    // Iterate over all specified file groups.
    this.files.forEach(function(file) {
      // Concat specified files.
      var src = file.src.filter(function(filepath) {
        // Warn on and remove invalid source files (if nonull was set).
        if (!grunt.file.exists(filepath)) {
          grunt.log.warn('Source file "' + filepath + '" not found.');
          return false;
        } else {
          return true;
        }
      }).map(function(filepath) {
        // Read file source.
        return grunt.file.read(filepath);
      }).join(grunt.util.normalizelf(options.separator));

      // Handle options.
      src += options.punctuation;

      // Write the destination file.
      grunt.file.write(file.dest, src);

      // Print a success message.
      grunt.log.writeln('File "' + file.dest + '" created.');
    });
  };

  var docstr = 'Grunt plugin to translate static assets.';
  grunt.registerMultiTask('statici18n', docstr, statici18n);

};

/*
 * statici18n
 * https://github.com/beck/grunt-static-i18n
 *
 * Copyright (c) 2014 Douglas Beck
 * Licensed under the MIT license.
 */

'use strict';

module.exports = function statici18n(grunt) {

  var options;

  var plugin = function() {
    options = this.options({
      localeDir: 'locale',
    });

    this.files.forEach(function task(file) {
      file.src.map();
    });
  };

  var docstr = 'Grunt plugin to translate static assets.';
  grunt.registerMultiTask('statici18n', docstr, plugin);

};

Restructure plugin module. Flatter. No anons


var Blocks = require('./blocks');

/**
 * @fileoverview
 * A Target is an abstract "code-running" object for the Scratch VM.
 * Examples include sprites/clones or potentially physical-world devices.
 */

/**
 * @param {?Blocks} blocks Blocks instance for the blocks owned by this target.
 * @constructor
 */
function Target (blocks) {
    if (!blocks) {
        blocks = new Blocks(this);
    }
    this.blocks = blocks;
}

module.exports = Target;

var Blocks = require('./blocks');
var uid = require('../util/uid');

/**
 * @fileoverview
 * A Target is an abstract "code-running" object for the Scratch VM.
 * Examples include sprites/clones or potentially physical-world devices.
 */

/**
 * @param {?Blocks} blocks Blocks instance for the blocks owned by this target.
 * @constructor
 */
function Target (blocks) {
    if (!blocks) {
        blocks = new Blocks(this);
    }
    /**
     * A unique ID for this target.
     * @type {string}
     */
    this.id = uid();
    /**
     * Blocks run as code for this target.
     * @type {!Blocks}
     */
    this.blocks = blocks;
}

module.exports = Target;

Add unique ID for Targets


const parsers = require('../parsers')
const transform = require('../transform')
const request = require('../request')
const { ConnectionFailedError, EmptyParseOutputError } = require('../errors')

async function parse(parser, text) {
  const parsed = await parsers[parser](text)
  if (!parsed) throw new EmptyParseOutputError()
  return transform(parsed)
}

module.exports = async function parseFromQuery({ url, parser }) {
  let content
  try {
    content = (await request(url)).text
  } catch (error) {
    if (error.code === 'ENOTFOUND') {
      throw new ConnectionFailedError(url)
    }
    throw error
  }

  if (parser) {
    return parse(parser, content)
  } else {
    for (let i = 0; i < parsers.keys.length; i++) {
      try {
        return parse(parsers.keys[i], content)
      } catch (error) {
        if (i !== parsers.keys.length - 1) {
          throw error
        }
        continue
      }
    }
  }
}

const parsers = require('../parsers')
const transform = require('../transform')
const request = require('../request')
const { ConnectionFailedError, EmptyParseOutputError } = require('../errors')

async function parse(parser, text) {
  const parsed = await parsers[parser](text)
  if (!parsed) throw new EmptyParseOutputError()
  return transform(parsed)
}

module.exports = async function parseFromQuery({ url, parser }) {
  let content
  try {
    content = (await request(url)).text
  } catch (error) {
    if (error.code === 'ENOTFOUND') {
      throw new ConnectionFailedError(url)
    }
    throw error
  }

  if (parser) {
    return parse(parser, content)
  } else {
    for (let i = 0; i < parsers.keys.length; i++) {
      try {
        return await parse(parsers.keys[i], content)
      } catch (error) {
        if (i < parsers.keys.length - 1) {
          continue
        }
        throw error
      }
    }
  }
}

Make error handling use full parser list


FamousFramework.scene('famous-tests:physics:basic:particle', {
    behaviors: {
        '.particle': {
            'size': [200, 200],
            'align': [0.5, 0.5],
            'mount-point': [0.5, 0.5],
            'position': function(position) {
                return position;
            },
            'style': {
                'background': 'whitesmoke',
                'border-radius': '50%'
            }
        }
    },
    events: {
        '$lifecycle': {
            'post-load': function($state) {
                $state.applyPhysicsForce('gravity1D', [ 'position' ]);
            }
        }
    },
    states: {
        'position': [0, 0, 0]
    },
    tree: `
        <node class="particle"></node>
    `
});

FamousFramework.scene('famous-tests:physics:basic:particle', {
    behaviors: {
        '.particle': {
            'size': [200, 200],
            'align': [0.5, 0.5],
            'mount-point': [0.5, 0.5],
            'style': {
                'background': 'whitesmoke',
                'border-radius': '50%'
            }
        },
        '.one': {
            'position': function(position1) {
                return position1;
            }
        },
        '.two': {
            'position': function(position2) {
                return position2;
            }
        }
    },
    events: {
        '$lifecycle': {
            'post-load': function($state) {
                $state
                    .applyPhysicsForce('gravity1D', [ 'position1' ])
                    .applyPhysicsConstraint('distance', [ 'position1', 'position2' ], {
                        length: 400
                    });
            }
        }
    },
    states: {
        'position1': [0, 0, 0],
        'position2': [0, -200, 0]
    },
    tree: `
        <node class="particle one"></node>
        <node class="particle two"></node>
    `
});

Add example of applying physics constraint


'use strict';

var httpclient = require('../../http-client');

function messageListener(db, from, channel, message) {

  var match = /(https?:\/\/[^ ]+)/.match(message)

  if (match) {

    var res = httpclient(db, match[1]);
    var match = /<title>(.+)<\/title>/.exec(res);

    if (match) {
      var decoded = match[1].replace(/&#\d+;/gm,function(s) {
        return String.fromCharCode(s.match(/\d+/gm)[0]);
      });
      decoded = decoded.replace(/&/g, "&")
                       .replace(/</g, "<")
                       .replace(/>/g, ">")
                       .replace(/"/g, "\"")
                       .replace(/'/g, "'");
      return [ { to: channel, message: decoded } ] ;
    }

  }

}

module.exports = messageListener;

'use strict';

var httpclient = require('../../http-client');

function messageListener(db, from, channel, message) {

  var match = /(https?:\/\/[^ ]+)/.exec(message)

  if (match) {

    var res = httpclient(db, match[1]);
    var match = /<title>(.+)<\/title>/.exec(res);

    if (match) {
      var decoded = match[1].replace(/&#\d+;/gm,function(s) {
        return String.fromCharCode(s.match(/\d+/gm)[0]);
      });
      decoded = decoded.replace(/&/g, "&")
                       .replace(/</g, "<")
                       .replace(/>/g, ">")
                       .replace(/"/g, "\"")
                       .replace(/'/g, "'");
      return [ { to: channel, message: decoded } ] ;
    }

  }

}

module.exports = messageListener;

Use the correct match function


let dependencyChecker = {}
let logger = {}
let colors = {}

try {
  dependencyChecker = require('check-dependencies')
  logger = require('../logger')
  colors = require('colors/safe')
} catch (err) {
  console.log('Dependencies could not be checked due to "' + err.message + '" error (NOT OK)')
  process.exit(1)
}

const validateDependencies = async ({ exitOnFailure = true } = {}) => {
  let success = true
  let dependencies = {}
  try {
    dependencies = await dependencyChecker()
  } catch (err) {
    logger.warn(`Dependencies could not be checked due to "${err.message}" error (${colors.red('NOT OK')})`)
  }

  if (dependencies.depsWereOk === true) {
    logger.info(`All dependencies are satisfied (${colors.green('OK')})`)
  } else {
    logger.warn(`Dependencies are not rightly satisfied (${colors.red('NOT OK')})`)
    success = false
    for (let err of dependencies.error) {
      console.log(err)
    }
  }

  if (!success && exitOnFailure) {
    logger.error(colors.red('Exiting due to unsatisfied dependencies!'))
    process.exit(1)
  }
}

module.exports = validateDependencies

let dependencyChecker = {}
try {
  dependencyChecker = require('check-dependencies')
} catch (err) {
  console.error('Please run "npm install" before starting the application!')
  process.exit(1)
}
const logger = require('../logger')
const colors = require('colors/safe')

const validateDependencies = async ({ exitOnFailure = true } = {}) => {
  let success = true
  let dependencies = {}
  try {
    dependencies = await dependencyChecker()
  } catch (err) {
    logger.warn(`Dependencies could not be checked due to "${err.message}" error (${colors.red('NOT OK')})`)
  }

  if (dependencies.depsWereOk === true) {
    logger.info(`All dependencies are satisfied (${colors.green('OK')})`)
  } else {
    logger.warn(`Dependencies are not rightly satisfied (${colors.red('NOT OK')})`)
    success = false
    for (let err of dependencies.error) {
      console.log(err)
    }
  }

  if (!success && exitOnFailure) {
    logger.error(colors.red('Exiting due to unsatisfied dependencies!'))
    process.exit(1)
  }
}

module.exports = validateDependencies

Move logger, colors out of try catch block


import express from 'express';
import path from 'path';
import open from 'open';

/*eslint-disable no-console*/

const port = 3000;
const app = express();

app.use(express.static('dist'));

app.get('*', (req,res) => {
  res.sendFile(path.join(__dirname, '../dist/index.html'));
});

app.listen(port, error => {
  if (error) {
    console.log(error);
  } else {
    console.log(`Server started at http://localhost:${port}`);
  }
});

import express from 'express';
import path from 'path';
import open from 'open';
import compression from 'compression';

/*eslint-disable no-console*/

const port = 3000;
const app = express();

app.use(compression());

app.use(express.static('dist'));

app.get('*', (req,res) => {
  res.sendFile(path.join(__dirname, '../dist/index.html'));
});

app.listen(port, error => {
  if (error) {
    console.log(error);
  } else {
    console.log(`Server started at http://localhost:${port}`);
  }
});

Add gzip compression to dist server


import ExtendableError from 'es6-error';

export default class MeaningError extends ExtendableError {
  constructor(message = 'Server Error') {
    super(message);

    this.message = message;

    if (!this.code) {
      this.code = 500;
    }
  }

  getCode() {
    return this.code;
  }

  getMessage() {
    return this.message;
  }
}

import ExtendableError from 'es6-error';

export default class MeaningError extends ExtendableError {
  constructor(message = 'Server Error') {
    super(message);

    this._message = message;

    if (!this.code) {
      this.code = 500;
    }
  }

  getCode() {
    return this.code;
  }

  getMessage() {
    return this._message;
  }
}

Fix not use message from inheritance class


import glob from 'glob'
import Router from 'koa-router'
import convert from 'koa-convert'

exports = module.exports = function initModules(app) {
  glob(`${__dirname}/*`, { ignore: '**/index.js' }, (err, matches) => {
    if (err) { throw err }

    matches.forEach((mod) => {
      const routerConfig = require(`${mod}/router`).default
      const router = new Router({ prefix: routerConfig.base })

      for (const [key, props] of Object.entries(routerConfig)) {
        if (key === 'base') { continue }

        const {
          method = '',
          route = '',
          middleware = [],
          controller
        } = props

        router[method.toLowerCase()](route, ...middleware, controller)

        app
          .use(router.routes())
          .use(router.allowedMethods())
      }
    })
  })
}

import glob from 'glob'
import Router from 'koa-router'

exports = module.exports = function initModules(app) {
  glob(`${__dirname}/*`, { ignore: '**/index.js' }, (err, matches) => {
    if (err) { throw err }

    matches.forEach((mod) => {
      const routerConfig = require(`${mod}/router`).default
      const router = new Router({ prefix: routerConfig.base })

      for (const [key, props] of Object.entries(routerConfig)) {
        if (key === 'base') { continue }

        const {
          method = '',
          route = '',
          handlers = []
        } = props

        router[method.toLowerCase()](route, ...handlers)

        app
          .use(router.routes())
          .use(router.allowedMethods())
      }
    })
  })
}

Update router generation with `handlers`


import { Meteor } from 'meteor/meteor';

import Week from '../model/week.js';
import WeekService from '../service/week-service.js';

if (Meteor.isServer) {
  Meteor.publish('weeks.last', function weeksPublication() {
    return Week.find({ leagueId: '2018-19' }, { sort: { nflWeek: -1 }, limit: 1 });
  });
}

Meteor.methods({
  'weeks.insert'(model) { return WeekService.insert(model); },
  'weeks.update'(model, field, value) { return WeekService.update(model, field, value); },
  'weeks.remove'(model) { return WeekService.remove(model); },
});

import { Meteor } from 'meteor/meteor';

import Week from '../model/week.js';
import WeekService from '../service/week-service.js';

if (Meteor.isServer) {
  Meteor.publish('weeks.last', function weeksPublication() {
    return Week.find({ leagueId: '2018-19', seasonType: { $ne: 'PRE' } }, { sort: { nflWeek: -1 }, limit: 1 });
  });
}

Meteor.methods({
  'weeks.insert'(model) { return WeekService.insert(model); },
  'weeks.update'(model, field, value) { return WeekService.update(model, field, value); },
  'weeks.remove'(model) { return WeekService.remove(model); },
});

Add additional filter on seasonType


const conf = require('./gulp.conf');
const wiredep = require('wiredep');

module.exports = function listFiles() {
  const wiredepOptions = Object.assign({}, conf.wiredep, {
<% if (framework === 'react') { -%>
    overrides: {
      react: { main: [ 'react-with-addons.js' ] }
    },
<% } -%>
    dependencies: true,
    devDependencies: true
  });

  const patterns = [
    ...wiredep(wiredepOptions).js,
<% if (framework === 'angular1') { -%>
    conf.path.tmp('**/*.js'),
    conf.path.src('**/*.html')
<% } -%>
<% if (framework === 'react') { -%>
    conf.path.tmp('app/**/*.js')
<% } -%>
  ];

  var files = patterns.map(pattern => ({ pattern: pattern }));
  files.push({
    pattern: conf.path.src('assets/**/*'),
    included: false,
    served: true,
    watched: false
  });
  return files;
}

const conf = require('./gulp.conf');
const wiredep = require('wiredep');

module.exports = function listFiles() {
  const wiredepOptions = Object.assign({}, conf.wiredep, {
<% if (framework === 'react') { -%>
    overrides: {
      react: { main: [ 'react-with-addons.js' ] }
    },
<% } -%>
    dependencies: true,
    devDependencies: true
  });

  const patterns = wiredep(wiredepOptions).js.concat([
<% if (framework === 'angular1') { -%>
    conf.path.tmp('**/*.js'),
    conf.path.src('**/*.html')
<% } -%>
<% if (framework === 'react') { -%>
    conf.path.tmp('app/**/*.js')
<% } -%>
  ];

  var files = patterns.map(pattern => ({ pattern: pattern }));
  files.push({
    pattern: conf.path.src('assets/**/*'),
    included: false,
    served: true,
    watched: false
  });
  return files;
}

Remove array spread for node v4 compatibility


$(function() {
  var cookieName = '_wheelmap_splash_seen';

  if(!$.cookie(cookieName)) {
    wwidth = $(window).width();
    left = (0.5 - 300/wwidth) * 100 + '%';

    $.blockUI({
      message: $("#splash"),
      css: {
              top: '25%',
              left: left,
              width: '600px'
           }
    });

    $("#splash .unblock-splash").click($.unblockUI)

    $('.blockOverlay').css('cursor', 'auto').click(function() {
      $.unblockUI();
      $.cookie(cookieName, true)
    });
  }

})

$(function() {
  var cookieName = '_wheelmap_splash_seen';

  if(!$.cookie(cookieName)) {
    width = 600; // splash width

    // calculate left edge so it is centered
    left = (0.5 - (width / 2)/($(window).width())) * 100 + '%';

    $.blockUI({
      message: $("#splash"),
      css: {
              top: '25%',
              left: left,
              width: width + 'px'
           }
    });

    var clickHandler = function() {
      $.unblockUI();
      $.cookie(cookieName, true);
      return false;
    };

    $("#splash .unblock-splash").click(clickHandler);
    $('.blockOverlay').css('cursor', 'auto').click(clickHandler);

    $('a.whatis').click(function() {
      $.cookie(cookieName, true);
      return true;
    });

  }

})

Set cookie also if "What is.." is clicked.


var _ = require('underscore');
var async = require('async');
var config = require('./config');
var fs = require('fs');
var mkdirp = require('mkdirp');
var path = require('path');

var pruneManifest = function (manifest) {
  return _.reduce(manifest, function (manifest, val, key) {
    if (key === '__VERSION__' || key === '__IN__') {
      manifest[key] = val;
    } else if (val.targetPaths && val.hash) {
      manifest[key] = _.omit(val, 'buffer');
    }
    return manifest;
  }, {});
};

module.exports = function (cb) {
  var manifestPath = config.get().manifestPath;
  if (!manifestPath) return cb();
  async.series([
    _.partial(mkdirp, path.dirname(manifestPath)),
    _.partial(
      fs.writeFile,
      manifestPath,
      JSON.stringify(pruneManifest(config.get().manifest))
    )
  ], cb);
};

var _ = require('underscore');
var async = require('async');
var config = require('./config');
var fs = require('fs');
var mkdirp = require('mkdirp');
var path = require('path');

var META_KEYS = ['__VERSION__', '__IN__', '__TRANSFORMERS__'];

var pruneManifest = function (manifest) {
  return _.reduce(manifest, function (manifest, val, key) {
    if (_.include(META_KEYS, key)) {
      manifest[key] = val;
    } else if (val.targetPaths && val.hash) {
      manifest[key] = _.omit(val, 'buffer');
    }
    return manifest;
  }, {});
};

module.exports = function (cb) {
  var manifestPath = config.get().manifestPath;
  if (!manifestPath) return cb();
  async.series([
    _.partial(mkdirp, path.dirname(manifestPath)),
    _.partial(
      fs.writeFile,
      manifestPath,
      JSON.stringify(pruneManifest(config.get().manifest))
    )
  ], cb);
};

Add __TRANSFORMERS__ to special keys


export function validate_account_name(value) {
    let i, label, len, length, ref, suffix;

    suffix = 'Account name should ';
    if (!value) {
        return suffix + 'not be empty.';
    }
    length = value.length;
    if (length < 3) {
        return suffix + 'be longer.';
    }
    if (length > 16) {
        return suffix + 'be shorter.';
    }
    if (/\./.test(value)) {
        suffix = 'Each account segment should ';
    }
    ref = value.split('.');
    for (i = 0, len = ref.length; i < len; i++) {
        label = ref[i];
        if (!/^[a-z]/.test(label)) {
            return suffix + 'start with a letter.';
        }
        if (!/^[a-z0-9-]*$/.test(label)) {
            return suffix + 'have only letters, digits, or dashes.';
        }
        if (/--/.test(label)) {
            return suffix + 'have only one dash in a row.';
        }
        if (!/[a-z0-9]$/.test(label)) {
            return suffix + 'end with a letter or digit.';
        }
        if (!(label.length >= 3)) {
            return suffix + 'be longer';
        }
    }
    return null;
}

export function validate_account_name(value) {
    const badActorList = /(polonox|poloneix|blocktrads|blocktrade|bittrexx)/;
    let i, label, len, length, ref, suffix;

    suffix = 'Account name should ';
    if (!value) {
        return suffix + 'not be empty.';
    }
    length = value.length;
    if (length < 3) {
        return suffix + 'be longer.';
    }
    if (length > 16) {
        return suffix + 'be shorter.';
    }
    if (/\./.test(value)) {
        suffix = 'Each account segment should ';
    }
    if (badActorList.test(value)) {
        return suffix = 'Use caution sending to this account. Please double check your spelling for possible phishing. ';
    }
    ref = value.split('.');
    for (i = 0, len = ref.length; i < len; i++) {
        label = ref[i];
        if (!/^[a-z]/.test(label)) {
            return suffix + 'start with a letter.';
        }
        if (!/^[a-z0-9-]*$/.test(label)) {
            return suffix + 'have only letters, digits, or dashes.';
        }
        if (/--/.test(label)) {
            return suffix + 'have only one dash in a row.';
        }
        if (!/[a-z0-9]$/.test(label)) {
            return suffix + 'end with a letter or digit.';
        }
        if (!(label.length >= 3)) {
            return suffix + 'be longer';
        }
    }
    return null;
}

Include bad actor list check in to column for transfering, include new warning for phishing accounts


var ws281x = require('../index.js');

var NUM_LEDS = parseInt(process.argv[2], 10) || 10,
    pixelData = new Uint32Array(NUM_LEDS);

ws281x.init(NUM_LEDS);

ws281x.setIndexMapping(ws281x.indexMapping.mirrorMatrixX(10,10));

// ---- trap the SIGINT and reset before exit
process.on('SIGINT', function () {
  ws281x.reset();
  process.nextTick(function () { process.exit(0); });
});

// ---- animation-loop
var offset = 0;
setInterval(function () {
  var i=NUM_LEDS;
  while(i--) {
      pixelData[i] = 0;
  }
  pixelData[offset] = 0xffffff;

  offset = (offset + 1) % NUM_LEDS;
  ws281x.render(pixelData);
}, 100);

console.log('Press <ctrl>+C to exit.');

var ws281x = require('../index.js');

var NUM_LEDS = parseInt(process.argv[2], 10) || 10,
    pixelData = new Uint32Array(NUM_LEDS);

ws281x.init(NUM_LEDS);

// ---- trap the SIGINT and reset before exit
process.on('SIGINT', function () {
  ws281x.reset();
  process.nextTick(function () { process.exit(0); });
});

// ---- animation-loop
var offset = 0;
setInterval(function () {
  var i=NUM_LEDS;
  while(i--) {
      pixelData[i] = 0;
  }
  pixelData[offset] = 0xffffff;

  offset = (offset + 1) % NUM_LEDS;
  ws281x.render(pixelData);
}, 100);

console.log('Press <ctrl>+C to exit.');

Remove index mapping in example.


define(['views/base_view'], function (baseView) {

    var run = function () {

        module("BaseView");

        test("Test BaseView rendering of compiled template", function () {
            var test_model = new Backbone.Model({name: "Test"}),
                TestView = baseView.extend({
                    template_selector: "#milestone-table-heading",
                    el: "#qunit-fixture"
                }), tv;

            tv = new TestView({model: test_model});
            equal(tv.template_selector, "#milestone-table-heading", "Correct selector");

            tv.render();
            equal(tv.el.firstChild.innerHTML, "Test",
                  "Rendered element contains model's data");
        });

        test("Test getTemplateData", function () {
            var test_model = new Backbone.Model({name: "Test"}),
                TestView = baseView.extend({
                    template_selector: "#milestone-table-heading",
                    el: "#qunit-fixture",
                    getTemplateData: function (data) {
                        data.name = "Changed test";
                        return data;
                    }
                }), tv;

            tv = new TestView({model: test_model});
            tv.render();
            equal(tv.el.firstChild.innerHTML, "Changed test",
                  "Rendered element contains getTemplateData changes");
        });

    };

    return run;

});

define(['views/base_view'], function (baseView) {

	jasmine.getFixtures().fixturesPath = 'tests/html_fixtures';

    describe("BaseView", function () {
    	beforeEach(function() {
    		loadFixtures('components/base_view.html');
    	});

        it("Test BaseView rendering of compiled template", function () {
            var test_model = new Backbone.Model({name: "Test"}),
                TestView = baseView.extend({
                    template_selector: "#milestone-table-heading",
                    el: "#qunit-fixture"
                }), tv;

            tv = new TestView({model: test_model});
            since("Correct selector");
            expect(tv.template_selector).toEqual("#milestone-table-heading");

            tv.render();
            since("Rendered element contains model's data");
            expect(tv.el.firstChild.innerHTML).toEqual("Test");
        });

        it("Test getTemplateData", function () {
            var test_model = new Backbone.Model({name: "Test"}),
                TestView = baseView.extend({
                    template_selector: "#milestone-table-heading",
                    el: "#qunit-fixture",
                    getTemplateData: function (data) {
                        data.name = "Changed test";
                        return data;
                    }
                }), tv;

            tv = new TestView({model: test_model});
            tv.render();
            since("Rendered element contains getTemplateData changes");
            expect(tv.el.firstChild.innerHTML).toEqual("Changed test");
        });
    });
});

Update base view tests to use Jasmine


/*
 * Optics / command.js
 * copyright (c) 2016 Susisu
 */

"use strict";

function endModule() {
    module.exports = Object.freeze({
        Argument,
        OptionalArgument,
        RequiredArgument,
        CommandBase,
        Command
    });
}

class Argument {
    constructor() {
    }
}

class OptionalArgument extends Argument {
    constructor(placeholder, defaultVal, reader) {
        super();
        this.placeholder = placeholder;
        this.defaultVal  = defaultVal;
        this.reader      = reader;
    }
}

class RequiredArgument extends Argument {
    constructor(placeholder, reader) {
        super();
        this.placeholder = placeholder;
        this.reader      = reader;
    }
}

class CommandBase {
    constructor() {
    }
}

class Command extends CommandBase {
    constructor(desc, opts, args, action) {
        super();
        this.desc   = desc;
        this.opts   = opts;
        this.args   = args;
        this.action = action;
    }
}

endModule();

/*
 * Optics / command.js
 * copyright (c) 2016 Susisu
 */

/**
 * @module command
 */

"use strict";

function endModule() {
    module.exports = Object.freeze({
        Argument,
        OptionalArgument,
        RequiredArgument,
        CommandBase,
        Command
    });
}

class Argument {
    constructor() {
    }
}

class OptionalArgument extends Argument {
    constructor(placeholder, defaultVal, reader) {
        super();
        this.placeholder = placeholder;
        this.defaultVal  = defaultVal;
        this.reader      = reader;
    }
}

class RequiredArgument extends Argument {
    constructor(placeholder, reader) {
        super();
        this.placeholder = placeholder;
        this.reader      = reader;
    }
}

/**
 * The `CommandBase` class is the base class of commands.
 * @static
 */
class CommandBase {
    /**
     * Create an instance of `CommandBase`.
     */
    constructor() {
    }

    /**
     * Parse `argv` and run action associated with the command.
     * @param {module:output.Output} output Output target.
     * @param {string[]} argv Command-line arguments.
     * @throws {Error} Not implemented.
     */
    run(output, argv) {
        throw new Error("not implemented");
    }
}

/**
 * The `Command` class represents an ordinary command.
 * @extends module:command.CommandBase
 * @static
 */
class Command extends CommandBase {
    /**
     * Create an instance of `Command`.
     * @param {string} desc
     * @param {module:option.Option[]} opts
     * @param {module:command.Argument[]} args
     * @param {Function} action
     */
    constructor(desc, opts, args, action) {
        super();
        this.desc   = desc;
        this.opts   = opts;
        this.args   = args;
        this.action = action;
    }
}

endModule();

Add JSDoc comments for CommandBase and Command


module.exports = function (grunt) {
  grunt.config.merge({
    connect: {
      server: {
        options: {
          base: {
            path: "build",
            options: {
              index: "index.html"
            }
          },
          livereload: true
        }
      }
    },
    watch: {
      sources: {
        options: {
          livereload: true
        },
        files: ["*.css", "app.js", "lib/**/*.js", "*.html"],
        tasks: ["dev"]
      },
      config: {
        options: {
          reload: true
        },
        files: ["Gruntfile.js", "tasks/*.js"],
        tasks: []
      }
    }
  });

  grunt.loadNpmTasks("grunt-contrib-connect");
  grunt.loadNpmTasks("grunt-contrib-watch");
};

module.exports = function (grunt) {
  grunt.config.merge({
    connect: {
      server: {
        options: {
          base: {
            path: "build",
            options: {
              index: "index.html"
            }
          },
          livereload: true
        }
      }
    },
    watch: {
      sources: {
        options: {
          livereload: true
        },
        files: ["*.css", "app.js", "lib/**/*.js", "*.html", "scss/**/*.scss"],
        tasks: ["dev"]
      },
      config: {
        options: {
          reload: true
        },
        files: ["Gruntfile.js", "tasks/*.js"],
        tasks: []
      }
    }
  });

  grunt.loadNpmTasks("grunt-contrib-connect");
  grunt.loadNpmTasks("grunt-contrib-watch");
};

Add scss to grunt watcher


var EventEmitter = require('events').EventEmitter

var helpers = require('broccoli-kitchen-sink-helpers')

module.exports = Watcher
function Watcher(builder) {
  this.builder = builder
  this.check()
}

Watcher.prototype = Object.create(EventEmitter.prototype)
Watcher.prototype.constructor = Watcher

Watcher.prototype.check = function() {
  try {
    var newStatsHash = this.builder.treesRead.map(function (tree) {
      return typeof tree === 'string' ? helpers.hashTree(tree) : ''
    }).join('\x00')
    if (newStatsHash !== this.statsHash) {
      this.statsHash = newStatsHash
      this.current = this.builder.build()
      this.current.then(function(directory) {
        this.emit('change', directory)
        return directory
      }.bind(this), function(error) {
        this.emit('error', error)
        throw error
      }.bind(this)).finally(this.check.bind(this))
    } else {
      setTimeout(this.check.bind(this), 100)
    }
  } catch (err) {
    console.error('Uncaught error in Broccoli file watcher:')
    console.error(err.stack)
    console.error('Watcher quitting') // do not schedule check with setTimeout
  }
}

Watcher.prototype.then = function(success, fail) {
  return this.current.then(success, fail)
}

var EventEmitter = require('events').EventEmitter

var helpers = require('broccoli-kitchen-sink-helpers')

module.exports = Watcher
function Watcher(builder, options) {
  this.builder = builder
  this.options = options || {}
  this.check()
}

Watcher.prototype = Object.create(EventEmitter.prototype)
Watcher.prototype.constructor = Watcher

Watcher.prototype.check = function() {
  try {
    var interval = this.options.interval || 100
    var newStatsHash = this.builder.treesRead.map(function (tree) {
      return typeof tree === 'string' ? helpers.hashTree(tree) : ''
    }).join('\x00')
    if (newStatsHash !== this.statsHash) {
      this.statsHash = newStatsHash
      this.current = this.builder.build()
      this.current.then(function(directory) {
        this.emit('change', directory)
        return directory
      }.bind(this), function(error) {
        this.emit('error', error)
        throw error
      }.bind(this)).finally(this.check.bind(this))
    } else {
      setTimeout(this.check.bind(this), interval)
    }
  } catch (err) {
    console.error('Uncaught error in Broccoli file watcher:')
    console.error(err.stack)
    console.error('Watcher quitting') // do not schedule check with setTimeout
  }
}

Watcher.prototype.then = function(success, fail) {
  return this.current.then(success, fail)
}

Allow Watcher interval to be configurable.


import path from 'path';

export default {
    colors: true,
    basePath: path.resolve('./'),
    files: [
        'lib/*.js'
    ],
    preprocessors: {
        'lib/*.js': 'webpack'
    },
    frameworks: [ 'mocha' ],
    webpack: {
        cache: true,
        resolve: {
            root: path.resolve('./')
        },
        module: {
            preLoaders: [
                {
                    test: /\.js$/,
                    exclude: [
                        path.resolve('lib/'),
                        path.resolve('node_modules/')
                    ],
                    loader: 'babel'
                },
                {
                    test: /\.js$/,
                    include: path.resolve('lib/'),
                    loader: 'isparta'
                }
            ]
        }
    },
    webpackMiddleware: {
        noInfo: true,
        quiet: true
    },
    coverageReporter: {
        dir: '../coverage',
        reporters: [
            { type: 'lcovonly', subdir: '.' }
        ]
    },
    browserNoActivityTimeout: 60 * 1000, // default 10 * 1000
    browserDisconnectTimeout: 10 * 1000, // default 2 * 1000
    browserDisconnectTolerance: 2, // default 0
    captureTimeout: 2 * 60 * 1000 // default 1 * 60 * 1000
};

import path from 'path';

export default {
    colors: true,
    files: [
        'lib/*.js'
    ],
    preprocessors: {
        'lib/*.js': 'webpack'
    },
    frameworks: [ 'mocha' ],
    webpack: {
        cache: true,
        resolve: {
            root: path.resolve('./')
        },
        module: {
            preLoaders: [
                {
                    test: /\.js$/,
                    exclude: [
                        path.resolve('lib/'),
                        path.resolve('node_modules/')
                    ],
                    loader: 'babel'
                },
                {
                    test: /\.js$/,
                    include: path.resolve('lib/'),
                    loader: 'isparta'
                }
            ]
        }
    },
    webpackMiddleware: {
        noInfo: true,
        quiet: true
    },
    coverageReporter: {
        dir: '../coverage',
        reporters: [
            { type: 'lcovonly', subdir: '.' }
        ]
    },
    browserNoActivityTimeout: 60 * 1000, // default 10 * 1000
    browserDisconnectTimeout: 10 * 1000, // default 2 * 1000
    browserDisconnectTolerance: 2, // default 0
    captureTimeout: 2 * 60 * 1000 // default 1 * 60 * 1000
};

Revert ":space_invader: attempt to fix travis tests run"


var gulp = require('gulp'),
  chai = require('chai'),
  expect = chai.expect,
  fs = require('fs'),
  multiline = require('multiline'),
  kssSplitter = require('../lib/modules/kss-splitter');

describe('KSS divider', function() {

  it('should parse single KSS block', function() {
    var str = multiline(function() {
      /*
// Comment
// Styleguide 1.0

.a { b: c }
      */
    }),
    result = [
      [
        'block',
        [
          'kss',
          '// Comment\n// Styleguide 1.0\n'
        ],
        [
          'code',
          '.a { b: c }'
        ]
      ]
    ],
    kssBlocks = kssSplitter.getAst(str);
    expect(kssBlocks).eql(result);
  });

});

var gulp = require('gulp'),
  chai = require('chai'),
  expect = chai.expect,
  fs = require('fs'),
  multiline = require('multiline'),
  kssSplitter = require('../lib/modules/kss-splitter');

describe('KSS divider', function() {

  it('should parse single KSS block', function() {
    var str = multiline(function() {
      /*
// Comment
// Styleguide 1.0

.a { b: c }
      */
    }),
    result = [
      [
        'block',
        [
          'kss',
          '// Comment\n// Styleguide 1.0\n'
        ],
        [
          'code',
          '.a { b: c }'
        ]
      ]
    ],
    kssBlocks = kssSplitter.getAst(str);
    expect(kssBlocks).eql(result);
  });

  it('should be agnostic to spaces in reference declaration', function(){
    var str = multiline(function() {
      /*
// Comment
//Styleguide 1.0

.a { b: c }
      */
    }),
    result = [
      [
        'block',
        [
          'kss',
          '// Comment\n//Styleguide 1.0 \n'
        ],
        [
          'code',
          '.a { b: c }'
        ]
      ]
    ],
    kssBlocks = kssSplitter.getAst(str);
    expect(kssBlocks).eql(result);
  });

});

Test to be space agnostic


module.exports = {
  apps: [
    {
      name: 'simplexnoisemapgenerator',
      script: 'index.js',
      max_memory_restart: '1024M',
      instances: 0,
      log_date_format: 'YYYY-MM-DD HH:mm:ss.SSS'
    }
  ]
}

module.exports = {
  apps: [
    {
      name: 'simplexnoisemapgenerator',
      script: 'index.js',
      max_memory_restart: '1024M',
      instances: 0,
      log_date_format: 'YYYY-MM-DD HH:mm:ss.SSS',
      watch: true
    }
  ]
}

Set up watch flag in pm2 ecosystem file


var JSCSFilter = require('broccoli-jscs');
var mergeTrees = require('broccoli-merge-trees');
var pickFiles  = require('broccoli-static-compiler');
var checker = require('ember-cli-version-checker');

module.exports = {
  name: 'broccoli-jscs',

  shouldSetupRegistryInIncluded: function() {
    return !checker.isAbove(this, '0.1.10');
  },

  lintTree: function(type, tree) {
    var jscsTree = new JSCSFilter(tree, this.app.options.jscsOptions);

    if (!jscsTree.enabled || jscsTree.bypass || jscsTree.disableTestGenerator) {
      return;
    }

    return jscsTree;
  },

  included: function(app) {
    var addonContext = this;
    this.app = app;
    this._super.included.apply(this, arguments);

    if (app.tests && this.shouldSetupRegistryInIncluded()) {
      app.registry.add('js', {
        name: 'broccoli-jscs',
        ext: 'js',
        toTree: function(tree, inputPath, outputPath, options) {
          var jscsTree = addonContext.lintTree('unknown-type', tree);

          if (!jscsTree) {
            return tree;
          }

          return mergeTrees([
            tree,
            pickFiles(jscsTree, {
              srcDir: '/',
              destDir: outputPath + '/tests/'
            })
          ], { overwrite: true });
        }
      });
    }
  }
};

var JSCSFilter = require('broccoli-jscs');
var mergeTrees = require('broccoli-merge-trees');
var pickFiles  = require('broccoli-static-compiler');
var checker = require('ember-cli-version-checker');

module.exports = {
  name: 'broccoli-jscs',

  shouldSetupRegistryInIncluded: function() {
    return !checker.isAbove(this, '0.1.10');
  },

  lintTree: function(type, tree) {
    var jscsTree = new JSCSFilter(tree, this.app.options.jscsOptions);

    if (!jscsTree.enabled || jscsTree.bypass || jscsTree.disableTestGenerator) {
      return tree;
    }

    return jscsTree;
  },

  included: function(app) {
    var addonContext = this;
    this.app = app;
    this._super.included.apply(this, arguments);

    if (app.tests && this.shouldSetupRegistryInIncluded()) {
      app.registry.add('js', {
        name: 'broccoli-jscs',
        ext: 'js',
        toTree: function(tree, inputPath, outputPath, options) {
          var jscsTree = addonContext.lintTree('unknown-type', tree);

          if (!jscsTree) {
            return tree;
          }

          return mergeTrees([
            tree,
            pickFiles(jscsTree, {
              srcDir: '/',
              destDir: outputPath + '/tests/'
            })
          ], { overwrite: true });
        }
      });
    }
  }
};

Return tree to prevent breaking builds


$(function () {

    // Initialize prompt buttons.
    $('[data-toggle="prompt"]').click(function (e) {

        e.preventDefault();

        bootbox.prompt($(this).data('message'), function (result) {

            var match = $(e.target).data('match') || 'yes';

            if (result === match) {
                $(e.target).unbind('click')[0].click();
            } else if (typeof result == 'string') {
                $('.bootbox.modal')
                    .find('.modal-dialog')
                    .addClass('animated shake');

                setTimeout(function () {
                    $('.bootbox.modal')
                        .find('.modal-dialog')
                        .removeClass('shake')
                        .removeClass('animated')
                }, 1000);

                return false;
            }
        });
    });
});

$(function () {

    // Initialize prompt buttons.
    $('[data-toggle="prompt"]').click(function (e) {

        e.preventDefault();

        var match = $(e.target).data('match') || 'yes';

        bootbox.prompt($(this).data('message').replace(':match:', match), function (result) {

            if (result === match) {
                $(e.target).unbind('click')[0].click();
            } else if (typeof result == 'string') {
                $('.bootbox.modal')
                    .find('.modal-dialog')
                    .addClass('animated shake');

                setTimeout(function () {
                    $('.bootbox.modal')
                        .find('.modal-dialog')
                        .removeClass('shake')
                        .removeClass('animated')
                }, 1000);

                return false;
            }
        });
    });
});

Fix issue where matcher was not being replaced correctly


import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
import { Router, Route, browserHistory } from 'react-router'
import { syncHistoryWithStore } from 'react-router-redux'

import HomeContainer from './routes/Home/HomeContainer';
import LoginContainer from './routes/Login/LoginContainer';
import SignUpContainer from './routes/SignUp/SignUpContainer';

import requiresAuth from './shared/components/requiresAuth/requiresAuth'

import configureStore from './store/configureStore'

import './index.css';

const store = configureStore();
const history = syncHistoryWithStore(browserHistory, store)

ReactDOM.render(
  <Provider store={store}>
     <Router history={history}>
      <Route path="/login" component={LoginContainer} />
      <Route path="/signup" component={SignUpContainer} />
      <Route path="/" component={HomeContainer} />
    </Router>
  </Provider>,
  document.getElementById('root')
);

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
import { Router, Route, browserHistory } from 'react-router'
import { syncHistoryWithStore } from 'react-router-redux'

import Home from './routes/Home/HomeContainer';
import Login from './routes/Login/LoginContainer';
import SignUp from './routes/SignUp/SignUpContainer';

import requiresAuth from './shared/components/requiresAuth/requiresAuth'

import configureStore from './store/configureStore'

import './index.css';

const store = configureStore();
const history = syncHistoryWithStore(browserHistory, store)

ReactDOM.render(
  <Provider store={store}>
     <Router history={history}>
      <Route path="/login" component={Login} />
      <Route path="/signup" component={SignUp} />
      <Route path="/" component={requiresAuth(Home)} />
    </Router>
  </Provider>,
  document.getElementById('root')
);

Add authentication to Home route


/////////////////////////
// Helpers and globals //
/////////////////////////

const expect = chai.expect;

////////////////
// Test suite //
////////////////

describe('Rainbow', function() {
    it('Basic things are defined', () => {
        expect(Rainbow).to.exist;
        expect(Rainbow.color).to.be.a('function');
        expect(Rainbow.extend).to.be.a('function');
        expect(Rainbow.onHighlight).to.be.a('function');
        expect(Rainbow.addClass).to.be.a('function');
        expect(Rainbow.addAlias).to.be.a('function');
    });
});

/* global Rainbow */

/////////////////////////
// Helpers and globals //
/////////////////////////

const expect = chai.expect;

////////////////
// Test suite //
////////////////

describe('Rainbow', () => {
    it('Basic things are defined', () => {
        expect(Rainbow).to.exist;
        expect(Rainbow.color).to.be.a('function');
        expect(Rainbow.extend).to.be.a('function');
        expect(Rainbow.onHighlight).to.be.a('function');
        expect(Rainbow.addClass).to.be.a('function');
        expect(Rainbow.addAlias).to.be.a('function');
    });

    it('Should apply global class', (done) => {
        Rainbow.extend([{
            name: 'name',
            pattern: /Craig/gm
        }]);

        Rainbow.addClass('global');

        Rainbow.color('My name is Craig', 'generic', function(result) {
            expect(result).to.equal('My name is Craig');
            done();
        });
    });
});

Add unit test for applying global class


exports.tests = {
 addmetatohead : function() {
    var meta = window.document.createElement("meta");
    var head = window.document.getElementsByTagName("body").item(0);
    head.appendChild(meta);
    assertTrue("last element should be the new meta tag",
                head.lastChild === meta);
    assertTrue("meta should not be stringified with a closing tag",
               window.document.innerHTML.indexOf("<meta>") > -1 &&
               window.document.innerHTML.indexOf("</meta>") === -1
               );
  }
};

exports.tests = {
 addmetatohead : function() {
    var meta = window.document.createElement("meta");
    window.document.getElementsByTagName("head").item(0).appendChild(meta);
    var elements = window.document.getElementsByTagName("head").item(0).childNodes;

    assertTrue("last element should be the new meta tag",
                elements.item(elements.length-1) === meta);
    assertTrue("meta should not be stringified with a closing tag",
               window.document.innerHTML.indexOf("<meta>") > -1 &&
               window.document.innerHTML.indexOf("</meta>") === -1
               );
  }
};

Revert "fix the meta tag test"


import Ember from 'ember';
import PikadayMixin from 'ember-pikaday/mixins/pikaday';
import layout from 'ember-pikaday/templates/pikaday-inputless';

export default Ember.Component.extend(PikadayMixin, {
  layout,

  didInsertElement() {
    this.set('field', this.$('.ember-pikaday-input')[0]);
    this.set('pikadayContainer', this.$('.ember-pikaday-container')[0]);
    this.setupPikaday();
  },

  onPikadayOpen: Ember.K,
  onPikadayClose: Ember.K,
});

import Ember from 'ember';
import PikadayMixin from 'ember-pikaday/mixins/pikaday';
import layout from 'ember-pikaday/templates/pikaday-inputless';

export default Ember.Component.extend(PikadayMixin, {
  layout,

  didInsertElement() {
    this.set('field', this.$('.ember-pikaday-input')[0]);
    this.set('pikadayContainer', this.$('.ember-pikaday-container')[0]);
    this.setupPikaday();
  },

  onPikadayOpen() {},
  onPikadayClose() {},
});

Replace Ember.K with JavaScript syntax


import _ from 'lodash';
import path from 'path';
import { Observable } from 'rx';

const basePath = process.cwd() + '/seed/challenges/';

export default function getFromDisk$(challenge) {
  delete require.cache[require.resolve(
    path.join(basePath, challenge.fileName)
  )];

  return Observable.just(require(path.join(basePath, challenge.fileName)))
    .map(challengeSpec => challengeSpec.challenges[challenge.suborder - 1])
    .map(challenge => {
      challenge.head = challenge.head || [];
      challenge.tail = challenge.tail || [];
      challenge.challengeType = '' + challenge.challengeType;

      challenge.name =
        _.capitalize(challenge.type) +
        ': ' +
        challenge.title.replace(/[^a-zA-Z0-9\s]/g, '');

      challenge.dashedName = challenge.name
        .toLowerCase()
        .replace(/\:/g, '')
        .replace(/\s/g, '-');

      return challenge;
    });
}

import _ from 'lodash';
import path from 'path';
import { Observable } from 'rx';

const basePath = process.cwd() + '/seed/challenges/';

export default function getFromDisk$(challenge) {
  if (challenge && !challenge.fileName) {
    throw new Error(
      `Challenge ${challenge.name} has no fileName.
      Did you remember run node seed?`
    );
  }
  delete require.cache[require.resolve(
    path.join(basePath, challenge.fileName)
  )];

  return Observable.just(require(path.join(basePath, challenge.fileName)))
    .map(challengeSpec => challengeSpec.challenges[challenge.suborder - 1])
    .map(challenge => {
      challenge.head = challenge.head || [];
      challenge.tail = challenge.tail || [];
      challenge.challengeType = '' + challenge.challengeType;

      challenge.name =
        _.capitalize(challenge.type) +
        ': ' +
        challenge.title.replace(/[^a-zA-Z0-9\s]/g, '');

      challenge.dashedName = challenge.name
        .toLowerCase()
        .replace(/\:/g, '')
        .replace(/\s/g, '-');

      return challenge;
    });
}

Add helpful error message if no filename


$(document).ready(function(){

	$('.rr').hover(
		function(){
			$(this).fadeTo("slow", 0.1);
		},
		function(){
			$(this).fadeTo("slow", 1);
		}
	)

	removeLoggedDiv();
});

function removeLoggedDiv(){
	setTimeout(function(){
		$('#logged').fadeOut('slow');
	}, 5000);
}

$(document).ready(function(){

	$('.rr').hover(
		function(){
			$(this).fadeTo("slow", 0.1);
		},
		function(){
			$(this).fadeTo("slow", 1);
		}
	)

	removeLoggedDiv();
});

function removeLoggedDiv(){
  setTimeout(function(){
    $('.notice').fadeOut('slow');
  }, 5000);
}

Update js that fade out flash notice to use class as selector instead of id.


"use strict";

$(document).ready(function () {

  var showFirst = true,
      activeClass = 'js-active',
      sections = [],
      headingIcon = 'Hide this section';

  function toggleSection(e, options){
    var data = e.data;
    data.trigger.toggleClass(activeClass);
    data.target.toggleClass(activeClass);
  }

  $('.collapsible').each(function(i,el){
    var $el = $(el);

    sections[i] = {
      index: i,
      trigger: $el,
      target: $el.next('.collapsible-section')
    }

    var buttonTitle = sections[i].trigger.text();
    sections[i].trigger.html('<button class="button--unstyled">' + headingIcon + buttonTitle  + '</button>');
    sections[i].trigger.on('click', sections[i], toggleSection);

    if(showFirst && i >= 1){
      sections[i].trigger.trigger('click');
    }
  })

});

"use strict";

// Add the aria hidden attribute to the collapsible content section.
//var showTip = function(el) {
//  el.setAttribute('aria-hidden', 'true');
//}

$(document).ready(function () {

  var showFirst = true,
      activeClass = 'js-active',
      sections = [],

// variable need to be refactored so it can toggle between "hide this text" and "show..."
      headingIcon = 'Show this section';

  function show(i){
    var item = sections[i];
    item.icon.text('Show this section');
    item.trigger.removeClass(activeClass);
    item.target.removeClass(activeClass);
    item.target.removeAttr('aria-hidden');
    item.hidden = false;
    console.log(item)
  }

  function hide(i){
    var item = sections[i];
    item.icon.text('Hide this section');
    item.trigger.addClass(activeClass);
    item.target.addClass(activeClass);
    item.target.attr('aria-hidden', 'true');
    item.hidden = true;
  }

  $('.collapsible').each(function(i,el){
    var $el = $(el);

    sections[i] = {
      index: i,
      trigger: $el,
      target: $el.next('.collapsible-section'),
      hidden: $el.hasClass(activeClass)
    }

    // Setup DOM
    var buttonTitle = sections[i].trigger.text();
    sections[i].trigger.html('<button class="button--unstyled">' + headingIcon + buttonTitle  + '</button>');
    sections[i].icon = sections[i].trigger.find('.visually-hidden');

    // Bind events
    sections[i].trigger.on('click', i, function(){
      (sections[i].hidden)? show(i) : hide(i);
    });

    // Trigger events
    if(showFirst && i >= 1){
      sections[i].trigger.trigger('click');
    }
  })

});

Add toggled show hide message and aria role to js.


'use strict';

module.exports = {
  scenarios: [
    {
      name: "ember-stable",
      dependencies: {
        "ember": "1.12.1"
      }
    }, {
      name: "ember-beta",
      dependencies: {
        "ember": "1.13.0-beta.2"
      }
    }
  ]
};

'use strict';

module.exports = {
  scenarios: [
    {
      name: "ember-stable",
      dependencies: {
        "ember": "1.12.1"
      }
    }, {
      name: "ember-beta",
      dependencies: {
        "ember": "1.13.0-beta.2"
      }
    }, {
      name: "ember-canary",
      dependencies: {
        "ember": "components/ember#canary"
      },
      resolutions: {
        "ember": "canary"
      }
    }
  ]
};

Add ember-canary to build targets


import React from 'react';

import { ReactComponent as Logo } from 'interface/images/logo.svg';

// This has an elevator music easter egg that occurs randomly once every 5 times.
// Lazy load it to minimize bundle impact. This whole joke has a tiny amount of overhead.
let elevator;
async function loadElevator() {
  elevator = (await import('./elevate')).default;
}
let useElevator = Math.random() < 0.2;

function scrollToTop() {
  if (!useElevator || !elevator) {
    window.scrollTo(0, 0);
  } else {
    elevator();
    // Only do it once to increase ~~confusion~~ mystery
    useElevator = false;
  }
}

const ScrollToTop = () => (
  <div className="clickable text-right" onClick={scrollToTop} onMouseEnter={useElevator ? loadElevator : undefined}>
    <Logo
      style={{ '--arrow': '#fab700', '--main': '#1c1c1b', maxWidth: 50 }}
    />
  </div>
);

export default ScrollToTop;

import React from 'react';

import { ReactComponent as Logo } from 'interface/images/logo.svg';

// This has an elevator music easter egg that occurs randomly once every 5 times.
// Lazy load it to minimize bundle impact. This whole joke has a tiny amount of overhead.
let elevator;
async function loadElevator() {
  elevator = (await import('./elevate')).default;
}
let useElevator = Math.random() < 0.1;

function scrollToTop() {
  if (!useElevator || !elevator) {
    window.scrollTo(0, 0);
  } else {
    elevator();
    // Only do it once to increase ~~confusion~~ mystery
    useElevator = false;
  }
}

const ScrollToTop = () => (
  <div className="clickable text-right" onClick={scrollToTop} onMouseEnter={useElevator ? loadElevator : undefined}>
    <Logo
      style={{ '--arrow': '#fab700', '--main': '#1c1c1b', maxWidth: 50 }}
    />
  </div>
);

export default ScrollToTop;

Reduce elevator chance to 1 in 10 again


var axsPath = require.resolve('../vendor/axs_testing')

exports.addCommand = function (client, requireName) {
  client.addCommand('auditAccessibility', function () {
    return this.execute(function (axsPath, requireName) {
      var axs = window[requireName](axsPath)
      var failures = axs.Audit.run().filter(function (result) {
        return result.result === 'FAIL'
      })

      if (failures.length > 0) {
        var message = 'Accessibilty audit failed\n\n'
        message += failures.map(function (result) {
          return axs.Audit.accessibilityErrorMessage(result)
        }).join('\n\n')

        return {
          message: message,
          failed: true,
          results: failures.map(function (result) {
            return {
              code: result.rule.code,
              elements: result.elements.map(function (element) {
                return axs.utils.getQuerySelectorText(element)
              }),
              message: result.rule.heading,
              severity: result.rule.severity,
              url: result.rule.url
            }
          })
        }
      } else {
        return {
          message: 'Accessibilty audit passed',
          results: [],
          failed: false
        }
      }
    }, axsPath, requireName).then(function (response) {
      return response.value
    })
  })
}

var axsPath = require.resolve('../vendor/axs_testing')

exports.addCommand = function (client, requireName) {
  client.addCommand('auditAccessibility', function () {
    return this.execute(function (axsPath, requireName) {
      var axs = window[requireName](axsPath)
      var config = {
        withConsoleApi: true,
        showUnsupportedRulesWarning: false
      }
      var failures = axs.Audit.run().filter(function (result) {
        return result.result === 'FAIL'
      })

      if (failures.length > 0) {
        var message = 'Accessibilty audit failed\n\n'
        message += failures.map(function (result) {
          return axs.Audit.accessibilityErrorMessage(result)
        }).join('\n\n')

        return {
          message: message,
          failed: true,
          results: failures.map(function (result) {
            return {
              code: result.rule.code,
              elements: result.elements.map(function (element) {
                return axs.utils.getQuerySelectorText(element)
              }),
              message: result.rule.heading,
              severity: result.rule.severity,
              url: result.rule.url
            }
          })
        }
      } else {
        return {
          message: 'Accessibilty audit passed',
          results: [],
          failed: false
        }
      }
    }, axsPath, requireName).then(function (response) {
      return response.value
    })
  })
}

Enable console api and remove warning


function something()
{
	var x = window.localStorage.getItem('aaa');

	x = x*1 + 1;

	window.localStorage.setItem('aaa', x);

	alert(x);
}

function add_to_cart(id)
{
	alert('You added pizza with id:' + id);
}

function something()
{
	var x = window.localStorage.getItem('aaa');

	x = x*1 + 1;

	window.localStorage.setItem('aaa', x);

	alert(x);
}

function add_to_cart(id)
{
	var key = 'product_' + id;

	var x = window.localStorage.getItem(key);
	x = x*1 + 1;
	window.localStorage.setItem(key, x);
}

Update add_to_cart function + refactoring


var AudioChannel = function(args) {
  this.context = AudioContext ? new AudioContext() : new webkitAudioContext();
  this.oscill = this.context.createOscillator();
  this.gain = this.context.createGain();

  this.oscill.connect(this.gain);
  this.gain.connect(this.context.destination);

  if(args) {
    this.oscill.frequency.value = args.freq ? args.freq : 220;
    this.oscill.type = args.wave ? args.wave : "triangle";
    this.gain.gain.value = args.gain ? args.gain : 0.05;
  }
}
AudioChannel.prototype.playSong = function(song) {
  this.oscill.start();
  var playNextNote = function(song) {
    this.oscill.frequency.value = song[0].freq;
    if(song[1]) {
      setTimeout(playNextNote.bind(this, song.slice(1)), song[0].length);
    }
    else {
      setTimeout(function() {
        this.gain.gain.value = 0;
      }.bind(this), song[0].length);
    }
  }.bind(this);
  playNextNote(song);
}

var AudioChannel = function(args) {
  this.context = AudioContext ? new AudioContext() : new webkitAudioContext();
  this.oscill = this.context.createOscillator();
  this.gain = this.context.createGain();

  if(args) {
    this.frequency = args.freq ? args.freq : 220;
    this.wave = args.wave ? args.wave : "triangle";
    this.volume = args.gain ? args.gain : 0.05;
  }
}
AudioChannel.prototype.setNodes = function() {
  this.oscill = this.context.createOscillator();
  this.gain = this.context.createGain();

  this.oscill.connect(this.gain);
  this.gain.connect(this.context.destination);

  this.oscill.frequency.value = this.frequency;
  this.oscill.type = this.wave;
  this.gain.gain.value = this.volume;
};
AudioChannel.prototype.playSong = function(song) {
  this.oscill.start();
  var playNextNote = function(song) {
    this.oscill.frequency.value = song[0].freq;
    if(song[1]) {
      setTimeout(playNextNote.bind(this, song.slice(1)), song[0].length);
    }
    else {
      setTimeout(function() {
        this.gain.gain.value = 0;
      }.bind(this), song[0].length);
    }
  }.bind(this);
  playNextNote(song);
};

Update AudioChannel to create nodes in member function instead of constructor


function Option(aliases, description, name, def) {
    var opt = {};

    if (aliases.constructor === Object) {
        opt = aliases;
    } else {
        opt.aliases = aliases;
        opt.value = null;
        opt.def = def;
        opt.description = description;
        opt.name = name;
    }

    if (typeof opt.aliases === "string") {
        opt.aliases = [opt.aliases];
    }

    opt.aliases.forEach(function (c, i) {
        opt.aliases[i] = c.length === 1 ? "-" + c : ("--" + c);
    });

    opt.is_provided = false;

    return opt;
}

module.exports = Option;

function Option(aliases, description, name, def) {
    var opt = {};

    if (aliases.constructor === Object) {
        opt = aliases;
    } else {
        opt.aliases = aliases;
        opt.value = null;
        opt.def = def;
        opt.description = description;
        opt.name = name;
    }

    if (typeof opt.aliases === "string") {
        opt.aliases = [opt.aliases];
    }

    opt.aliases.forEach(function (c, i) {
        opt.aliases[i] = c.length === 1 ? "-" + c : ("--" + c);
    });

    opt.aliases.sort(function (a, b) {
        return a.length === 2 ? -1 : 1;
    });

    opt.is_provided = false;

    return opt;
}

module.exports = Option;

Sort the single letter aliases first


/*!
 * Jade - self closing tags
 * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
 * MIT Licensed
 */

module.exports = [
    'area'
  , 'base'
  , 'br'
  , 'col'
  , 'embed'
  , 'hr'
  , 'img'
  , 'input'
  , 'keygen'
  , 'link'
  , 'menuitem'
  , 'meta'
  , 'param'
  , 'source'
  , 'track'
  , 'wbr'
];

/*!
 * Jade - self closing tags
 * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
 * MIT Licensed
 */

// source: http://www.w3.org/html/wg/drafts/html/master/syntax.html#void-elements

module.exports = [
    'area'
  , 'base'
  , 'br'
  , 'col'
  , 'embed'
  , 'hr'
  , 'img'
  , 'input'
  , 'keygen'
  , 'link'
  , 'menuitem'
  , 'meta'
  , 'param'
  , 'source'
  , 'track'
  , 'wbr'
];

Add link to source of void elements


import {resolve} from "path";

export default function (filename, regexps, root) {
	for(let redirectPair of regexps) {
		if(redirectPair[0].test(filename)) {
			console.log("FOUND MATCH", redirectPair[0]);
			return resolve(root, redirectPair[1]);
		}
	}

	return null;
}

import {resolve} from "path";

export default function (filename, regexps, root) {
	for(let redirectPair of regexps) {
		const regexp = redirectPair[0];

		if(regexp.test(filename)) {
			console.log("FOUND MATCH", regexp);
			let redirect = redirectPair[1];

			// if redirect is of "different/path/$1.js" form
			if(/\$\d/.test(redirect)) {
				// "abs/path/to/path/lib.js".match(/path/(\w+).js$/)[0] ->
				// "path/lib.js".replace(/path/(\w+).js$/, "different/path/$1.js") ->
				// "different/path/lib.js"
				redirect = filename.match(regexp)[0].replace(regexp, redirect);
			}

			return resolve(root, redirect);
		}
	}

	return null;
}

Allow to use groups ($1) in redirect target


module.exports = function(old, cfg) {

  var data = cfg;

  if (data.clock) {
    data.clock.showTenths = data.pref.clockTenths;
    data.clock.showBar = data.pref.clockBar;
  }

  if (cfg.game.moves) data.game.moves = data.game.moves.split(' ');
  else data.game.moves = [];

  return data;
};

module.exports = function(old, cfg) {

  var data = cfg;

  if (data.clock) {
    data.clock.showTenths = data.pref.clockTenths;
    data.clock.showBar = data.pref.clockBar;
  }

  if (data.correspondence) {
  	data.correspondence.showBar = data.pref.clockBar;
  }

  if (cfg.game.moves) data.game.moves = data.game.moves.split(' ');
  else data.game.moves = [];

  return data;
};

Fix clock-time bar in correspondence


const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpack = require('webpack')

module.exports = {
  mode: 'development',
  output: {
    filename: 'static/[name].js',
    publicPath: '/',
  },
  module: {
    rules: [
      {
        test: /\.elm$/,
        exclude: [/elm-stuff/, /node_modules/],
        use: [
          { loader: 'elm-hot-webpack-loader' },
          {
            loader: 'elm-webpack-loader',
            options: {
              debug: true
            },
          },
        ],
      },
      {
        test: /\.css$/,
        exclude: [/node_modules/],
        loader: ['style-loader', 'css-loader'],
      },
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: 'public/index.html',
      favicon: 'public/favicon.png',
    }),
    new webpack.HotModuleReplacementPlugin(),
  ],
  devServer: {
    inline: true,
    hot: true,
    stats: { colors: true },
    proxy: [{
      context: ['/api', '/debug', '/serve'],
      target: 'http://localhost:9000',
      ws: true,
    }],
  },
};

const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpack = require('webpack')

module.exports = {
  mode: 'development',
  output: {
    filename: 'static/[name].js',
    publicPath: '/',
  },
  module: {
    rules: [
      {
        test: /\.elm$/,
        exclude: [/elm-stuff/, /node_modules/],
        use: [
          { loader: 'elm-hot-webpack-loader' },
          {
            loader: 'elm-webpack-loader',
            options: {
              debug: true
            },
          },
        ],
      },
      {
        test: /\.css$/,
        exclude: [/node_modules/],
        loader: ['style-loader', 'css-loader'],
      },
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: 'public/index.html',
      favicon: 'public/favicon.png',
    }),
    new webpack.HotModuleReplacementPlugin(),
  ],
  devServer: {
    inline: true,
    historyApiFallback: true,
    hot: true,
    stats: { colors: true },
    overlay: true,
    open: true,
    proxy: [{
      context: ['/api', '/debug', '/serve'],
      target: 'http://localhost:9000',
      ws: true,
    }],
    watchOptions: {
      ignored: /node_modules/,
    },
  },
};

Enable compile-error dev server overlay, SPA routing


var extend = require('xtend')
var getFormData = require('form-data-set/element')

var BaseEvent = require('./base-event.js')

var VALID_CHANGE = ['checkbox', 'file', 'select-multiple', 'select-one'];
var VALID_INPUT = ['color', 'date', 'datetime', 'datetime-local', 'email',
    'month', 'number', 'password', 'range', 'search', 'tel', 'text', 'time',
    'url', 'week'];

module.exports = BaseEvent(changeLambda);

function changeLambda(ev, broadcast) {
    var target = ev.target

    var isValid =
        (ev.type === 'input' && VALID_INPUT.indexOf(target.type) !== -1) ||
        (ev.type === 'change' && VALID_CHANGE.indexOf(target.type) !== -1);

    if (!isValid) {
        if (ev.startPropagation) {
            ev.startPropagation()
        }
        return
    }

    var value = getFormData(ev.currentTarget)
    var data = extend(value, this.data)

    broadcast(data)
}

var extend = require('xtend')
var getFormData = require('form-data-set/element')

var BaseEvent = require('./base-event.js')

var VALID_CHANGE = ['checkbox', 'file', 'select-multiple', 'select-one'];
var VALID_INPUT = ['color', 'date', 'datetime', 'datetime-local', 'email',
    'month', 'number', 'password', 'range', 'search', 'tel', 'text', 'time',
    'url', 'week', 'textarea'];

module.exports = BaseEvent(changeLambda);

function changeLambda(ev, broadcast) {
    var target = ev.target

    var isValid =
        (ev.type === 'input' && VALID_INPUT.indexOf(target.type) !== -1) ||
        (ev.type === 'change' && VALID_CHANGE.indexOf(target.type) !== -1);

    if (!isValid) {
        if (ev.startPropagation) {
            ev.startPropagation()
        }
        return
    }

    var value = getFormData(ev.currentTarget)
    var data = extend(value, this.data)

    broadcast(data)
}

Allow textarea as a valid input source


"use strict"

let Confidence = require('confidence')

let settings = {
  mongodb: {
    $filter: 'env',
    development: {
      url: 'mongodb://localhost:27017/aciojs'
    },
    production: {
      url: 'mongodb://localhost:27017/aciojs'
    },
    $default: {
      url: 'mongodb://localhost:27017/aciojs'
    }
  },
  server: {
    $filter: 'env',
    development: {
      port: process.env.PORT || 3000
    },
    production: {
      port: process.env.PORT || 3000
    },
    $default: {
      port: process.env.PORT || 3000
    }
  }
}

//Init Confidence
let store = new Confidence.Store(settings)

exports.get = (key) => {
  return store.get(key, {
    env: process.env.NODE_ENV
  })
}

"use strict"

let Confidence = require('confidence')

let settings = {
  mongodb: {
    $filter: 'env',
    development: {
      url: 'mongodb://localhost:3001/meteor'
    },
    production: {
      url: 'mongodb://localhost:27017/aciojs'
    },
    $default: {
      url: 'mongodb://localhost:27017/aciojs'
    }
  },
  server: {
    $filter: 'env',
    development: {
      port: 8000
    },
    production: {
      port: 8000
    },
    $default: {
      port: 8000
    }
  }
}

//Init Confidence
let store = new Confidence.Store(settings)

exports.get = (key) => {
  return store.get(key, {
    env: process.env.NODE_ENV
  })
}

Change port and use Meteor db for development


var config = {};

config.redis = {};

config.port = process.env.NODE_PORT || 80;

config.redis.port = process.env.REDIS_PORT || 6379;
config.redis.host = process.env.REDIS_HOST || '127.0.0.1';
config.redis.auth = process.env.REDIS_AUTH || '';

module.exports = config;

var config = {};

config.redis = {};

config.port = process.env.NODE_PORT || 4000;

config.redis.port = process.env.REDIS_PORT || 6379;
config.redis.host = process.env.REDIS_HOST || '127.0.0.1';
config.redis.auth = process.env.REDIS_AUTH || '';

module.exports = config;

Reset default port to 4000


import { join } from 'path';
import { replaceCode, appendFile, addNpmPackage } from '../utils';

async function generateMongodbDatabase(params) {
  switch (params.framework) {
    case 'express':
      const app = join(__base, 'build', params.uuid, 'app.js');
      const mongooseRequire = join(__base, 'modules', 'database', 'mongodb', 'mongoose-require.js');
      const mongooseConnect = join(__base, 'modules', 'database', 'mongodb', 'mongoose-connect.js');

      await replaceCode(app, 'DATABASE_REQUIRE', mongooseRequire);
      await replaceCode(app, 'DATABASE_CONNECTION', mongooseConnect, { leadingBlankLine: true });

      await appendFile(join(__base, 'build', params.uuid, '.env'), 'MONGODB=mongodb://localhost/test');

      await addNpmPackage('mongoose', params);
      break;
    case 'hapi':
      break;
    case 'meteor':
      break;
    default:
  }
}

export default generateMongodbDatabase;

import { join } from 'path';
import { replaceCode, mkdirs, copy, appendFile, addNpmPackage } from '../utils';

async function generateMongodbDatabase(params) {
  const build = join(__base, 'build', params.uuid);

  switch (params.framework) {
    case 'express':
      const app = join(build, 'app.js');
      const mongooseRequire = join(__base, 'modules', 'database', 'mongodb', 'mongoose-require.js');
      const mongooseConnect = join(__base, 'modules', 'database', 'mongodb', 'mongoose-connect.js');
      const mongooseUserModel = join(__base, 'modules', 'database', 'mongodb', 'mongoose-model.js');

      await replaceCode(app, 'DATABASE_REQUIRE', mongooseRequire);
      await replaceCode(app, 'DATABASE_CONNECTION', mongooseConnect, { leadingBlankLine: true });

      await appendFile(join(build, '.env'), 'MONGODB=mongodb://localhost/test');

      await addNpmPackage('mongoose', params);

      // Create models dir
      await mkdirs(join(__base, 'build', params.uuid, 'models'));
      await copy(mongooseUserModel, join(build, 'models', 'user.js'));

      break;
    case 'hapi':
      break;
    case 'meteor':
      break;
    default:
  }
}

export default generateMongodbDatabase;

Create models dir and copy user model for mongodb generator


const check = require('validator').check
const db = require('../lib/db');

const Issuers = db.table('issuers', {
  fields: [
    'id',
    'slug',
    'name',
    'url',
    'description',
    'email',
    'imageId'
  ],
});

Issuers.validateRow = function (row) {
  const errors = []
  this.fields.forEach(function (field) {
    try {
      const validator = validation[field] || noop
      validator(row[field])
    }
    catch(e) {
      e.field = field
      errors.push(e)
    }
  })
  return errors
}

const validation = {
  id: function (id) {
    if (typeof id == 'undefined') return;
    check(id).isInt();
  },
  slug: function (slug) {
    check(slug).len(1, 50);
  },
  name: function (name) {
    check(name).len(1, 50);
  },
  url: function (url) {
    check(url).isUrl();
  },
  description: function (desc) {
    check(desc).len(0, 255);
  },
  email: function (email) {
    if (typeof email == 'undefined') return;
    check(email).isEmail();
  },
}

function noop() {}

exports = module.exports = Issuers;

const check = require('validator').check
const db = require('../lib/db');

const Issuers = db.table('issuers', {
  fields: [
    'id',
    'slug',
    'name',
    'url',
    'description',
    'email',
    'imageId'
  ],
});

Issuers.validateRow = function (row) {
  return this.fields.reduce(function (errors, field) {
    try {
      const validator = validation[field] || noop
      validator(row[field])
    }
    catch(e) {
      e.field = field
      errors.push(e)
    }
    return errors
  }, [])
}

const validation = {
  id: function (id) {
    if (typeof id == 'undefined') return;
    check(id).isInt();
  },
  slug: function (slug) {
    check(slug).len(1, 50);
  },
  name: function (name) {
    check(name).len(1, 50);
  },
  url: function (url) {
    check(url).isUrl();
  },
  description: function (desc) {
    check(desc).len(0, 255);
  },
  email: function (email) {
    if (typeof email == 'undefined') return;
    check(email).isEmail();
  },
}

function noop() {}

exports = module.exports = Issuers;

Use a reduce instead of forEach


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

module.exports = function(mongoose) {

  /**
   * Paginates and executes a query
   */
  mongoose.Query.prototype.paginate = function paginate(page, limit, callback) {

    // The number of pages to show
    page  = parseInt(page, 10) || 1;
    limit = parseInt(limit, 10) || 20;

    if(page  < 1) page = 1;
    if(limit < 1) limit = 1;

    var skip  = (page * limit) - limit;
    var query = this.skip(skip).limit(limit);

    if(!callback) return query;

    query.exec(function(fErr, result) {
      if(fErr) return callback(fErr, null, null);

      query.model.count(query._conditions).exec(function(cErr, count) {
        if(cErr) return callback(cErr, null, null);
        var totalPages = Math.ceil(count / limit);

        var info = {
          enabled: count > limit,
          items: {
            total: count,
            remaining: limit < count ? count - limit : 0,
            limit: limit
          },
          pages: {
            current: page,
            total: totalPages || 1,
            prev: page > 1 && page <= totalPages ? page - 1 : null,
            next: page < totalPages ? page + 1 : null,
            first: 1,
            last: totalPages
          }
        };

        return callback(null, result, info);
      });
    });
  };

};

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

module.exports = function(mongoose) {

  /**
   * Paginates and executes a query
   */
  mongoose.Query.prototype.paginate = function paginate(page, limit, callback) {

    // The number of pages to show
    page  = parseInt(page, 10) || 1;
    limit = parseInt(limit, 10) || 20;

    if(page  < 1) page = 1;
    if(limit < 1) limit = 1;

    var skip  = (page * limit) - limit;
    var query = this.skip(skip).limit(limit);

    if(!callback) return query;

    query.exec(function(fErr, result) {
      if(fErr) return callback(fErr, null, null);

      query.model.count(query._conditions).exec(function(cErr, count) {
        if(cErr) return callback(cErr, null, null);
        var totalPages = Math.ceil(count / limit);

        var info = {
          enabled: count > limit,
          items: {
            total: count,
            remaining: limit < count ? count - limit : 0,
            limit: limit
          },
          pages: {
            current: page,
            total: totalPages || 1,
            prev: page > 1 && page <= totalPages ? page - 1 : null,
            next: page < totalPages ? page + 1 : null,
            first: 1,
            last: totalPages || 1
          }
        };

        return callback(null, result, info);
      });
    });
  };

};

Fix pagination for last page


'use strict';

togglbutton.render('#channel_name:not(.toggl)', { observe: true }, function() {
  var link,
    placeholder = $('.channel_title_info'),
    project = $('#team_name').textContent,
    description = $('#channel_name')
      .textContent.trim()
      .replace(/^#/, '');

  link = togglbutton.createTimerLink({
    className: 'slack',
    description: description,
    projectName: project,
    buttonType: 'minimal'
  });

  placeholder.parentNode.insertBefore(link, placeholder);
});

togglbutton.render('.c-message--hover:not(.toggl)', { observe: true }, function(
  elem
) {
  var link,
    placeholder = $('.c-message_actions__button--last-child'),
    description = $('.c-message__body', elem).textContent,
    project = $('#team_name').textContent,
    button = document.createElement('button');

  link = togglbutton.createTimerLink({
    className: 'slack-message',
    projectName: project,
    description: description,
    buttonType: 'minimal'
  });

  button.className = 'c-button-unstyled c-message_actions__button';
  button.setAttribute('type', 'button');
  button.appendChild(link);

  placeholder.parentNode.insertBefore(button, placeholder);
});

'use strict';

togglbutton.render('#channel_name:not(.toggl)', { observe: true }, function() {
  var link,
    placeholder = $('.channel_title_info'),
    project = $('#team_name').textContent,
    description = $('#channel_name')
      .textContent.trim()
      .replace(/^#/, '');

  link = togglbutton.createTimerLink({
    className: 'slack',
    description: description,
    projectName: project,
    buttonType: 'minimal'
  });

  placeholder.parentNode.insertBefore(link, placeholder);
});

togglbutton.render('.c-message--hover:not(.toggl)', { observe: true }, function(
  elem
) {
  var link,
    placeholder = $('.c-message_actions__button:last-child'),
    description = $('.c-message__body', elem).textContent,
    project = $('#team_name').textContent,
    button = document.createElement('button');

  link = togglbutton.createTimerLink({
    className: 'slack-message',
    projectName: project,
    description: description,
    buttonType: 'minimal'
  });

  button.className = 'c-button-unstyled c-message_actions__button';
  button.setAttribute('type', 'button');
  button.appendChild(link);

  placeholder.parentNode.insertBefore(button, placeholder);
});

Fix individual message button in Slack


const fs = require('fs')
const util = require('util')
const path = require('path')

class FsUtils {
  static get chmod() {
    return util.promisify(fs.chmod)
  }

  static get readFile() {
    return util.promisify(fs.readFile)
  }

  static get symlink() {
    return util.promisify(fs.symlink)
  }

  static get writeFile() {
    return util.promisify(fs.writeFile)
  }

  static get mkdir() {
    return util.promisify(fs.mkdir)
  }

  static get rename() {
    return util.promisify(fs.rename)
  }

  static fileExist(filename = '') {
    try {
      fs.lstatSync(filename)
    } catch (e) {
      return false
    }

    return true
  }

  static mkdirp(pathname = '') {
    const pathParts = pathname.split('/')

    return pathParts.reduce((promise, part) => promise
      .then(previousPath => {
        const newPath = path.join(previousPath, part)

        if (FsUtils.fileExist(newPath)) {
          return newPath
        }

        return FsUtils.mkdir(newPath)
          .catch(error => {
            if (error.code !== 'EEXIST') {
              throw new Error('dir_create_failed')
            }
          })
          .then(() => newPath)
      }), Promise.resolve(pathname[0] === '/' ? '/' : ''))
  }
}

module.exports = exports = { FsUtils }

const fs = require('fs')
const util = require('util')
const path = require('path')

class FsUtils {
  static get chmod() {
    return util.promisify(fs.chmod)
  }

  static get readFile() {
    return util.promisify(fs.readFile)
  }

  static get symlink() {
    return util.promisify(fs.symlink)
  }

  static get writeFile() {
    return util.promisify(fs.writeFile)
  }

  static get mkdir() {
    return util.promisify(fs.mkdir)
  }

  static get rename() {
    return util.promisify(fs.rename)
  }

  static fileExist(filename = '', followLink = true) {
    try {
      if (followLink) {
        fs.accessSync(filename)
      } else {
        fs.lstatSync(filename)
      }
    } catch (e) {
      return false
    }

    return true
  }

  static mkdirp(pathname = '') {
    const pathParts = pathname.split('/')

    return pathParts.reduce((promise, part) => promise
      .then(previousPath => {
        const newPath = path.join(previousPath, part)

        if (FsUtils.fileExist(newPath)) {
          return newPath
        }

        return FsUtils.mkdir(newPath)
          .catch(error => {
            if (error.code !== 'EEXIST') {
              throw new Error('dir_create_failed')
            }
          })
          .then(() => newPath)
      }), Promise.resolve(pathname[0] === '/' ? '/' : ''))
  }
}

module.exports = exports = { FsUtils }

Add the possibility to follow or not a symlink


import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';

export default class MeIndexRoute extends Route {
  @service router;

  redirect() {
    this.router.replaceWith('settings');
  }
}

import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';

export default class MeIndexRoute extends Route {
  @service router;

  redirect() {
    // `cargo login` is showing links to https://crates.io/me to access the API tokens,
    // so we need to keep this route and redirect the user to the API tokens settings page.
    this.router.replaceWith('settings.tokens');
  }
}

Adjust `/me` redirections to `/settings/tokens`


import Ember from "ember";
import config from "../config/environment";

export default Ember.Service.extend({
  session: Ember.inject.service(),

  error: function(reason) {
    if (reason.status === 0) {
      return;
    }
    console.info(reason);
    if (config.environment === "production" || config.staging) {
      var userName = this.get("session.currentUser.fullName");
      var userId = this.get("session.currentUser.id");
      var error = reason instanceof Error || typeof reason != "object" ?
          reason : JSON.stringify(reason);

      var airbrake = new airbrakeJs.Client();
      airbrake.setHost(config.APP.AIRBRAKE_HOST);
      airbrake.setProject(config.APP.AIRBRAKE_PROJECT_ID, config.APP.AIRBRAKE_PROJECT_KEY);
      airbrake.setEnvironmentName(config.staging ? "staging" : config.environment);

      airbrake.push({
        error: error,
        context: { userId: userId, userName: userName }
      });
    }
  }
});

import Ember from "ember";
import config from "../config/environment";

export default Ember.Service.extend({
  session: Ember.inject.service(),

  error: function(reason) {
    if (reason.status === 0) {
      return;
    }
    console.info(reason);
    if (config.environment === "production" || config.staging) {
      var userName = this.get("session.currentUser.fullName");
      var userId = this.get("session.currentUser.id");
      var error = reason instanceof Error || typeof reason != "object" ?
          reason : JSON.stringify(reason);
      var environment = config.staging ? "staging" : config.environment;
      var version = `${config.APP.SHA} (shared ${config.APP.SHARED_SHA})`;

      var airbrake = new airbrakeJs.Client({
        projectId: config.APP.AIRBRAKE_PROJECT_ID,
        projectKey: config.APP.AIRBRAKE_PROJECT_KEY
      });
      airbrake.setHost(config.APP.AIRBRAKE_HOST);
      airbrake.notify({ error, context: { userId, userName, environment, version } });
    }
  }
});

Update errbit calls to use non-deprecated methods, and add app version


module.exports = {
  title: 'Animación',
  subtitle: '',
  description: `
    Para hacer animaciones con glamorous, puedes usar las transiciones regulares de CSS para cosas sencillas,
    y para cosas más avanzadas, puedes usar ~keyframes~ vía ~glamor~'s ~css.keyframes~
    API.

    ~~~js
    // importamos css desde glamor
    import { css } from 'glamor'

    // Definimos los estilos de animación
    const animationStyles = props => {
      const bounce = css.keyframes({
        '0%': { transform: ~scale(1.01)~ },
        '100%': { transform: ~scale(0.99)~ }
      })
      return {animation: ~\${bounce} 0.2s infinite ease-in-out alternate~}
    }

    // Definimos el elemento
    const AnimatedDiv = glamorous.div(animationStyles)

    // Y lo Utilizamos en una función render
    <AnimatedDiv>
      Bounce.
    </AnimatedDiv>
    ~~~
  `.replace(/~/g, '`'),
  codeSandboxId: '31VMyP7XO',
  codeSandboxSummary: 'Ejemplo de animación',
  filename: __filename,
}

module.exports = {
  title: 'Animación',
  subtitle: '',
  description: `
    Para hacer animaciones con glamorous, puedes usar las transiciones regulares de CSS para cosas sencillas,
    y para cosas más avanzadas, puedes usar ~keyframes~ a través de la API ~css.keyframes~ de ~glamor~.

    ~~~js
    // importamos css desde glamor
    import { css } from 'glamor'

    // Definimos los estilos de animación
    const animationStyles = props => {
      const bounce = css.keyframes({
        '0%': { transform: ~scale(1.01)~ },
        '100%': { transform: ~scale(0.99)~ }
      })
      return {animation: ~\${bounce} 0.2s infinite ease-in-out alternate~}
    }

    // Definimos el elemento
    const AnimatedDiv = glamorous.div(animationStyles)

    // Y lo utilizamos en una función render
    <AnimatedDiv>
      Bounce.
    </AnimatedDiv>
    ~~~
  `.replace(/~/g, '`'),
  codeSandboxId: '31VMyP7XO',
  codeSandboxSummary: 'Ejemplo de animación',
  filename: __filename,
}

Correct translation of the description.


export var ObjectUtilities = {
    isEqual: function (objA, objB) {
        var aKeys = Object.keys(objA);
        var bKeys = Object.keys(objB);
        if (aKeys.length !== bKeys.length) {
            return false;
        }
        for (var i = 0, len = aKeys.length; i < len; i++) {
            var key = aKeys[i];
            if (!objB.hasOwnProperty(key) || objA[key] !== objB[key]) {
                return false;
            }
        }
        return true;
    }
};

export var StringUtil = {
    pluralize: function (str, count) {
        var s = str;
        if (count > 1) {
            if (str.endsWith("y")) {
                s = str.substring(0, str.length - 2) + 'ies';
            } else {
                s += 's';
            }
        }
        return s;
    }
};

export var ObjectUtilities = {
    isEqual: function (objA, objB) {
        if( !objA || !objB ) {
            return (!objA && !objB);
        }
        var aKeys = Object.keys(objA);
        var bKeys = Object.keys(objB);
        if (aKeys.length !== bKeys.length) {
            return false;
        }
        for (var i = 0, len = aKeys.length; i < len; i++) {
            var key = aKeys[i];
            if (!objB.hasOwnProperty(key) || objA[key] !== objB[key]) {
                return false;
            }
        }
        return true;
    }
};

export var StringUtil = {
    pluralize: function (str, count) {
        var s = str;
        if (count > 1) {
            if (str.endsWith("y")) {
                s = str.substring(0, str.length - 2) + 'ies';
            } else {
                s += 's';
            }
        }
        return s;
    }
};

Fix bug where object comparison is done on null object.


var fs = require('fs');
var cluster = require('cluster');

exports['default'] = {
  logger: function(api){
    var logger = {transports: []};

    // console logger
    if(cluster.isMaster){
      logger.transports.push(function(api, winston){
        return new (winston.transports.Console)({
          colorize: true,
          level: 'info',
          timestamp: true,
        });
      });
    }

    // file logger
    if(api.config.general.paths.log.length === 1){
      var logDirectory = api.config.general.paths.log[0];
      try{
        fs.mkdirSync(logDirectory);
      }catch(e){
        if(e.code !== 'EEXIST'){
          throw(new Error('Cannot create log directory @ ' + logDirectory));
        }
      }
    }

    logger.transports.push(function(api, winston){
      return new (winston.transports.File)({
        filename: api.config.general.paths.log[0] + '/' + api.pids.title + '.log',
        level: 'info',
        timestamp: true
      });
    });

    // the maximum length of param to log (we will truncate)
    logger.maxLogStringLength = 100;

    // you can optionally set custom log levels
    // logger.levels = {good: 0, bad: 1};

    // you can optionally set custom log colors
    // logger.colors = {good: 'blue', bad: 'red'};

    return logger;
  }
};

exports.test = {
  logger: function(api){
    return {
      transports: null
    };
  }
};

var fs = require('fs');
var cluster = require('cluster');

exports['default'] = {
  logger: function(api){
    var logger = {transports: []};

    // console logger
    if(cluster.isMaster){
      logger.transports.push(function(api, winston){
        return new (winston.transports.Console)({
          colorize: true,
          level: 'info',
          timestamp: true,
        });
      });
    }

    // file logger
    logger.transports.push(function(api, winston){
      if(api.config.general.paths.log.length === 1){
        var logDirectory = api.config.general.paths.log[0];
        try{
          fs.mkdirSync(logDirectory);
        }catch(e){
          if(e.code !== 'EEXIST'){
            throw(new Error('Cannot create log directory @ ' + logDirectory));
          }
        }
      }

      return new (winston.transports.File)({
        filename: api.config.general.paths.log[0] + '/' + api.pids.title + '.log',
        level: 'info',
        timestamp: true
      });
    });

    // the maximum length of param to log (we will truncate)
    logger.maxLogStringLength = 100;

    // you can optionally set custom log levels
    // logger.levels = {good: 0, bad: 1};

    // you can optionally set custom log colors
    // logger.colors = {good: 'blue', bad: 'red'};

    return logger;
  }
};

exports.test = {
  logger: function(api){
    return {
      transports: null
    };
  }
};

Support when actionhero is installed on read-only filesystem - Delay creation of log directory until after all config has been loaded


'use strict';

module.exports = function(grunt) {
    // Configuration.
    grunt.initConfig({
        jscs: {
            options: {
                config: '.jscsrc'
            },
            grunt: 'Gruntfile.js',
            tasks: 'tasks/**/*.js'
        },
        jshint: {
            options: {
                jshintrc: true
            },
            grunt: 'Gruntfile.js',
            tasks: 'tasks/**/*.js'
        },
        jsonlint: {
            jshint: ['.jshintrc'],
            npm: ['package.json']
        },
        nodeunit: {
            all: 'test/**/*.js'
        }
    });

    // Load tasks.
    grunt.loadTasks('./tasks');
    grunt.loadNpmTasks('grunt-contrib-jshint');
    grunt.loadNpmTasks('grunt-contrib-nodeunit');
    grunt.loadNpmTasks('grunt-jscs-checker');
    grunt.loadNpmTasks('grunt-jsonlint');
    grunt.loadNpmTasks('grunt-release');

    // Register tasks.
    grunt.renameTask('release', 'bump');
    grunt.registerTask('lint', ['jsonlint', 'jshint', 'jscs']);
    grunt.registerTask('release', function() {
        grunt.task.run('lint', Array.prototype.concat.apply('bump', arguments).join(':'));
    });
    grunt.registerTask('test', ['nodeunit']);
    grunt.registerTask('travis', ['lint', 'test', 'validate']);
    grunt.registerTask('validate', ['npm-validate']);
    grunt.registerTask('default', ['lint', 'test', 'validate']);
};

'use strict';

module.exports = function(grunt) {
    // Configuration.
    grunt.initConfig({
        jscs: {
            options: {
                config: '.jscsrc'
            },
            grunt: 'Gruntfile.js',
            tasks: 'tasks/**/*.js'
        },
        jshint: {
            options: {
                jshintrc: true
            },
            grunt: 'Gruntfile.js',
            tasks: 'tasks/**/*.js'
        },
        jsonlint: {
            jshint: ['.jshintrc'],
            npm: ['package.json']
        },
        nodeunit: {
            all: 'test/**/*.js'
        },
        'npm-validate': {
            options: {
                strict: true
            }
        }
    });

    // Load tasks.
    grunt.loadTasks('./tasks');
    grunt.loadNpmTasks('grunt-contrib-jshint');
    grunt.loadNpmTasks('grunt-contrib-nodeunit');
    grunt.loadNpmTasks('grunt-jscs-checker');
    grunt.loadNpmTasks('grunt-jsonlint');
    grunt.loadNpmTasks('grunt-release');

    // Register tasks.
    grunt.renameTask('release', 'bump');
    grunt.registerTask('lint', ['jsonlint', 'jshint', 'jscs']);
    grunt.registerTask('release', function() {
        grunt.task.run('lint', Array.prototype.concat.apply('bump', arguments).join(':'));
    });
    grunt.registerTask('test', ['nodeunit']);
    grunt.registerTask('travis', ['lint', 'test', 'validate']);
    grunt.registerTask('validate', ['npm-validate']);
    grunt.registerTask('default', ['lint', 'test', 'validate']);
};

Enable strict validation of `package.json`.