/**
 * @fileoverview Run phantom bootstrapper jobs
 * Spawns a child process to run the phantomjs job, with a callback on
 * completion
 */

var childProcess = require('child_process'),
    phantomjs = require('phantomjs'),
    binPath = phantomjs.path,
    config = require('../../config');

// Host mapping of environments to js livefyre.js file hosts
var hostMap = {
    'prod': 'zor',
    'staging': 'zor.t402',
    'qa': 'zor.qa-ext'
};

/**
 * @param {string} type
 * @param {Object} data
 * @param {function()} callback
 */
exports.run = function(type, data, callback) {
    data.host = (hostMap[config.environment.type] || 'zor') + '.livefyre.com';

    var childArgs = [
        __dirname + '/../../phantom/bootstrapper.js',
        type,
        encodeURIComponent(JSON.stringify(data))
    ];

    console.log('Running phantom child proc to get bs data', data);
    var phantomInst = childProcess.spawn(binPath, childArgs), html = '';

    phantomInst.stdout.on('data', function(data) {
        html += data.toString();
    });

    phantomInst.on('error', function(err) {
        console.error('Something went wrong with phantom child proc');
        console.error(err.toString());
    });

    phantomInst.on('close', function() {
        callback(html);
    });
};

/**
 * @fileoverview Run phantom bootstrapper jobs
 * Spawns a child process to run the phantomjs job, with a callback on
 * completion
 */

var childProcess = require('child_process'),
    phantomjs = require('phantomjs'),
    binPath = phantomjs.path,
    config = require('../../config');

// Host mapping of environments to js livefyre.js file hosts
var hostMap = {
    'prod': 'zor',
    'staging': 'zor.t402',
    'qa': 'zor.qa-ext'
};

/**
 * @param {string} type
 * @param {Object} data
 * @param {function()} callback
 */
exports.run = function(type, data, callback) {
    data.host = (hostMap[config.environment.type] || 'zor') + '.livefyre.com';

    var childArgs = [
        '--load-images=false',
        '--disk-cache=true',
        __dirname + '/../../phantom/bootstrapper.js',
        type,
        encodeURIComponent(JSON.stringify(data))
    ];

    console.log('Running phantom child proc to get bs data', data);
    var phantomInst = childProcess.spawn(binPath, childArgs),
        html = '';

    phantomInst.stdout.on('data', function(data) {
        html += data.toString();
    });

    phantomInst.on('error', function(err) {
        console.error('Something went wrong with phantom child proc');
        console.error(err.toString());
    });

    phantomInst.on('close', function() {
        callback(html);
    });
};

Add optional arguments to improve phantomjs rendering perf.


(function() {
  'use strict';

  this.IO = {
    load: function(url, async) {
      if (async) {
        return this.loadAsync(url);
      }
      return this.loadSync(url);
    },
    loadAsync: function(url) {
      var deferred = when.defer();
      var xhr = new XMLHttpRequest();
      xhr.overrideMimeType('text/plain');
      xhr.addEventListener('load', function() {
        if (xhr.status == 200) {
          deferred.resolve(xhr.responseText);
        } else {
          deferred.reject();
        }
      });
      xhr.addEventListener('abort', function(e) {
        return when.reject(e);
      });
      xhr.open('GET', url, true);
      xhr.send('');
      return deferred.promise;
    },

    loadSync: function(url) {
      var deferred = when.defer();
      var xhr = new XMLHttpRequest();
      xhr.overrideMimeType('text/plain');
      xhr.open('GET', url, false);
      xhr.send('');
      if (xhr.status == 200) {
        defer.resolve(xhr.responseText);
      } else {
        defer.reject();
      }
      return defer.promise;
    }
  }

}).call(L20n);

(function() {
  'use strict';

  this.IO = {
    load: function(url, async) {
      var deferred = when.defer();
      var xhr = new XMLHttpRequest();
      xhr.overrideMimeType('text/plain');
      xhr.addEventListener('load', function() {
        if (xhr.status == 200) {
          deferred.resolve(xhr.responseText);
        } else {
          deferred.reject();
        }
      });
      xhr.addEventListener('abort', function(e) {
        return deferred.reject(e);
      });
      xhr.open('GET', url, async);
      xhr.send('');
      return deferred.promise;
    },
  }

}).call(L20n);

Simplify IO and fix the s/defer/deferred/ typo


import jwt from 'jsonwebtoken';

// Components
import UserDAO from './sql';

import schema from './schema.graphqls';
import createResolvers from './resolvers';
import { refreshTokens } from './auth';
import tokenMiddleware from './token';
import Feature from '../connector';

const SECRET = 'secret, change for production';

const User = new UserDAO();

export default new Feature({
  schema,
  createResolversFunc: createResolvers,
  createContextFunc: async (req, connectionParams) => {
    let tokenUser = null;

    if (
      connectionParams &&
      connectionParams.token &&
      connectionParams.token !== 'null'
    ) {
      try {
        const { user } = jwt.verify(connectionParams.token, SECRET);
        tokenUser = user;
      } catch (err) {
        const newTokens = await refreshTokens(
          connectionParams.token,
          connectionParams.refreshToken,
          User,
          SECRET
        );
        tokenUser = newTokens.user;
      }
    } else if (req) {
      tokenUser = req.user;
    }

    return {
      User,
      user: tokenUser,
      SECRET,
      req
    };
  },
  middleware: tokenMiddleware(SECRET, User)
});

import jwt from 'jsonwebtoken';

// Components
import UserDAO from './sql';

import schema from './schema.graphqls';
import createResolvers from './resolvers';
import { refreshTokens } from './auth';
import tokenMiddleware from './token';
import Feature from '../connector';

const SECRET = 'secret, change for production';

const User = new UserDAO();

export default new Feature({
  schema,
  createResolversFunc: createResolvers,
  createContextFunc: async (req, connectionParams) => {
    let tokenUser = null;

    if (
      connectionParams &&
      connectionParams.token &&
      connectionParams.token !== 'null' &&
      connectionParams.token !== 'undefined'
    ) {
      try {
        const { user } = jwt.verify(connectionParams.token, SECRET);
        tokenUser = user;
      } catch (err) {
        const newTokens = await refreshTokens(
          connectionParams.token,
          connectionParams.refreshToken,
          User,
          SECRET
        );
        tokenUser = newTokens.user;
      }
    } else if (req) {
      tokenUser = req.user;
    }

    return {
      User,
      user: tokenUser,
      SECRET,
      req
    };
  },
  middleware: tokenMiddleware(SECRET, User)
});

Handle another corner case where refreshToken is undefined


let argv = require('yargs').argv;
let command = require('node-cmd');
let jigsaw = require('./tasks/bin');
let mix = require('laravel-mix');

let AfterBuild = require('on-build-webpack');
let BrowserSync = require('browser-sync');
let BrowserSyncPlugin = require('browser-sync-webpack-plugin');
let Watch = require('webpack-watch');

const env = argv.e || argv.env || 'local';
const port = argv.p || argv.port || 3000;
const buildPath = 'build_' + env + '/';

let browserSyncInstance;

let plugins = [
    new AfterBuild(() => {
        command.get(jigsaw.path() + ' build ' + env, (error, stdout, stderr) => {
            console.log(error ? stderr : stdout);
            browserSyncInstance.reload();
        });
    }),

    new BrowserSyncPlugin({
        proxy: null,
        port: port,
        server: { baseDir: buildPath },
        notify: false,
    },
    {
        reload: false,
        callback: function() {
            browserSyncInstance = BrowserSync.get('bs-webpack-plugin');
        },
    }),

    new Watch({
        paths: ['source/**/*.md', 'source/**/*.php'],
        options: { ignoreInitial: true }
    }),
];

mix.webpackConfig({ plugins });
mix.disableSuccessNotifications();
mix.setPublicPath('source/assets/');

mix.js('source/_assets/js/main.js', 'js/')
    .sass('source/_assets/sass/main.scss', 'css/')
    .version();

let argv = require('yargs').argv;
let command = require('node-cmd');
let jigsaw = require('./tasks/bin');
let mix = require('laravel-mix');

let AfterBuild = require('on-build-webpack');
let BrowserSync = require('browser-sync');
let BrowserSyncPlugin = require('browser-sync-webpack-plugin');
let Watch = require('webpack-watch');

const env = argv.e || argv.env || 'local';
const port = argv.p || argv.port || 3000;
const buildPath = 'build_' + env + '/';

let browserSyncInstance;

let plugins = [
    new AfterBuild(() => {
        command.get(jigsaw.path() + ' build ' + env, (error, stdout, stderr) => {
            console.log(error ? stderr : stdout);

            if (browserSyncInstance) {
                browserSyncInstance.reload();
            }
        });
    }),

    new BrowserSyncPlugin({
        proxy: null,
        port: port,
        server: { baseDir: buildPath },
        notify: false,
    },
    {
        reload: false,
        callback: function() {
            browserSyncInstance = BrowserSync.get('bs-webpack-plugin');
        },
    }),

    new Watch({
        paths: ['source/**/*.md', 'source/**/*.php'],
        options: { ignoreInitial: true }
    }),
];

mix.webpackConfig({ plugins });
mix.disableSuccessNotifications();
mix.setPublicPath('source/assets/');

mix.js('source/_assets/js/main.js', 'js/')
    .sass('source/_assets/sass/main.scss', 'css/')
    .version();

Allow webpack errors to be displayed (suppress browserSyncInstance.reload)


const lernaAliases = require('lerna-alias').jest()

module.exports = {
  testEnvironment: 'node',
  moduleNameMapper: Object.assign(lernaAliases, {
    '^redux-saga/effects$': lernaAliases['^redux-saga$'].replace(/index\.js$/, 'effects.js'),
  }),
  transform: {
    '.js$': __dirname + '/babel-transformer.jest.js',
  },
}

const lernaAliases = require('lerna-alias').jest()

module.exports = {
  testEnvironment: 'node',
  moduleNameMapper: Object.assign(lernaAliases, {
    '^redux-saga/effects$': lernaAliases['^redux-saga$'].replace(/index\.js$/, 'effects.js'),
    '^@redux-saga/core/effects$': lernaAliases['^@redux-saga/core$'].replace(/index\.js$/, 'effects.js'),
  }),
  transform: {
    '.js$': __dirname + '/babel-transformer.jest.js',
  },
}

Fix lerna alias in testing-utils


import React from "react"
import { StaticQuery, graphql } from "gatsby"

import Member from "./Member"

import "./index.module.css"

const Team = ({ members }) => (

    {members.map((data, index) => (

        <Member {...data} />

    ))}

)

export default () => (
  <StaticQuery
    query={teamQuery}
    render={({ allTeamYaml: { edges: team } }) => (
      <Team members={team.map(m => m.node)} />
    )}
  />
)

const teamQuery = graphql`
  query TeamQuery {
    allTeamYaml {
      edges {
        node {
          name
          role
          photo {
            childImageSharp {
              fluid(maxWidth: 320, quality: 100) {
                ...GatsbyImageSharpFluid_noBase64
              }
            }
          }
          social {
            twitter
            github
            dribbble
          }
        }
      }
    }
  }
`

Fix Team component (missing style)


var request = require('request');
var assert = require('assert');
var helpers = require('../../../lib/test_helpers.js');

describe('hljomaholl', function() {
  // The only thing that changes is the form attribute, so why not just re-use the object
  var fieldsToCheckFor = [
    'date', 'time', 'image', 'title', 'description', 'location', 'buyTicketURL',
    'moreInfoURL'
  ];

  it ('should return an array of items with correct fields', function(done) {
    var params = helpers.testRequestParams('/hljomaholl');
    var resultHandler = helpers.testRequestHandlerForFields(
      done, fieldsToCheckFor, null
    );
    request(params, resultHandler);
  });
});

var request = require('request');
var assert = require('assert');
var helpers = require('../../../lib/test_helpers.js');

describe('hljomaholl', function() {
  // The only thing that changes is the form attribute, so why not just re-use the object
  var fieldsToCheckFor = [
    'date', 'time', 'image', 'title', 'description', 'location', 'buyTicketURL',
    'moreInfoURL'
  ];

  it ('should return an array of items with correct fields', function(done) {
    var params = helpers.testRequestParams('/hljomaholl');
    var resultHandler = helpers.testRequestHandlerForFields(
      done, fieldsToCheckFor, null, true
    );
    request(params, resultHandler);
  });
});

Allow hljomaholl results to be empty


import mockReq from 'mock-require';
import chai from 'chai';
import { shallow } from 'enzyme';

import Match from 'react-router/Match';

chai.should();

global.OKAPI_URL = 'http://localhost:9130';

mockReq('stripes-loader!', { modules: {
  app: [ {
    displayName: 'someApp',
    module: 'some-app',
    getModule: () => () => 'Close enough to a React component for this purpose.',
    route: '/someapp'
  } ]
} });

mockReq('some-app', () =>

);

const routes = require('../src/moduleRoutes').default;
const inst = shallow(routes[0]).instance();
describe('routes', () => {
  it('should be an array of Match components', () => {
    routes.should.be.a('array');
    inst.should.be.instanceOf(Match);
  });
});

import mockReq from 'mock-require';
import chai from 'chai';
import { shallow } from 'enzyme';

import Match from 'react-router/Match';

chai.should();

global.OKAPI_URL = 'http://localhost:9130';

mockReq('stripes-loader', { modules: {
  app: [ {
    displayName: 'someApp',
    module: 'some-app',
    getModule: () => () => 'Close enough to a React component for this purpose.',
    route: '/someapp'
  } ]
} });

mockReq('some-app', () =>

);

const routes = require('../src/moduleRoutes').default;
const inst = shallow(routes[0]).instance();
describe('routes', () => {
  it('should be an array of Match components', () => {
    routes.should.be.a('array');
    inst.should.be.instanceOf(Match);
  });
});

Update test to mock stripes-loader alias


import React, { PropTypes } from 'react';
import styles from './Button.css';

const Button = (props) => {
  const classNames = [styles.Button];
  if (props.state) {
    classNames.push(styles[`Button--state-${props.state}`]);
  }
  if (props.type) {
    classNames.push(styles[`Button--type-${props.type}`]);
  }
  return (
    <button className={classNames.join(' ')}>
      {props.children}
    </button>
  );
};

Button.propTypes = {
  children: PropTypes.node,
  state: PropTypes.string,
  type: PropTypes.string,
};

export default Button;

import React, { PropTypes } from 'react';
import styles from './Button.css';

const Button = (props) => {
  const classNames = [styles.Button];
  if (props.state) {
    classNames.push(styles[`Button--state-${props.state}`]);
  }
  if (props.type) {
    classNames.push(styles[`Button--type-${props.type}`]);
  }
  return (
    <button className={classNames.join(' ')} {...props}>
      {props.children}
    </button>
  );
};

Button.propTypes = {
  children: PropTypes.node,
  state: PropTypes.string,
  type: PropTypes.string,
};

export default Button;

Extend props for handlers like onClick


const way = require('senseway');

const historyDeleteFrame = (hist, t) => {
  const newHist = way.clone(hist);
  newHist.map((ch) => {
    return ch.splice(t, 1);
  });
  return newHist;
};

const historyDuplicateFrame = (hist, t) => {
  const newHist = way.clone(hist);
  newHist.map((ch, i) => {
    const cellValue = ch[t];
    return ch.splice(t, 0, cellValue);
  });
  return newHist;
};

const historySetValue = (hist, ev) => {
  const newHist = way.clone(hist);
  newHist[ev.channel][ev.time] = ev.value;
  return newHist;
};

module.exports = (model, ev) => {
  switch (ev.type) {

    case 'DELETE_HISTORY_CHANNEL': {
      return Object.assign({}, model, {
        history: model.history.filter((ch, c) => c !== ev.channel)
      });
    }

    case 'DELETE_HISTORY_FRAME': {
      return Object.assign({}, model, {
        history: historyDeleteFrame(model.history, ev.time)
      });
    }

    case 'DUPLICATE_HISTORY_CHANNEL': {
      const c = ev.channel;
      const ch = [model.history[c]];
      const pre = model.history.slice(0, c);
      const post = model.history.slice(c);
      return Object.assign({}, model, {
        history: [].concat(pre, ch, post)
      });
    }

    case 'DUPLICATE_HISTORY_FRAME': {
      return Object.assign({}, model, {
        history: historyDuplicateFrame(model.history, ev.time)
      });
    }

    case 'SET_HISTORY_VALUE': {
      return Object.assign({}, model, {
        history: historySetValue(model.history, ev)
      });
    }

    default:
      return model;
  }
};

const way = require('senseway');

module.exports = (model, ev) => {
  switch (ev.type) {

    case 'DELETE_HISTORY_CHANNEL': {
      return Object.assign({}, model, {
        history: way.dropChannel(model.history, ev.channel),
      });
    }

    case 'DELETE_HISTORY_FRAME': {
      return Object.assign({}, model, {
        history: way.dropAt(model.history, ev.time)
      });
    }

    case 'DUPLICATE_HISTORY_CHANNEL': {
      return Object.assign({}, model, {
        history: way.repeatChannel(model.history, ev.channel),
      });
    }

    case 'DUPLICATE_HISTORY_FRAME': {
      return Object.assign({}, model, {
        history: way.repeatAt(model.history, ev.time)
      });
    }

    case 'SET_HISTORY_VALUE': {
      return Object.assign({}, model, {
        history: way.set(model.history, ev.channel, ev.time, ev.value)
      });
    }

    default:
      return model;
  }
};

Use way.repeatAt .repeatChannel .dropChannel .dropAt .set


const continuationBit = 1 << 7;
const lowBitsMask = ~continuationBit & 0xff;

/**
 * @template {ArrayBufferView} T
 *
 * @param {ReadableStream<Uint8Array>} stream
 * @param {number} length of the returned ArrayBufferView
 * @param {new (length: number) => T} Type
 *
 * @returns {T}
 */
export async function decompress(stream, length, Type) {
    const reader = stream.getReader();
    const output = new Type(length);
    let outIndex = 0;

    let result = 0;
    let shift = 0;

    while (true) {
        const { done, value } = await reader.read();

        if (done) {
            return output;
        }

        for (const byte of value) {
            result |= (byte & lowBitsMask) << shift;

            if ((byte & continuationBit) === 0) {
                output[outIndex++] = result;
                result = shift = 0;
                continue;
            }

            shift += 7;
        }
    }
}

const continuationBit = 1 << 7;
const lowBitsMask = ~continuationBit & 0xff;

/**
 * Consume a readable stream of unsigned LEB128 encoded bytes, writing the
 * results into the provided buffer
 *
 * @template {ArrayBufferView} T
 * @param {ReadableStream<Uint8Array>} stream A stream of concatenated unsigned
 * LEB128 encoded values
 * @param {T} buffer An ArrayBuffer view to write the decoded values to
 * @returns {T} the passed in `buffer`
 */
export async function decompress(stream, buffer) {
    const reader = stream.getReader();

    let index = 0;
    let result = 0;
    let shift = 0;

    while (true) {
        const { done, value } = await reader.read();

        if (done) {
            return buffer;
        }

        for (const byte of value) {
            result |= (byte & lowBitsMask) << shift;

            if ((byte & continuationBit) === 0) {
                buffer[index++] = result;
                result = shift = 0;
                continue;
            }

            shift += 7;
        }
    }
}

Bring your own buffer for leb128


/**
 * This script keeps the height set on elements with regard to their
 * width for a given aspect ratio. Great for "display:block" elements
 *
 * Example usage:
 *
 * <div data-keep-aspect-ratio="16:9">
 *
 * Author: Rod McNew
 * License: BSD
 */
new function () {
    var setHeights = function () {
        $.each($('[data-keep-aspect-ratio]'), function () {
            var ele = $(this);
            var ratioParts = ele.attr('data-keep-aspect-ratio').split(':');
            var ratioWidth = ratioParts[0];
            var ratioHeight = ratioParts[1];
            var width = ele.width();
            var newHeight = width * ratioHeight / ratioWidth;
            ele.css('height', newHeight);
        })
    };
    //Run onReady
    $(setHeights);
    //Run when window is resized
    $(window).resize(setHeights);
    //Run when target elements are resized without a window resize
    $('body').on('resize', '[data-keep-aspect-ratio]', setHeights);
    $('body').on('orientationchange', '[data-keep-aspect-ratio]', setHeights);
};

/**
 * This script keeps the height set on elements with regard to their
 * width for a given aspect ratio. Great for "display:block" elements
 *
 * Example usage:
 *
 * <div data-keep-aspect-ratio="16:9">
 *
 * Author: Rod McNew
 * License: BSD
 */
new function () {
    var setHeights = function () {
        $.each($('[data-keep-aspect-ratio]'), function () {
            var ele = $(this);
            var ratioParts = ele.attr('data-keep-aspect-ratio').split(':');
            var ratioWidth = ratioParts[0];
            var ratioHeight = ratioParts[1];
            var width = ele.width();
            var newHeight = width * ratioHeight / ratioWidth;
            ele.css('height', newHeight);
            ele.css('overflow', 'hidden');

            ele.find("iframe").height(newHeight).width(width);
            setTimeout(function() { refreshIframe(ele); }, 500);
        })
    };

    var refreshIframe = function(ele) {
        var iframe = ele.find("iframe");
        iframe.attr('src', iframe.attr('src'));
    };

    //Run onReady
    $(setHeights);
    //Run when window is resized
    $(window).resize(setHeights);
    //Run when target elements are resized without a window resize
    $('body').on('resize', '[data-keep-aspect-ratio]', setHeights);
};

Fix for brightcove player when orintation is changed


/**
 * Checks if value is array-like.
 * A value is considered array-like if it’s not a function and has a `value.length` that’s an
 * integer greater than or equal to 0 and less than or equal to `Number.MAX_SAFE_INTEGER`.
 * @param {*} value The value to check.
 * @return {Boolean} Returns true if value is array-like, else false.
 */
function isArrayLike(value) {
    'use strict';

    var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || Math.pow(2, 53) - 1,
        len = value && value.length;

    return value != null && typeof value !== 'function' &&
        typeof len === 'number' && len > -1 && len % 1 === 0 && len <= MAX_SAFE_INTEGER;
}

/**
 * Checks if value is array-like.
 * A value is considered array-like if it’s not a function and has a `value.length` that’s an
 * integer greater than or equal to 0 and less than or equal to `Number.MAX_SAFE_INTEGER`.
 * @param {*} value The value to check.
 * @return {Boolean} Returns true if value is array-like, else false.
 */
function isArrayLike(value) {
    'use strict';

    var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || Math.pow(2, 53) - 1,
        len = !!value && value.length;

    return value != null && typeof value !== 'function' &&
        typeof len === 'number' && len > -1 && len % 1 === 0 && len <= MAX_SAFE_INTEGER;
}

Fix issue that would cause a falsey value like 0 to pass the tests


define(function () {
  return {

    /**
     * Format monetary to standard format with ISO 4217 code.
     */
    format: function (m) {
      if (m.isInvalid()) {
        return "Invalid monetary!";
      }

      return m.currency() + " " + m.amount();
    }
  };
});

define(function () {
  return {

    /**
     * Format monetary to standard format with ISO 4217 code.
     */
    format: function (m) {
      if (m.isInvalid()) {
        return "Invalid monetary!";
      }

      return m.currency() + " " + m.amount().toFixed(2);
    }
  };
});

Add temporary monetary rounding fix


// We faced a bug in babel so that transform-runtime with export * from 'x' generates import statements in transpiled code
// Tracked here : https://github.com/babel/babel/issues/2877
// We tested the workaround given here https://github.com/babel/babel/issues/2877#issuecomment-270700000 with success so far
import _ from 'lodash'
import * as errors from './errors'
import * as permissions from './permissions'

export { errors }
export { permissions }

// Append a parameter value to a given URL
export function addQueryParameter(baseUrl, parameter, value) {
  // Check if this is the first parameter to be added or not
  const prefix = (baseUrl.includes('?') ? '&' : '?')
  return `${baseUrl}${prefix}${parameter}=${Array.isArray(value) ? JSON.stringify(value) : value}`
}

// Build an encoded URL from a given set of parameters
export function buildUrl(baseUrl, parameters) {
  let url = baseUrl
  _.forOwn(parameters, function(value, key) {
    url = addQueryParameter(url, key, value)
  })
  return encodeURI(url)
}

// We faced a bug in babel so that transform-runtime with export * from 'x' generates import statements in transpiled code
// Tracked here : https://github.com/babel/babel/issues/2877
// We tested the workaround given here https://github.com/babel/babel/issues/2877#issuecomment-270700000 with success so far
import _ from 'lodash'
import * as errors from './errors'
import * as permissions from './permissions'

export { errors }
export { permissions }

// Append a parameter value to a given URL
export function addQueryParameter(baseUrl, parameter, value) {
  // Check if this is the first parameter to be added or not
  const prefix = (baseUrl.includes('?') ? '&' : '?')
  return `${baseUrl}${prefix}${parameter}=${Array.isArray(value) ? JSON.stringify(value) : value}`
}

// Build an URL from a given set of parameters
export function buildUrl(baseUrl, parameters) {
  let url = baseUrl
  _.forOwn(parameters, function(value, key) {
    url = addQueryParameter(url, key, value)
  })
  return url
}

// Build an encoded URL from a given set of parameters
export function buildEncodedUrl(baseUrl, parameters) {
  return encodeURI(buildEncodedUrl(baseUrl, parameters))
}

Make buildUrl return a non-encoded url by default


exports.config = {
    specs: ['ui/**/*.spec.js'],
    sauceUser: process.env.SAUCE_USERNAME,
    sauceKey: process.env.SAUCE_ACCESS_KEY,
    baseUrl: 'http://localhost:3000',
    capabilities: {
      'browserName': 'chrome',
      'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
      'build': process.env.TRAVIS_BUILD_NUMBER,
      'name': 'AgileJS Training Repo'
    }
};

exports.config = {
    specs: ['ui/**/*.spec.js'],
    sauceUser: process.env.SAUCE_USERNAME,
    sauceKey: process.env.SAUCE_ACCESS_KEY,
    baseUrl: 'http://localhost:3000',
    capabilities: {
      'browserName': 'firefox',
      'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
      'build': process.env.TRAVIS_BUILD_NUMBER,
      'name': 'AgileJS Training Repo'
    }
};

Correct commands are sent to Saucelabs, but they are misinterpreted. Maybe the Firefox driver works better?


const path = require('path');

module.exports = {

  development: {
    client: 'pg',
    connection: 'postgres://localhost/envelopresent',
    migrations: {
      directory: path.join(__dirname, '..', 'datasource', 'migrations'),
      tableName: 'knex_migrations'
    }
  },

  production: {
    client: 'pg',
    connection: process.env.DATABASE_URL,
    pool: {
      min: 2,
      max: 10
    },
    migrations: {
      directory: path.join(__dirname, '..', 'datasource','migrations'),
      tableName: 'knex_migrations'
    }
  }

};

const path = require('path');

module.exports = {

  development: {
    client: 'pg',
    connection: 'postgres://localhost/envelopresent',
    migrations: {
      directory: path.join(__dirname, '..', 'datasource', 'migrations'),
      tableName: 'knex_migrations'
    }
  },

  production: {
    client: 'ps',
    connection: process.env.DATABASE_URL,
    pool: {
      min: 2,
      max: 10
    },
    migrations: {
      directory: path.join(__dirname, '..', 'datasource','migrations'),
      tableName: 'knex_migrations'
    }
  }

};

Revert "Fix DB vendor alias"


var cv = require('../lib/opencv');

var COLOR = [0, 255, 0]; // default red
var thickness = 2; // default 1

cv.readImage('./files/mona.png', function(err, im) {
  if (err) throw err;
  if (im.width() < 1 || im.height() < 1) throw new Error('Image has no size');

  im.detectObject('../data/haarcascade_frontalface_alt2.xml', {}, function(err, faces) {
    if (err) throw err;

    for (var i = 0; i < faces.length; i++) {
      face = faces[i];
      im.rectangle([face.x, face.y], [face.x + face.width, face.y + face.height], COLOR, 2);
    }

    im.save('./tmp/face-detection-rectangle.png');
    console.log('Image saved to ./tmp/face-detection-rectangle.png');
  });

});

var cv = require('../lib/opencv');

var COLOR = [0, 255, 0]; // default red
var thickness = 2; // default 1

cv.readImage('./files/mona.png', function(err, im) {
  if (err) throw err;
  if (im.width() < 1 || im.height() < 1) throw new Error('Image has no size');

  im.detectObject('../data/haarcascade_frontalface_alt2.xml', {}, function(err, faces) {
    if (err) throw err;

    for (var i = 0; i < faces.length; i++) {
      face = faces[i];
      im.rectangle([face.x, face.y], [face.width, face.height], COLOR, 2);
    }

    im.save('./tmp/face-detection-rectangle.png');
    console.log('Image saved to ./tmp/face-detection-rectangle.png');
  });

});

Fix mistake in face detection example


var loaders = require("./loaders");
var preloaders = require("./preloaders");
var HtmlWebpackPlugin = require('html-webpack-plugin');
var webpack = require('webpack');

module.exports = {
    entry: ['./src/index.ts'],
    output: {
        filename: 'build.js',
        path: 'dist'
    },
    devtool: '',
    resolve: {
        root: __dirname,
        extensions: ['', '.ts', '.js', '.json']
    },
    resolveLoader: {
        modulesDirectories: ["node_modules"]
    },
    plugins: [
        new webpack.optimize.UglifyJsPlugin(
            {
                warning: false,
                mangle: true,
                comments: false
            }
        ),
        new HtmlWebpackPlugin({
            template: './src/index.html',
            inject: 'body',
            hash: true
        }),
        new webpack.ProvidePlugin({
            $: 'jquery',
            jQuery: 'jquery',
            'window.jQuery': 'jquery',
            'window.jquery': 'jquery'
        })
    ],
    module:{
        preLoaders:preloaders,
        loaders: loaders
    },
    tslint: {
        emitErrors: true,
        failOnHint: true
  }
};

const loaders = require("./loaders");
const preloaders = require("./preloaders");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const webpack = require("webpack");

module.exports = {
    context: path.join(__dirname, ".."),
    entry: ["./src/index.ts"],
    output: {
        filename: "build.js",
        path: "dist"
    },
    devtool: "",
    resolve: {
        root: __dirname,
        extensions: [".ts", ".js", ".json"]
    },
    resolveLoader: {
        modulesDirectories: ["node_modules"]
    },
    plugins: [
        new webpack.optimize.UglifyJsPlugin(
            {
                warning: false,
                mangle: true,
                comments: false
            }
        ),
        new HtmlWebpackPlugin({
            template: "./src/index.html",
            inject: "body",
            hash: true
        }),
        new webpack.ProvidePlugin({
            $: "jquery",
            jQuery: "jquery",
            "window.jQuery": "jquery",
            "window.jquery": "jquery"
        })
    ],
    module:{
        preLoaders:preloaders,
        loaders: loaders
    },
    tslint: {
        emitErrors: true,
        failOnHint: true
  }
};

Replace single quotes -> double quotes, var -> const


import Ember from 'ember';
import { moduleFor, test } from 'ember-qunit';
import { Offline } from 'ember-flexberry-data';
import startApp from 'dummy/tests/helpers/start-app';

let App;

moduleFor('service:user', 'Unit | Service | user', {
  needs: [
    'model:i-c-s-soft-s-t-o-r-m-n-e-t-security-agent',
  ],

  beforeEach() {
    App = startApp();
    App.unregister('service:store');
    App.register('service:store', Offline.Store);
    App.register('store:local', Offline.LocalStore);
  },

  afterEach() {
    Ember.run(App, 'destroy');
  },
});

test('it works', function(assert) {
  assert.expect(4);
  let done = assert.async();
  let service = this.subject(App.__container__.ownerInjection());

  Ember.run(() => {
    service.getCurrentUser().then((user) => {
      assert.equal(user.get('name'), 'user');
      assert.ok(user.get('isUser'));
      assert.notOk(user.get('isGroup'));
      assert.notOk(user.get('isRole'));
      done();
    });
  });
});

import Ember from 'ember';
import { moduleFor, test } from 'ember-qunit';
import { Offline } from 'ember-flexberry-data';
import startApp from 'dummy/tests/helpers/start-app';

let App;

moduleFor('service:user', 'Unit | Service | user', {
  needs: [
    'model:i-c-s-soft-s-t-o-r-m-n-e-t-security-agent',
  ],

  beforeEach() {
    App = startApp();
    App.unregister('service:store');
    App.register('service:store', Offline.Store);
    App.register('store:local', Offline.LocalStore);
  },

  afterEach() {
    Ember.run(App, 'destroy');
  },
});

test('it works', function(assert) {
  // TODO: Replace this with your real tests.
  let service = this.subject(App.__container__.ownerInjection());
  assert.ok(!!service);
});

Test user service until better times


"use strict";

// DOUBLY-LINKED LIST

// define constructor
function Node(val) {
	this.data = val;
	this.previous = null;
	this.next = null;
}

function DoublyLinkedList() {
	this._length = 0;
	this.head = null;
	this.tail = null;
}

// add node to doubly linked list
DoublyLinkedList.prototype.add = function(val) {
	var node = new Node(val); // create new instance of node

	if(this._length) {
		this.tail.next = node;
		node.previous = this.tail; // account for bi-directional movement
		this.tail = node; // new node now tail
	} else { // if doubly linked list is empty
		this.head = node;
		this.tail = node;
	}

	this._length++;

	return node;
}

// search nodes at specific positions in doubly linked list
DoublyLinkedList.prototype.searchNodeAt = function(position) {
	var currentNode = this.head,
		length = this._length,
		count = 1,
		message = {failure: 'Failure: non-existent node in this list'};

	// first case: invalid position
	if (length === 0 || position < 1 || position > length) {
		throw new Error(message.failure);
	}

	// second case: valid position
	while (count < position) {
		// go through entire doubly linked list until currentNode is equal to position
		currentNode = currentNode.next;
		count++;
	}

	return currentNode;
};

Add search-node method to doubly linked list


var southWest = L.latLng(45.36638, -75.73674),
    northEast = L.latLng(45.36933, -75.73316),
    bounds = L.latLngBounds(southWest, northEast);

var map = L.map('map', {
    center: [45.36806, -75.73408],
    zoom: 18,
    minZoom: 18,
    maxZoom: 19,
    maxBounds: bounds,
});

L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
    attribution: '© OpenStreetMap contributors'
}).addTo(map);

var southWest = L.latLng(45.36638, -75.73674),
    northEast = L.latLng(45.36933, -75.73316),
    bounds = L.latLngBounds(southWest, northEast);

var map = L.map('map', {
    center: [45.36806, -75.73408],
    zoom: 18,
    minZoom: 18,
    maxZoom: 19,
    maxBounds: bounds,
});

L.tileLayer('http://{s}.tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png', {
    attribution: '© OpenStreetMap contributors'
}).addTo(map);

Use black and white basemap


function Words(sentence){
    "use strict";
    sentence = sentence.toLowerCase();
    var wordMatches = sentence.match(/\w+/g);
    var result = {};
    for(var idx = 0 ; idx < wordMatches.length ; idx++) {
        result[wordMatches[idx]] = (result[wordMatches[idx]] || 0) + 1;
    }
    return { count: result };
};

module.exports = Words;

function Words(sentence){
    "use strict";

    return { count: countWords(sentence) };

    function countWords(sentence){
        sentence = sentence.toLowerCase();
        var wordMatches = sentence.match(/\w+/g);
        var result = {};
        for(var idx = 0 ; idx < wordMatches.length ; idx++) {
            result[wordMatches[idx]] = (result[wordMatches[idx]] || 0) + 1;
        }
        return result;
    }
};

module.exports = Words;

Use a separate counting function.


'use strict';

var SearchService = function($http, $q) {

	this.search = function(query) {

		var deferred = $q.defer();

		var from = query.from ? '&from=' + query.from : "";
		var size = query.size ? '&size=' + query.size : "";
		var facetlist = "&facet=resource.provenance&facet=resource.types";

		// facet values in der Backend-URI: mit "resource."
		var fq = query.fq;
        if (fq == null) {
			fq = "";
		} else if (typeof fq === 'string') {
			fq = "&fq=resource."+fq;
		} else {
			fq = "&fq=resource." + fq.join("&fq=resource.");
		}

		var uri = '/data/period/?q=' + query.q + from + size + facetlist + fq;
		$http.get(uri).then(
			function success(result) {
				deferred.resolve(result.data);
			},
			function error(err) {
				console.warn(err);
				deferred.reject();
			}
		);

		return deferred.promise;
	}

}

angular.module('chronontology.services').service('searchService',
	["$http", "$q", SearchService]);

'use strict';

var SearchService = function($http, $q) {

	this.search = function(query) {

		var deferred = $q.defer();

		var from = query.from ? '&from=' + query.from : "";
		var size = query.size ? '&size=' + query.size : "";
		var facetlist = "&facet=resource.provenance&facet=resource.types";

		// facet values in der Backend-URI: mit "resource."
		var fq = query.fq;
        if (fq == null) {
			fq = "";
		} else if (typeof fq === 'string') {
			fq = "&fq=resource."+fq;
		} else {
			fq = "&fq=resource." + fq.join("&fq=resource.");
		}

		var exists = query.exists;
        if (exists == null) {
			exists = "";
		} else if (typeof exists === 'string') {
			exists = "&exists=resource."+exists;
		} else {
			exists = "&exists=resource." + exists.join("&exists=resource.");
		}

		var uri = '/data/period/?q=' + query.q + from + size + facetlist + fq + exists;
		$http.get(uri).then(
			function success(result) {
				deferred.resolve(result.data);
			},
			function error(err) {
				console.warn(err);
				deferred.reject();
			}
		);

		return deferred.promise;
	}

}

angular.module('chronontology.services').service('searchService',
	["$http", "$q", SearchService]);

Make sure exists filter values are sent to backend


var express = require('express')
var vhost = require('vhost')

var app2014 = require('./2014/app')
var app2015 = require('./2015/app')
var app2016 = require('./2016/app')
var appSplash = require('./splash/app')

//var redirect = express()

//redirect.get('/', function (req, res) {
//  res.redirect(301, 'https://2016.coldfrontconf.com')
//})

var server = express()
server.set('port', process.env.PORT || 8080)

//server.use(vhost('2014.coldfrontconf.com', app2014))
//server.use(vhost('2015.coldfrontconf.com', app2015))
//server.use(vhost('2016.coldfrontconf.com', app2016))
//server.use(vhost('coldfrontconf.com', redirect))
server.use(vhost('localhost', app2016))

server.listen(server.get('port'), function () {
  console.log('Express server listening on port %d in %s mode', server.get('port'), server.settings.env)
})

var express = require('express')
var vhost = require('vhost')

var app2014 = require('./2014/app')
var app2015 = require('./2015/app')
var app2016 = require('./2016/app')
var appSplash = require('./splash/app')

var redirect = express()

redirect.get('/', function (req, res) {
 res.redirect(301, 'https://2016.coldfrontconf.com')
})

var server = express()
server.set('port', process.env.PORT || 8080)

server.use(vhost('2014.coldfrontconf.com', app2014))
server.use(vhost('2015.coldfrontconf.com', app2015))
server.use(vhost('2016.coldfrontconf.com', app2016))
server.use(vhost('coldfrontconf.com', redirect))
server.use(vhost('localhost', app2016))

server.listen(server.get('port'), function () {
  console.log('Express server listening on port %d in %s mode', server.get('port'), server.settings.env)
})

Fix broken redirects and serving of older websites


var hapi = require('hapi');
var moonboots = require('moonboots_hapi');
var config = require('getconfig');
var templatizer = require('templatizer');
var Good = require('good');
var ElectricFence = require('electricfence');
var server = hapi.createServer(8080, 'localhost');

server.pack.register([
    {
        plugin: moonboots,
        options: {
            appPath: '/{p*}',
            moonboots: {
                main: __dirname + '/client/app.js',
                developmentMode: config.isDev,
                stylesheets: [
                    __dirname + '/public/css/local.css',
                    __dirname + '/public/css/bootstrap.css'
                ],
                beforeBuildJS: function () {
                    templatizer(__dirname + '/templates', __dirname + '/client/templates.js');
                }
            }
        }
    }, {
        plugin: Good,
        options: {
            reporters: [{
                reporter: Good.GoodConsole
            }]
        }
    }, {
        plugin: ElectricFence,
        options: config.electricfence
    }
], function () {
    server.start(function () {
        server.log(['info'], 'lift.zone running on the year ' + server.info.port);
    });
});

var hapi = require('hapi');
var moonboots = require('moonboots_hapi');
var config = require('getconfig');
var templatizer = require('templatizer');
//var Good = require('good');
var ElectricFence = require('electricfence');
var server = hapi.createServer(8080, 'localhost');

server.pack.register([
    {
        plugin: moonboots,
        options: {
            appPath: '/{p*}',
            moonboots: {
                main: __dirname + '/client/app.js',
                developmentMode: config.isDev,
                stylesheets: [
                    __dirname + '/public/css/local.css',
                    __dirname + '/public/css/bootstrap.css'
                ],
                beforeBuildJS: function () {
                    templatizer(__dirname + '/templates', __dirname + '/client/templates.js');
                }
            }
        }
    //}, {
        //plugin: Good,
        //options: {
            //reporters: [{
                ////reporter: Good.GoodConsole
            //}]
        //}
    }, {
        plugin: ElectricFence,
        options: config.electricfence
    }
], function () {
    server.start(function () {
        server.log(['info'], 'lift.zone running on the year ' + server.info.port);
    });
});

Disable Good cause pm2 pukes


var express = require('express');
var app = express();
var port = process.env.PORT || 3000;

// Asset paths first
app.get('/js/:file', serveFile.bind(null, 'dist/js'));
app.get('/css/:file', serveFile.bind(null, 'dist/css'));
app.get('/img/:file', serveFile.bind(null, 'dist/img'));
app.get('/templates/:file', serveFile.bind(null, 'dist/templates'));

// Anything else is sent to index.html to support SPA routing
app.get('/*', function (req, res) {
    res.sendFile('index.html', { root: 'dist' });
});

// Start the server!
var server = app.listen(port, serverStarted);

function serverStarted() {
    console.log("Bloc Base Project is running");
}

function serveFile( root, req, res ) {
    res.sendFile(req.params.file, { root: root });
}

// Sets a timer to retrieve data from Heroku so the app doesnt sleep

var https = require("https");

setInterval(function(){
    https.get("https://pomodoro-app-timer.herokuapp.com/");
    console.log("Applicaton is awake");
}, 250000);

Set timer for app so it will retrieve the data from heroku every 4.5 min


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

import {relative, sep} from 'path'
import {getProjectDir} from '../../../paths'

const PROJECT_DIR = getProjectDir()

function fileName(file) {
  const RELATIVE = relative(PROJECT_DIR, file)
  let nodes = RELATIVE.split(sep)

  if (nodes[0] === 'src')
    nodes.shift()

  return `${nodes.join('/')}?[sha512:hash:base64:8]`
}

export default function () {
  return {
    exclude: /\.(css|jsx?|mjs)$/,
    use: [{
      loader: 'url-loader',
      options: {
        fallback: 'file-loader',
        limit: 1024,
        name: fileName,
      },
    }],
  }
}

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

import {
  getProjectDir,
  TOOLBOX_DIR,
} from '../../../paths'

import {isPathSubDirOf} from '../../../utils'
import {relative, sep} from 'path'

const PROJECT_DIR = getProjectDir()

/**
 * Calculate the file path for the loaded asset.
 */
function fileName(file) {
  let prefix = null
  let reference = null

  if (isPathSubDirOf(file, PROJECT_DIR)) {
    // Assets from the target project.
    prefix = []
    reference = PROJECT_DIR
  } else if (isPathSubDirOf(file, TOOLBOX_DIR)) {
    // Assets from the Toolbox.
    prefix = ['borela-js-toolbox']
    reference = TOOLBOX_DIR
  }

  const RELATIVE = relative(reference, file)
  let nodes = RELATIVE.split(sep)

  if (nodes[0] === 'src')
    nodes.shift()

  if (prefix.length > 0)
    nodes = [...prefix, ...nodes]

  return `${nodes.join('/')}?[sha512:hash:base64:8]`
}

export default function () {
  return {
    exclude: /\.(css|jsx?|mjs)$/,
    use: [{
      loader: 'url-loader',
      options: {
        fallback: 'file-loader',
        limit: 1024,
        name: fileName,
      },
    }],
  }
}

Update asset loader path resolution


import React from 'react'
import { prefixLink } from './gatsby-helpers'

if (process.env.NODE_ENV === `production`) {
  let stylesStr
  try {
    stylesStr = require(`!raw!public/styles.css`)
  } catch (e) {
    // ignore
  }
}

const htmlStyles = (args = {}) => {
  if (process.env.NODE_ENV === `production`) {
    if (args.link) {
      // If the user wants to reference the external stylesheet return a link.
      return <link rel="stylesheet" type="text/css" href={prefixLink(`/styles.css`)} media="screen" />
    } else {
      // Default to returning the styles inlined.
      return <style id="gatsby-inlined-css" dangerouslySetInnerHTML={{ __html: stylesStr }} />
    }
  }

  // In dev just return an empty style element.
  return <style />
}

module.exports = htmlStyles

import React from 'react'
import { prefixLink } from './gatsby-helpers'

let stylesStr
if (process.env.NODE_ENV === `production`) {
  try {
    stylesStr = require(`!raw!public/styles.css`)
  } catch (e) {
    // ignore
  }
}

const htmlStyles = (args = {}) => {
  if (process.env.NODE_ENV === `production`) {
    if (args.link) {
      // If the user wants to reference the external stylesheet return a link.
      return <link rel="stylesheet" type="text/css" href={prefixLink(`/styles.css`)} media="screen" />
    } else {
      // Default to returning the styles inlined.
      return <style id="gatsby-inlined-css" dangerouslySetInnerHTML={{ __html: stylesStr }} />
    }
  }

  // In dev just return an empty style element.
  return <style />
}

module.exports = htmlStyles

Move variable declaration up a level to ensure it exists later


var media = require('rtc-media');
var h = require('hyperscript');
var crel = require('crel');
var screenshare = require('..')({
  chromeExtension: 'rtc.io screenshare',
  version: '^1.0.0'
});

var buttons = {
  install: h('button', 'Install Extension', { onclick: function() {
    chrome.webstore.install();
  }}),

  capture: h('button', 'Capture Screen', { onclick: shareScreen })
};

function shareScreen() {
  screenshare.request(function(err, constraints) {
    if (err) {
      return console.error('Could not capture window: ', err);
    }

    console.log('attempting capture with constraints: ', constraints);
    media({
      constraints: constraints,
      target: document.getElementById('main')
    });
  });
}

// detect whether the screenshare plugin is available and matches
// the required version
screenshare.available(function(err, version) {
  var actions = document.getElementById('actions');

  if (err) {
    return actions.appendChild(buttons.install);
  }

  actions.appendChild(buttons.capture);
});

// on install show the capture button and remove the install button if active
screenshare.on('activate', function() {
  if (buttons.install.parentNode) {
    buttons.install.parentNode.removeChild(buttons.install);
  }

  document.getElementById('actions').appendChild(buttons.capture);
});

var media = require('rtc-media');
var h = require('hyperscript');
var crel = require('crel');
var screenshare = require('..')({
  chromeExtension: 'rtc.io screenshare',
  version: '^1.0.0'
});

var buttons = {
  install: h('button', 'Install Extension', { onclick: function() {
    chrome.webstore.install();
  }}),

  capture: h('button', 'Capture Screen', { onclick: shareScreen })
};

function shareScreen() {
  screenshare.request(function(err, constraints) {
    if (err) {
      return console.error('Could not capture window: ', err);
    }

    console.log('attempting capture with constraints: ', constraints);
    media({
      constraints: constraints,
      target: document.getElementById('main')
    });
  });

  // you better select something quick or this will be cancelled!!!
  setTimeout(screenshare.cancel, 5e3);
}

// detect whether the screenshare plugin is available and matches
// the required version
screenshare.available(function(err, version) {
  var actions = document.getElementById('actions');

  if (err) {
    return actions.appendChild(buttons.install);
  }

  actions.appendChild(buttons.capture);
});

// on install show the capture button and remove the install button if active
screenshare.on('activate', function() {
  if (buttons.install.parentNode) {
    buttons.install.parentNode.removeChild(buttons.install);
  }

  document.getElementById('actions').appendChild(buttons.capture);
});

Include code the cancels the request after 5s


Tinytest.add('meteor-assert', function (test) {
  var isDefined = false;
  try {
    assert;
    isDefined = true;
  }
  catch (e) {
  }
  test.isTrue(isDefined, "assert is not defined");
  test.throws(function () {
    assert.equal('a', 'b');
  });
  if (Meteor.isServer) {
    console.log("Meteor.isServer");
    test.equal(1, 2);
  }
  else {
    console.log("Meteor.isClient");
    test.equal(1, 3);
  }
});

Tinytest.add('meteor-assert', function (test) {
  var isDefined = false;
  try {
    assert;
    isDefined = true;
  }
  catch (e) {
  }
  test.isTrue(isDefined, "assert is not defined");
  test.throws(function () {
    assert.equal('a', 'b');
  });
});

Revert "Test for Travis CI."


import React from 'react';
import Archive from '../../component/archive/archive-list-container';
import PropTypes from 'prop-types';

const render = ({ match }) => <Archive name={match.params.name} />;
render.propTypes = {
    match: PropTypes.object,
};

export default render;

import React from 'react';
import Archive from '../../component/archive/archive-list-container';
import PropTypes from 'prop-types';

const render = ({ match: { params }, history }) => <Archive name={params.name} history={history} />;
render.propTypes = {
    match: PropTypes.object,
    history: PropTypes.object,
};

export default render;

Add 'history' prop to the archive-list.


/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule LazyRenderer
 */
'use strict';

var React = require('React');
var TimerMixin = require('TimerMixin');

var LazyRenderer = React.createClass({
  mixin: [TimerMixin],

  propTypes: {
    render: React.PropTypes.func.isRequired,
  },

  componentWillMount: function(): void {
    this.setState({
      _lazyRender : true,
    });
  },

  componentDidMount: function(): void {
    requestAnimationFrame(() => {
      this.setState({
        _lazyRender : false,
      });
    });
  },

  render: function(): ?ReactElement {
    return this.state._lazyRender ? null : this.props.render();
  },
});

module.exports = LazyRenderer;

/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule LazyRenderer
 */
'use strict';

var React = require('React');
var TimerMixin = require('react-timer-mixin');

var LazyRenderer = React.createClass({
  mixin: [TimerMixin],

  propTypes: {
    render: React.PropTypes.func.isRequired,
  },

  componentWillMount: function(): void {
    this.setState({
      _lazyRender : true,
    });
  },

  componentDidMount: function(): void {
    requestAnimationFrame(() => {
      this.setState({
        _lazyRender : false,
      });
    });
  },

  render: function(): ?ReactElement {
    return this.state._lazyRender ? null : this.props.render();
  },
});

module.exports = LazyRenderer;

Replace local copy of TimerMixin with module from npm.


define([], function() {
    'use strict';

    return withFileDrop;

    function withFileDrop() {

        this.after('initialize', function() {
            var self = this;

            if (!this.handleFilesDropped) {
                return console.warn('Implement handleFilesDropped');
            }

            this.node.ondragover = function() {
                $(this).addClass('file-hover'); return false;
            };
            this.node.ondragenter = function() {
                $(this).addClass('file-hover'); return false;
            };
            this.node.ondragleave = function() {
                $(this).removeClass('file-hover'); return false;
            };
            this.node.ondrop = function(e) {
                e.preventDefault();
                e.stopPropagation();

                if (self.$node.hasClass('uploading')) return;

                self.handleFilesDropped(e.dataTransfer.files, event);
            };
        });
    }
});

define([], function() {
    'use strict';

    return withFileDrop;

    function withFileDrop() {

        this.after('initialize', function() {
            var self = this;

            if (!this.handleFilesDropped) {
                return console.warn('Implement handleFilesDropped');
            }

            this.node.ondragover = function() {
                $(this).addClass('file-hover'); return false;
            };
            this.node.ondragenter = function() {
                $(this).addClass('file-hover'); return false;
            };
            this.node.ondragleave = function() {
                $(this).removeClass('file-hover'); return false;
            };
            this.node.ondrop = function(e) {
                if (e.dataTransfer && e.dataTransfer.files) {
                    e.preventDefault();
                    e.stopPropagation();

                    if (self.$node.hasClass('uploading')) return;

                    self.handleFilesDropped(e.dataTransfer.files, event);
                }
            };
        });
    }
});

Fix issue in detail pane where dragging entities to empty space in detail pane would cause it to stay there


import { Class } from '../lib/objects';
import { isQueryExpression } from './expression';

export default Class.extend({
  evaluator: null,

  init(evaluator) {
    this.evaluator = evaluator;
  },

  evaluate(expression) {
    if (isQueryExpression(expression)) {
      let operator = this.evaluator.operators[expression.op];

      if (!operator) { throw new Error('Unable to find operator: ' + expression.op); }

      return operator.evaluate(this, expression.args);
    } else {
      return expression;
    }
  }
});

import { isQueryExpression } from './expression';

export default class QueryContext {
  constructor(evaluator) {
    this.evaluator = evaluator;
  }

  evaluate(expression) {
    if (isQueryExpression(expression)) {
      let operator = this.evaluator.operators[expression.op];

      if (!operator) { throw new Error('Unable to find operator: ' + expression.op); }

      return operator.evaluate(this, expression.args);
    } else {
      return expression;
    }
  }
}

Convert QueryContext to ES class.


Package.describe({
  version: '1.1.2',
  summary: 'Meteor bundle analysis and visualization.',
  documentation: 'README.md',
});

Npm.depends({
  "d3-selection": "1.0.5",
  "d3-shape": "1.0.6",
  "d3-hierarchy": "1.1.4",
  "d3-transition": "1.0.4",
  "d3-collection": "1.0.4",
  "pretty-bytes": "4.0.2",
});

Package.onUse(function(api) {
  api.use('isobuild:dynamic-import@1.5.0');
  api.use([
    'ecmascript',
    'dynamic-import',
    'http',
  ]);
  api.mainModule('server.js', 'server');
  api.mainModule('client.js', 'client');
});

Package.describe({
  version: '1.1.3',
  summary: 'Meteor bundle analysis and visualization.',
  documentation: 'README.md',
});

Npm.depends({
  "d3-selection": "1.0.5",
  "d3-shape": "1.0.6",
  "d3-hierarchy": "1.1.4",
  "d3-transition": "1.0.4",
  "d3-collection": "1.0.4",
  "pretty-bytes": "4.0.2",
});

Package.onUse(function(api) {
  api.use('isobuild:dynamic-import@1.5.0');
  api.use([
    'ecmascript',
    'dynamic-import',
    'http',
    'webapp',
  ]);
  api.mainModule('server.js', 'server');
  api.mainModule('client.js', 'client');
});

Make bundle-visualizer depend on webapp, since it imports meteor/webapp.


/**
 * @fileoverview
 * Utility functions for handling tutorial images in multiple languages
 */

import {enImages as defaultImages} from './en-steps.js';

let savedImages = {};
let savedLocale = '';

const loadSpanish = () =>
    import(/* webpackChunkName: "es-steps" */ './es-steps.js')
        .then(({esImages: imageData}) => imageData);

const translations = {
    es: () => loadSpanish()
};

const loadImageData = locale => {
    if (translations.hasOwnProperty(locale)) {
        translations[locale]()
            .then(newImages => {
                savedImages = newImages;
                savedLocale = locale;
            });
    }
};

/**
 * Return image data for tutorials based on locale (default: en)
 * @param {string} imageId key in the images object, or id string.
 * @param {string} locale requested locale
 * @return {string} image
 */
const translateImage = (imageId, locale) => {
    if (locale !== savedLocale || !savedImages.hasOwnProperty(imageId)) {
        return defaultImages[imageId];
    }
    return savedImages[imageId];
};

export {
    loadImageData,
    translateImage
};

/**
 * @fileoverview
 * Utility functions for handling tutorial images in multiple languages
 */

import {enImages as defaultImages} from './en-steps.js';

let savedImages = {};
let savedLocale = '';

const loadSpanish = () =>
    import(/* webpackChunkName: "es-steps" */ './es-steps.js')
        .then(({esImages: imageData}) => imageData);

const translations = {
    'es': () => loadSpanish(),
    'es-419': () => loadSpanish()
};

const loadImageData = locale => {
    if (translations.hasOwnProperty(locale)) {
        translations[locale]()
            .then(newImages => {
                savedImages = newImages;
                savedLocale = locale;
            });
    }
};

/**
 * Return image data for tutorials based on locale (default: en)
 * @param {string} imageId key in the images object, or id string.
 * @param {string} locale requested locale
 * @return {string} image
 */
const translateImage = (imageId, locale) => {
    if (locale !== savedLocale || !savedImages.hasOwnProperty(imageId)) {
        return defaultImages[imageId];
    }
    return savedImages[imageId];
};

export {
    loadImageData,
    translateImage
};

Make Spanish tutorial images also load for Latinamerican Spanish


var hostname = location.hostname.replace(/^www\./, '');

var style = document.createElement('link');
style.rel = 'stylesheet';
style.href = chrome.extension.getURL('styles/' + hostname + '.css');
document.documentElement.appendChild(style);

var defaultStyle = document.createElement('link');
defaultStyle.rel = 'stylesheet';
defaultStyle.href = chrome.extension.getURL('styles/default.css');
document.documentElement.appendChild(defaultStyle);

var script = document.createElement('script');
script.src = chrome.extension.getURL('scripts/' + hostname + '.js');
document.documentElement.appendChild(script);

var defaultScript = document.createElement('script');
defaultScript.src = chrome.extension.getURL('scripts/default.js');
document.documentElement.appendChild(defaultScript);

var hostname = location.hostname.replace(/^www\./, '');

var appendScript = function(path){
	var xhr = new XMLHttpRequest();
	xhr.onload = function(){
		eval(this.responseText);
	}
	xhr.open('GET', chrome.extension.getURL(path));
	xhr.send();
}

var defaultStyle = document.createElement('link');
defaultStyle.rel = 'stylesheet';
defaultStyle.href = chrome.extension.getURL('styles/default.css');
document.head.appendChild(defaultStyle);

var style = document.createElement('link');
style.rel = 'stylesheet';
style.href = chrome.extension.getURL('styles/' + hostname + '.css');
document.head.appendChild(style);

appendScript('scripts/default.js');
appendScript('scripts/' + hostname + '.js');

Revert back to evaluating scripts


import callbacks from 'ember-encore/mixins/adapter-callbacks';

export default DS.RESTAdapter.extend(callbacks, {
  defaultSerializer: '-encore',

  pathForType: function(type) {
    return Ember.String.pluralize(Ember.String.underscore(type));
  },

  ajaxError: function(jqXHR) {
    var error = this._super(jqXHR);
    var data = JSON.parse(jqXHR.responseText);

    if (jqXHR && jqXHR.status === 422) {
      var errors = data.errors.reduce(function(memo, errorGroup) {
        memo[errorGroup.field] = errorGroup.types[0];
        return memo;
      }, {});

      return new DS.InvalidError(errors);

    } else {
      return error;
    }
  }
});

import callbacks from 'ember-encore/mixins/adapter-callbacks';

export default DS.RESTAdapter.extend(callbacks, {
  defaultSerializer: '-encore',

  pathForType: function(type) {
    return Ember.String.pluralize(Ember.String.underscore(type));
  },

  ajaxError: function(jqXHR) {
    var error = this._super(jqXHR);
    var data = JSON.parse(jqXHR.responseText);

    if (jqXHR && jqXHR.status === 422) {
      var errors = data.errors.reduce(function(memo, errorGroup) {
        memo[errorGroup.field] = errorGroup.types;
        return memo;
      }, {});

      return new DS.InvalidError(errors);

    } else {
      return error;
    }
  }
});

Fix errors format to when creating a DS.InvalidError object


// import React and Redux dependencies
var React = require('react');
var connect = require('react-redux').connect;
var $ = require('jquery');

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

function mapStatetoProps (state, ownProps) {
  return {
    depVars: state.Experiments.getIn([ownProps.expId, 'depVars']).toJS()
  };
}

var DepVars = React.createClass({
  handleSubmit: function (event) {
    $.post('/submit', $(event.target).serializeArray());
  },

  render: function () {
    var depVars = this.props.depVars.map(function(depVarId) {
      return <DepVar key={depVarId} depVarId={depVarId} />;
    });
    return (
      <div>
        <form onSubmit={this.handleSubmit}>
          <input name="optionIndex" type="hidden" value={this.props.optionIndex} />
          <input name="expId" type="hidden" value={this.props.expId} />
          {depVars}
          <button type="submit">Submit Sample</button>
        </form>
      </div>
    );
  }
});

module.exports = connect(mapStatetoProps)(DepVars);

// import React and Redux dependencies
var React = require('react');
var connect = require('react-redux').connect;
var _ = require('underscore');
var $ = require('jquery');
require('jquery-serializejson');

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

function mapStatetoProps (state, ownProps) {
  return {
    depVars: state.Experiments.getIn([ownProps.params.expid, 'depVars']).toJS(),
    indVars: state.Samples.getIn([ownProps.params.sampleid, 'indVarStates']).toJS()
  };
}

var DepVars = React.createClass({
  handleSubmit: function (event) {
    event.preventDefault();

    var data = $(event.target).serializeJSON();
    $.post('/submit', data);
  },

  render: function () {
    var depVars = this.props.depVars.map(function(depVarId) {
      return <DepVar key={depVarId} depVarId={depVarId} />;
    });
    var indVars = [];
    _.each(this.props.indVars, function(indVar){
      indVars.push(<input
                name={'indVars[' + indVar._id + '][value]'}
                type="hidden"
                value={indVar.value} />);
      indVars.push(<input
                name={'indVars[' + indVar._id + '][name]'}
                type="hidden"
                value={indVar.name} />);
    });
    return (
      <div>
        <form onSubmit={this.handleSubmit}>
          <input name="expId" type="hidden" value={this.props.params.expid} />
          {indVars}
          {depVars}
          <button type="submit">Submit Sample</button>
        </form>
      </div>
    );
  }
});

module.exports = connect(mapStatetoProps)(DepVars);

Add support for multiple IndVars


'use strict';

let rpc = require('./rpc');
let util = require('./util');

let idKey = util.idKey;
let realmKey = util.realmKey;
let registeredConstructors = {};

exports.create = create;
exports.registerConstructors = registerConstructors;

function create(realmId, info) {
    let schema = info.schema;
    let constructor = (registeredConstructors[realmId] || {})[schema.name];
    let object = constructor ? Object.create(constructor.prototype) : {};
    let props = {};

    object[realmKey] = realmId;
    object[idKey] = info.id;

    for (let prop of schema.properties) {
        let name = prop.name;

        props[name] = {
            get: getterForProperty(name),
            set: setterForProperty(name),
        };
    }

    Object.defineProperties(object, props);

    return object;
}

function registerletructors(realmId, letructors) {
    registeredletructors[realmId] = letructors;
}

function getterForProperty(name) {
    return function() {
        return rpc.getObjectProperty(this[realmKey], this[idKey], name);
    };
}

function setterForProperty(name) {
    return function(value) {
        rpc.setObjectProperty(this[realmKey], this[idKey], name, value);
    };
}

'use strict';

let rpc = require('./rpc');
let util = require('./util');

let idKey = util.idKey;
let realmKey = util.realmKey;
let registeredConstructors = {};

exports.create = create;
exports.registerConstructors = registerConstructors;

function create(realmId, info) {
    let schema = info.schema;
    let constructor = (registeredConstructors[realmId] || {})[schema.name];
    let object = constructor ? Object.create(constructor.prototype) : {};
    let props = {};

    object[realmKey] = realmId;
    object[idKey] = info.id;

    for (let prop of schema.properties) {
        let name = prop.name;

        props[name] = {
            get: getterForProperty(name),
            set: setterForProperty(name),
        };
    }

    Object.defineProperties(object, props);

    return object;
}

function registerConstructors(realmId, constructors) {
    registeredConstructors[realmId] = constructors;
}

function getterForProperty(name) {
    return function() {
        return rpc.getObjectProperty(this[realmKey], this[idKey], name);
    };
}

function setterForProperty(name) {
    return function(value) {
        rpc.setObjectProperty(this[realmKey], this[idKey], name, value);
    };
}

Fix "const" search and replace


import update from 'immutability-helper'

import ActionTypes from '../actionTypes'

const initialState = {
  currentPageIndex: 0,
  isLoading: false,
  listItems: [],
  totalPageCount: 0,
}

export default (state = initialState, action) => {
  switch (action.type) {
  case ActionTypes.INFINITE_LIST_INITIALIZE:
    return update(state, {
      currentPageIndex: { $set: 0 },
      isLoading: { $set: true },
      listItems: { $set: [] },
      totalPageCount: { $set: 0 },
    })
  case ActionTypes.INFINITE_LIST_REQUEST:
    return update(state, {
      currentPageIndex: { $set: action.meta.page },
      isLoading: { $set: true },
    })
  case ActionTypes.INFINITE_LIST_SUCCESS:
    return update(state, {
      isLoading: { $set: false },
      listItems: { $push: action.payload.data },
      totalPageCount: {
        $set: Math.floor(action.payload.total / action.payload.limit),
      },
    })
  case ActionTypes.INFINITE_LIST_FAILURE:
    return update(state, {
      isLoading: { $set: false },
    })
  default:
    return state
  }
}

import update from 'immutability-helper'

import ActionTypes from '../actionTypes'

const initialState = {
  currentPageIndex: 0,
  isLoading: false,
  listItems: [],
  totalPageCount: 0,
}

export default (state = initialState, action) => {
  switch (action.type) {
  case ActionTypes.INFINITE_LIST_INITIALIZE:
    return update(state, {
      currentPageIndex: { $set: 0 },
      isLoading: { $set: true },
      listItems: { $set: [] },
      totalPageCount: { $set: 0 },
    })
  case ActionTypes.INFINITE_LIST_REQUEST:
    return update(state, {
      currentPageIndex: { $set: action.meta.page },
      isLoading: { $set: true },
    })
  case ActionTypes.INFINITE_LIST_SUCCESS:
    return update(state, {
      isLoading: { $set: false },
      listItems: { $push: action.payload.data },
      totalPageCount: {
        $set: Math.ceil(action.payload.total / action.payload.limit),
      },
    })
  case ActionTypes.INFINITE_LIST_FAILURE:
    return update(state, {
      isLoading: { $set: false },
    })
  default:
    return state
  }
}

Fix wrong counting of total pages for infinite lists


import openDevToolsWindow from './openWindow';

const menus = [
  { id: 'devtools-left', title: 'To left' },
  { id: 'devtools-right', title: 'To right' },
  { id: 'devtools-bottom', title: 'To bottom' },
  { id: 'devtools-panel', title: 'In panel' }
];
let pageUrl;
let pageTab;

let shortcuts = {};
chrome.commands.getAll(commands => {
  commands.forEach(({ name, shortcut }) => {
    shortcuts[name] = shortcut;
  });
});

export default function createMenu(forUrl, tabId) {
  if (typeof tabId !== 'number' || tabId === pageTab) return;

  let url = forUrl;
  let hash = forUrl.indexOf('#');
  if (hash !== -1) url = forUrl.substr(0, hash);
  if (pageUrl === url) return;
  pageUrl = url; pageTab = tabId;
  chrome.contextMenus.removeAll();

  menus.forEach(({ id, title }) => {
    chrome.contextMenus.create({
      id: id,
      title: title + ' (' + shortcuts[id] + ')',
      contexts: ['all'],
      documentUrlPatterns: [url],
      onclick: () => { openDevToolsWindow(id); }
    });
  });

  chrome.pageAction.show(tabId);
}

import openDevToolsWindow from './openWindow';

const menus = [
  { id: 'devtools-left', title: 'To left' },
  { id: 'devtools-right', title: 'To right' },
  { id: 'devtools-bottom', title: 'To bottom' },
  { id: 'devtools-panel', title: 'In panel' }
];
let pageUrl;
let pageTab;

let shortcuts = {};
chrome.commands.getAll(commands => {
  commands.forEach(({ name, shortcut }) => {
    shortcuts[name] = shortcut;
  });
});

export default function createMenu(forUrl, tabId) {
  if (typeof tabId !== 'number') return; // It is an extension's background page
  chrome.pageAction.show(tabId);
  if (tabId === pageTab) return;

  let url = forUrl;
  let hash = forUrl.indexOf('#');
  if (hash !== -1) url = forUrl.substr(0, hash);
  if (pageUrl === url) return;
  pageUrl = url; pageTab = tabId;
  chrome.contextMenus.removeAll();

  menus.forEach(({ id, title }) => {
    chrome.contextMenus.create({
      id: id,
      title: title + ' (' + shortcuts[id] + ')',
      contexts: ['all'],
      documentUrlPatterns: [url],
      onclick: () => { openDevToolsWindow(id); }
    });
  });
}

Fix missing page action (popup icon) on page reload


import React, { PropTypes } from 'react';
import Immutable from 'immutable';
import { Button } from 'react-bootstrap';
import styles from '../styles.module.css';

const EditProfile = ({onSubmit}) => {
  return(

EditProfileComponent!

      <Button onClick={onSubmit}>Submit</Button>

  );
};

EditProfile.propTypes = {
  onSubmit: PropTypes.func.isRequired
};

export default EditProfile;

import React, { PropTypes } from 'react';
import Immutable from 'immutable';
import { Button } from 'react-bootstrap';
import DocumentTitle from 'react-document-title';
import Page from '../../../components/page/Page';
import styles from '../styles.module.css';

const EditProfile = ({ userDetails, onSubmit }) => {
  return (
    <DocumentTitle title="Edit Profile">
      <Page>
        <Page.Header>
          <Page.Title>Edit Profile</Page.Title>
        </Page.Header>

EditProfileComponent!

        <Button onClick={onSubmit}>Submit</Button>
      </Page>
    </DocumentTitle>
  );
};

EditProfile.propTypes = {
  onSubmit: PropTypes.func.isRequired
};

export default EditProfile;

Add document title and page header


/*globals PushStateTree, it, expect */
describe('ObjectEvent should', function() {
  'use strict';

  it('be available on global scope', function() {
    expect(PushStateTree).toBeDefined();
  });

  it('throw an error if not using "new" operator', function() {
    expect(PushStateTree).toThrow();
  });

});

/*globals PushStateTree, it, expect */
describe('PushStateTree should', function() {
  'use strict';

  it('be available on global scope', function() {
    expect(PushStateTree).toBeDefined();
  });

  it('throw an error if not using "new" operator', function() {
    expect(PushStateTree).toThrow();
  });

  it('construct and became a HTMLElement instance', function(){
    expect(new PushStateTree()).toEqual(jasmine.any(HTMLElement));
    expect(new PushStateTree({})).toEqual(jasmine.any(HTMLElement));
  });
});

Test for element instance creating


"use strict";
let fs = require("fs");
let path = require("path");

let Q = require("q");
let express = require("express");
let bodyParser = require("body-parser");
let restify = require("restify");

function Server() {
    Q.longStackSupport = true;
    this.__setup();
};

Server.prototype.__setup = function () {
    this.app = restify.createServer({ name: "BeaconSpam" });
    this.app.use(bodyParser.json());
    this.__setupRouting();
};

Server.prototype.listen = function() {
    console.log("Server started!");
    this.app.listen(8080);
};

Server.prototype.__setupRouting = function () {
    // Static files are added last as they match every request
    this.app.get(".*", restify.serveStatic({
        directory: "client/",
        default: "index.html"
    }));
};

module.exports = Server;

"use strict";
let fs = require("fs");
let path = require("path");

let Q = require("q");
let express = require("express");
let bodyParser = require("body-parser");
let restify = require("restify");

function Server() {
    Q.longStackSupport = true;
    this.__setup();
};

Server.prototype.__setup = function () {
    this.app = restify.createServer({ name: "BeaconSpam" });
    this.app.use(bodyParser.json());
    this.__setupRouting();
};

Server.prototype.listen = function() {
    console.log("Server started!");
    this.app.listen(8080);
};

let handleBeaconInfo = function (req, res) {
    /**
     * {
     *  id: ....,
     *  name: ...,
     *  txPower: ....,
     *  samples: [{rssi: ...., timestamp: .....}]
     * }
     */
    var beaconData = {
        id: req.body.id,
        name: req.body.name,
        txPower: req.body.txPower,
        samples: req.body.samples
    }
    res.send();
}

Server.prototype.__setupRouting = function () {
    this.app.post("/beacon/data", handleBeaconInfo);

    // Static files are added last as they match every request
    this.app.get(".*", restify.serveStatic({
        directory: "client/",
        default: "index.html"
    }));
};

module.exports = Server;

Add rest endpoint for beacon data


if ('angular' in window) {
  angular.module('ng').run(['$rootScope', '$window', function ($rootScope, window) {

  	$rootScope.$watch(function() {
		  return $window.location.pathname + $window.location.search;
  	}, function (newUrl, oldUrl) {
      if (newUrl === oldUrl) {
        return;
      }

      var evt = document.createEvent('CustomEvent');
      evt.initCustomEvent('spriteLoaderLocationUpdated', false, false, {
        newUrl: newUrl
      });
      window.dispatchEvent(evt);
    });
  }]);
}

if ('angular' in window) {
  angular.module('ng').run(['$rootScope', '$window', function ($rootScope, $window) {

  	$rootScope.$watch(function() {
		  return $window.location.pathname + $window.location.search;
  	}, function (newUrl, oldUrl) {
      if (newUrl === oldUrl) {
        return;
      }

      var evt = document.createEvent('CustomEvent');
      evt.initCustomEvent('spriteLoaderLocationUpdated', false, false, {
        newUrl: newUrl
      });
      window.dispatchEvent(evt);
    });
  }]);
}

Watch location update instead of listening events since there is no event when $locatoin update is failed: window -> $window typo


import NotificationProvider from "./NotificationProvider";
import LibNotify from "node-notifier/notifiers/notifysend";
import which from "utils/node/fs/which";
import UTIL from "util";

function NotificationProviderLibNotify() {
	this.provider = new LibNotify();
}

UTIL.inherits( NotificationProviderLibNotify, NotificationProvider );

NotificationProviderLibNotify.platforms = {
	linux: "growl"
};

/**
 * @returns {Promise}
 */
NotificationProviderLibNotify.test = function() {
	return which( "notify-send" );
};

export default NotificationProviderLibNotify;

import NotificationProvider from "./NotificationProvider";
import LibNotify from "node-notifier/notifiers/notifysend";
import which from "utils/node/fs/which";
import UTIL from "util";

function NotificationProviderLibNotify() {
	this.provider = new LibNotify({
		// don't run `which notify-send` twice
		suppressOsdCheck: true
	});
}

UTIL.inherits( NotificationProviderLibNotify, NotificationProvider );

NotificationProviderLibNotify.platforms = {
	linux: "growl"
};

/**
 * @returns {Promise}
 */
NotificationProviderLibNotify.test = function() {
	return which( "notify-send" );
};

export default NotificationProviderLibNotify;

Disable node-notifier's which call of notify-send


module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    pkg: '<json:package.json>',
    test: {
      files: ['test/**/*.js']
    },
    lint: {
      files: ['grunt.js', 'lib/**/*.js', 'test/**/*.js']
    },
    watch: {
      files: '<config:lint.files>',
      tasks: 'default'
    },
    jshint: {
      options: {
        curly: true,
        eqeqeq: true,
        immed: true,
        latedef: true,
        newcap: true,
        noarg: true,
        sub: true,
        undef: true,
        boss: true,
        eqnull: true,
        node: true
      },
      globals: {
        exports: true
      }
    }
  });

  // Default task.
  grunt.registerTask('default', 'lint test');

};

'use strict';
module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    pkg: '<json:package.json>',
    test: {
      files: ['test/**/*.js']
    },
    lint: {
      files: ['lib/**/*.js', 'test/**/*.coffee']
    },
    watch: {
      files: ['<config:coffee.app.src>', 'src/**/*.jade'],
      tasks: 'coffee cp'
    },
    coffee: {
      app: {
        src: ['src/**/*.coffee'],
        dest: './lib',
        options: {
          preserve_dirs: true,
          base_path: 'src'
        }
      }
    },
    coffeelint: {
      all: {
        src: ['src/**/*.coffee', 'test/**/*.coffee'],
      }
    },
    clean:{
      folder: 'lib'
    },
    jshint: {
      options: {
        curly: true,
        eqeqeq: true,
        immed: true,
        latedef: true,
        newcap: true,
        noarg: true,
        sub: true,
        undef: true,
        boss: true,
        eqnull: true,
        node: true
      },
      globals: {
        exports: true
      }
    }
  });

  // Default task.
  grunt.registerTask('default', 'coffeelint coffee');
  grunt.loadNpmTasks('grunt-coffee');
  grunt.loadNpmTasks('grunt-coffeelint');
  grunt.loadNpmTasks('grunt-cp');
  grunt.loadNpmTasks('grunt-clean');
};

Set up for compilation from src to lib


connect = require('connect');
serveServer = require('serve-static');
jade = require("./lib/processor/jade.js");

function miniharp(root) {
  //console.log(root);
  var app = connect()
    .use(serveServer(root))
    .use(jade(root));
  return app;
};

module.exports = miniharp;

connect = require('connect');
serveServer = require('serve-static');
jade = require("./lib/processor/jade.js");
less = require("./lib/processor/less.js");

function miniharp(root) {
  //console.log(root);
  var app = connect()
    .use(serveServer(root))
    .use(jade(root))
    .use(less(root));
  return app;
};

module.exports = miniharp;

Add less preprocessor to the mini-harp app


'use strict';

var got = require('got');
var cheerio = require('cheerio');
var md = require('html-md');

module.exports = function(id, callback) {
  var url = 'http://www.stm.dk/_p_' + id + '.html';

  got(url, function(err, data){
    var $ = cheerio.load(data);

    var meta = $('meta[name="created"]');
    var speech = $('.maininner.maininner-page');

    speech.find('h1').remove();
    speech.find('.nedtonet').remove();

    var data = {
      source: url,
      date: meta.attr('content'),
      html: speech.html(),
      markdown: md(speech.html(), {inline: true}).replace(/\\/gi, '')
    };

    var linkElement = speech.find('a[onclick]');
    if(linkElement.length){
      var link = linkElement.attr('onclick').split('\'')[1];
      linkElement.attr('href', link);

      data.video = link;

      speech.children().last().remove();
      speech.children().last().remove();
      speech.children().last().remove();
    }

    callback(null, data)
  });
};

'use strict';

var got = require('got');
var cheerio = require('cheerio');
var md = require('html-md');

module.exports = function(id, callback) {
  var url = 'http://www.stm.dk/_p_' + id + '.html';

  got(url, function(err, data){
    var $ = cheerio.load(data);

    var meta = $('meta[name="created"]');
    var speech = $('.maininner.maininner-page');

    speech.find('h1').remove();
    speech.find('.nedtonet').remove();

    var data = {
      source: url,
      date: meta.attr('content'),
      html: speech.html(),
      markdown: md(speech.html(), {inline: true}).replace(/\\/gi, '')
    };
    var image = speech.find('.a110051').find('img').attr('src');
    if(image){
      speech.find('.a110051').remove();
      image = 'http://www.stm.dk/' + image;
    }

    var linkElement = speech.find('a[onclick]');
    if(linkElement.length){
      var link = linkElement.attr('onclick').split('\'')[1];
      linkElement.attr('href', link);

      data.video = link;

      speech.children().last().remove();
      speech.children().last().remove();
      speech.children().last().remove();
    }

    callback(null, data)
  });
};

Add image property to callback data


"use strict";

// Generates a string from "color-1" to "color-32" based on an input string
module.exports = function(str) {
	let hash = 0;

	for (let i = 0; i < str.length; i++) {
		hash += str.charCodeAt(i);
	}

	return "color-" + (1 + hash % 32);
};

"use strict";

// Generates a string from "color-1" to "color-32" based on an input string
module.exports = function(str) {
	let hash = 0;

	for (let i = 0; i < str.length; i++) {
		hash += str.charCodeAt(i);
	}

	/*
		Modulo 32 lets us be case insensitive for ascii
		due to A being ascii 65 (100 0001)
		 while a being ascii 97 (110 0001)
	*/
	return "color-" + (1 + hash % 32);
};

Add reminder that ascii is awesome.


// http://stackoverflow.com/questions/11935175/sampling-a-random-subset-from-an-array
var featureCollection = require('turf-featurecollection');

/**
 * Takes a {@link FeatureCollection} and returns a FeatureCollection with given number of {@link Feature|features} at random.
 *
 * @module turf/sample
 * @category data
 * @param {FeatureCollection} features a FeatureCollection of any type
 * @param {number} n number of features to select
 * @return {FeatureCollection} a FeatureCollection with `n` features
 * @example
 * var points = turf.random('points', 1000);
 *
 * //=points
 *
 * var sample = turf.sample(points, 10);
 *
 * //=sample
 */
module.exports = function(fc, num){
  var outFC = featureCollection(getRandomSubarray(fc.features, num));
  return outFC;
};

function getRandomSubarray(arr, size) {
  var shuffled = arr.slice(0), i = arr.length, min = i - size, temp, index;
  while (i-- > min) {
      index = Math.floor((i + 1) * Math.random());
      temp = shuffled[index];
      shuffled[index] = shuffled[i];
      shuffled[i] = temp;
  }
  return shuffled.slice(min);
}

// http://stackoverflow.com/questions/11935175/sampling-a-random-subset-from-an-array
var featureCollection = require('turf-featurecollection');

/**
 * Takes a {@link FeatureCollection} and returns a FeatureCollection with given number of {@link Feature|features} at random.
 *
 * @module turf/sample
 * @category data
 * @param {FeatureCollection<(Point|LineString|Polygon)>} features a FeatureCollection of any type
 * @param {Number} n number of features to select
 * @return {FeatureCollection<(Point|LineString|Polygon)>} a FeatureCollection with `n` features
 * @example
 * var points = turf.random('points', 1000);
 *
 * //=points
 *
 * var sample = turf.sample(points, 10);
 *
 * //=sample
 */
module.exports = function(fc, num){
  var outFC = featureCollection(getRandomSubarray(fc.features, num));
  return outFC;
};

function getRandomSubarray(arr, size) {
  var shuffled = arr.slice(0), i = arr.length, min = i - size, temp, index;
  while (i-- > min) {
      index = Math.floor((i + 1) * Math.random());
      temp = shuffled[index];
      shuffled[index] = shuffled[i];
      shuffled[i] = temp;
  }
  return shuffled.slice(min);
}

Switch doc to closure compiler templating


require('./env');
var http = require('http');
var koa = require('koa');
var app = koa();

app.use(require('koa-bodyparser')());
app.use(function *(next) {
  if (typeof this.request.body === 'undefined' || this.request.body === null) {
    this.request.body = {};
  }
  yield next;
});

app.use(require('koa-views')('views', { default: 'jade' }));
app.use(function *(next) {
  this.api = API;
  var token = this.cookies.get('session-token');
  if (token) {
    this.api.$header('x-session-token', token);
  }
  yield next;
});

app.use(function *(next) {
  try {
    yield next;
  } catch (err) {
    if (err.body) {
      if (err.body.status === 401) {
        this.redirect('/account/signin');
      } else {
        this.body = err.body;
      }
    }
    console.error(err.stack);
  }
});

require('koa-mount-directory')(app, require('path').join(__dirname, 'routes'));

app.use(require('koa-mount')('/api', require('./api')));

if (require.main === module) {
  app.listen($config.port);
} else {
  module.exports = app;
}

require('./env');
var mount = require('koa-mount');
var koa = require('koa');
var app = koa();

app.use(require('koa-bodyparser')());
app.use(function *(next) {
  if (typeof this.request.body === 'undefined' || this.request.body === null) {
    this.request.body = {};
  }
  yield next;
});

app.use(mount('/build', require('koa-static')('build', { defer: true })));

app.use(require('koa-views')('views', { default: 'jade' }));
app.use(function *(next) {
  this.api = API;
  var token = this.cookies.get('session-token');
  if (token) {
    this.api.$header('x-session-token', token);
  }
  yield next;
});

app.use(function *(next) {
  try {
    yield next;
  } catch (err) {
    if (err.body) {
      if (err.body.status === 401) {
        this.redirect('/account/signin');
      } else {
        this.body = err.body;
      }
    }
    console.error(err.stack || err);
  }
});

require('koa-mount-directory')(app, require('path').join(__dirname, 'routes'));

app.use(require('koa-mount')('/api', require('./api')));

if (require.main === module) {
  app.listen($config.port);
} else {
  module.exports = app;
}

Use koa-static to serve static files


import React, {Component} from 'react'
import './Reaction.sass'

class Reaction extends Component {
  constructor() {
    super();
    this.state = { React: false };
  }

  componentDidMount() {
    var thiz = this;

    setTimeout(function () {
      thiz.setState({ React: true });
    }, Math.random()*5000);
  }

  render() {
    let content =
 'READY?!'
;

    if (this.state.React) {
      content = ;
    }
    return (

{content}

    );
  }
};

export default Reaction

import React, {Component} from 'react'
import './Reaction.sass'

class Reaction extends Component {
  constructor() {
    super();
    this.state = { React: false };
  }

  componentDidMount() {
    var thiz = this;

    setTimeout(function () {
      thiz.setState({ React: true });
    }, Math.random()*5000);
  }

getReactionTime(event) {
  let reactionTime = Date.now();
  console.log(reactionTime);
  return reactionTime;
}

getCreate(event) {
  let create = Date.now();
  console.log(create);
  return create;

}

reactionTimePlayer() {
  let timePlayer = getReactionTime() - getCreate();
  console.log(timePlayer);
  return timePlayer;
}

  render() {
    let content =
 'READY?!'
;

    if (this.state.React) {
      content = ;
    }
    return (

          {content}

          Time: {this.reactionTimePlayer.bind(this)}

      );

  }
};

export default Reaction

Add OnClick and Onload for reaction and create times


'use strict';

require('./patch/functionName');

(function (window, document, undefined) {
var oldMappy = window.Mappy
var Mappy = require('./lib/main')

//Node style module export
if (typeof module === 'object' && typeof module.exports === 'object') {
  module.exports = Mappy;
}

/**
 * Resets the window.Mappy variable to its original state and returns
 * the Mappy object
 */
Mappy.noConflict = function () {
  window.Mappy = oldMappy;
  return this;
};

window.Mappy = Mappy;

})(window, document)

'use strict';

(function (window, document, undefined) {
var oldMappy = window.Mappy
var Mappy = require('./lib/main')

//Node style module export
if (typeof module === 'object' && typeof module.exports === 'object') {
  module.exports = Mappy;
}

/**
 * Resets the window.Mappy variable to its original state and returns
 * the Mappy object
 */
Mappy.noConflict = function () {
  window.Mappy = oldMappy;
  return this;
};

window.Mappy = Mappy;

})(window, document)

Remove function constructor name patch


#!/usr/bin/env node

var program = require('commander');
var userArgs = process.argv.splice(2);
var message = userArgs.join(' ');

if (message.length > 140) {
  console.log('Message was too long. Can only be 140 characters. It was: ', message.length);
  process.exit(1);
}

console.log(message);

#!/usr/bin/env node

var program = require('commander');
var userArgs = process.argv.splice(2);
var message = userArgs.join(' ');
var confluenceUrl = process.env.CONFLUENCE_URL

if (message.length > 140) {
  console.log('Message was too long. Can only be 140 characters. It was: ', message.length);
  process.exit(1);
}

if (!confluenceUrl) {
  console.log('Please set the environment variable CONFLUENCE_URL.')
  process.exit(2);
}

console.log(message);

Read confluence url from env


/*
 * gaze
 * https://github.com/shama/gaze
 *
 * Copyright (c) 2014 Kyle Robinson Young
 * Licensed under the MIT license.
 */

try {
  // Attempt to resolve optional pathwatcher
  require('bindings')('pathwatcher.node');
  var version = process.versions.node.split('.');
  module.exports = (version[0] === '0' && version[1] === '8')
    ? require('./lib/gaze04.js')
    : require('./lib/gaze.js');
} catch (err)  {
  // Otherwise serve gaze04
  module.exports = require('./lib/gaze04.js');
}

/*
 * gaze
 * https://github.com/shama/gaze
 *
 * Copyright (c) 2014 Kyle Robinson Young
 * Licensed under the MIT license.
 */

// If on node v0.8, serve gaze04
var version = process.versions.node.split('.');
if (version[0] === '0' && version[1] === '8') {
  module.exports = require('./lib/gaze04.js');
} else {
  try {
    // Check whether pathwatcher successfully built without running it
    require('bindings')({ bindings: 'pathwatcher.node', path: true });
    // If successfully built, give the better version
    module.exports = require('./lib/gaze.js');
  } catch (err) {
    // Otherwise serve gaze04
    module.exports = require('./lib/gaze04.js');
  }
}

Determine whether pathwatcher built without running it. Ref GH-98.


var httpProxy = require('http-proxy');
var http = require('http');

var CORS_HEADERS = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Methods': 'HEAD, POST, GET, PUT, PATCH, DELETE',
  'Access-Control-Max-Age': '86400',
  'Access-Control-Allow-Headers': "X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept"
};

// Create a proxy server pointing at spreadsheets.google.com.
//var proxy = httpProxy.createProxyServer({target: 'https://spreadsheets.google.com'});
var proxy = httpProxy.createProxyServer();

http.createServer(function(req, res) {
  if (req.method === 'OPTIONS') {
    res.writeHead(200, CORS_HEADERS);
    res.end();
    return
  }
  delete req.headers.host;
  proxy.web(req, res, {
    target: 'https://spreadsheets.google.com:443',
    xfwd: false
  });
}).listen(process.env.PORT || 5000);

var httpProxy = require('http-proxy');
var http = require('http');

var CORS_HEADERS = {
  'access-control-allow-origin': '*',
  'access-control-allow-methods': 'HEAD, POST, GET, PUT, PATCH, DELETE',
  'access-control-max-age': '86400',
  'access-control-allow-headers': "X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept, Authorization"
};

// Create a proxy server pointing at spreadsheets.google.com.
//var proxy = httpProxy.createProxyServer({target: 'https://spreadsheets.google.com'});
var proxy = httpProxy.createProxyServer();

https.createServer(function(req, res) {
  if (req.method === 'OPTIONS') {
    // Respond to OPTIONS requests advertising we support full CORS for *
    res.writeHead(200, CORS_HEADERS);
    res.end();
    return
  }
  // Remove our original host so it doesn't mess up Google's header parsing.
  delete req.headers.host;
  proxy.web(req, res, {
    target: 'https://spreadsheets.google.com:443',
    xfwd: false
  });
  // Add CORS headers to every other request also.
  proxy.on('proxyRes', function(proxyRes, req, res) {
    for (var key in CORS_HEADERS) {
      proxyRes.headers[key] = CORS_HEADERS[key];
    }
  });
}).listen(process.env.PORT || 5000);

Fix CORS handling for POST and DELETE


'use strict'

var path = require('path')

module.exports = function (file) {
  var segments = file.split(path.sep)
  var index = segments.lastIndexOf('node_modules')
  if (index === -1) return
  if (!segments[index + 1]) return
  var scoped = segments[index + 1].indexOf('@') === 0
  var name = scoped ? segments[index + 1] + '/' + segments[index + 2] : segments[index + 1]
  if (!name) return
  var offset = scoped ? 3 : 2
  return {
    name: name,
    basedir: segments.slice(0, index + offset).join(path.sep),
    path: segments.slice(index + offset).join(path.sep)
  }
}

'use strict'

var path = require('path')

module.exports = function (file) {
  var segments = file.split(path.sep)
  var index = segments.lastIndexOf('node_modules')
  if (index === -1) return
  if (!segments[index + 1]) return
  var scoped = segments[index + 1][0] === '@'
  var name = scoped ? segments[index + 1] + '/' + segments[index + 2] : segments[index + 1]
  if (!name) return
  var offset = scoped ? 3 : 2
  return {
    name: name,
    basedir: segments.slice(0, index + offset).join(path.sep),
    path: segments.slice(index + offset).join(path.sep)
  }
}

Improve speed of detection of scoped packages


'use strict';

const YQL = require('yql');
const _ = require('lodash');

module.exports = (opts, callback) => {
	opts = opts || [];

	let query;

	if (_.isEmpty(opts)) {
		query = new YQL('select * from weather.forecast where woeid in (select woeid from geo.places(1) where text="Dhaka, Bangladesh")');
	} else {
		query = new YQL('select * from weather.forecast where woeid in (select woeid from geo.places(1) where text="' + opts[0] + ', ' + opts[1] + '")');
	}

	query.exec((err, response) => {
		if (err) {
			return callback(err);
		}

		callback(null, response);
	});
};

'use strict';

const axios = require('axios');
const API_URL = 'https://micro-weather.now.sh';

module.exports = (opts) => {
  let city = opts[0] || 'Dhaka';
  let country = opts[1] || 'Bangladesh';

  return axios.get(`${API_URL}?city=${city}&country=${country}`);
};

Refactor main weather module to use API