/**
 * QClient.js
 * ==========
 * Main module exported from this library.
 */

/**
 * QClient
 * -------
 * The main function exposed to your code.
 * QClient will take your client ID and token
 * and set up subsequent API calls.
 *
 * `clientId` [String] - The Client ID you are given when signing up for Q
 * `secretToken` [String] - The secret token you are given when signin up for Q
 */
function QClient(clientId, secretToken) {
  // body...
}

/**
 * QClient.js
 * ==========
 * Main module exported from this library.
 */

// Require our dependencies
var http    = require('http'),
    crypto  = require('crypto');

/**
 * QClient
 * -------
 * The main function exposed to your code.
 * QClient will take your client ID and token
 * and set up subsequent API calls.
 *
 * `clientId` [String] - The Client ID you are given when signing up for Q
 * `secretToken` [String] - The secret token you are given when signin up for Q
 */
function QClient(clientId, secretToken) {
  if (!(this instanceof QClient)) return new QClient(clientId, secretToken);

  this.setApiKey(clientId);
  this.setToken(secretToken);
}

QClient.prototype = {
  setApiKey: function(key) {
    if (key) this.clientId = key;
  },

  setToken: function(secret) {
    if (secret) this.secretToken = secret;
  },

  /**
   * Signature
   * ---------
   * Build a signature to send in the
   * request header.
   */
  signature: function(endpoint, reqBody) {
    var hmac = crypto.createHmac('sha1', this.secretToken).setEncoding('hex');
    hmac.write(endpoint + reqBody);
    hmac.end();
    return hmac.read();
  },

  getSubsidy: function(state, zip, demographics) {
    var endpoint      = ['/plans', state, zip].join('/'),
        demographics  = JSON.stringify(demographics);

    var request = http.request({
      host: 'localhost',
      path: endpoint,
      port: 9292,
      method: 'POST',
      headers: { 'Authorization': [this.clientId, this.signature(endpoint, demographics)].join(':') }
    }, function(response) {
      var reply = '';
      response.on('data', function(chunk) {
        reply += chunk;
      });

      response.on('end', function() {
        console.log(reply);

        return reply;
      });
    });

    request.write(demographics);

    console.log(endpoint, [this.clientId, this.signature(endpoint, demographics)].join(':'));

    return request.end();
  }
};

module.exports = QClient;

Add QClient function and object prototype.


Ractive.decorators.autoNumeric = function (node, relativeKeypath, boundValue) {
    var self = this,
        $el = $(node),
        context = self.getContext(node),
        updateModel = function () {
            context.set(relativeKeypath, $el.autoNumeric('getNumber'));
        };

    if (typeof relativeKeypath !== 'string') {
        throw new Error('The binding name is required.');
    }

    if (arguments.length !== 3) {
        throw new Error('Invalid number of arguments specified. Correct usage: as-autoNumeric="\'relativeKeypath\', relativeKeypath"');
    }

    if (context.isBound()) {
        throw new Error('You cannot use two-way binding for the "autoNumeric" decorator.');
    }

    $el.autoNumeric('init')
       .autoNumeric('set', boundValue)
       .on('input', updateModel);

    return {
        update: function (newRelativeKeypath, newValue) {
            if (newRelativeKeypath !== relativeKeypath) {
                relativeKeypath = newRelativeKeypath;
            }

            if ($el.autoNumeric('getNumber') !== newValue) {
                $el.autoNumeric('set', newValue);
            }
        },

        teardown: function () {
            $el.off('input', updateModel)
               .autoNumeric('destroy');
        }
    };
};

Ractive.decorators.autoNumeric = function (node, relativeKeypath, boundValue, autoNumericCfg) {
    var self = this,
        $el = $(node),
        context = self.getContext(node),
        updateModel = function () {
            context.set(relativeKeypath, $el.autoNumeric('getNumber'));
        };

    if (typeof relativeKeypath !== 'string') {
        throw new Error('The binding name is required.');
    }

    if (arguments.length !== 3) {
        throw new Error('Invalid number of arguments specified. Correct usage: as-autoNumeric="\'relativeKeypath\', relativeKeypath"');
    }

    if (context.isBound()) {
        throw new Error('You cannot use two-way binding for the "autoNumeric" decorator.');
    }

    $el.autoNumeric('init', autoNumericCfg)
       .autoNumeric('set', boundValue)
       .on('input', updateModel);

    return {
        update: function (newRelativeKeypath, newValue) {
            if (newRelativeKeypath !== relativeKeypath) {
                relativeKeypath = newRelativeKeypath;
            }

            if ($el.autoNumeric('getNumber') !== newValue) {
                $el.autoNumeric('set', newValue);
            }
        },

        teardown: function () {
            $el.off('input', updateModel)
               .autoNumeric('destroy');
        }
    };
};

Add ability to pass autoNumeric config object to the decorator function


#!/usr/bin/env node

// Native
const path = require('path')

// Packages
const asyncToGen = require('async-to-gen/register')
const updateNotifier = require('update-notifier')
const {red} = require('chalk')
const nodeVersion = require('node-version')
const isAsyncSupported = require('is-async-supported')

// Ours
const pkg = require('../package')

// Support for keywords "async" and "await"
if (!isAsyncSupported()) {
  const modulesDir = path.join(__dirname, '..', 'node_modules')

  asyncToGen({
    excludes: new RegExp(`.*${modulesDir}.*`),
    sourceMaps: false
  })
}

// Throw an error if node version is too low
if (nodeVersion.major < 6) {
  console.error(`${red('Error!')} Serve requires at least version 6 of Node. Please upgrade!`)
  process.exit(1)
}

// Let user know if there's an update
// This isn't important when deployed to Now
if (!process.env.NOW && pkg.dist) {
  updateNotifier({pkg}).notify()
}

// Load package core with async/await support
require('../lib')

#!/usr/bin/env node

// Native
const path = require('path')

// Packages
const asyncToGen = require('async-to-gen/register')
const updateNotifier = require('update-notifier')
const {red} = require('chalk')
const nodeVersion = require('node-version')
const isAsyncSupported = require('is-async-supported')

// Ours
const pkg = require('../package')

// Support for keywords "async" and "await"
if (!isAsyncSupported()) {
  const modulesDir = path.join(__dirname, '..', 'node_modules')

  asyncToGen({
    exclude: `**/${modulesDir}/**`,
    sourceMaps: false
  })
}

// Throw an error if node version is too low
if (nodeVersion.major < 6) {
  console.error(`${red('Error!')} Serve requires at least version 6 of Node. Please upgrade!`)
  process.exit(1)
}

// Let user know if there's an update
// This isn't important when deployed to Now
if (!process.env.NOW && pkg.dist) {
  updateNotifier({pkg}).notify()
}

// Load package core with async/await support
require('../lib')

Use glob pattern for excluding files

Use glob pattern for excluding files


({
  name: 'prop-played',
  description: 'Property "played"',
  spec: 'http://dev.w3.org/html5/spec/Overview.html#dom-mediacontroller-played',
  assert: function(finish) {
    var audio = this.audio = new Audio();

    audio.addEventListener('timeupdate', function() {
      if (audio.currentTime > 0) {
        try {
          finish(audio.played.length);
        } catch (e) {
          finish(false);
        }
      }
    }, false);

    audio.addEventListener('loadedmetadata', function() {
      audio.volume = 0;
      audio.play();
    }, false);

    audio.setAttribute('src', AWPY.sound.mini.stream_url());
  }
})

({
  name: 'prop-played',
  description: 'Property "played"',
  spec: 'http://dev.w3.org/html5/spec/the-iframe-element.html#dom-media-played',
  reports: {
    firefox: {
      desc: 'Patch should get landed soon.',
      link: 'https://bugzilla.mozilla.org/show_bug.cgi?id=462959'
    }
  },
  assert: function(finish) {
    var audio = this.audio = new Audio();

    audio.addEventListener('timeupdate', function() {
      if (audio.currentTime > 0) {
        try {
          finish(audio.played.length);
        } catch (e) {
          finish(false);
        }
      }
    }, false);

    audio.addEventListener('loadedmetadata', function() {
      audio.volume = 0;
      audio.play();
    }, false);

    audio.setAttribute('src', AWPY.sound.mini.stream_url());
  }
})

Test played changes including firefox report


'use strict';

var setupTestHooks     = require('ember-cli-blueprint-test-helpers/lib/helpers/setup');
var BlueprintHelpers   = require('ember-cli-blueprint-test-helpers/lib/helpers/blueprint-helper');
var generateAndDestroy = BlueprintHelpers.generateAndDestroy;

describe('Acceptance: ember generate and destroy initializer-addon', function() {
  setupTestHooks(this);

  it('initializer-addon foo', function() {
    // pass any additional command line options in the arguments array
    return generateAndDestroy(['initializer-addon', 'foo'], {
      // define files to assert, and their contents
      target: 'addon',
      files: [
        {
          file: 'app/initializers/foo.js',
          contains: "export { default, initialize } from 'my-addon/initializers/foo';"
        }
      ]
    });
  });

});

'use strict';

var blueprintHelpers = require('ember-cli-blueprint-test-helpers/helpers');
var setupTestHooks = blueprintHelpers.setupTestHooks;
var emberNew = blueprintHelpers.emberNew;
var emberGenerateDestroy = blueprintHelpers.emberGenerateDestroy;

var chai = require('ember-cli-blueprint-test-helpers/chai');
var expect = chai.expect;

describe('Acceptance: ember generate and destroy initializer-addon', function() {
  setupTestHooks(this);

  it('initializer-addon foo', function() {
    var args = ['initializer-addon', 'foo'];

    return emberNew({ target: 'addon' })
      .then(() => emberGenerateDestroy(args, _file => {
        expect(_file('app/initializers/foo.js'))
          .to.contain("export { default, initialize } from 'my-addon/initializers/foo';");
      }));
  });
});

Use alternative blueprint test helpers

tests/initializer-addon: Use alternative blueprint test helpers


var gulp = require('gulp');
var config = require('../gulp.config')();
var Server = require('karma').Server;
var remapIstanbul = require('remap-istanbul/lib/gulpRemapIstanbul');

/**
 * Run test once and exit
 */
gulp.task('test', ['clean-report', 'tslint', 'unit-test']);

gulp.task('unit-test', ['tsc'], function (done) {
    new Server({
        configFile: __dirname + '/../karma.conf.js',
        singleRun: true
    }, karmaDone).start();

    function karmaDone (exitCode) {
    	console.log('Test Done with exit code: ' + exitCode);
    	console.log('Remapping coverage to TypeScript format...');
    	remapCoverage();
        console.log('Remapping done! View the result in report/remap/html-report');
        if(exitCode === 0) {
            done();
        } else {
            done('Unit test failed.');
        }
    }
});

function remapCoverage () {
    gulp.src(config.report.path + 'report-json/coverage-final.json')
        .pipe(remapIstanbul({
            reports: {
                'lcovonly': config.report.path + 'remap/lcov.info',
                'json': config.report.path + 'remap/coverage.json',
                'html': config.report.path + 'remap/html-report'
            }
        }));
}

var gulp = require('gulp');
var config = require('../gulp.config')();
var Server = require('karma').Server;
var remapIstanbul = require('remap-istanbul/lib/gulpRemapIstanbul');

/**
 * Run test once and exit
 */
gulp.task('test', ['clean-report', 'tslint', 'unit-test']);

gulp.task('unit-test', ['tsc'], function (done) {
    new Server({
        configFile: __dirname + '/../karma.conf.js',
        singleRun: true
    }, karmaDone).start();

    function karmaDone (exitCode) {
    	console.log('Test Done with exit code: ' + exitCode);
    	remapCoverage();
        if(exitCode === 0) {
            done();
        } else {
            done('Unit test failed.');
        }
    }
});

function remapCoverage () {
    console.log('Remapping coverage to TypeScript format...');
    gulp.src(config.report.path + 'report-json/coverage-final.json')
        .pipe(remapIstanbul({
            reports: {
                'lcovonly': config.report.path + 'remap/lcov.info',
                'json': config.report.path + 'remap/coverage.json',
                'html': config.report.path + 'remap/html-report'
            }
        }))
        .on('finish', function () {
            console.log('Remapping done! View the result in report/remap/html-report');
        });
}

Add on finish for remap istanbul

Add on finish for remap istanbul


module.exports = {
    apiUrl: 'https://manifold-api-pre.azurewebsites.net',
    analytics: 'UA-98003629-1',
    apiUrl2: 'https://appimagegenerator-prod.azurewebsites.net'
}

module.exports = {
    apiUrl: 'https://manifold-api-prod.azurewebsites.net',
    analytics: 'UA-98003629-1',
    apiUrl2: 'https://appimagegenerator-prod.azurewebsites.net'
}

Revert "target to API in preview"

Revert "target to API in preview"

This reverts commit c1fe5d796af9b677faa5197e7ea830d5df026183.


var cssnext = require('postcss-cssnext');
var postcssFocus = require('postcss-focus');
var postcssReporter = require('postcss-reporter');

module.exports = {
  output: {
    publicPath: '/',
    libraryTarget: 'commonjs2',
  },
  resolve: {
    extensions: ['', '.js', '.jsx'],
    modules: [
      'client',
      'node_modules',
    ],
  },
  module: {
    loaders: [
      {
        test: /\.css$/,
        exclude: /node_modules/,
        loader: 'style-loader!css-loader?localIdentName=[name]__[local]__[hash:base64:5]&modules&importLoaders=1&sourceMap!postcss-loader',
      },
      {
        test: /\.jpe?g$|\.gif$|\.png$|\.svg$/i,
        loader: 'url-loader?limit=10000',
      },
    ],
  },
  postcss: () => [
    postcssFocus(),
    cssnext({
      browsers: ['last 2 versions', 'IE > 10'],
    }),
    postcssReporter({
      clearMessages: true,
    }),
  ],
};

var cssnext = require('postcss-cssnext');
var postcssFocus = require('postcss-focus');
var postcssReporter = require('postcss-reporter');

var cssModulesIdentName = '[name]__[local]__[hash:base64:5]';
if (process.env.NODE_ENV === 'production') {
  cssModulesIdentName = '[hash:base64]';
}

module.exports = {
  output: {
    publicPath: '/',
    libraryTarget: 'commonjs2',
  },
  resolve: {
    extensions: ['', '.js', '.jsx'],
    modules: [
      'client',
      'node_modules',
    ],
  },
  module: {
    loaders: [
      {
        test: /\.css$/,
        exclude: /node_modules/,
        loader: 'style-loader!css-loader?localIdentName=' + cssModulesIdentName + '&modules&importLoaders=1&sourceMap!postcss-loader',
      },
      {
        test: /\.jpe?g$|\.gif$|\.png$|\.svg$/i,
        loader: 'url-loader?limit=10000',
      },
    ],
  },
  postcss: () => [
    postcssFocus(),
    cssnext({
      browsers: ['last 2 versions', 'IE > 10'],
    }),
    postcssReporter({
      clearMessages: true,
    }),
  ],
};

Fix CSS Modules ident name for production build (from Sai Kishore Komanduri)


/**
 * Check if given `slug` could conflict with existing routes or
 * if it's a reserved keyword.
 *
 * The list is mostly based on frontend `src/server/pages.js` file and
 * `src/pages/static` content.
 *
 * @param {String} slug
 */
export function isBlacklistedCollectiveSlug(slug) {
  return [
    'about',
    'chapters',
    'create',
    'discover',
    'faq',
    'hosts',
    'learn-more',
    'opensource',
    'privacypolicy',
    'redeem',
    'redeemed',
    'search',
    'signin',
    'subscriptions',
    'tos',
    'widgets',
  ].includes(slug);
}

/**
 * Check if given `slug` could conflict with existing routes or
 * if it's a reserved keyword.
 *
 * The list is mostly based on frontend `src/server/pages.js` file and
 * `src/pages/static` content.
 *
 * @param {String} slug
 */
export function isBlacklistedCollectiveSlug(slug) {
  return [
    'about',
    'chapters',
    'create',
    'discover',
    'faq',
    'hosts',
    'learn-more',
    'opensource',
    'privacypolicy',
    'redeem',
    'redeemed',
    'search',
    'signin',
    'subscriptions',
    'tos',
    'widgets',
    'edit',
    'expenses',
    'orders',
  ].includes(slug);
}

Add more keywords to blacklisted slugs

chore(Collective): Add more keywords to blacklisted slugs


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

class Counter extends Component {
  constructor() {
    super();
    this.state = { counter: 0 };

    this.increment = this.increment.bind(this);
    this.decrement = this.decrement.bind(this);
  }

  increment() {
    const state = { counter: this.state.counter + 1 };
    window.devToolsExtension && window.devToolsExtension.send('increment', state);
    this.setState(state);
  }

  decrement() {
    const state = { counter: this.state.counter - 1 };
    window.devToolsExtension && window.devToolsExtension.send('decrement', state);
    this.setState(state);
  }

  render() {
    const { counter } = this.state;
    return (
      <p>
        Clicked: {counter} times
        {' '}
        <button onClick={this.increment}>+</button>
        {' '}
        <button onClick={this.decrement}>-</button>
      </p>
    );
  }
}

export default Counter;

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

class Counter extends Component {
  constructor() {
    super();
    this.state = { counter: 0 };

    this.increment = this.increment.bind(this);
    this.decrement = this.decrement.bind(this);
  }

  increment() {
    const state = { counter: this.state.counter + 1 };
    this.setState(state);
  }

  decrement() {
    const state = { counter: this.state.counter - 1 };
    this.setState(state);
  }

  render() {
    const { counter } = this.state;
    return (
      <p>
        Clicked: {counter} times
        {' '}
        <button onClick={this.increment}>+</button>
        {' '}
        <button onClick={this.decrement}>-</button>
      </p>
    );
  }
}

export default Counter;

Revert "Send changes in react-counter-messaging"

This reverts commit ff9167ce9607408eaadc188435dca89192933915.


// analytics.js
CircleBlvd.Services.analytics = function ($window, $location) {

	// Reference:
	// https://developers.google.com/analytics/devguides/collection/analyticsjs/pages
	var trackPage = function () {
		if ($window.ga) {
			var hash = $window.location.hash;
			var path;

			if (hash.length > 0) {
				path = $location.path();
			}
			else {
				path = $window.location.pathname;
			}

			$window.ga('send', 'pageview', path);
		}
	};

	var trackEvent = function (category, action) {
		if ($window.ga) {
			$window.ga('send', 'event', category, action);
		}
	};

	var trackError = function (status) {
		if ($window.ga) {
			$window.ga('send', 'event', 'error', status);
		}
	};

	return {
		trackPage: trackPage,
		trackEvent: trackEvent,
		trackError: trackError
	};
};
CircleBlvd.Services.analytics.$inject = ['$window', '$location'];

// analytics.js
CircleBlvd.Services.analytics = function ($window, $location) {

	// Reference:
	// https://developers.google.com/analytics/devguides/collection/analyticsjs/pages
	var trackPage = function () {
		if ($window.ga) {
			var hash = $window.location.hash;
			var path;

			if (hash.length > 0) {
				path = $location.path();
			}
			else {
				path = $window.location.pathname;
			}

			var search = $window.location.search;
			if (search) {
				path = path + search;
			}

			$window.ga('send', 'pageview', path);
		}
	};

	var trackEvent = function (category, action) {
		if ($window.ga) {
			$window.ga('send', 'event', category, action);
		}
	};

	var trackError = function (status) {
		if ($window.ga) {
			$window.ga('send', 'event', 'error', status);
		}
	};

	return {
		trackPage: trackPage,
		trackEvent: trackEvent,
		trackError: trackError
	};
};
CircleBlvd.Services.analytics.$inject = ['$window', '$location'];

Add search string to page view

Analytics: Add search string to page view

As we want to see which advertising channels
are most effective, we want to record the query
parameters.


import {parse} from 'cookie';
module.exports = (router) => {
  // router is props.history from a component served by react router
  let cookies = parse(document.cookie);
  console.log(cookies);
  if (!cookies.fridgrSesh) {
    router.push('/login');
    return;
  }

  let fridgrSesh = JSON.parse(cookies.fridgrSesh.slice(2));
  console.log(fridgrSesh);
  if (!(fridgrSesh.userId && fridgrSesh.houseId)) {
    router.push('/login');
  }

};

import {parse} from 'cookie';
module.exports = (router) => {
  // router is props.history from a component served by react router
  let cookies = parse(document.cookie);
  if (!cookies.fridgrSesh) {
    router.push('/login');
    return;
  }

  let fridgrSesh = JSON.parse(cookies.fridgrSesh.slice(2));
  if (!(fridgrSesh.userId && fridgrSesh.houseId)) {
    router.push('/login');
  }

};

Remove console logs from auth

Remove console logs from auth


/* eslint-env mocha */

var chai = require('chai')
chai.use(require('chai-as-promised'))
chai.should()

var schema = {
  type: 'object',
  additionalProperties: false,
  patternProperties: {
    '[0-9]*': {
      type: 'object',
      additionalProperties: false,
      properties: {
        browserName: {type: 'string'},
        version: {type: 'string'},
        platform: {type: 'string'},
        base: {type: 'string', enum: ['SauceLabs']}
      },
      required: ['browserName', 'version', 'platform', 'base']
    }
  }
}

var ZSchema = require('z-schema')
var validator = new ZSchema()
var browsers = require('../')

describe('exports', function () {
  this.timeout(10000)

  it('is a promise that fulfills according to the schema', function (done) {
    return browsers.then(function (browsers) {
      var valid = validator.validate(browsers, schema)
      if (!valid) {
        var errors = validator.getLastErrors()
        console.log(errors)
      }
      return valid
    }).should.eventually.be.true
  })
})

/* eslint-env mocha */

var chai = require('chai')
chai.use(require('chai-as-promised'))
chai.should()

var schema = {
  type: 'object',
  additionalProperties: false,
  patternProperties: {
    '[0-9]*': {
      type: 'object',
      additionalProperties: false,
      properties: {
        browserName: {type: 'string'},
        version: {type: 'string'},
        platform: {type: 'string'},
        base: {type: 'string', enum: ['SauceLabs']}
      },
      required: ['browserName', 'version', 'platform', 'base']
    }
  }
}

var ZSchema = require('z-schema')
var validator = new ZSchema()
var browsers = require('../')

describe('exports', function () {
  this.timeout(10000)

  it('is a promise that fulfills according to the schema', function () {
    return browsers.then(function (browsers) {
      var valid = validator.validate(browsers, schema)
      if (!valid) {
        var errors = validator.getLastErrors()
        console.log(errors)
      }
      return valid
    }).should.eventually.be.true
  })
})

Fix test: redundant done argument

Fix test: redundant done argument


var promisify = require("../");
var assert = require("assert");

describe("Promisify", function() {
  it("can convert a basic async function", function() {
    function test(cb) {
      cb(null, true);
    }

    var wrappedTest = promisify(test);

    return wrappedTest().then(function(value) {
      assert.ok(value);
    });
  });

  it("exports a callbacks array", function() {
    assert(Array.isArray(promisify.callbacks));
  });
});

var promisify = require("../");
var assert = require("assert");

describe("Promisify", function() {
  it("can convert a basic async function", function() {
    function test(cb) {
      cb(null, true);
    }

    var wrappedTest = promisify(test);

    return wrappedTest().then(function(value) {
      assert.ok(value);
    });
  });

  it("exports a callbacks array", function() {
    assert(Array.isArray(promisify.callbacks));
  });

  describe("asynchronous method inference", function() {
    var later = function(cb) {
      setTimeout(cb(null), 0);
    };

    it("does not modify methods that do not appear to be asynchronous", function() {
      var obj = {
        a: function(probably, not, async) {}
      };
      var wrappedObj = promisify(obj);

      assert.equal(
        obj.a,
        wrappedObj.a
      );
    });

    it("can infer callback-accepting functions by argument list", function() {
      var obj = promisify({
        a: function(cb) { later(cb); }
      });

      return obj.a();
    });

    it("can infer callback-accepting functions by argument list", function() {
      var obj = promisify({
        a: function(callback) { later(callback); }
      });

      return obj.a();
    });

    it("can infer callback-accepting functions by argument list", function() {
      var obj = promisify({
        a: function(callback_) { later(callback_); }
      });

      return obj.a();
    });

    it("can infer callback-accepting functions by argument list", function() {
      var obj = promisify({
        a: function(done) { later(done); }
      });

      return obj.a();
    });
  });
});

Add tests for asynchronous method inference


import webpack from 'webpack';
import baseConfig from './base.babel';

const host = 'localhost';
const port = 3000;

const baseDevConfig = {
  ...baseConfig,
  mode: 'development',
  devtool: 'inline-source-map',
  devServer: { host, port },
  output: {
    ...baseConfig.output,
    publicPath: `http://localhost:${port}/js/`,
  },
  module: {
    rules: [
      ...baseConfig.module.rules,
      {
        test: /\.css?$/,
        use: ['style-loader', 'raw-loader'],
      },
    ],
  },
  plugins: [
    ...baseConfig.plugins,
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('development'),
    }),
  ],
};

const buildDevConfig = config => ({
  ...baseDevConfig,
  ...config,
});

export default [
  buildDevConfig({
    entry: './app/index',
    target: 'electron-renderer',
  }),
  buildDevConfig({
    entry: './app/worker/index.js',
    resolve: {
      ...baseDevConfig.resolve,
      aliasFields: ['browser'],
    },
    output: {
      ...baseDevConfig.output,
      filename: 'RNDebuggerWorker.js',
      libraryTarget: undefined,
    },
    target: 'webworker',
  }),
];

import webpack from 'webpack';
import baseConfig from './base.babel';

const host = 'localhost';
const port = 3000;

const baseDevConfig = {
  ...baseConfig,
  mode: 'development',
  devtool: 'inline-source-map',
  devServer: { host, port, disableHostCheck: true },
  output: {
    ...baseConfig.output,
    publicPath: `http://localhost:${port}/js/`,
  },
  module: {
    rules: [
      ...baseConfig.module.rules,
      {
        test: /\.css?$/,
        use: ['style-loader', 'raw-loader'],
      },
    ],
  },
  plugins: [
    ...baseConfig.plugins,
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('development'),
    }),
  ],
};

const buildDevConfig = config => ({
  ...baseDevConfig,
  ...config,
});

export default [
  buildDevConfig({
    entry: './app/index',
    target: 'electron-renderer',
  }),
  buildDevConfig({
    entry: './app/worker/index.js',
    resolve: {
      ...baseDevConfig.resolve,
      aliasFields: ['browser'],
    },
    output: {
      ...baseDevConfig.output,
      filename: 'RNDebuggerWorker.js',
      libraryTarget: undefined,
    },
    target: 'webworker',
  }),
];

Add disableHostCheck for dev server

Add disableHostCheck for dev server


/*
 * Copyright (C) 2015 SUSE Linux
 *
 * This software may be modified and distributed under the terms
 * of the MIT license.  See the LICENSE.txt file for details.
 */

(function () {
  'use strict';

  angular.module('janusHangouts')
    .service('StatesService', StatesService);

  StatesService.$inject = ['$q', '$stateParams', 'RoomService', 'UserService'];

  function StatesService($q, $stateParams, RoomService, UserService) {
    /**
     * Sets the current user and room according to params and/or cookies.
     */
    this.setRoomAndUser = function() {
      var deferred = $q.defer();

      // Set user
      // TODO: Read userName from a cookie if not received as param
      var userName = $stateParams.user;
      if (userName !== undefined) {
        UserService.signin(userName);
      }

      // Set room
      var roomId = parseInt($stateParams.room);
      if (Number.isNaN(roomId)) {
        RoomService.setRoom(null);
        deferred.resolve();
      } else {
        RoomService.getRooms().then(function(rooms) {
          var result = _.find(rooms, function(room) { return room.id === roomId; });
          RoomService.setRoom(result || null);
          deferred.resolve();
        });
      }
      return deferred.promise;
    };
  }
})();

/*
 * Copyright (C) 2015 SUSE Linux
 *
 * This software may be modified and distributed under the terms
 * of the MIT license.  See the LICENSE.txt file for details.
 */

(function () {
  'use strict';

  angular.module('janusHangouts')
    .service('StatesService', StatesService);

  StatesService.$inject = ['$q', '$stateParams', 'RoomService', 'UserService'];

  function StatesService($q, $stateParams, RoomService, UserService) {
    /**
     * Sets the current user and room according to params and/or cookies.
     */
    this.setRoomAndUser = function() {
      var deferred = $q.defer();

      // Set user
      var userName = $stateParams.user || UserService.getSetting("lastUsername");
      if (userName !== undefined) {
        UserService.signin(userName);
      }

      // Set room
      var roomId = parseInt($stateParams.room);
      if (Number.isNaN(roomId)) {
        RoomService.setRoom(null);
        deferred.resolve();
      } else {
        RoomService.getRooms().then(function(rooms) {
          var result = _.find(rooms, function(room) { return room.id === roomId; });
          RoomService.setRoom(result || null);
          deferred.resolve();
        });
      }
      return deferred.promise;
    };
  }
})();

Set default user name on persistent URLs


var util = require('util'),
    connect = require('connect'),
    port = 9000;

connect.createServer(connect.static(__dirname)).listen(process.env.PORT || port);
util.puts('Listening on ' + (process.env.PORT || port) + '...');
util.puts('Press Ctrl + C to stop.');

var util = require('util');
var connect = require('connect');
var serveStatic = require('serve-static');
var path = require('path');

var port = 9000;
var app = connect();

// parent folder
app.use(serveStatic(path.resolve(__dirname)));

// Listen
app.listen(process.env.PORT || port);
util.puts('Listening on ' + (process.env.PORT || port) + '...');
util.puts('Press Ctrl + C to stop.');

Update server to use connect serve-static


'use strict';

const http = require('http');
const W3CWebSocket = require('websocket').w3cwebsocket;
const WebSocketAsPromised = require('websocket-as-promised');
const createWsServer = require('../ws_server');

const PORT = 5600;
const httpServer = http.createServer();
createWsServer({ httpServer });

const wsClient = new WebSocketAsPromised(`ws://localhost:${PORT}/`, {
  createWebSocket: url => new W3CWebSocket(url, 'jcm2018'),
  packMessage: data => JSON.stringify(data),
  unpackMessage: message => JSON.parse(message),
  attachRequestId: (data, requestId) => ({ ...data, requestId }),
  extractRequestId: data => data && data.requestId,
  timeout: 5000
});

beforeAll(async () => {
  httpServer.listen(PORT);
  await wsClient.open();
});

afterAll(async () => {
  await wsClient.close();
  httpServer.close();
});

it('basic connectivity', async () => {
  await wsClient.sendRequest({});
});

'use strict';

const http = require('http');
const W3CWebSocket = require('websocket').w3cwebsocket;
const WebSocketAsPromised = require('websocket-as-promised');
const Actions = require('../../common');
const createWsServer = require('../ws_server');

const PORT = 5600;
const httpServer = http.createServer();
createWsServer({ httpServer });

const wsClient = new WebSocketAsPromised(`ws://localhost:${PORT}/`, {
  createWebSocket: url => new W3CWebSocket(url, 'jcm2018'),
  packMessage: data => JSON.stringify(data),
  unpackMessage: message => JSON.parse(message),
  attachRequestId: (data, requestId) => ({ ...data, requestId }),
  extractRequestId: data => data && data.requestId,
  timeout: 5000
});

beforeAll(async () => {
  httpServer.listen(PORT);
  await wsClient.open();
});

afterAll(async () => {
  await wsClient.close();
  httpServer.close();
});

afterEach(() => {
  wsClient.onMessage.removeAllListeners();
});

it('basic connectivity', async () => {
  await wsClient.sendRequest({});
});

it('unparsable message', async done => {
  wsClient.onMessage.addListener(message => {
    const parsed = JSON.parse(message);
    expect(parsed.code).toEqual(Actions.CODE_UNPARSABLE_MESSAGE);
    done();
  });

  await wsClient.send('--');
});

Test sending an unparsable message.

Test sending an unparsable message.


var libraryLoaded = false;
var callbacks = [];

module.exports = function(callback) {
    if (!libraryLoaded) {
        callbacks.push(callback);
    } else {
        callback.call();
    }
};

var cb = 'kwfUp-recaptchaCallback'.replace('-', '_');
window[cb] = function() {
    callbacks.forEach(function(callback) {
        callback.call();
    });
    libraryLoaded = true;
};
(function(cb){
    a='https://www.google.com/recaptcha/api.js?onload=' + cb + '&render=explicit';
    b=document;c='script';d=b.createElement(c);d.src=a;d.type='text/java'+c;d.async=true;
    a=b.getElementsByTagName(c)[0];a.parentNode.insertBefore(d,a);
})(cb);

var isRequested = false;
var isLoaded = false;
var callbacks = [];

module.exports = function(callback) {
    if (!isLoaded) {
        callbacks.push(callback);
    } else {
        callbacks.call();
    }

    if (!isRequested) {
        var cb = 'kwfUp-recaptchaCallback'.replace('-', '_');
        window[cb] = function() {
            isLoaded = true;
            callbacks.forEach(function(callback) {
                callback.call();
            });
        };
        (function(cb){
            var el = document.createElement('script');
            el.src = 'https://www.google.com/recaptcha/api.js?onload=' + cb + '&render=explicit';
            el.type = 'text/javascript';
            el.async = true;
            var targetEl = document.getElementsByTagName('script')[0];
            targetEl.parentNode.insertBefore(el, targetEl);
        })(cb);
        isRequested = true;
    }
};

Load captcha library only when used

Load captcha library only when used


//open command bar
Mousetrap.bindGlobal(['shift+up'], function(e) {
  document.getElementById('light').style.display='block';
  return false;
});

//close command bar
Mousetrap.bindGlobal(['shift+down', 'esc'], function(e) {
  document.getElementById('light').style.display='none';
  return false;
});

//open command bar and trigger voice search
Mousetrap.bindGlobal(['ctrl+shift+.'], function(e) {
  document.getElementById('light').style.display='block';document.getElementById('fade').style.display='block'
  return false;
});

jQuery( "iframe" ).each( function( i, iframe )
{
  Mousetrap.bindEventsTo( iframe.contentDocument );
} );

//open command bar
Mousetrap.bindGlobal(['shift+up'], function(e) {
  document.getElementById('light').style.display='block';
  return false;
});

//close command bar
Mousetrap.bindGlobal(['shift+down', 'esc'], function(e) {
	document.getElementById('lightboxInput').value="";
	document.getElementById('light').style.display='none';

  return false;
});

//open command bar and trigger voice search
Mousetrap.bindGlobal(['ctrl+shift+.'], function(e) {
  document.getElementById('light').style.display='block';document.getElementById('fade').style.display='block'
  return false;
});

jQuery( "iframe" ).each( function( i, iframe )
{
  Mousetrap.bindEventsTo( iframe.contentDocument );
} );

Clean imput box with shortcut keys

Clean imput box with shortcut keys


'use strict';

require('posts', function (Posts) {
  var setPostHeat = function () {
    var allArr = Posts.find({
      oldPoints: {
        $gt: 1
      }
    }).fetch();

    console.log('Setting post heat for ' + allArr.length + ' posts');
    _(allArr).forEach(function (post) {
      var now = new Date();
      var secondsAgo = Math.abs(now - (new Date(post.createdAt))) / 1000
      var hoursAgo = secondsAgo / 60 / 60;
      var points = (post.oldPoints - 1);
      var decay = Math.pow(hoursAgo + 2, 1.5);
      Posts.update({
        _id: post._id
      },{
        $set: {
          heat: points / decay
        }
      });
    });
  };

  // run every 6 seconds
  Meteor.setInterval(setPostHeat, 6 * 1000);
});

'use strict';

require('posts', function (Posts) {
  var setPostHeat = function () {
    var allArr = Posts.find({
      oldPoints: {
        $gt: 1
      }
    }).fetch();

    console.log('Setting post heat for ' + allArr.length + ' posts');
    _(allArr).forEach(function (post) {
      var now = new Date();
      var secondsAgo = Math.abs(now - (new Date(post.createdAt))) / 1000;
      var hoursAgo = secondsAgo / 60 / 60;
      var points = (post.oldPoints - 1);
      var decay = Math.pow(hoursAgo + 2, 1.5);
      Posts.update({
        _id: post._id
      },{
        $set: {
          heat: points / decay
        }
      });
    });
  };

  // run every 6 seconds
  Meteor.setInterval(setPostHeat, 6 * 1000);
});

Add a semicolon because I'm stupid

Add a semicolon because I'm stupid


function loadTopmenu() {
        var iframe = document.createElement("iframe");
        iframe.setAttribute("src", "/topmenu.html");
        iframe.setAttribute("width", "100%");
        iframe.setAttribute("height", "51px");
        iframe.frameBorder = 0;
        document.body.insertBefore(iframe, document.body.firstChild);
        document.title += ' [SF <%= scope.function_hiera(["sf_version"]) %>]';
};

if (document.body) {
    loadTopmenu();
} else {
    window.onload = function() {loadTopmenu();};
};

function loadTopmenu() {
        var iframe = document.createElement("iframe");
        iframe.setAttribute("src", "/topmenu.html");
        iframe.setAttribute("width", "100%");
        iframe.setAttribute("height", "51px");
        iframe.frameBorder = 0;
        document.body.insertBefore(iframe, document.body.firstChild);
        document.title += ' [SF <%= scope.function_hiera(["sf_version"]) %>]';
};

if (document.addEventListener) {
    document.addEventListener("DOMContentLoaded", loadTopmenu, false);
} else {
    window.onload = loadTopmenu;
}

Fix bug in Jenkins configuration page

Fix bug in Jenkins configuration page

The Jenkins configuration page (http://tests.dom/jenkins/configure) was
basically unusable, because there is an gray overlay box that is blocking the
form fields and thus these fields are unusable. This overlay should be displayed
only during page load; but it didn't disappeared after due to a issue with the
JavaScript that loads the topmenu.

Tested on Firefox, Chrome and Safari.

Change-Id: Ia1ffbfd4200ffb3839cbf7f676737aa058ad119e


/*
 * Copyright 2018 ThoughtWorks, Inc.
 *
 * 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.
 */

const AjaxHelper = require("helpers/ajax_helper");
const parseError = require("helpers/mrequest").unwrapErrorExtractMessage;
const Dfr        = require("jquery").Deferred;

/** This helper parses data, etags, and errors for a convenient API */
function req(exec) {
  return Dfr(function ajax() {
    const success = (data, _s, xhr) => this.resolve(data, parseEtag(xhr), xhr.status);
    const failure = (xhr) => this.reject(parseError(xhr.responseJSON, xhr));

    exec().then(success, failure);
  }).promise();
}

function parseEtag(req) { return (req.getResponseHeader("ETag") || "").replace(/--(gzip|deflate)/, ""); }

const ApiHelper = {};

for (const key in AjaxHelper) {
  ApiHelper[key] = (config) => req(() => AjaxHelper[key](config));
}

module.exports = ApiHelper;

/*
 * Copyright 2018 ThoughtWorks, Inc.
 *
 * 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.
 */

const AjaxHelper = require("helpers/ajax_helper");
const parseError = require("helpers/mrequest").unwrapErrorExtractMessage;
const Dfr        = require("jquery").Deferred;

/** This helper parses data, etags, and errors for a convenient API */
function req(exec) {
  return Dfr(function ajax() {
    const success = (data, _s, xhr) => this.resolve(data, parseEtag(xhr), xhr.status);
    const failure = (xhr) => this.reject(parseError(xhr.responseJSON, xhr), xhr.status);

    exec().then(success, failure);
  }).promise();
}

function parseEtag(req) { return (req.getResponseHeader("ETag") || "").replace(/--(gzip|deflate)/, ""); }

const ApiHelper = {};

for (const key in AjaxHelper) {
  ApiHelper[key] = (config) => req(() => AjaxHelper[key](config));
}

module.exports = ApiHelper;

ApiHelper: Make HTTP status available on failure as well


import React from 'react';
import IconSVG from './IconSVG';

import { iconClass } from './util';
import { prefixable } from '../../decorators';

export const Icon = (props) => {
  const { sprite, icon, title, circle, background, size, div, prefix } = props;
  const backgroundClass = !background ? `icon-${sprite}-${iconClass(sprite, icon)}` : `icon-${background}`;

  const sldsClasses = [
    { icon_container: true },
    { 'icon_container--circle': circle },
    { [`${backgroundClass}`]: true },
  ];

  const WrapperElement = div ? 'div' : 'span';

  return (
    <WrapperElement className={prefix(sldsClasses, props)} title={title}>
      <IconSVG sprite={sprite} icon={icon} size={size} />
      {title}
    </WrapperElement>
  );
};

Icon.propTypes = {
  /**
   * the prefix function from the prefixable HOC
   */
  prefix: React.PropTypes.func,
  sprite: React.PropTypes.oneOf(['action', 'custom', 'doctype', 'standard', 'utility']).isRequired,
  icon: React.PropTypes.string.isRequired,
  title: React.PropTypes.string,
  circle: React.PropTypes.bool,
  div: React.PropTypes.bool,
  background: React.PropTypes.string,
  size: React.PropTypes.oneOf(['x-small', 'small', 'medium', 'large']),
};

export default prefixable(Icon);

import React from 'react';
import IconSVG from './IconSVG';

import { iconClass } from './util';
import { prefixable } from '../../decorators';

export const Icon = (props) => {
  const { sprite, icon, title, circle, background, size, div, prefix } = props;
  const backgroundClass = !background ? `icon-${sprite}-${iconClass(sprite, icon)}` : `icon-${background}`;

  const sldsClasses = [
    { icon_container: true },
    { 'icon_container--circle': circle },
    { [`${backgroundClass}`]: true },
  ];

  const WrapperElement = div ? 'div' : 'span';

  return (
    <WrapperElement className={prefix(sldsClasses, props)} title={title}>
      <IconSVG sprite={sprite} icon={icon} size={size} background={background} />
      {title}
    </WrapperElement>
  );
};

Icon.propTypes = {
  /**
   * the prefix function from the prefixable HOC
   */
  prefix: React.PropTypes.func,
  sprite: React.PropTypes.oneOf(['action', 'custom', 'doctype', 'standard', 'utility']).isRequired,
  icon: React.PropTypes.string.isRequired,
  title: React.PropTypes.string,
  circle: React.PropTypes.bool,
  div: React.PropTypes.bool,
  background: React.PropTypes.string,
  size: React.PropTypes.oneOf(['x-small', 'small', 'medium', 'large']),
};

export default prefixable(Icon);

Fix support of custom background on icons


(function ($) {
    var methods = {
        init: function(options) {
            return this.each(function() {
                show($(this), false);
                $(this).change(function() {
                    show($(this), true);
                });

                function show(element, replace) {
                    var id = element.attr('id');
                    var selectedValue = element.val();
                    var prototypeElement = $('#' + id + '_' + selectedValue);
                    var container = $(prototypeElement.data('container'));

                    if (replace) {
                        container.html('
' + prototypeElement.data('prototype'));
                    } else if (!container.html().trim()) {
                        container.html('
' + prototypeElement.data('prototype'));
                    }
                };
            });
        }
    };

    $.fn.handlePrototypes = function(method) {
        if (methods[method]) {
            return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
        } else if (typeof method === 'object' || !method) {
            return methods.init.apply(this, arguments);
        } else {
            $.error( 'Method ' +  method + ' does not exist on jQuery.handlePrototypes' );
        }
    };
})(jQuery);

(function ($) {
    var methods = {
        init: function(options) {
            var settings = $.extend({
              'prototypePrefix': false,
              'prototypeElementPrefix': '<hr />',
              'containerSelector': false,
            }, options);

            return this.each(function() {
                show($(this), false);
                $(this).change(function() {
                    show($(this), true);
                });

                function show(element, replace) {
                    var id = element.attr('id');
                    var selectedValue = element.val();
                    var prototypePrefix = id;
                    if (false != settings.prototypePrefix) {
                        prototypePrefix = settings.prototypePrefix;
                    }

                    var prototypeElement = $('#' + prototypePrefix + '_' + selectedValue);
                    var container;
                    if (settings.containerSelector) {
                        container = $(settings.containerSelector);
                    } else {
                        container = $(prototypeElement.data('container'));
                    }

                    if (replace) {
                        container.html(settings.prototypeElementPrefix + prototypeElement.data('prototype'));
                    } else if (!container.html().trim()) {
                        container.html(settings.prototypeElementPrefix + prototypeElement.data('prototype'));
                    }
                };
            });
        }
    };

    $.fn.handlePrototypes = function(method) {
        if (methods[method]) {
            return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
        } else if (typeof method === 'object' || !method) {
            return methods.init.apply(this, arguments);
        } else {
            $.error( 'Method ' +  method + ' does not exist on jQuery.handlePrototypes' );
        }
    };
})(jQuery);

Add some options for prototype handler.

Add some options for prototype handler.


/* eslint-disable react/no-danger */
/* eslint-disable camelcase */
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import DOMPurify from 'dompurify';

const ServiceDetailStdout = ({ taskid }) => {
  const [Taskresults, setTaskresults] = useState('');
  const [Error, SetError] = useState(undefined);

  useEffect(() => {
    miqSparkleOn();
    API.wait_for_task(taskid)
      .then(({ task_results }) => setTaskresults(task_results))
      .catch((error) => {
        console.error(error);
        SetError(error.message);
      })
      .then(() => {
        miqSparkleOff();
        API.delete(`/api/tasks/${taskid}`);
      });
  }, [taskid]);

  return (

      {Error && (
        <>

            {' '}
            {_('Error loading data:')}

            {' '}
            {Error}

        </>
      )}
      {Taskresults && (
        <>

{ _('Standard Output:')}

        </>
      )}

  );
};

export default ServiceDetailStdout;

ServiceDetailStdout.propTypes = {
  taskid: PropTypes.number.isRequired,
};

/* eslint-disable react/no-danger */
/* eslint-disable camelcase */
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import DOMPurify from 'dompurify';

const ServiceDetailStdout = ({ taskid }) => {
  const [Taskresults, setTaskresults] = useState('');
  const [Error, SetError] = useState(undefined);

  useEffect(() => {
    miqSparkleOn();
    API.wait_for_task(taskid)
      .then(({ task_results }) => setTaskresults(task_results))
      .catch((error) => {
        console.error(error);
        SetError(error.message);
      })
      .then(() => {
        miqSparkleOff();
        API.delete(`/api/tasks/${taskid}`);
      });
  }, [taskid]);

  return (

      {Error && (
        <>

            {' '}
            {__('Error loading data:')}

            {' '}
            {Error}

        </>
      )}
      {Taskresults && (
        <>

{__('Standard Output:')}

        </>
      )}

  );
};

export default ServiceDetailStdout;

ServiceDetailStdout.propTypes = {
  taskid: PropTypes.number.isRequired,
};

Fix missing text in services page

Fix missing text in services page


"use strict";

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

module.exports = jsonTransformPlugin;

function jsonTransformPlugin(schema) {
  schema.set('toJSON', {transform: filterFields});
}

/**
 * Transform a document to a json doc.
 *
 * - options.fieldSpec The fields to show/hide
 */
function filterFields(doc, ret, options) {
  ret = filter(doc, ret, options);
  ret = addLinks(doc, ret, options);
  return ret;
}

function addLinks(doc, ret, options) {
  if (doc.constructor.collection) {
    if (options.apiBaseUrl) {
      ret.url = [
        options.apiBaseUrl,
        doc.constructor.collection.name.toLowerCase(),
        doc.id
      ].join('/');
    }
    if (options.baseUrl && doc.slug) {
      ret.html_url = [
        options.baseUrl,
        doc.constructor.collection.name.toLowerCase(),
        doc.slug
      ].join('/');
    }
  }
  return ret;
}

"use strict";

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

module.exports = jsonTransformPlugin;

function jsonTransformPlugin(schema) {
  schema.set('toJSON', {transform: filterFields});
}

/**
 * Transform a document to a json doc.
 *
 * - options.fieldSpec The fields to show/hide
 */
function filterFields(doc, ret, options) {
  ret = filter(doc, ret, options);
  ret = addLinks(doc, ret, options);
  return ret;
}

function addLinks(doc, ret, options) {
  if (doc.constructor.collection) {
    if (options.apiBaseUrl) {
      ret.url = [
        options.apiBaseUrl,
        doc.constructor.collection.name.toLowerCase(),
        doc._id
      ].join('/');
    }
    if (options.baseUrl && doc.slug) {
      ret.html_url = [
        options.baseUrl,
        doc.constructor.collection.name.toLowerCase(),
        doc.slug
      ].join('/');
    }
  }
  return ret;
}

Use doc _id field to construct url

Use doc _id field to construct url

Docs don't always have an id property, so we use _id directly instead.


import * as validation from 'utils/validation';
import memoize from 'lru-memoize';

const nameInput = [
  validation.valueRequired,
];

const feedbackInput = [
  validation.valueRequired,
];

const formValidation = validation.createValidator({
  nameInput,
  feedbackInput,
});

const validator = memoize(10)(formValidation);
export default validator;

import * as validation from 'utils/validation';
import memoize from 'lru-memoize';

const nameInput = [
  validation.valueRequired,
];

const urlInput = [
  validation.valueRequired,
];

const formValidation = validation.createValidator({
  nameInput,
  urlInput,
});

const validator = memoize(10)(formValidation);
export default validator;

Validate url input for feedback

Fix: Validate url input for feedback


import { reduceApi, getBaseType } from '../../common/api'

import {
  LIST_TODOS,
  VIEW_TODO,
  CREATE_TODO,
  UPDATE_TODO,
  DELETE_TODO,
  MOVE_TODO
} from './actions';

export default function todoReducer(state = {todos: []}, action) {
  switch (getBaseType(action.type)) {
    case LIST_TODOS.BASE:
      state = reduceApi(state, action, LIST_TODOS, (data) => { return {todos: data}; });
    case VIEW_TODO.BASE:
      state = reduceApi(state, action, VIEW_TODO, (data) => { return {todo: data}; });
    case CREATE_TODO.BASE:
      state = reduceApi(state, action, CREATE_TODO, (data) => { return {todo: data}; });
    default:
      return state;
  }
}

import { reduceApi, getBaseType } from '../../common/api'

import {
  LIST_TODOS,
  VIEW_TODO,
  CREATE_TODO,
  UPDATE_TODO,
  DELETE_TODO,
  MOVE_TODO
} from './actions';

export default function todoReducer(state = {todos: []}, action) {
  switch (getBaseType(action.type)) {
    case LIST_TODOS.BASE:
      state = reduceApi(state, action, LIST_TODOS, (data) => { return {todos: data}; });
    case VIEW_TODO.BASE:
      state = reduceApi(state, action, VIEW_TODO, (data) => { return {todo: data}; });
    case CREATE_TODO.BASE:
      state = reduceApi(state, action, CREATE_TODO, (data) => {
        return {todos: [...state.todos, data]};
      });
    default:
      return state;
  }
}

Add new record to list

Add new record to list


import config from '../../../data/__config.json';
var selectedThemeModule = './themes/'+config.currentTheme+'/boot.js';
const Theme = require(selectedThemeModule);
export default Theme.default;

import config from '../../../data/__config.json';

const theme = require(`./themes/${config.currentTheme}/boot.js`);

export default theme.default;

Simplify syntax and use ES6 features

Simplify syntax and use ES6 features


const initialState = {
  time: 0,
  songIndex: 0,
  isPlaying: false
}

export default function player(state = initialState, action) {
  switch (action.type) {
  case 'UPDATE_CURRENT_PLAYER_TIME':
    if (state.isPlaying) {
      return Object.assign({}, state, {
        time: action.time,
        isPlaying: true
      })
    }
    else {
      return Object.assign({}, state, {
        time: action.time,
        isPlaying: false
      })
   }
  case 'UPDATE_CURRENT_PLAYLIST_SONG':
    return Object.assign({}, state, {
      songIndex: action.songIndex
    })
  case 'SET_IS_PLAYING':
    return Object.assign({}, state, {
      isPlaying: true
    })
  case 'SET_NOT_PLAYING':
    return Object.assign({}, state, {
      isPlaying: false
    })
  default:
    return state
  }
}

const initialState = {
  time: 0,
  songIndex: 0,
  isPlaying: false,
  isPaused: false
}

export default function player(state = initialState, action) {
  switch (action.type) {
  case 'UPDATE_CURRENT_PLAYER_TIME':
    if (!state.isPaused) {
      return Object.assign({}, state, {
        time: action.time,
        isPlaying: true
      })
    }
    else {
      return Object.assign({}, state, {
        time: action.time,
        isPlaying: false
      })
    }
  case 'UPDATE_CURRENT_PLAYLIST_SONG':
    return Object.assign({}, state, {
      songIndex: action.songIndex
    })
  case 'SET_IS_PLAYING':
    return Object.assign({}, state, {
      isPlaying: true,
      isPaused: false
    })
  case 'SET_NOT_PLAYING':
    return Object.assign({}, state, {
      isPlaying: false,
      isPaused: true
    })
  default:
    return state
  }
}

Fix join on room with currently playing song bug


(function($){
  $.fn.textareaAutoExpand = function(){
    return this.each(function(){
      var textarea = $(this);
      var height = textarea.height();
      var diff = parseInt(textarea.css('borderBottomWidth')) + parseInt(textarea.css('borderTopWidth')) +
                 parseInt(textarea.css('paddingBottom')) + parseInt(textarea.css('paddingTop'));
      var hasInitialValue = (this.value.replace(/\s/g, '').length > 0);

      if (textarea.css('box-sizing') === 'border-box' ||
          textarea.css('-moz-box-sizing') === 'border-box' ||
          textarea.css('-webkit-box-sizing') === 'border-box') {
        height = textarea.outerHeight();

        if (this.scrollHeight + diff == height) // special case for Firefox where scrollHeight isn't full height on border-box
          diff = 0;
      } else {
        diff = 0;
      }

      if (hasInitialValue) {
        textarea.height(this.scrollHeight);
      }

      textarea.on('scroll input', function(event){
        if (event.keyCode == 13 && !event.shiftKey) {
          // just allow default behavior to enter new line
          if (this.value.replace(/\s/g, '').length == 0) {
            event.stopImmediatePropagation();
            event.stopPropagation();
          }
        }

        textarea.height(0);
        //textarea.height(Math.max(height - diff, this.scrollHeight - diff));
        textarea.height(this.scrollHeight - diff);
      });
    });
  }
})(jQuery);

(function($){
  $.fn.textareaAutoExpand = function(){
    return this.each(function(){
      var textarea = $(this);
      var height = textarea.height();
      var diff = parseInt(textarea.css('borderBottomWidth')) + parseInt(textarea.css('borderTopWidth')) +
                 parseInt(textarea.css('paddingBottom')) + parseInt(textarea.css('paddingTop'));
      var hasInitialValue = (this.value.replace(/\s/g, '').length > 0);

      if (textarea.css('box-sizing') === 'border-box' ||
          textarea.css('-moz-box-sizing') === 'border-box' ||
          textarea.css('-webkit-box-sizing') === 'border-box') {
        height = textarea.outerHeight();

        if (this.scrollHeight + diff == height) // special case for Firefox where scrollHeight isn't full height on border-box
          diff = 0;
      } else {
        diff = 0;
      }

      if (hasInitialValue) {
        textarea.height(this.scrollHeight);
      }

      textarea.on('scroll input keyup', function(event){ // keyup isn't necessary but when deleting text IE needs it to reset height properly
        if (event.keyCode == 13 && !event.shiftKey) {
          // just allow default behavior to enter new line
          if (this.value.replace(/\s/g, '').length == 0) {
            event.stopImmediatePropagation();
            event.stopPropagation();
          }
        }

        textarea.height(0);
        //textarea.height(Math.max(height - diff, this.scrollHeight - diff));
        textarea.height(this.scrollHeight - diff);
      });
    });
  }
})(jQuery);

Add keyup to event list to fix IE so it updates height properly when deleting text.


/**
 * Created by AMOS on 10/7/16.
 */

//TODO: Add various imports for challenge(s)
import * as mcq from './mcq/mcq.js';

//TODO: Add config for routing to various challenges
export function configRoute($routeProvider, routes){
    $routeProvider
        .when(routes.viewMcq, {
            template: mcq.showTmpl
        });

}

configRoute.$inject = ['$routeProvider', 'routes'];

//TODO: Clarify what should and should not be a component
// export const component = {
//
// }

/**
 * Created by AMOS on 10/7/16.
 */

//TODO: Add various imports for challenge(s)
import * as mcq from './mcq/mcq.js';

//TODO: Add config for routing to various challenges
export function configRoute($routeProvider, routes){
    $routeProvider
        .when(routes.viewMcq, {
            template: mcq.newMcqTmpl,
            controller: mcq.viewCtrl,
            controllerAs: 'ctrl'
            // resolve: {
            //     initialData:
            // }
        })

        .when(routes.editMcq, {
            template: mcq.editMcqTmpl,
            controller: mcq.EditCtrl,
            controllerAs: 'ctrl'
            // resolve: {
            //     initialData:
            // }
        });

}

configRoute.$inject = ['$routeProvider', 'routes'];

//TODO: Clarify what should and should not be a component
// export const component = {
//
// }

Revert "Revert "attempt to add input option field""

This reverts commit 5910ed3e285808fb9961644b27cb108e28027e9a.


var chai = require('chai');
var os = require('os');
var fs = require('fs-extra');
var path = require('path');

var fileTemplater =  require('../../bin/fileTemplater')();

chai.use(require('chai-fs'));
chai.should();

var TEST_TEMP_DIR = path.join(os.tmpdir(), 'file-templater');

function changeToOsTempDir() {
    fs.ensureDirSync(TEST_TEMP_DIR);

    process.chdir(TEST_TEMP_DIR);
}

xdescribe('As a user of the file templater module', function() {
    before(changeToOsTempDir);

    describe('When templating one file', function() {

        it('should be correctly template the contents', function() {

        })

    })
})

var chai = require('chai');
var os = require('os');
var fs = require('fs-extra');
var path = require('path');

var fileTemplater =  require('../../bin/fileTemplater')();

chai.use(require('chai-fs'));
chai.should();

var TEST_TEMP_DIR = path.join(os.tmpdir(), 'file-templater');

function changeToOsTempDirAndCopyFixtures() {
    fs.ensureDirSync(TEST_TEMP_DIR);
    fs.copySync(path.resolve('./', 'test', 'cli', 'fixtures'), TEST_TEMP_DIR);
    process.chdir(TEST_TEMP_DIR);
}

xdescribe('As a user of the file templater module', function() {
    before(changeToOsTempDir);

    describe('When templating one file', function() {

        it('should be correctly template the contents', function() {

        })

    })
})

Copy over fixtures to temp dir

test: Copy over fixtures to temp dir


const React = require("react");
const {connect} = require("react-redux");
const {selectSpotlight} = require("selectors/selectors");
const GroupedActivityFeed = require("components/ActivityFeed/ActivityFeed");
const Spotlight = require("components/Spotlight/Spotlight");
const {RequestMoreBookmarks} = require("common/action-manager").actions;
const LoadMore = require("components/LoadMore/LoadMore");

const TimelineBookmarks = React.createClass({
  getMore() {
    const bookmarks = this.props.Bookmarks.rows;
    if (!bookmarks.length) {
      return;
    }
    const beforeDate = bookmarks[bookmarks.length - 1].lastModified;
    this.props.dispatch(RequestMoreBookmarks(beforeDate));
  },
  render() {
    const props = this.props;
    return (<div className="wrapper">
      <Spotlight sites={props.Spotlight.rows} />
      <GroupedActivityFeed title="Just now" sites={props.Bookmarks.rows} length={20} dateKey="bookmarkDateCreated" />
      <LoadMore loading={props.Bookmarks.isLoading} hidden={!props.Bookmarks.canLoadMore || !props.Bookmarks.rows.length} onClick={this.getMore}
        label="See more activity"/>
    </div>);
  }
});

TimelineBookmarks.propTypes = {
  Spotlight: React.PropTypes.object.isRequired,
  Bookmarks: React.PropTypes.object.isRequired
};

module.exports = connect(state => {
  return {
    Spotlight: selectSpotlight(state),
    Bookmarks: state.Bookmarks
  };
})(TimelineBookmarks);

module.exports.TimelineBookmarks = TimelineBookmarks;

const React = require("react");
const {connect} = require("react-redux");
const GroupedActivityFeed = require("components/ActivityFeed/ActivityFeed");
const {RequestMoreBookmarks} = require("common/action-manager").actions;
const LoadMore = require("components/LoadMore/LoadMore");

const TimelineBookmarks = React.createClass({
  getMore() {
    const bookmarks = this.props.Bookmarks.rows;
    if (!bookmarks.length) {
      return;
    }
    const beforeDate = bookmarks[bookmarks.length - 1].lastModified;
    this.props.dispatch(RequestMoreBookmarks(beforeDate));
  },
  render() {
    const props = this.props;
    return (<div className="wrapper">
      <GroupedActivityFeed title="Just now" sites={props.Bookmarks.rows} length={20} dateKey="bookmarkDateCreated" />
      <LoadMore loading={props.Bookmarks.isLoading} hidden={!props.Bookmarks.canLoadMore || !props.Bookmarks.rows.length} onClick={this.getMore}
        label="See more activity"/>
    </div>);
  }
});

TimelineBookmarks.propTypes = {
  Bookmarks: React.PropTypes.object.isRequired
};

module.exports = connect(state => {
  return {
    Bookmarks: state.Bookmarks
  };
})(TimelineBookmarks);

module.exports.TimelineBookmarks = TimelineBookmarks;

Remove Highlights from Bookmarks page

fix(bookmarks): Remove Highlights from Bookmarks page


/* global angular, console */

import 'ionic-sdk/release/js/ionic.bundle';

angular.module('app.modals.select-account', [])
.controller('SelectAccountCtrl', function ($scope, Wallet) {
	'use strict';

	const network = $scope.data.network;

	$scope.cancel = cancel;
	$scope.select = select;

	$scope.accounts = getAccounts();
	$scope.heading	= $scope.data.heading;

	function getAccounts() {
		return Wallet.accountList
		.filter(item => item.network === network)
		.map(item => item.alias);
	}

	function cancel() {
		$scope.closeModalService();
	}

	function select(contact) {
		$scope.modalResolve(contact);
	}
});

/* global angular */

import 'ionic-sdk/release/js/ionic.bundle';

angular.module('app.modals.select-account', [])
.controller('SelectAccountCtrl', function ($scope, Wallet) {
	'use strict';

	const network = $scope.data.network;

	$scope.cancel = cancel;
	$scope.select = select;

	$scope.heading	= $scope.data.heading;

	const accountList = getAccounts();
	if ('filter' in $scope.data) {
		$scope.accounts = accountList.filter($scope.data.filter);
	} else {
		$scope.accounts = accountList;
	}

	function getAccounts() {
		return Wallet.accountList
		.filter(item => item.network === network)
		.map(item => item.alias);
	}

	function cancel() {
		$scope.closeModalService();
	}

	function select(contact) {
		$scope.modalResolve(contact);
	}
});

Add account filtering to modal

Add account filtering to modal


var gulp   = require("gulp");
var CFG    = require("./utils/config.js");
var $      = require("gulp-load-plugins")();
var exec   = require("child_process").exec;
var path   = require("path");
var notify = require("./utils/notify-style-lint");

/**
 * style:lint
 * @see github.com/causes/scss-lint
 * @see rubygems.org/gems/scss-lint
 */
gulp.task("style:lint", function (callback) {
  var scsslintProcess = exec("bundle " + [
    "exec",
    "scss-lint",
    "--config",
    path.join(CFG.FILE.config.styleLint),
    path.join(CFG.DIR.src, CFG.DIR.style)
  ].join(" "), function(err, stdout, stderr) {
    $.util.log("[style:lint] stdout:", stdout);
    $.util.log("[style:lint] stderr: ", stderr);

    notify(stdout);

    if(null !== err) {
      $.util.log("[style:lint] err: ", err);
    }

    callback();
  });
});

var gulp   = require("gulp");
var CFG    = require("./utils/config.js");
var $      = require("gulp-load-plugins")();
var exec   = require("child_process").exec;
var path   = require("path");
var notify = require("./utils/notify-style-lint");

/**
 * style:lint
 * @see github.com/causes/scss-lint
 * @see rubygems.org/gems/scss-lint
 */
gulp.task("style:lint", function (callback) {
  var scsslintProcess = exec([
    "bundle",
    "exec",
    "scss-lint",
    "--config",
    path.join(CFG.FILE.config.styleLint),
    path.join(CFG.DIR.src, CFG.DIR.style)
  ].join(" "), function(err, stdout, stderr) {
    $.util.log("[style:lint] stdout:", stdout);
    $.util.log("[style:lint] stderr: ", stderr);

    notify(stdout);

    if(null !== err) {
      $.util.log("[style:lint] err: ", err);
    }

    callback();
  });
});

Simplify arguments passed to exec.

Simplify arguments passed to exec.


'use strict';

(function() {
  angular
      .module('bitcoincom.controllers')
      .controller('buyBitcoinPurchaseHistoryController', purchaseHistoryController);

  function purchaseHistoryController($scope) {
    var vm = this;

    vm.history = [];

    $scope.$on('$ionicView.beforeEnter', _onBeforeEnter);

    function _initVariables() {
      vm.history = [];
      moonPayService.getTransactions().then(
        function onGetTransactionsSuccess(transactions) {
          vm.history = transactions;
        }
      );
    }

    function _onBeforeEnter() {
      _initVariables();
    }

  }
})();

'use strict';

(function() {
  angular
      .module('bitcoincom.controllers')
      .controller('buyBitcoinPurchaseHistoryController', purchaseHistoryController);

  function purchaseHistoryController(
    popupService,
    $scope, $ionicHistory
  ) {
    var vm = this;

    vm.history = [];

    $scope.$on('$ionicView.beforeEnter', _onBeforeEnter);

    function _initVariables() {
      vm.history = [];
      moonPayService.getTransactions().then(
        function onGetTransactionsSuccess(transactions) {
          vm.history = transactions;
        },
        function onGetTransactionsError(err) {
          var title = gettextCatalog.getString('Error Getting Transaction History');
          var message = err.message || gettextCatalog.getString('An error occurred when getting your transaction history.');
          var okText = gettextCatalog.getString('Go Back');
          popupService.showAlert(title, message,
            function onAlertDismissed() {
              $ionicHistory.goBack();
            },
            okText
          );
        }
      );
    }

    function _onBeforeEnter() {
      _initVariables();
    }

  }
})();

Handle error for the purchase history

Handle error for the purchase history


import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';

import { regular } from './icon-definitions';

const StyledIcon = styled.i`
  display: inline-block;
  font-family: ${props => props.fontFamily} !important;
  font-size: ${props => props.fontSize};
  font-style: normal;
  font-weight: inherit;
  font-variant: normal;
  speak: none;
  text-decoration: none;
  text-transform: none;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  vertical-align: text-bottom;
  line-height: 1;

  &:before {
    content: "\\e${props => props.content}";
  }
`;

function Icon({ name, size, ...other }) {
  const iconName = `im-icon-${name}`;

  const styledIconProps = {
    content: regular[iconName],
    fontFamily: 'indv-mkt-icons',
    fontSize: size !== undefined ? `${size}px` : 'inherit'
  };

  return <StyledIcon aria-hidden {...styledIconProps} {...other} />;
}

Icon.propTypes = {
  /** Name of the icon to display */
  name: PropTypes.string.isRequired,
  /** Specify icon size in pixels */
  size: PropTypes.number
};

Icon.defaultProps = {
  size: undefined
};

export default Icon;

import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';

import { regular } from './icon-definitions';

const StyledIcon = styled.i`
  display: inline-block;
  font-family: ${props => props.fontFamily} !important;
  font-size: ${props => props.fontSize};
  font-style: normal;
  font-weight: inherit;
  font-variant: normal;
  line-height: 1;
  speak: none;
  text-decoration: none;
  text-transform: none;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  vertical-align: text-bottom;

  &:before {
    content: "\\e${props => props.content}";
  }
`;

function Icon({ name, size, ...other }) {
  const iconName = `im-icon-${name}`;

  const styledIconProps = {
    content: regular[iconName],
    fontFamily: 'indv-mkt-icons',
    fontSize: size !== undefined ? `${size}px` : 'inherit'
  };

  return <StyledIcon aria-hidden {...styledIconProps} {...other} />;
}

Icon.propTypes = {
  /** Name of the icon to display */
  name: PropTypes.string.isRequired,
  /** Specify icon size in pixels */
  size: PropTypes.number
};

Icon.defaultProps = {
  size: undefined
};

export default Icon;

Fix: Move line-height property to correct location


describe('Chosen options: inherit-select-classes', function () {
  beforeEach(function () {
    chosenSelectHelper.compileWithAttributes({
      inheritSelectClasses: true,
      'class': 'inherited-class'
    });
  });

  it('passes classes from the select tag to the chosen container', function () {
    expect(chosenSelectHelper.chosenContainer().hasClass('inherited-class')).toBeTruthy();
  });
});

describe('Chosen options: inherit-select-classes', function () {

  it('passes classes from the select tag to the chosen container', function () {
    chosenSelectHelper.compileWithAttributes({
      inheritSelectClasses: true,
      'class': 'inherited-class'
    });

    expect(chosenSelectHelper.chosenContainer().hasClass('inherited-class')).toBeTruthy();
  });

  it('passes classes from the select tag to the chosen container', function () {
    chosenSelectHelper.compileWithAttributes({
      inheritSelectClasses: false,
      'class': 'inherited-class'
    });

    expect(chosenSelectHelper.chosenContainer().hasClass('inherited-class')).toBeFalsy();
  });
});

Make sure false don't add the class

test(inherit-select-classes): Make sure false don't add the class


import React from 'react';

import { shallow, mount } from 'enzyme';
import Immutable from 'immutable';
import {expect} from 'chai';

import CodeCell from '../../../../src/notebook/components/cell/code-cell';
import * as commutable from 'commutable';
import { displayOrder, transforms } from 'transformime-react';

const sharedProps = { displayOrder, transforms };
describe('CodeCell', () => {
  it('can be rendered', () => {
    const cell = shallow(
      <CodeCell cell={commutable.emptyCodeCell} {...sharedProps}
      cellStatus={Immutable.Map({'outputHidden': false, 'inputHidden': false})}/>
    );
    expect(cell).to.not.be.null;
  });
  it('creates an editor', () => {
    const cell = mount(
      <CodeCell cell={commutable.emptyCodeCell} {...sharedProps}
      cellStatus={Immutable.Map({'outputHidden': false, 'inputHidden': false})}/>
    );
    expect(cell.find('.input').length).to.be.greaterThan(0);
  });
});

import React from 'react';

import { shallow, mount } from 'enzyme';
import Immutable from 'immutable';
import {expect} from 'chai';

import CodeCell from '../../../../src/notebook/components/cell/code-cell';
import * as commutable from 'commutable';
import { displayOrder, transforms } from 'transformime-react';

const sharedProps = { displayOrder, transforms };
describe('CodeCell', () => {
  it('can be rendered', () => {
    const cell = shallow(
      <CodeCell cell={commutable.emptyCodeCell} {...sharedProps}
      cellStatus={Immutable.Map({'outputHidden': false, 'inputHidden': false})}/>
    );
    expect(cell).to.not.be.null;
  });
  it('creates an editor', () => {
    const cell = mount(
      <CodeCell cell={commutable.emptyCodeCell} {...sharedProps}
      cellStatus={Immutable.Map({'outputHidden': false, 'inputHidden': false})}/>
    );
    expect(cell.find('.input').length).to.be.greaterThan(0);
  });
  it('creates a pager', () => {
    const cell = mount(
      <CodeCell cell={commutable.emptyCodeCell} {...sharedProps}
      cellStatus={Immutable.Map({'outputHidden': false, 'inputHidden': false})}
      pagers={Immutable.Map({'content':
        Immutable.Map({'data': Immutable.Map({'text/plain': 'one'})})})}/>
    );
    expect(cell.find('.pagers').length).to.be.greaterThan(0);
  });

});

Add more tests for CodeCell

Add more tests for CodeCell


describe("About Functions", function () {

  describe("Default Parameters", function () {

    it("should understand default parameters basic usage", function () {

      function greeting(string = 'party') {
        return 'Welcome to the ' + string + ' pal!';
      }

      expect(greeting()).toEqual(FILL_ME_IN);
      expect(greeting('get together')).toEqual(FILL_ME_IN);
      expect(undefined).toEqual(FILL_ME_IN);
      expect(null).toEqual(FILL_ME_IN);

      function getDefaultValue() {
        return 'party';
      }

      function greetingAgain(string = getDefaultValue()) {
        return 'Welcome to the ' + string + ' pal!';
      }

      expect(greetingAgain()).toEqual(FILL_ME_IN);
    });
  });

  // describe('Rest Paramaters', function() {
  //
  // })
  //
  // describe('Spread Parameters', function() {
  //
  // })
  //
  // describe('Arrow Functions', function() {
  //
  // })
});

describe("About Functions", function () {

  describe("Default Parameters", function () {

    it("should understand default parameters basic usage", function () {

      function greeting(string = 'party') {
        return 'Welcome to the ' + string + ' pal!';
      }

      expect(greeting()).toEqual(FILL_ME_IN);
      expect(greeting('get together')).toEqual(FILL_ME_IN);
      expect(undefined).toEqual(FILL_ME_IN);
      expect(null).toEqual(FILL_ME_IN);

      function getDefaultValue() {
        return 'party';
      }

      function greetingAgain(string = getDefaultValue()) {
        return 'Welcome to the ' + string + ' pal!';
      }

      expect(greetingAgain()).toEqual(FILL_ME_IN);
    });
  });

  // describe('Rest Paramaters', function() {
  //
  // })
  //
  // describe('Spread Parameters', function() {
  //
  // })
  //
  describe('Arrow Functions', function() {

    it('shound understand basic usage', function() {

      var arrowFn = () => { 'hello world!'; };

      expect(arrowFn()).toEqual(FILL_ME_IN);

      var arrowFnTwo = (number) => { number * 2; }

      expect(arrowFnTwo(4)).toEqual(FILL_ME_IN);

    })

  })
});

Add initial arrow function test

feat: Add initial arrow function test


'use strict'

// Code thanks to MDN
export function storageAvailable (type) {
  try {
    let storage = window[type]
    let x = '__storage_test__'
    storage.setItem(x, x)
    storage.removeItem(x)
    return true
  } catch (e) {
    let storage = window[type]
    return e instanceof DOMException && (
      // everything except Firefox
      e.code === 22 ||
      // Firefox
      e.code === 1014 ||
      // test name field too, because code might not be present
      // everything except Firefox
      e.name === 'QuotaExceededError' ||
      // Firefox
      e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
      // acknowledge QuotaExceededError only if there's something already stored
      storage.length !== 0
  }
}

function storagePopulated () {
  if (Storage.length !== 0) {
    console.log('populated')
    return true
  }
}

'use strict'

// Code thanks to MDN
export function storageAvailable (type) {
  try {
    let storage = window[type]
    let x = '__storage_test__'
    storage.setItem(x, x)
    storage.removeItem(x)
    return true
  } catch (e) {
    let storage = window[type]
    return e instanceof DOMException && (
      // everything except Firefox
      e.code === 22 ||
      // Firefox
      e.code === 1014 ||
      // test name field too, because code might not be present
      // everything except Firefox
      e.name === 'QuotaExceededError' ||
      // Firefox
      e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
      // acknowledge QuotaExceededError only if there's something already stored
      storage.length !== 0
  }
}

export function storagePopulated () {
  if (window.localStorage.length !== 0) {
    console.log('populated')
    return true
  }
}

export function populateStorage () {
  window.localStorage.setItem('pomodoroTime', document.getElementById('pomodoroInput').value)
  window.localStorage.setItem('shortBreakTime', document.getElementById('shortBreakInput').value)
  window.localStorage.setItem('longBreakTime', document.getElementById('longBreakInput').value)
}

Add function to populate storage

Add function to populate storage


import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Box, Tabs, Tab } from 'grommet';
import { Map } from 'react-d3-map';
import { Table } from '../components';
import { deviceActions } from '../actions';

const mapConf = {
  width: 1400,
  height: 600,
  scale: 4000,
  scaleExtent: [1 << 12, 1 << 13],
  center: [111, 36]
};

class Device extends Component {
  componentDidMount() {
    this.props.actions.loadDevices();
  }

  render() {
    const { records } = this.props;
    return (
      <Box pad="small">
        <Tabs>
          <Tab title="Location">
            <Box size="large">
              <Map {...mapConf}/>
            </Box>
            <Table data={records}/>
          </Tab>
          <Tab title="Model" />
          <Tab title="Customer" />
        </Tabs>
      </Box>
    );
  }
}

let mapStateToProps = (state) => {
  return {
    records: state.device.records
  };
};

let mapDispatchProps = (dispatch) => ({
  actions: bindActionCreators(deviceActions, dispatch)
});

export default connect(mapStateToProps, mapDispatchProps)(Device);

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Box, Tabs, Tab } from 'grommet';
import { Map, MarkerGroup } from 'react-d3-map';
import { Table } from '../components';
import { deviceActions } from '../actions';

const mapConf = {
  width: 1400,
  height: 600,
  scale: 4000,
  scaleExtent: [1 << 12, 1 << 13],
  center: [111, 36]
};

class Device extends Component {
  componentDidMount() {
    this.props.actions.loadDevices();
  }

  renderMarkerGroup(records) {
    return records.map((record) => {
      var data = {
          "type": "Feature",
          "properties": {
            "text": record.name
          },
          "geometry": {
              "type": "Point",
              "coordinates": [record.longitude, record.latitude]
          }
      }
      return <MarkerGroup data={data}/>
    });
  }

  render() {
    const { records } = this.props;
    return (
      <Box pad="small">
        <Tabs>
          <Tab title="Location">
            <Box size="large">
              <Map {...mapConf}>
                { this.renderMarkerGroup(records) }
              </Map>
            </Box>
            <Table data={records}/>
          </Tab>
          <Tab title="Model" />
          <Tab title="Customer" />
        </Tabs>
      </Box>
    );
  }
}

let mapStateToProps = (state) => {
  return {
    records: state.device.records
  };
};

let mapDispatchProps = (dispatch) => ({
  actions: bindActionCreators(deviceActions, dispatch)
});

export default connect(mapStateToProps, mapDispatchProps)(Device);

Add MarkerGroup in Map by device geo info


export default {
  'id': -9,
  'title': 'EOC Pre Campaign',
  'dashboardType': 'EocCampaign',
  'builtin': true,
  'charts': [
    {
      'title': 'tableData',
      'type': 'TableChart',
      'indicators': [2, 5, 6, 11, 12, 18, 22, 24, 25, 26, 28, 1],
      'groupBy': 'indicator',
      'timeRange': {
        months: 0
      }
      // 'yFormat': ',.0f',
      // 'xFormat': ',.0f'
    }, {
      'title': 'trendData',
      'type': 'LineChart',
      'indicators': [21],
      'timeRange': {
        months: 12
      }
    }, {
      'title': 'mapData',
      'type': 'ChoroplethMap',
      'locations': 'sublocations',
      'timeRange': 0,
      'indicators': [21]
    }
  ]
}

export default {
  'id': -9,
  'title': 'EOC Pre Campaign',
  'dashboardType': 'EocCampaign',
  'builtin': true,
  'charts': [
    {
      'title': 'tableData',
      'type': 'TableChart',
      'indicators': [28, 1, 2, 5, 6, 11, 12, 18, 22, 24, 25, 26],
      'groupBy': 'indicator',
      'timeRange': {
        months: 0
      }
      // 'yFormat': ',.0f',
      // 'xFormat': ',.0f'
    }, {
      'title': 'trendData',
      'type': 'LineChart',
      'indicators': [21],
      'timeRange': {
        months: 12
      }
    }, {
      'title': 'mapData',
      'type': 'ChoroplethMap',
      'locations': 'sublocations',
      'timeRange': 0,
      'indicators': [21]
    }
  ]
}

Put LPD status as first in pre-campaign dashboard


import Em from 'ember';

export default Em.Component.extend({
  classNames: ['button-select'],
  buttons: null,
  selected: null,

  setup: Em.on('init', function() {
    this.buttons = [];
  }),

  actions: {
    select: function(button) {
      this.set('selected', button.get('value'));
    },

    register: function(button) {
      this.buttons.addObject(button);
    },

    unregister: function(button) {
      // how should we handle the case where the object being destroyed
      // is the selected button?
      this.buttons.removeObject(button);
    }
  }
});

import Em from 'ember';

export default Em.Component.extend({
  classNames: ['button-select'],
  buttons: null,
  selected: null,

  setup: Em.on('init', function() {
    this.buttons = Em.A();
  }),

  actions: {
    select: function(button) {
      this.set('selected', button.get('value'));
    },

    register: function(button) {
      this.buttons.addObject(button);
    },

    unregister: function(button) {
      // how should we handle the case where the object being destroyed
      // is the selected button?
      this.buttons.removeObject(button);
    }
  }
});

Use Em.A rather than [] to allow for unextended prototypes


var express = require('express');

var app = express();

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

app.get('/', function (request, response) {
    response.send('Hello, world.');
})

var server = app.listen(8080, function () {
    var host = server.address().address;
    var port = server.address().port;

    console.log('Server listening on http://%s:%s', host, port);
})

var express = require('express');

var app = express();

app.use('/static', express.static('resources'));

app.get('/', function (request, response) {
    response.send('Hello, world.');
})

var server = app.listen(8080, function () {
    var host = server.address().address;
    var port = server.address().port;

    console.log('Server listening on http://%s:%s', host, port);
})

Create virtual path for static resources


var OpenLayersPlusLegend = function(opts) {
    var self = this;
    this.map = $(opts[0]).data('map');

    this.setLegend = function(layer) {
        // The layer param may vary based on the context from which we are called.
        layer = layer.object ? layer.object : layer;
        if ('legend' in layer) {
            var legend_content = layer.legend || 'your mother';
            var legends = $('div.openlayers-legends', self.map.div);
            if (layer.visibility && !('legendDiv' in layer)) {
                layer.legendDiv = $("

").append(legend_content);
                legends.append(layer.legendDiv);
            }
            else if (!layer.visibility && ('legendDiv' in layer)) {
                layer.legendDiv.remove();
                delete layer.legendDiv;
            }
        }
    };

    for (i in this.map.layers) {
        var layer = this.map.layers[i];
        if (!$('div.openlayers-legends', self.map.div).size()) {
            $(self.map.div).append("

");
        }
        layer.events.register('visibilitychanged', layer, self.setLegend);
        self.setLegend(layer);
    }
};

var OpenLayersPlusLegend = function(opts) {
    if (opts == null) {
        return;
    }
    var self = this;
    this.map = $(opts).data('map');

    this.setLegend = function(layer) {
        // The layer param may vary based on the context from which we are called.
        layer = layer.object ? layer.object : layer;
        if ('legend' in layer) {
            var legend_content = layer.legend || 'your mother';
            var legends = $('div.openlayers-legends', self.map.div);
            if (layer.visibility && !('legendDiv' in layer)) {
                layer.legendDiv = $("

").append(legend_content);
                legends.append(layer.legendDiv);
            }
            else if (!layer.visibility && ('legendDiv' in layer)) {
                layer.legendDiv.remove();
                delete layer.legendDiv;
            }
        }
    };

    for (i in this.map.layers) {
        var layer = this.map.layers[i];
        if (!$('div.openlayers-legends', self.map.div).size()) {
            $(self.map.div).append("

");
        }
        layer.events.register('visibilitychanged', layer, self.setLegend);
        self.setLegend(layer);
    }
};

Allow legend behavior to be called without arguments.


export const parameters = {
  actions: { argTypesRegex: "^on[A-Z].*" },
}

import '@instructure/canvas-theme'

import React from 'react'
import {ApplyTheme} from '@instructure/ui-themeable'
import '@instructure/canvas-high-contrast-theme'
import '@instructure/canvas-theme'

export const parameters = {
  actions: { argTypesRegex: "^on[A-Z].*" },
}

export const globalTypes = {
  canvasTheme: {
    name: 'Canvas Theme',
    description: 'Default or High Contrast',
    defaultValue: 'canvas',
    toolbar: {
      icon: 'user',
      items: ['canvas', 'canvas-high-contrast']
    }
  }
}

const canvasThemeProvider = (Story, context) => {
  const canvasTheme = context.globals.canvasTheme
  return (
    <ApplyTheme theme={ApplyTheme.generateTheme(canvasTheme)}>
      <Story {...context}/>
    </ApplyTheme>
  )
}

export const decorators = [canvasThemeProvider]

Add high contrast to storybook

Add high contrast to storybook

closes OUT-4252

flag=none

Test-plan: - Click user icon in global toolbar
 - Switch between 'canvas' and 'canvas-high-contrast'
 - Verify that components display in expected contrast
 - Verify that all stories are rendering as expected and that
   'canvas' theme option displays stories as they were before
   this PS
Change-Id: I0d139e228ee01f2c3bc45d6b20e69acce88cfae1
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/258545
Tested-by: Service Cloud Jenkins <9144042a601061f88f1e1d7a1753ea3e2972119d@instructure.com>
Reviewed-by: Manoel Quirino <2f47f75c3182f071e1410d24f7ab0d255a1149f4@instructure.com>
Reviewed-by: Michael Brewer-Davis <785693f4c81267e5b88c0f98d39b56a39a2b6910@instructure.com>
QA-Review: Chrystal Langston <d2663071bf6fe682e3f489a1b66462ff19cc17ab@instructure.com>
Product-Review: Jody Sailor


BEST.module('arkady.pevzner:timeline-example', {
    behaviors: {
        '#ui-el' : {
            size: [100, 100],
            style:  function(myStyle) {
                return myStyle;
            },
            position: function(myTimeline, time) {
                return BEST.helpers.timeline(myTimeline)(time);
            }
        }
    },
    events: {
        '#ui-el' : {
            'famous:events:click' : function($state) {
                $state.set('time', 2000, {duration: 2000});
            }
        }
    },
    states: {
        time: 0,
        myTimeline: [
            [0, [100, 100], 'outExo'],
            [500, [600, 100], 'inExpo'],
            [1000, [600, 600], 'inOutBack'],
            [2000, [100, 100]]
        ],
        myStyle : {
            'background-color' : 'black',
            'text-align' : 'center',
            'line-height' : '100px',
            'color' : 'white',
            'cursor' : 'pointer'
        }
    },
    tree: 'timeline-example.html',
})
.config({
    imports: {
        'famous:core': ['ui-element']
    }
});

BEST.module('arkady.pevzner:timeline-example', {
    behaviors: {
        '#ui-el' : {
            size: [100, 100],
            style:  function(myStyle) {
                return myStyle;
            },
            position: function(myTimeline, time) {
                return BEST.helpers.piecewise(myTimeline)(time);
            }
        }
    },
    events: {
        '#ui-el' : {
            'famous:events:click' : function($state) {
                $state.set('time', 2000, {duration: 2000});
            }
        }
    },
    states: {
        time: 0,
        myTimeline: [
            [0, [100, 100], 'outExo'],
            [500, [600, 100], 'inExpo'],
            [1000, [600, 600], 'inOutBack'],
            [2000, [100, 100]]
        ],
        myStyle : {
            'background-color' : 'black',
            'text-align' : 'center',
            'line-height' : '100px',
            'color' : 'white',
            'cursor' : 'pointer'
        }
    },
    tree: 'timeline-example.html',
})
.config({
    imports: {
        'famous:core': ['ui-element']
    }
});

Convert a module's call to BEST.helpers.timeline -> BEST.helpers.piecewise


/*
    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 Vue from 'vue'
import App from './App'
import BootstrapVue from 'bootstrap-vue'
import '@/common/assets/css/bootstrap-theme.min.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import store from './store'
import ApiService from '@/common/services/ApiService.js'

ApiService.init()

Vue.use(BootstrapVue)

/* eslint-disable no-new */
new Vue({
  el: '#app',
  store,
  components: { App },
  template: '<App/>'
})

/*
    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 'babel-polyfill'
import Vue from 'vue'
import App from './App'
import BootstrapVue from 'bootstrap-vue'
import '@/common/assets/css/bootstrap-theme.min.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import store from './store'
import ApiService from '@/common/services/ApiService.js'

ApiService.init()

Vue.use(BootstrapVue)

/* eslint-disable no-new */
new Vue({
  el: '#app',
  store,
  components: { App },
  template: '<App/>'
})

Add babel-polyfill to aquifers SPA

Add babel-polyfill to aquifers SPA


function getOpeningHoursMessage(status) {
  if (status.isOpen) {
    const closedNext = status.until;
    const closedTime = closedNext.format('h:mm a');
    const closedDay = closedNext.calendar(status.moment, {
      sameDay: '[today]',
      nextDay: '[tomorrow]',
      nextWeek: 'dddd',
      lastDay: '[yesterday]',
      lastWeek: '[last] dddd',
      sameElse: 'DD/MM/YYYY',
    });
    return (
      ((closedDay === 'tomorrow' && closedTime === '12:00 am')
        || (closedDay === 'today' && closedTime === '11:59 pm')) ?
        'Open until midnight' :
        `Open until ${closedTime} ${closedDay}`);
  }
  const openNext = status.until;
  if (!openNext) {
    return 'Call for opening times.';
  }
  const timeUntilOpen = openNext.diff(status.moment, 'minutes');
  const openDay = openNext.calendar(status.moment, {
    sameDay: '[today]',
    nextDay: '[tomorrow]',
    nextWeek: 'dddd',
    lastDay: '[yesterday]',
    lastWeek: '[last] dddd',
    sameElse: 'DD/MM/YYYY',
  });
  return (
    (timeUntilOpen <= 60) ?
      `Opening in ${timeUntilOpen} minutes` :
      `Closed until ${openNext.format('h:mm a')} ${openDay}`);
}

module.exports = getOpeningHoursMessage;

function closesAtMidnightToday(closedDay, closedTime) {
  return (
    (closedDay === 'tomorrow' && closedTime === '12:00 am')
    || (closedDay === 'today' && closedTime === '11:59 pm')
  );
}

function getOpeningHoursMessage(status) {
  if (status.isOpen) {
    const closedNext = status.until;
    const closedTime = closedNext.format('h:mm a');
    const closedDay = closedNext.calendar(status.moment, {
      sameDay: '[today]',
      nextDay: '[tomorrow]',
      nextWeek: 'dddd',
      lastDay: '[yesterday]',
      lastWeek: '[last] dddd',
      sameElse: 'DD/MM/YYYY',
    });
    return (
      closesAtMidnightToday(closedDay, closedTime) ?
        'Open until midnight' :
        `Open until ${closedTime} ${closedDay}`);
  }
  const openNext = status.until;
  if (!openNext) {
    return 'Call for opening times.';
  }
  const timeUntilOpen = openNext.diff(status.moment, 'minutes');
  const openDay = openNext.calendar(status.moment, {
    sameDay: '[today]',
    nextDay: '[tomorrow]',
    nextWeek: 'dddd',
    lastDay: '[yesterday]',
    lastWeek: '[last] dddd',
    sameElse: 'DD/MM/YYYY',
  });
  return (
    (timeUntilOpen <= 60) ?
      `Opening in ${timeUntilOpen} minutes` :
      `Closed until ${openNext.format('h:mm a')} ${openDay}`);
}

module.exports = getOpeningHoursMessage;

Add method for describing intent

:book: Add method for describing intent


var isTestling = typeof __testlingConsole != 'undefined';
var remoteSignaller = '//switchboard.rtc.io';
// var remoteSignaller = 'http://localhost:3000';

module.exports = {
  signallingServer: isTestling ? location.origin : remoteSignaller
};

var isTestling = typeof __testlingConsole != 'undefined';
var remoteSignaller = '//switchboard.rtc.io';
// var remoteSignaller = 'http://localhost:3000';

module.exports = {
  signallingServer: require('./signaling-server') // isTestling ? location.origin : remoteSignaller
};

Connect to the testing signaling server

Connect to the testing signaling server


const knex = require('knex')
const config = require('server/services/config')

var defaults = {
  pool: {
    min: 2,
    max: 10
  },
  migrations: {
    tableName: 'schema_migrations',
    directory: '/'
  },
  seeds: {
    directory: '/'
  }
}

module.exports = knex({
  ...defaults,
  ...config.database
})

const knex = require('knex')
const config = require('server/services/config')

var defaults = {
  pool: {
    min: 2,
    max: 10
  },
  migrations: {
    tableName: 'schema_migrations',
    directory: '/'
  },
  seeds: {
    directory: '/'
  }
}

module.exports = knex(Object.assign({}, defaults, config.database))

Replace object spread operator with Object.assign