"use strict";

const TagPair = require('../../theme/renderable/tagPair');
const Registry = require('../../../util/registry');
const merge = require('../../../util/merge');

class Container extends TagPair {
  constructor(context) {
    super();
    this.context = context;
    this.message = {};
  }

  set(id, content, type = 'default') {
    if (typeof this.message[type] === 'undefined') {
      this.message[type] = new Registry();
    }
    this.message[type].set({id, content});
    return this;
  };

  commit() {
    // TODO: Each message should be its own renderable.
    let contentBlock = '';
    for (let type in this.message) {
      let content = '';
      for (let message of this.message[type].getOrderedElements()) {
        content += `<div class="message ${message.id}">${message.content}</div>`;
      }
      contentBlock += `<div class="message-block ${type}">${content}</div>`;
      // Clear the messages.
      delete this.message[type];
    }
    merge(this.data, {
      attributes: {
        class: ['messages'],
      },
      content: contentBlock,
    });
    return super.commit();
  }
}

Container.templateFile = __dirname + '/../html/container.html';
Container.variables = [
  'attributes',
  'content',
];

module.exports = Container;

"use strict";

const TagPair = require('../../theme/renderable/tagPair');
const Registry = require('../../../util/registry');
const merge = require('../../../util/merge');

class Container extends TagPair {
  constructor(context) {
    super();
    this.context = context;
    this.messageRegistryMap = {};
  }

  set(id, content, type = 'default') {
    if (typeof this.messageRegistryMap[type] === 'undefined') {
      this.messageRegistryMap[type] = new Registry();
    }
    this.messageRegistryMap[type].set({id, content});
    return this;
  };

  commit() {
    // TODO: Each message should be its own renderable.
    let contentBlock = '';
    for (let type in this.messageRegistryMap) {
      let content = '';
      for (let message of this.messageRegistryMap[type].getOrderedElements()) {
        content += `<div class="message ${message.id}">${message.content}</div>`;
      }
      contentBlock += `<div class="message-block ${type}">${content}</div>`;
      // Clear the messages.
      delete this.messageRegistryMap[type];
    }
    merge(this.data, {
      attributes: {
        class: ['messages'],
      },
      content: contentBlock,
    });
    return super.commit();
  }
}

Container.templateFile = __dirname + '/../html/container.html';
Container.variables = [
  'attributes',
  'content',
];

module.exports = Container;

Rename Message Container.message to Container.messageRegistryMap


import { Projects } from '../../../../lib/collections/projects.js';

/*****************************************************************************/
/* DiscussionButtons: Event Handlers */
/*****************************************************************************/
Template.DiscussionButtons.events({
    // Open the modal that enable the discussion
    'click .open-discussion': function(event, template) {
        event.preventDefault();
        // Launch modal
        Modal.show('DiscussModal', function() {
            var thisProjectID = Projects.findOne({ 'discussions': {'$in': [template.data._id]} })._id;
            Session.set('thisProject', thisProjectID);
            Session.set('discussionToShow', thisProjectID + "-" + template.data.attachedTo);
            console.log("t",template);
            console.log("room",Session.get('discussionToShow'));
            return {
                "discussionId": template.data._id
            }
        });
    },
});

/*****************************************************************************/
/* DiscussionButtons: Helpers */
/*****************************************************************************/
Template.DiscussionButtons.helpers({
});

/*****************************************************************************/
/* DiscussionButtons: Lifecycle Hooks */
/*****************************************************************************/
Template.DiscussionButtons.onCreated(function () {
    Meteor.subscribe('projects');
    // Load variables
    //thisDiscussionID = this.data._id;
    //console.log("JU", Projects.findOne({ 'discussions': {'$in': [this.data._id]} }) );
    //thisProjectID2 = Projects.findOne({ 'discussions': thisDiscussionID }).projectId;
    // Set variables
    //Session.set('thisProject', thisProjectID);
    //console.log("ap",thisProjectID2);
});

Template.DiscussionButtons.onRendered(function () {
});

Template.DiscussionButtons.onDestroyed(function () {
});

import { Projects } from '../../../../lib/collections/projects.js';
import { Discussions } from '../../../../lib/collections/discussions.js';

/*****************************************************************************/
/* DiscussionButtons: Event Handlers */
/*****************************************************************************/
Template.DiscussionButtons.events({
    // Open the modal that enable the discussion
    'click .open-discussion': function(event, template) {
        event.preventDefault();
        // Launch modal
        Modal.show('DiscussModal', function() {
            var thisProjectID = Projects.findOne({ 'discussions': {'$in': [template.data._id]} })._id;
            var thisDiscussion = Discussions.findOne({ '_id': template.data._id});
            Session.set('thisProject', thisProjectID);
            Session.set('discussionToShow', thisProjectID + "-" + thisDiscussion.attachedTo);
            return {
                "discussionId": template.data._id
            }
        });
    },
});

/*****************************************************************************/
/* DiscussionButtons: Helpers */
/*****************************************************************************/
Template.DiscussionButtons.helpers({
});

/*****************************************************************************/
/* DiscussionButtons: Lifecycle Hooks */
/*****************************************************************************/
Template.DiscussionButtons.onCreated(function () {
    Meteor.subscribe('projects');
    Meteor.subscribe('discussions');
});

Template.DiscussionButtons.onRendered(function () {
});

Template.DiscussionButtons.onDestroyed(function () {
});

Fix discussion in modal from tabular


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

export default class ColumnHeader extends React.PureComponent {

  static propTypes = {
    icon: PropTypes.string,
    type: PropTypes.string,
    active: PropTypes.bool,
    onClick: PropTypes.func,
    columnHeaderId: PropTypes.string,
  };

  handleClick = () => {
    this.props.onClick();
  }

  render () {
    const { type, active, columnHeaderId } = this.props;

    let icon = '';

    if (this.props.icon) {
      icon = <i className={`fa fa-fw fa-${this.props.icon} column-header__icon`} />;
    }

    return (
      <div role='heading' tabIndex='0' className={`column-header ${active ? 'active' : ''}`} onClick={this.handleClick} id={columnHeaderId || null}>
        {icon}
        {type}
      </div>
    );
  }

}

import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

export default class ColumnHeader extends React.PureComponent {

  static propTypes = {
    icon: PropTypes.string,
    type: PropTypes.string,
    active: PropTypes.bool,
    onClick: PropTypes.func,
    columnHeaderId: PropTypes.string,
  };

  handleClick = () => {
    this.props.onClick();
  }

  render () {
    const { icon, type, active, columnHeaderId } = this.props;
    let iconElement = '';

    if (icon) {
      iconElement = <i className={`fa fa-fw fa-${icon} column-header__icon`} />;
    }

    return (
      <h1 className={classNames('column-header', { active })} id={columnHeaderId || null}>
        <button onClick={this.handleClick}>
          {iconElement}
          {type}
        </button>
      </h1>
    );
  }

}

Fix style of legacy column headers


import gulp from 'gulp';
import babel from 'gulp-babel';
import nodemon from 'gulp-nodemon';
import mocha from 'gulp-mocha';
import istanbul from 'gulp-babel-istanbul';
import injectModules from 'gulp-inject-modules';

gulp.task('start', () => {
  nodemon({
    script: 'index.js',
    env: { NODE_ENV: 'development' }
  });
});

gulp.task('coverage', function (cb) {
  gulp.src('src/**/*.js')
    .pipe(istanbul())
    .pipe(istanbul.hookRequire())
    .on('finish', () => {
      gulp.src('test/**/*.js')
        .pipe(babel())
        .pipe(injectModules())
        .pipe(mocha())
        .pipe(istanbul.writeReports())
        .pipe(istanbul.enforceThresholds({ thresholds: { global: 90 } }))
        .on('end');
    });
});

import gulp from 'gulp';
import babel from 'gulp-babel';
import nodemon from 'gulp-nodemon';
import mocha from 'gulp-mocha';
import istanbul from 'gulp-babel-istanbul';
import injectModules from 'gulp-inject-modules';

gulp.task('start', () => {
  nodemon({
    script: 'index.js',
    env: { NODE_ENV: 'development' }
  });
});

gulp.task('coverage', (cb) => {
  gulp.src('src/**/*.js')
    .pipe(istanbul())
    .pipe(istanbul.hookRequire())
    .on('finish', () => {
      gulp.src('test/**/*.js')
        .pipe(babel())
        .pipe(injectModules())
        .pipe(mocha())
        .pipe(istanbul.writeReports())
        .pipe(istanbul.enforceThresholds({ thresholds: { global: 90 } }))
        .on('end', cb);
    });
});

Refactor code using ES6 syntax


const config = require('./config');
const path = require('path');
// transpile mapbox-gl utils imported by react-map-gl@2
// these files are ES6 and break UglifyJS
config.module.rules.push({
  test: /node_modules\/mapbox-gl\/js.*\.js$/,
  loader: 'babel-loader'
});

module.exports = Object.assign(config, {

  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js'
  }

});

const config = require('./config');
const path = require('path');
// transpile mapbox-gl utils imported by react-map-gl@2
// these files are ES6 and break UglifyJS
config.module.rules.push({
  test: /node_modules\/mapbox-gl\/js.*\.js$/,
  loader: 'babel-loader'
});

module.exports = Object.assign(config, {

  output: {
    path: path.resolve(__dirname, '../dist'),
    filename: 'bundle.js'
  }

});

Fix output path for bundle.js


import React, { Component } from 'react';
import Promise from 'bluebird';
import FontAwesome from 'react-fontawesome';
import quotes from './quotes.json';
import styles from './jumboQuote.css';

class JumboQuote extends Component {
  constructor(props) {
    super(props);

    this.state = {
      quote: {}
    };
  }

  componentDidMount() {
    // TODO: Pull this from an API with fetch
    Promise.try(
      () => quotes.quotes[Math.floor(Math.random() * quotes.quotes.length)]
    ).then((quote) => {
      this.setState({ quote });
    });
  }

  render() {
    return (

          <FontAwesome name="quote-left" className={styles.quoteIcon} />
          {this.state.quote.body}
          <FontAwesome name="quote-right" className={styles.quoteIcon} />

{this.state.quote.author}

    );
  }
}

export default JumboQuote;

import React, { Component } from 'react';
import Promise from 'bluebird';
import FontAwesome from 'react-fontawesome';
import quotes from './quotes.json';
import styles from './jumboQuote.css';

class JumboQuote extends Component {
  constructor(props) {
    super(props);

    this.state = {
      quote: {}
    };
  }

  componentDidMount() {
    // TODO: Pull this from an API with fetch
    // (kylemh: recommend axios if using promises) source: https://daveceddia.com/ajax-requests-in-react/
    Promise.try(
      () => quotes.quotes[Math.floor(Math.random() * quotes.quotes.length)]
    ).then((quote) => {
      this.setState({ quote });
    });
  }

  render() {
    return (

          <FontAwesome name="quote-left" className={styles.quoteIcon} />
          {this.state.quote.body}
          <FontAwesome name="quote-right" className={styles.quoteIcon} />

{this.state.quote.author}

    );
  }
}

export default JumboQuote;

Add note on TODO for handling quotes via API


const awsCredentialsPageCommands = {
  enterAwsCredentials(region) {
    const regionOption = `select#awsRegion option[value=${region}]`;
    return this
      .setValue('@awsAccessKey', process.env.AWS_ACCESS_KEY_ID)
      .setValue('@secretAccesskey', process.env.AWS_SECRET_ACCESS_KEY)
      .waitForElementPresent(regionOption, 60000)
      .click(regionOption)
      .click('@nextStep');
  },
};

module.exports = {
  url: '',
  commands: [awsCredentialsPageCommands],
  elements: {
    awsAccessKey: {
      selector: 'input#accessKeyId',
    },
    secretAccesskey: {
      selector: 'input#secretAccessKey',
    },
    nextStep: {
      selector: '//*[text()[contains(.,"Next Step")]]',
      locateStrategy: 'xpath',
    },
  },
};

const awsCredentialsPageCommands = {
  enterAwsCredentials(region) {
    const regionOption = `select#awsRegion option[value=${region}]`;
    this
      .setField('@awsAccessKey', process.env.AWS_ACCESS_KEY_ID)
      .setField('@secretAccesskey', process.env.AWS_SECRET_ACCESS_KEY)
      .waitForElementPresent(regionOption, 60000)
      .click(regionOption)
      .expect.element(regionOption).to.be.selected;
    return this.click('@nextStep');
  },
};

module.exports = {
  url: '',
  commands: [awsCredentialsPageCommands],
  elements: {
    awsAccessKey: {
      selector: 'input#accessKeyId',
    },
    secretAccesskey: {
      selector: 'input#secretAccessKey',
    },
    nextStep: {
      selector: '//*[text()[contains(.,"Next Step")]]',
      locateStrategy: 'xpath',
    },
  },
};

Clean up AWS Credentials page tests and add assertions


'use strict';

// MODULES //

var isnan = require( '@stdlib/math/base/utils/is-nan' );
var NINF = require( '@stdlib/math/constants/float64-ninf' );

// MAIN //

/**
* Evaluates the logarithm of the cumulative distribution function (CDF) for a degenerate distribution with a point mass at `mu`.
*
* @param {number} x - input value
* @param {number} mu - constant value of distribution
* @returns {Probability} evaluated logarithm of cumulative distribution function
*
* @example
* var y = logcdf( 2.0, 3.0 );
* // returns Number.NEGATIVE_INFINITY
* @example
* var y = logcdf( 4.0, 3.0 );
* // returns 0
* @example
* var y = logcdf( 3.0, 3.0 );
* // returns 0
* @example
* var y = logcdf( NaN, 0.0 );
* // returns NaN
* @example
* var y = logcdf( 0.0, NaN );
* // returns NaN
*/
function logcdf( x, mu ) {
	if ( isnan( x ) || isnan( mu ) ) {
		return NaN;
	}
	return x < mu ? NINF : 0.0;
} // end FUNCTION logcdf()

// EXPORTS //

module.exports = logcdf;

'use strict';

// MODULES //

var isnan = require( '@stdlib/math/base/utils/is-nan' );
var NINF = require( '@stdlib/math/constants/float64-ninf' );

// MAIN //

/**
* Evaluates the natural logarithm of the cumulative distribution function (logCDF) for a degenerate distribution with mean `mu`.
*
* @param {number} x - input value
* @param {number} mu - constant value of distribution
* @returns {number} natural logarithm of cumulative distribution function
*
* @example
* var y = logcdf( 2.0, 3.0 );
* // returns Number.NEGATIVE_INFINITY
* @example
* var y = logcdf( 4.0, 3.0 );
* // returns 0.0
* @example
* var y = logcdf( 3.0, 3.0 );
* // returns 0.0
* @example
* var y = logcdf( NaN, 0.0 );
* // returns NaN
* @example
* var y = logcdf( 0.0, NaN );
* // returns NaN
*/
function logcdf( x, mu ) {
	if ( isnan( x ) || isnan( mu ) ) {
		return NaN;
	}
	return ( x < mu ) ? NINF : 0.0;
} // end FUNCTION logcdf()

// EXPORTS //

module.exports = logcdf;

Fix return type and add decimal points


import Liker from './content/liker';
import OptionManager from './utils/option-manager';
import Debug from './content/debug';

const debug = new Debug();

(async function () {
  try {
    debug.log('navigated:', window.location.href);

    if (process.env.NODE_ENV === 'development') {
      ['yt-navigate', 'yt-navigate-finish', 'yt-page-data-updated'].forEach((eventType) => {
        const appRoot = document.querySelector('ytd-app');

        if (appRoot) {
          appRoot.addEventListener(eventType, (e) => {
            debug.log('event:', e.type);

            if (eventType === 'yt-navigate-finish') {
              debug.log('navigated:', window.location.href);
            }
          });
        }
      });
    }

    // Create an OptionManager
    const defaults = {
      like_what: 'subscribed',
      like_when: 'instantly',
      like_when_minutes: '2',
      like_when_percent: '50',
      disabled: false,
    };
    const optionManager = new OptionManager(defaults);

    // Fetch our options then fire things up
    debug.log('loading options...');

    const options = await optionManager.get();

    debug.log('...options loaded', `(${JSON.stringify(options, null, 2)})`);

    const liker = new Liker({ options, log: debug.log });
    liker.onStop = debug.save;
  } catch (err) {
    debug.log(err);
  } finally {
    debug.save();
  }
})();

import Liker from './content/liker';
import OptionManager from './utils/option-manager';
import Debug from './content/debug';

const debug = new Debug();

(async function () {
  try {
    debug.log('navigated:', window.location.href);

    if (process.env.NODE_ENV === 'development') {
      ['yt-navigate', 'yt-navigate-finish', 'yt-page-data-updated'].forEach((eventType) => {
        const appRoot = document.querySelector('ytd-app');

        if (appRoot) {
          appRoot.addEventListener(eventType, (e) => {
            debug.log('event:', e.type);

            if (eventType === 'yt-navigate-finish') {
              debug.log('navigated:', window.location.href);
            }
          });
        }
      });
    }

    // Create an OptionManager
    const defaults = {
      like_what: 'subscribed',
      like_when: 'instantly',
      like_when_minutes: '2',
      like_when_percent: '50',
      disabled: false,
    };
    const optionManager = new OptionManager(defaults);

    // Fetch our options then fire things up
    debug.log('loading options...');

    const options = await optionManager.get();

    debug.log('...options loaded', `(${JSON.stringify(options, null, 2)})`);

    const liker = new Liker({ options, log: debug.log });
    liker.onPause = debug.save;
  } catch (err) {
    debug.log(err);
  } finally {
    debug.save();
  }
})();

Fix log not saving on pause


exports.name = "WG Note-Echidna";
exports.config = require("./WG-NOTE").config;

var base = require("./base");
exports.rules = base.insertAfter(
        require("./WD").rules
    ,   "sotd.status"
    ,   [
            require("../rules/echidna/editor-ids.js")
        ,   require("../rules/echidna/todays-date.js")
        ]
);

exports.name = "WG-NOTE-Echidna";
exports.config = require("./WG-NOTE").config;

var base = require("./base");
exports.rules = base.insertAfter(
        require("./WD").rules
    ,   "sotd.status"
    ,   [
            require("../rules/echidna/editor-ids.js")
        ,   require("../rules/echidna/todays-date.js")
        ]
);

Fix codename of Echidna-flavoured WG Note profile


// @ts-check

const path = require("path")
const WebpackManifestPlugin = require("webpack-manifest-plugin")
const { HashedModuleIdsPlugin } = require("webpack")
const { getCSSManifest } = require("../utils/getCSSManifest")

const {
  BUILD_SERVER,
  NODE_ENV,
  isProduction,
} = require("../../src/lib/environment")

const buildCSS = isProduction && !BUILD_SERVER

exports.productionConfig = {
  parallelism: 75,
  mode: NODE_ENV,
  devtool: "source-map",
  output: {
    filename: "[name].[contenthash].js",
  },
  plugins: [
    new HashedModuleIdsPlugin(),
    new WebpackManifestPlugin({
      fileName: path.resolve(__dirname, "../../manifest.json"),
      basePath: "/assets/",
      seed: buildCSS ? getCSSManifest() : {},
    }),
  ],
  optimization: {
    minimize: false,
  },
}

// @ts-check

const path = require("path")
const WebpackManifestPlugin = require("webpack-manifest-plugin")
const { HashedModuleIdsPlugin } = require("webpack")
const { getCSSManifest } = require("../utils/getCSSManifest")
const TerserPlugin = require("terser-webpack-plugin")

const {
  BUILD_SERVER,
  NODE_ENV,
  isProduction,
} = require("../../src/lib/environment")

const buildCSS = isProduction && !BUILD_SERVER

exports.productionConfig = {
  parallelism: 75,
  mode: NODE_ENV,
  devtool: "source-map",
  output: {
    filename: "[name].[contenthash].js",
  },
  plugins: [
    new HashedModuleIdsPlugin(),
    new WebpackManifestPlugin({
      fileName: path.resolve(__dirname, "../../manifest.json"),
      basePath: "/assets/",
      seed: buildCSS ? getCSSManifest() : {},
    }),
  ],
  optimization: {
    minimizer: [
      new TerserPlugin({
        cache: false,
        parallel: false,
        sourceMap: true, // Must be set to true if using source-maps in production
      }),
    ],
  },
}

Disable parallelism, caching for terser


import * as React from 'react';
import PropTypes from 'prop-types';

const DataFetcher = ({children, getData, parameters}) => {
    const [isLoading, setIsLoading] = React.useState(true);
    const [data, setData] = React.useState(null);

    const fetchData = parameters => {
        if (parameters === null) return;
        setIsLoading(true);
        getData(parameters).then(results => {
            setData(results);
            setIsLoading(false);
        });
    };

    React.useEffect(
        () => {
            fetchData(parameters);
        },
        [parameters]
    );

    return children({data, isLoading});
};

DataFetcher.propTypes = {
    children: PropTypes.func.isRequired,
    getData: PropTypes.func.isRequired,
    parameters: PropTypes.any,
};

export default DataFetcher;

import * as React from 'react';
import PropTypes from 'prop-types';

const DataFetcher = ({children, getData, parameters}) => {
    const [state, dispatch] = React.useReducer(
        (state, action) => {
            switch (action.type) {
                case 'FETCH_DATA':
                    return {
                        isLoading: true,
                        data: null,
                    };
                case 'SUCCESS':
                    return {
                        isLoading: false,
                        data: action.payload,
                    };
                default:
                    throw new Error('The reducer of DataFetch was called with the wrong action');
            }
        },
        {
            isLoading: true,
            data: null,
        }
    );

    const fetchData = parameters => {
        if (parameters === null) return;
        dispatch({type: 'FETCH_DATA'});
        getData(parameters).then(results => {
            dispatch({
                type: 'SUCCESS',
                payload: results,
            });
        });
    };

    React.useEffect(
        () => {
            fetchData(parameters);
        },
        [parameters]
    );

    return children({data: state.data, isLoading: state.isLoading});
};

DataFetcher.propTypes = {
    children: PropTypes.func.isRequired,
    getData: PropTypes.func.isRequired,
    parameters: PropTypes.any,
};

export default DataFetcher;

Replace useState by useReducer in DataFetcher


import * as slimdom from 'slimdom';

import {
	evaluateXPathToBoolean,
	evaluateXPathToString
} from 'fontoxpath';

let documentNode;
beforeEach(() => {
	documentNode = new slimdom.Document();
});

describe('Context related functions', () => {
	describe('fn:current-dateTime', () => {
		it('returns the current dateTime',
			() => chai.assert.isOk(evaluateXPathToString('current-dateTime()', documentNode)));
		it('returns the same value during the execution of the query',
			() => chai.assert.isTrue(evaluateXPathToBoolean('current-dateTime() eq current-dateTime()')));
		it('returns different values for different queries',
			() => chai.assert.notEqual(evaluateXPathToString('current-dateTime()'), setTimeout(evaluateXPathToString('current-dateTime()'), 100)));
	});
	describe('fn:current-date', () => {
		it('returns the current date',
			() => chai.assert.isOk(evaluateXPathToString('current-date()', documentNode)));
	});
	describe('fn:current-time', () => {
		it('returns the current time',
			() => chai.assert.isOk(evaluateXPathToString('current-time()', documentNode)));
	});
	describe('fn:implicit-timezone', () => {
		it('returns the implicit timezone',
			() => chai.assert.isOk(evaluateXPathToString('implicit-timezone()', documentNode)));
	});
});

import * as slimdom from 'slimdom';

import {
	evaluateXPathToBoolean,
	evaluateXPathToString
} from 'fontoxpath';

let documentNode;
beforeEach(() => {
	documentNode = new slimdom.Document();
});

describe('Context related functions', () => {
	describe('fn:current-dateTime', () => {
		it('returns the current dateTime',
			() => chai.assert.isOk(evaluateXPathToString('current-dateTime()', documentNode)));
		it('returns the same value during the execution of the query',
			() => chai.assert.isTrue(evaluateXPathToBoolean('current-dateTime() eq current-dateTime()')));
		it('returns different values for different queries', async () => {
			const dateTime = evaluateXPathToString('current-dateTime()');
			const dateTimeLater = await new Promise(resolve => setTimeout(() => resolve(evaluateXPathToString('current-dateTime()')), 100));

			chai.assert.notEqual(dateTime, dateTimeLater);
		});
	});
	describe('fn:current-date', () => {
		it('returns the current date',
			() => chai.assert.isOk(evaluateXPathToString('current-date()', documentNode)));
	});
	describe('fn:current-time', () => {
		it('returns the current time',
			() => chai.assert.isOk(evaluateXPathToString('current-time()', documentNode)));
	});
	describe('fn:implicit-timezone', () => {
		it('returns the implicit timezone',
			() => chai.assert.isOk(evaluateXPathToString('implicit-timezone()', documentNode)));
	});
});

Fix test for dateTime inequality over time


Ext.define('SlateAdmin.model.person.progress.NoteRecipient', {
    extend: 'Ext.data.Model',

    groupField: 'RelationshipGroup',

    fields: [
        {
            name: 'PersonID',
            type: 'integer'
        },
        {
            name: 'FullName',
            type: 'string'
        },
        {
            name: 'Email',
            type: 'string',
            allowNull: true
        },
        {
            name: 'Label',
            type: 'string',
            allowNull: true
        },
        {
            name: 'RelationshipGroup',
            convert: function (v) {
                return v || 'Other';
            }
        },

        // virtual fields
        {
            name: 'selected',
            type: 'boolean',
            convert: function (v, record) {
                var selected = !Ext.isEmpty(record.get('Status'));

                return selected;
            }
        }
    ],

    proxy: {
        type: 'slaterecords',
        startParam: null,
        limitParam: null,
        api: {
            read: '/notes/progress/recipients',
            update: '/notes/save',
            create: '/notes/save',
            destory: '/notes/save'
        },
        reader: {
            type: 'json',
            rootProperty: 'data'
        },
        writer: {
            type: 'json',
            rootProperty: 'data',
            writeAllFields: false,
            allowSingle: false
        }
    }
});

Ext.define('SlateAdmin.model.person.progress.NoteRecipient', {
    extend: 'Ext.data.Model',

    groupField: 'RelationshipGroup',

    fields: [
        {
            name: 'PersonID',
            type: 'integer'
        },
        {
            name: 'FullName',
            type: 'string'
        },
        {
            name: 'Email',
            type: 'string',
            allowNull: true
        },
        {
            name: 'Label',
            type: 'string',
            convert: function (v) {
                return v || 'Custom';
            }
        },
        {
            name: 'RelationshipGroup',
            convert: function (v) {
                return v || 'Other';
            }
        },

        // virtual fields
        {
            name: 'selected',
            type: 'boolean',
            convert: function (v, record) {
                var selected = !Ext.isEmpty(record.get('Status'));

                return selected;
            }
        }
    ],

    proxy: {
        type: 'slaterecords',
        startParam: null,
        limitParam: null,
        api: {
            read: '/notes/progress/recipients',
            update: '/notes/save',
            create: '/notes/save',
            destory: '/notes/save'
        },
        reader: {
            type: 'json',
            rootProperty: 'data'
        },
        writer: {
            type: 'json',
            rootProperty: 'data',
            writeAllFields: false,
            allowSingle: false
        }
    }
});

Use "Custom" as default recipient label


angular.module("Prometheus.services").factory('MetricNamesQuerier', ["$http", function($http) {
  var metricNamesCache = {};

  return function(server_id, url, scope) {
    if (metricNamesCache[server_id]) {
      scope.metricNames = metricNamesCache[server_id];
      return;
    }

    $http.get(url + 'api/metrics').success(function(metricNames) {
      metricNamesCache[server_id] = metricNames;
      scope.metricNames = metricNames;
      return;
    }).error(function() {
      alert("Error loading available metrics!");
    });
  }
}]);

angular.module("Prometheus.services").factory('MetricNamesQuerier', ["$http", function($http) {
  var metricNamesCache = {};

  return function(server_id, url, scope) {
    if (metricNamesCache[server_id]) {
      scope.metricNames = metricNamesCache[server_id];
      return;
    }

    $http.get(url + 'api/metrics').success(function(metricNames) {
      metricNamesCache[server_id] = metricNames;
      scope.metricNames = metricNames;
      return;
    }).error(function() {
      console.log("Error loading available metrics!");
    });
  }
}]);

Change alert() -> console.log() for metrics load errors.


// not tested (account is needed)

keySocket.init(
    "overcast.fm",
    {
        "play-pause": function () {
            if (!player.paused) {
                player.pause();
            } else {
                player.play();
            }
        }
        // prev is skipped
        // next is skipped
        // stop is omitted
    }
);

keySocket.init("overcast.fm", function (key) {
    if (key === keySocket.NEXT) {
        var seekForwardButton = document.getElementById("seekforwardbutton");
        keySocket.simulateClick(seekForwardButton);
    } else if (key === keySocket.PLAY) {
        var playPauseButton = document.getElementById("playpausebutton");
        keySocket.simulateClick(playPauseButton);
    } else if (key === keySocket.PREV) {
        var seekBackButton = document.getElementById("seekbackbutton");
        keySocket.simulateClick(seekBackButton);
    }
});

Add proper, tested support for Overcast.fm.


import React from 'react';
import * as Datetime from 'react-datetime';

export const TimePicker = ({ onChange }) => (
    <Datetime timeFormat={'HH:mm'} dateFormat={false} defaultValue={new Date()} onChange={onChange} />
)

import React from 'react';
import Datetime from 'react-datetime';

export const TimePicker = ({ onChange }) => (
    <Datetime timeFormat={'HH:mm'} dateFormat={false} defaultValue={new Date()} onChange={onChange} />
)

Fix test to pass build


import React from 'react'
import { NavLink } from 'react-router-dom'
import EditTools from './EditTools'
import PreviewTools from './PreviewTools'

import styles from './Toolbar.styl'

export default ({ params, project, layout, theme, onLayoutChanged, onThemeChanged }) => {
  const { username, owner, project: projectName, mode } = params
  const linkBase = ['', username, owner, projectName].join('/').replace('//', '/')

  return (

        <NavLink to={linkBase + '/edit'} className={styles.navLink} activeClassName={styles.active}>

          Edit
        </NavLink>
        <NavLink
          to={linkBase + '/preview'}
          className={styles.navLink}
          activeClassName={styles.active}
        >

          Preview
        </NavLink>

      {mode === 'edit' && <EditTools />}
      {mode === 'preview' &&
        <PreviewTools
          href={`/preview/${username}/${projectName}`}
          layout={layout}
          theme={theme}
          onLayoutChanged={onLayoutChanged}
          onThemeChanged={onThemeChanged}
        />}

  )
}

import React from 'react'
import { NavLink } from 'react-router-dom'
import EditTools from './EditTools'
import PreviewTools from './PreviewTools'

import styles from './Toolbar.styl'

export default ({ params, project, layout, theme, onLayoutChanged, onThemeChanged }) => {
  const { username, owner, project: projectName, mode } = params
  const linkBase = ['', username, owner, projectName].join('/').replace('//', '/')

  return (

        <NavLink to={linkBase + '/edit'} className={styles.navLink} activeClassName={styles.active}>

          Edit
        </NavLink>
        <NavLink
          to={linkBase + '/preview'}
          className={styles.navLink}
          activeClassName={styles.active}
        >

          Preview
        </NavLink>

      {mode === 'edit' && <EditTools />}
      {mode === 'preview' &&
        <PreviewTools
          href={`/preview/${owner || username}/${projectName}`}
          layout={layout}
          theme={theme}
          onLayoutChanged={onLayoutChanged}
          onThemeChanged={onThemeChanged}
        />}

  )
}

Fix open in new window URL


import { Template } from 'meteor/templating';

import { openQueue, cutoffQueue, endQueue } from '/imports/api/queues/methods.js';

import './queue-status-dropdown.html';

Template.QueueStatusDropdown.events({
  'click .js-open-queue'(event) {
    event.preventDefault();

    openQueue.call({
      queueId: this.queue._id,
    }, (err) => {
      if (err) console.log(err);
    });
  },

  'click .js-cutoff-queue'(event) {
    event.preventDefault();

    cutoffQueue.call({
      queueId: this.queue._id,
    }, (err) => {
      if (err) console.log(err);
    });
  },

  'click .js-end-queue'(event) {
    event.preventDefault();

    endQueue.call({
      queueId: this.queue._id,
    }, (err) => {
      if (err) console.log(err);
    });
  },
});

import { Template } from 'meteor/templating';

import { openQueue, cutoffQueue, endQueue } from '/imports/api/queues/methods.js';

import './queue-status-dropdown.html';

Template.QueueStatusDropdown.events({
  'click .js-open-queue'(event) {
    event.preventDefault();

    openQueue.call({
      queueId: this.queue._id,
    }, (err) => {
      if (err) console.log(err);
    });
  },

  'click .js-cutoff-queue'(event) {
    event.preventDefault();

    cutoffQueue.call({
      queueId: this.queue._id,
    }, (err) => {
      if (err) console.log(err);
    });
  },

  'click .js-end-queue'(event) {
    event.preventDefault();

    const ok = confirm('Are you sure you want to end this queue?');
    if (!ok) return;

    endQueue.call({
      queueId: this.queue._id,
    }, (err) => {
      if (err) console.log(err);
    });
  },
});

Add confirm before ending queue


/* globals Mozilla */
const page = require("./page").page;

let model;

exports.launch = function(m) {
  model = m;
  render();
};

function render() {
  page.render(model);
}

document.addEventListener("addon-present", () => {
  if (location.hash === "#hello") {
    document.dispatchEvent(new CustomEvent("request-onboarding"));
  } else if (location.hash === "#tour") {
    try {
      Mozilla.UITour.showHighlight("screenshots");
    } catch (e) {
      console.warn("Attempted to start #tour on non-Firefox version or unsupported version");
      throw e;
    }
  }
});

document.dispatchEvent(new CustomEvent("request-addon-present"));

window.controller = exports;

/* globals Mozilla */
const page = require("./page").page;

let model;

exports.launch = function(m) {
  model = m;
  render();
};

function render() {
  page.render(model);
}

document.addEventListener("addon-present", () => {
  if (location.hash === "#hello") {
    document.dispatchEvent(new CustomEvent("request-onboarding"));
  } else if (location.hash === "#tour") {
    try {
      if (typeof Mozilla == "undefined") {
        // The UITour-lib.js library hasn't loaded yet
        let count = 10;
        let interval = setInterval(() => {
          if (typeof Mozilla == "undefined") {
            count--;
            if (count <= 0) {
              clearTimeout(interval);
              throw new Error("UITour-lib.js didn't load up after 1 second");
            }
            return;
          }
          Mozilla.UITour.showHighlight("screenshots");
        }, 100);
      } else {
        Mozilla.UITour.showHighlight("screenshots");
      }
    } catch (e) {
      console.warn("Attempted to start #tour on non-Firefox version or unsupported version");
      throw e;
    }
  }
});

document.dispatchEvent(new CustomEvent("request-addon-present"));

window.controller = exports;

Fix bug when UITour-lib.js loads after homepage-bundle.js


import React from 'react'
import ReactDOM from 'react-dom'
import ReactSVG from 'react-svg'

ReactDOM.render(
  <ReactSVG
    // Required props.
    src="svg.svg"
    // Optional props.
    evalScripts="always"
    onInjected={(error, svg) => {
      if (error) {
        console.error(error)
        return
      }
      console.log(svg)
    }}
    renumerateIRIElements={false}
    svgClassName="svg-class-name"
    svgStyle={{ width: 200 }}
    // Non-documented props.
    className="wrapper-class-name"
    onClick={() => {
      console.log('wrapper onClick')
    }}
  />,
  document.getElementById('root')
)

import React from 'react'
import ReactDOM from 'react-dom'
import ReactSVG from 'react-svg'

ReactDOM.render(
  <ReactSVG
    // Required props.
    src="svg.svg"
    // Optional props.
    evalScripts="always"
    fallback={Error!}
    onInjected={(error, svg) => {
      if (error) {
        console.error(error)
        return
      }
      console.log(svg)
    }}
    renumerateIRIElements={false}
    svgClassName="svg-class-name"
    svgStyle={{ width: 200 }}
    // Non-documented props.
    className="wrapper-class-name"
    onClick={() => {
      console.log('wrapper onClick')
    }}
  />,
  document.getElementById('root')
)

Add fallback to api-usage example


(function($){
  var SEL = {
    list : "neo-rails-scenarios-list",
    open : "neo-rails-scenarios-list-open"
  };

  var url_param = function(name){
    var results = new RegExp('[\\?&]' + name + '=([^&#]*)').exec(window.location.href);
    return results[1] || null;
  };

  $(function(){
    $("."+SEL.list).each(function(){
      var ui = $(this);
      ui.on("click", "h2", function(evt){
        ui.toggleClass(SEL.open);
      });

      var active_scenario = url_param("scenario");
      if(active_scenario !== null){
        ui.addClass(SEL.open);
      };
    });
  })
})(jQuery);

(function($){
  var SEL = {
    list : "neo-rails-scenarios-list",
    open : "neo-rails-scenarios-list-open"
  };

  var url_param = function(name){
    var results = new RegExp('[\\?&]' + name + '=([^&#]*)').exec(window.location.href);
    return results !== null && results[1];
  };

  $(function(){
    $("."+SEL.list).each(function(){
      var ui = $(this);
      ui.on("click", "h2", function(evt){
        ui.toggleClass(SEL.open);
      });

      var active_scenario = url_param("scenario");
      if (active_scenario){
        ui.addClass(SEL.open);
      };
    });
  })
})(jQuery);

Fix scenario rendering when no param matched.


'use strict';

angular.module('confRegistrationWebApp')
  .service('ConfCache', function ConfCache($cacheFactory, $rootScope, $http, $q) {
    var cache = $cacheFactory('conf');

    var path = function (id) {
      return 'conferences/' + (id || '');
    };

    var checkCache = function (path, callback) {
      var cachedConferences = cache.get(path);
      if (angular.isDefined(cachedConferences)) {
        callback(cachedConferences, path);
      } else {
        $http.get(path).success(function (conferences) {
          cache.put(path, conferences);
          callback(conferences, path);
        });
      }
    };

    this.query = function (id) {
      checkCache(path(id), function (conferences, path) {
        $rootScope.$broadcast(path, conferences);
      });
    };

    this.get = function (id) {
      var defer = $q.defer();
      checkCache(path(id), function (conferences) {
        defer.resolve(conferences);
      });
      return defer.promise;
    };

    this.create = function (name) {
      var data = {
        name: name
      };
      return $http.post(path(), data).success(function (conference) {
        cache.put(path(conference.id), conference);
        return conference;
      });
    };
  });

'use strict';

angular.module('confRegistrationWebApp')
  .service('ConfCache', function ConfCache($cacheFactory, $rootScope, $http, $q) {
    var cache = $cacheFactory('conf');

    var path = function (id) {
      return 'conferences/' + (id || '');
    };

    var checkCache = function (path, callback) {
      var cachedConferences = cache.get(path);
      if (angular.isDefined(cachedConferences)) {
        callback(cachedConferences, path);
      } else {
        $http.get(path).success(function (conferences) {
          cache.put(path, conferences);
          callback(conferences, path);
        });
      }
    };

    this.query = function (id) {
      checkCache(path(id), function (conferences, path) {
        $rootScope.$broadcast(path, conferences);
      });
    };

    this.get = function (id) {
      var defer = $q.defer();
      checkCache(path(id), function (conferences) {
        defer.resolve(conferences);
      });
      return defer.promise;
    };

    this.create = function (name) {
      var data = {
        name: name
      };
      return $http.post(path(), data).then(function (response) {
        var conference = response.data;
        cache.put(path(conference.id), conference);
        return conference;
      });
    };
  });

Create should return conference instead of $http response


var webdriver = require('selenium-webdriver');

function loadAssets (driver) {
  var s = '';
  s += 'var s = document.createElement("script");';
  s += 's.type = "text/javascript";'
  s += 's.src = "http://localhost:7000/core.js";';
  s += 'var domel = document.getElementsByTagName("script")[0];';
  s += 'domel.parentNode.insertBefore(s, domel);';
  driver.get('http://quailpages/forms/simple-form.html');
  driver.executeScript(s);
}

/**
 * creates a webdriver client
 * @param callBack or promise
 */
exports.createClient = function (callBack) {
  var serverConfig = 'http://127.0.0.1:4444/wd/hub';
  var capabilities = {
    silent: true, // maybe output more for tests?
    browserName: 'phantomjs',
    javascriptEnabled: true,
    takesScreenshot: true,
    databaseEnabled: false,
    cssSelectorsEnabled:true,
    webStorageEnabled: true
  };

  var driver = new webdriver
    .Builder()
    .usingServer(serverConfig)
    .withCapabilities(capabilities)
    .build();

  loadAssets(driver);

  if (typeof callBack === 'function') {
    return callBack (driver);
  }
  else if ( typeof callBack === 'object' && typeof callBack.resolve === 'function' ) {
    return callBack.resolve(driver);
  }
  else {
    return driver;
  }
}

var webdriver = require('selenium-webdriver');

function loadAssets (driver) {
  var s = '';
  s += 'var s = document.createElement("script");';
  s += 's.type = "text/javascript";'
  s += 's.src = "http://localhost:7000/core.js";';
  s += 'var domel = document.getElementsByTagName("head")[0];';
  s += 'if (domel) domel.appendChild(s, domel);';
  driver.get('http://quailpages/forms/simple-form.html');
  driver.executeScript(s);
}

/**
 * creates a webdriver client
 * @param callBack or promise
 */
exports.createClient = function (callBack) {
  var serverConfig = 'http://127.0.0.1:4444/wd/hub';
  var capabilities = {
    silent: true, // maybe output more for tests?
    browserName: 'phantomjs',
    javascriptEnabled: true,
    takesScreenshot: true,
    databaseEnabled: false,
    cssSelectorsEnabled:true,
    webStorageEnabled: true
  };

  var driver = new webdriver
    .Builder()
    .usingServer(serverConfig)
    .withCapabilities(capabilities)
    .build();

  loadAssets(driver);

  if (typeof callBack === 'function') {
    return callBack (driver);
  }
  else if ( typeof callBack === 'object' && typeof callBack.resolve === 'function' ) {
    return callBack.resolve(driver);
  }
  else {
    return driver;
  }
}

Append scripts to the load page


/* @flow */

export default function() {
  return {
    visitor: {
      ClassExpression(path: Object, state: Object) {
        if (!path.node.decorators || !path.node.decorators.length) {
          return
        }
        // Default to @style
        const decoratorName = state.opts.decoratorName || 'style'
        const isMotionStyle = path.node.decorators.some(function(item) {
          return item.expression && item.expression.type === 'Identifier' && item.expression.name === decoratorName
        })
        console.log(isMotionStyle)
      }
    }
  }
}

/* @flow */

export default function({ types: t }: { types: Object }) {
  const classBodyVisitor = {
    JSXOpeningElement(path: Object, state: Object) {
      const styleKey = state.get('motion$style')
      path.node.attributes.push(t.jSXAttribute(
        t.jSXIdentifier('motion$style'),
        t.jSXExpressionContainer(t.identifier(styleKey))
      ))
    }
  }

  return {
    visitor: {
      ClassExpression(path: Object, state: Object) {
        const node = path.node
        if (!node.decorators || !node.decorators.length) {
          return
        }
        // -- Validate if class is what we're looking for
        // Default to @style
        const decoratorName = state.opts.decoratorName || 'style'
        const isMotionStyle = node.decorators.some(function(item) {
          return item.expression && item.expression.type === 'Identifier' && item.expression.name === decoratorName
        })
        // -- Add a unique var to scope and all of JSX elements
        if (isMotionStyle) {
          const id = path.scope.generateUidIdentifier('motion$style')
          path.scope.push({ id, init: t.objectExpression([]) })
          state.set('motion$style', id.name)
          path.traverse(classBodyVisitor, state)
        }
      }
    }
  }
}

Add a unique property that's same on class and jsx elements


"use strict";

app.Router = Backbone.Router.extend({
  routes: {
    "": "default"
  },

  // default route
  default: function() {
    var view = new app.AppView({ model: new app.AppModel(), collection: new app.AppModels() });
    app.renderFetch(view);
    // models/collections can be fetch as well: app.fetchRender(view, view$.model, view.collection, ...);
  },

  navigate: function(fragment, options) {
    options = (options || { trigger: true });
    return Backbone.Router.prototype.navigate.call(this, fragment, options);
  }
});

"use strict";

app.Router = Backbone.Router.extend({
  routes: {
    "": "default"
  },

  // default route
  default: function() {
    var view = new app.AppView({ model: new app.AppModel(), collection: new app.AppModels() });
    app.renderFetch(view);
    // models/collections can be fetch as well: app.fetchRender(view, view.model, view.collection, ...);
  },

  navigate: function(fragment, options) {
    options = (options || { trigger: true });
    return Backbone.Router.prototype.navigate.call(this, fragment, options);
  }
});

Fix typo in fetchRender example


'use strict'

const { notarize } = require('electron-notarize')

async function main(context) {
  const { electronPlatformName, appOutDir } = context
  if (electronPlatformName !== 'darwin') {
    return
  }

  const appName = context.packager.appInfo.productFilename
  const appleId = 'accounts+apple@balena.io'

  await notarize({
    appBundleId: 'io.balena.etcher',
    appPath: `${appOutDir}/${appName}.app`,
    appleId,
    appleIdPassword: `@keychain:Application Loader: ${appleId}`
  })
}

exports.default = main

'use strict'

const { notarize } = require('electron-notarize')
const { ELECTRON_SKIP_NOTARIZATION } = process.env

async function main(context) {
  const { electronPlatformName, appOutDir } = context
  if (electronPlatformName !== 'darwin' || ELECTRON_SKIP_NOTARIZATION === 'true') {
    return
  }

  const appName = context.packager.appInfo.productFilename
  const appleId = 'accounts+apple@balena.io'

  await notarize({
    appBundleId: 'io.balena.etcher',
    appPath: `${appOutDir}/${appName}.app`,
    appleId,
    appleIdPassword: `@keychain:Application Loader: ${appleId}`
  })
}

exports.default = main

Allow skipping notarization when building package


(function() {
  'use strict';

  angular
    .module('afterHeap')
    .config(routerConfig);

  /** @ngInject */
  function routerConfig($stateProvider, $urlRouterProvider) {
    $stateProvider
      .state('city', {
        abstract: true,
        url: '/:cityId',
        template: '<ui-view/>'
      })
      .state('city.highlights', {
        url: '/',
        templateUrl: 'app/highlights/highlights.html',
        controller: 'HighlightsController',
        controllerAs: 'highlightsController',
        resolve: {
          highlights: function(api, processResponse, $stateParams){
            return api.getCityFeed($stateParams.cityId).then(function(res) {
              return processResponse.createTopSuggestions(res);
            })
          }
        }
      })
      .state('city.feed', {
        url: '/feed',
        templateUrl: 'app/activityfeed/activityfeed.html',
        controller: 'ActivityFeedController',
        controllerAs: 'activityFeed',
        resolve: {
          checkins: function(api, processResponse, $stateParams){
            return api.getCityFeed($stateParams.cityId).then(function(res){
              return processResponse.removeBlackListed(res);
            });
          }
        }
      });

    $urlRouterProvider.otherwise('/Tampere/');
  }

})();

(function() {
  'use strict';

  angular
    .module('afterHeap')
    .config(routerConfig);

  /** @ngInject */
  function routerConfig($stateProvider, $urlRouterProvider) {
    $stateProvider
      .state('city', {
        abstract: true,
        url: '/:cityId',
        template: '<ui-view/>'
      })
      .state('city.highlights', {
        url: '/',
        templateUrl: 'app/highlights/highlights.html',
        controller: 'HighlightsController',
        controllerAs: 'highlightsController',
        resolve: {
          highlights: function(api, processResponse, $stateParams){
            return api.getCityFeed($stateParams.cityId).then(function(res) {
              return processResponse.createTopSuggestions(res);
            })
          }
        }this_should_break_the_build
      })
      .state('city.feed', {
        url: '/feed',
        templateUrl: 'app/activityfeed/activityfeed.html',
        controller: 'ActivityFeedController',
        controllerAs: 'activityFeed',
        resolve: {
          checkins: function(api, processResponse, $stateParams){
            return api.getCityFeed($stateParams.cityId).then(function(res){
              return processResponse.removeBlackListed(res);
            });
          }
        }
      });

    $urlRouterProvider.otherwise('/Tampere/');
  }

})();

Test that typo in sources break the CI build


import React, { Component } from 'react';

export default class ReferralInfo extends Component {
  render() {
    return (

Give 50GB, Get 50GB

Give your friends 50GB of storage and 50GB of bandwidth on Storj for free. Once they spend $10, we'll add the same amount to your account. There is no limit to how many friends you can refer.

    );
  }
}

import React, { Component } from 'react';

export default class ReferralInfo extends Component {
  render() {
    return (

Give 50GB, Get 50GB

            Give your friends 50GB of storage and 50GB of bandwidth per month
            on Storj, for the next three months — all on us.

 After they've spent $10 on Storj, we'll give you the same.

There is no limit to how many friends you can refer.

    );
  }
}

Update copy and formatting for referral info


export default class CardController {
	constructor($rootScope, $scope, $log, _, playerModel, cardsApi) {
		'ngInject';

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

		this._ = _;
		this.cardsApi = cardsApi;
		this.playerModel = playerModel.model;

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

	$onInit() {
		let onSuccess = (res => {
				this.$log.info('onSuccess()', res, this);

				if (res.status === 200) {
					this.$scope.cardData = res.data[0];
				}
			}),
			onError = (err => {
				this.$log.error(err);
			});

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

		this.$scope.cardData = {};
		this.$scope.isDisabled = !this.cardId || this.cardType === 'storage';

		if (this.cardId) {
			this.cardsApi
				.get(this.cardId)
				.then(onSuccess, onError);
		}
	}

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

	onClick($e) {
		this.$log.info('onClick()', this);

		$e.preventDefault();
	}
};

export default class CardController {
	constructor($rootScope, $scope, $log, _, playerModel, cardsApi) {
		'ngInject';

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

		this._ = _;
		this.cardsApi = cardsApi;
		this.playerModel = playerModel.model;

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

	$onInit() {
		let onSuccess = (res => {
				this.$log.info('onSuccess()', res, this);

				if (res.status === 200) {
					this.$scope.cardData = res.data[0];
				}
			}),
			onError = (err => {
				this.$log.error(err);
			});

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

		this.$scope.cardData = {};
		this.$scope.isDisabled = !this.cardId || this.cardType === 'storage' || !this.player.isActive;

		if (this.cardId) {
			this.cardsApi
				.get(this.cardId)
				.then(onSuccess, onError);
		}
	}

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

	onClick($e) {
		this.$log.info('onClick()', this);

		$e.preventDefault();
	}
};

Set card to 'disabled' if player isn't 'active'


'use strict';

/*jslint nomen: true, stupid: true*/

module.exports = function (grunt) {
    grunt.registerTask('integration-test', 'Run integration tests', [
        'docker-integration-test'
    ]);

    grunt.registerMultiTask('docker-integration-test', function runTask() {
        /*eslint-disable no-invalid-this*/
        if (String(global.build.options.BuildConfig.nodeMajorVersion) === process.env.DOCKER_INTEGRATION_TEST_NODE_VERSION) {
            grunt.log.writeln('Integration test requested.');

            var childProcess = require('child_process');
            var path = require('path');
            var fs = require('fs');

            var directory = path.join(__dirname, 'integration');
            var file = path.join(directory, 'build.sh');

            grunt.log.writeln('Running integration test script.');
            /*eslint-disable no-sync*/
            childProcess.execFileSync(file, {
                cwd: directory,
                encoding: 'utf8'
            });
            /*eslint-enable no-sync*/
        } else {
            grunt.log.writeln('Skipping integration test.');
        }
        /*eslint-enable no-invalid-this*/
    });

    return {
        tasks: {
            'docker-integration-test': {
                full: {}
            }
        }
    };
};

'use strict';

/*jslint nomen: true, stupid: true*/

module.exports = function (grunt) {
    grunt.registerTask('integration-test', 'Run integration tests', [
        'docker-integration-test'
    ]);

    grunt.registerMultiTask('docker-integration-test', function runTask() {
        /*eslint-disable no-invalid-this*/
        if (String(global.build.options.BuildConfig.nodeMajorVersion) === process.env.DOCKER_INTEGRATION_TEST_NODE_VERSION) {
            grunt.log.writeln('Integration test requested.');

            var childProcess = require('child_process');
            var path = require('path');
            var fs = require('fs');

            var directory = path.join(__dirname, 'integration');
            var file = path.join(directory, 'build.sh');

            grunt.log.writeln('Running integration test script.');
            /*eslint-disable no-sync*/
            var output = childProcess.execFileSync(file, {
                cwd: directory,
                encoding: 'utf8'
            });
            /*eslint-enable no-sync*/

            grunt.log.writeln(output);
        } else {
            grunt.log.writeln('Skipping integration test.');
        }
        /*eslint-enable no-invalid-this*/
    });

    return {
        tasks: {
            'docker-integration-test': {
                full: {}
            }
        }
    };
};

Add integration test via docker


import Page from './../components/templates/page.jsx';
import React from 'react';
import atomicDesign from './atomic-design';

export default (injectDeps, context) => {
  const {mount, page} = context;
  const PageCtx = injectDeps(Page);

  atomicDesign(React, PageCtx, page, mount);
};

import Page from './../components/templates/page.jsx';
import React from 'react';
import atomicDesign from './atomic-design';
import testing from './testing';

export default (injectDeps, context) => {
  const {mount, page} = context;
  const PageCtx = injectDeps(Page);

  atomicDesign(React, PageCtx, page, mount);
  testing(React, PageCtx, page, mount);
};

Add route towards testing page


const buildRequest = function (url, method, responseType, headers) {
  const internalHeaders = headers || {};

  return new Promise((resolve, reject) => {
    const req = new XMLHttpRequest();
    req.responseType = responseType;
    req.open(method.toUpperCase(), url);

    Object.keys(internalHeaders).forEach((headerKey) => {
      req.setRequestHeader(headerKey, internalHeaders[headerKey]);
    });

    req.onload = () => {
      if (req.status >= 200 && req.status <= 299) {
        resolve(req.response);
      }
      else {
        reject(req);
      }
    };

    req.onerror = () => {
      console.error('Network error.');
    };

    req.send();
  });
};

const get = function (url, responseType, headers) {
  if (!url) {
    console.error(`URL can't be ${url}.`);
  }

  return _buildRequest(url, 'GET', responseType, headers);
};

export {get};

const buildRequest = function (url, method, responseType, headers) {
  const internalHeaders = headers || {};

  return new Promise((resolve, reject) => {
    const req = new XMLHttpRequest();
    req.responseType = responseType;
    req.open(method.toUpperCase(), url);

    Object.keys(internalHeaders).forEach((headerKey) => {
      req.setRequestHeader(headerKey, internalHeaders[headerKey]);
    });

    req.onload = () => {
      if (req.status >= 200 && req.status <= 299) {
        resolve(req.response);
      }
      else {
        reject(req);
      }
    };

    req.onerror = () => {
      console.error('Network error.');
    };

    req.send();
  });
};

const get = function (url, responseType, headers) {
  if (!url) {
    console.error(`URL can't be ${url}.`);
  }

  return buildRequest(url, 'GET', responseType, headers);
};

export {get};

Remove underlines that indicate privacy


(function($){
	$.entwine('ss', function($){
		$('.field label.right').entwine({
			onadd:function() {
				this.hide();
				this.closest('.field').addClass('help-text');
			}
		});

		$('.field.help-text label.left').entwine({
			onmouseenter:function() {
				var field = this.closest('.field'),
					helpText = field.find('label.right'),
					pop = $('

' + helpText.html() + '
'),
					pos = this.offset();

				this.append(pop);
			},
			onmouseleave:function() {
				var field = this.closest('.field');
				field.find('.help-pop').remove();
			}
		});
	});
})(jQuery);

(function($){
	$.entwine('ss', function($){
		$('.field label.right, .field.span.description').entwine({
			onadd:function() {
				this.hide();
				var field = this.closest('.field');

				if (!field.hasClass('checkbox')) field.addClass('help-text');
			}
		});

		$('.field.help-text label.left').entwine({
			onmouseenter:function() {
				var field = this.closest('.field'),
					helpText = field.find('label.right').length ? field.find('label.right') : field.find('span.description'),
					pop = $('

' + helpText.html() + '
'),
					pos = this.offset();

				this.append(pop);
			},
			onmouseleave:function() {
				var field = this.closest('.field');
				field.find('.help-pop').remove();
			}
		});
	});
})(jQuery);

Fix for checkboxes and descriptions


// Firefox 1.0+
module.exports = {
  isFirefox: function () {
    return typeof InstallTrigger !== 'undefined'
  },

  // Internet Explorer 6-11
  isIE: function () {
    return !!document.documentMode
  },

  // Edge 20+
  isEdge() {
    return !this.isIE() && !!window.StyleMedia
  },

  // Chrome 1+
  isChrome: function () {
    return !!window.chrome && !!window.chrome.webstore
  }

  // Opera 8.0+
  // let isOpera = (!!window.opr && !!opr.addons) || !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0

  // At least Safari 3+: "[object HTMLElementConstructor]"
  // let isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0
}

// Firefox 1.0+
module.exports = {
  isFirefox: function () {
    return typeof InstallTrigger !== 'undefined'
  },

  // Internet Explorer 6-11
  isIE: function () {
    return !!document.documentMode
  },

  // Edge 20+
  isEdge: function() {
    return !this.isIE() && !!window.StyleMedia
  },

  // Chrome 1+
  isChrome: function () {
    return !!window.chrome && !!window.chrome.webstore
  }

  // Opera 8.0+
  // let isOpera = (!!window.opr && !!opr.addons) || !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0

  // At least Safari 3+: "[object HTMLElementConstructor]"
  // let isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0
}

Change es6 shorthand notation to es5 notation


import 'bootstrap/dist/css/bootstrap.min.css';
import 'nvd3/build/nv.d3.min.css';
import 'react-select/dist/react-select.min.css';
import 'fixed-data-table/dist/fixed-data-table.min.css';
import ReactDOM from 'react-dom';
import React from 'react';
import customDataHandlers from './customDataHandlers';
import { settings } from './settings';
import App from './App';
import { Router, Route, browserHistory } from 'react-router';

ReactDOM.render(<App />, document.getElementById('root'));

import 'bootstrap/dist/css/bootstrap.min.css';
import 'nvd3/build/nv.d3.min.css';
import 'react-select/dist/react-select.min.css';
import 'fixed-data-table/dist/fixed-data-table.min.css';
import ReactDOM from 'react-dom';
import React from 'react';
import customDataHandlers from './customDataHandlers';
import { settings } from './settings';
import App from './App';
import { Router, Route, browserHistory } from 'react-router';

console.log('JJ', jeckyll);

ReactDOM.render(<App />, document.getElementById('root'));

Test for jekyll env - 1


$(".spoiler").each(function(index) {
	var t = $(this);
	t.hide();
	var l = $('
<input type="button" value="Show spoiler">
');
	l.insertBefore($(this));
	l.click(function() {
		t.show();
		$(this).hide();
	});
});

$(".ispoiler").each(function(index) {
	var t = $(this);
	t.hide();
	var l = $('<input type="button" value="Show spoiler">');
	l.insertBefore($(this));
	l.click(function() {
		t.show();
		$(this).hide();
	});
});

Add javascript for spoiler tags.


'use strict';

var gulp = require('gulp');
var replace = require('gulp-replace-task');

gulp.task('deploy-site', ['build'], function(done) {
  gulp.src('./dist/**/*.*')
    .pipe(gulp.dest('./../'));
});

'use strict';

// *

// Deploy code
gulp.task('paths', function(done) {
  gulp.src('./dist/**/*.js')
  .pipe(replace({
      patterns: [
        {
          match: /assets/g,
          replacement: function () {
            return 'build/dist/assets'; // replaces "foo" to "bar"
          }
        }
      ]
    }))
  .pipe(gulp.dest('./test'));
});

'use strict';

var gulp = require('gulp');
var replace = require('gulp-replace-task');

// AWS reqs
var s3 = require("gulp-s3");
var fs = require('fs');

// Call aws.json file
var aws = JSON.parse(fs.readFileSync('./aws.json'));

gulp.task('clean', require('del').bind(null, ['dist']));

// Update asset paths in JS files
gulp.task('assets-paths', function(done) {
  gulp.src('./dist/scripts/*.js')
    // Rewrite assets paths to AWS CDN
    .pipe(replace({
      patterns: [
        {
          match: /assets/g,
          replacement: function () {
            return 'https://s3-eu-west-1.amazonaws.com/robeasthope.com'; // replaces "foo" to "bar"
          }
        }
      ]
    }))
    .pipe(gulp.dest('./dist/scripts/*.js'));
});

// Update asset, JS, and CSS paths
gulp.task('deploy-index', function(done) {
  gulp.src(['./dist/index.html', ''])
    // Replace CSS paths
    .pipe(replace({
      patterns: [
        {
          match: /styles/g,
          replacement: function () {
            return 'https://s3-eu-west-1.amazonaws.com/robeasthope.com/styles';
          }
        }
      ]
    }))

    // Replace JS paths
    .pipe(replace({
      patterns: [
        {
          match: /scripts/g,
          replacement: function () {
            return 'https://s3-eu-west-1.amazonaws.com/robeasthope.com/scripts';
          }
        }
      ]
    }))

    // Rewrite assets paths to AWS CDN
    .pipe(replace({
      patterns: [
        {
          match: /assets/g,
          replacement: function () {
            return 'https://s3-eu-west-1.amazonaws.com/robeasthope.com';
          }
        }
      ]
    }))
  .pipe(gulp.dest('./'));
});

// Deploy assets to AWS
gulp.task('aws-assets', function(done) {
  gulp.src(['./dist/**/*.*'])
    .pipe(s3(aws));
});

// Deploy task
gulp.task('deploy', ['clean', 'build', 'asset-paths', 'aws-assets', 'deploy-index' ], function(done) {

});

Update deployment task for AWS deployment


//= require ace-builds/src/ace
//= require ace-builds/src/theme-tomorrow
//= require ace-builds/src/mode-markdown
//= require ace-builds/src/ext-language_tools
//= require bootstrap-markdown-editor
//= require marked

$(document).ready(function() {
  var content = $('#post_body').val();
  $('#post_body').hide();
  $('#post_body').after('

');
  $('div#editor').markdownEditor({
    preview: true,
    imageUpload: false,
    width: 'calc(80% - 22px)',
    onPreview: function(content, callback) {
      callback(marked(content));
    }
  });
  $('div#editor').markdownEditor('setContent', content);
  $('form#edit_post').submit(function(event) {
    var content = $('div#editor').markdownEditor('content');
    $('#post_body').val(content);
  });
});

Remove a redundant blank line


var fileIO  = require('rw')
var path    = require('path')

module.exports =
{
  readFromFile: function(filename, callback)
  {
    fileIO.readFile(path.resolve(__dirname, filename), 'utf8', function(error, data) {
      if (error)
        throw error
      callback(data)
    })
  },
  writeInFile: function(filename, data, callback)
  {
    fileIO.readFile(path.resolve(__dirname, filename), data, 'utf8', function(error) {
      if (error)
        throw error
      callback(data)
    })
  }
}

var fileIO  = require('rw')
var path    = require('path')

module.exports =
{
  readFromFile: function(filename, callback)
  {
    fileIO.readFile(path.resolve(__dirname, filename), 'utf8', function(error, data) {
      if (error)
        throw error
      callback(data)
    })
  },
  writeInFile: function(filename, data, callback)
  {
    this.readFromFile(path.resolve(__dirname, filename), function(result) {
      result += data
      fileIO.writeFile(path.resolve(__dirname, filename), result, function(error) {
        if (error)
          throw error
      })
    })
  }
}

Append to End of File While Writing in File


'use strict';

module.exports = convert

var serializeInline = require('./inline').serializeInline

/**
 * convert(elem, s) performs the actual work of converting an element
 * into its abstract representation.
 *
 * @param {Element} elem
 * @param {Serialize} s
 */
function convert (elem, s) {
  var node = elem,
      children = [],
      info,
      i

  while (node) {
    if (node.nodeType === Node.ELEMENT_NODE && node.firstChild) {
      info = serializeInline(node)
      for (i = 0; i < info.length; i += 1)
        info[i].start = s.length

      children.push(info)

      node = node.firstChild
      continue
    }

    if (node.nodeType === Node.TEXT_NODE)
      s.text += node.data
    else if (node.nodeName === 'BR')
      s.text += '\n'

    while (!node.nextSibling && node !== elem) {
      info = children.pop()
      for (i = 0; i < info.length; i += 1)
        info[i].end = s.length

      s.addMarkups(info)

      node = node.parentNode
    }

    if (node === elem)
      break

    node = node.nextSibling
  }
}

'use strict';

module.exports = convert

var serializeInline = require('./inline').serializeInline

/**
 * convert(elem, s) performs the actual work of converting an element
 * into its abstract representation.
 *
 * @param {Element} elem
 * @param {Serialize} s
 */
function convert (elem, s) {
  var allMarkups = [],
      node = elem,
      markups,
      i

  while (node) {
    if (node.nodeType === Node.ELEMENT_NODE && node.firstChild) {
      markups = serializeInline(node)
      for (i = 0; i < markups.length; i += 1)
        markups[i].start = s.length

      allMarkups.push(markups)

      node = node.firstChild
      continue
    }

    if (node.nodeType === Node.TEXT_NODE)
      s.text += node.data
    else if (node.nodeName === 'BR')
      s.text += '\n'

    while (!node.nextSibling && node !== elem) {
      markups = allMarkups.pop()
      for (i = 0; i < markups.length; i += 1)
        markups[i].end = s.length

      allMarkups.unshift(markups)

      node = node.parentNode
    }

    if (node === elem)
      break

    node = node.nextSibling
  }

  s.addMarkups(allMarkups.reduce(function (arr, markups) {
    return arr.concat(markups)
  }, []))
}

Apply markups from the outside in


// See Purple/license.txt for Google BSD license
// Copyright 2011 Google, Inc. johnjbarton@johnjbarton.com

define(['lib/Base'],
  function (Base) {

  var EventIndex = {};

  EventIndex.recv = function(data) {
    this.objectBuffer.push(data);
  };

  EventIndex._getMatcher = function(constraints) {
    return function(obj) { return true; };
  };

  // API

  EventIndex.filter = function(constraints, thenFnOfObject) {
    // flush any new events to the object buffer
    this._update();

    var matcher = this._getMatcher(constraints);
    var max = this.objectBuffer.length;
    for (var i = 0; i < max; i++) {
      var obj = this.objectBuffer[i];
      if (matcher(obj)) {
        if (!thenFnOfObject(obj)) break;
      }
    }
  };

  EventIndex.initialize = function() {
      this.objectBuffer = [];
  };

  EventIndex.disconnect = function() {
      delete this.objectBuffer;
  };

  return Base.extend(EventIndex);
});

// See Purple/license.txt for Google BSD license
// Copyright 2011 Google, Inc. johnjbarton@johnjbarton.com

define(['lib/Base'],
  function (Base) {

  var SparseArray = {};

  SparseArray.set = function(p_id, data) {
    this.objectsByP_ID[p_id] = data;
    this.p_ids.push(p_id);
  };

  // maybe undefined.
  SparseArray.get = function(p_id) {
    return this.objectsByP_ID[p_id];
  };

  SparseArray.Iterator = Base.extend({
    initialize: function(sparseArray) {
      this.sparseArray = sparseArray;
    },
    next: function() {
      if (!this.index) {
        this.index = this.sparseArray.p_ids.length;
      }
      this.index--;
      if (this.index < 0) {
        return undefined;
      } else {
        var p_id = this.p_ids[this.index];
        return this.sparseArray.objectsByP_ID[p_id];
      }
    }
  });

  SparseArray.initialize = function(name) {
    this.name = name;
    this.objectsByP_ID = {};
    this.p_ids = [];
  };

  return Base.extend(SparseArray);
});

Rewrite as sparse array class


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

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

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

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

  return mask;
};

NAVDATA_OPTION_MASK(constants.options.DEMO) |
  NAVDATA_OPTION_MASK(constants.options.VISION_DETECT) |
  NAVDATA_OPTION_MASK(constants.options.EULER_ANGLES)

Remove detritus which is causing errors.


var assert = require('chai').assert;
var sinon = require('sinon');

let LMLayer = require('../../build');
var breakASCIIWords = require('../../build/intermediate').wordBreakers['ascii'];

describe('The ASCIIWordBreaker', function() {
    it('breaks simple English sentences', function () {
        let breaks = breakASCIIWords('Look! -- The quick brown fox jumps... over the lazy dog!');
        let words = breaks.map(span => span.text);
        assert.deepEqual(words, ['Look', 'The', 'quick', 'brown', 'fox', 'jumps', 'over', 'the', 'lazy', 'dog']);
    });
});

var assert = require('chai').assert;

var breakASCIIWords = require('../../build/intermediate').wordBreakers['ascii'];

describe('The ASCII word breaker', function () {
  it('should break simple English sentences', function () {
    let breaks = breakASCIIWords('Look! -- The quick brown fox jumps... over the lazy dog!');
    let words = breaks.map(span => span.text);
    assert.deepEqual(words, ['Look', 'The', 'quick', 'brown', 'fox', 'jumps', 'over', 'the', 'lazy', 'dog']);
  });
});

Clean up ASCII word breaker unit test.


var React = require('react');
var QBer = require('./components/QBer.react');
var MessagePanel = require('./components/MessagePanel.react');

React.render(
  <QBer />,
  document.getElementById('qber')
);

React.render(
  <MessagePanel />,
  document.getElementById('message_panel')
);

var React = require('react');
var QBer = require('./components/QBer.react');
var MessagePanel = require('./components/MessagePanel.react');

var DatasetActions = require('./actions/DatasetActions');

DatasetActions.initializeStore();

React.render(
  <QBer />,
  document.getElementById('qber')
);

React.render(
  <MessagePanel />,
  document.getElementById('message_panel')
);

Initialize store when loading the page


/* eslint-disable react/forbid-prop-types */
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2016
 */

import React from 'react';
import { View } from 'react-native';
import PropTypes from 'prop-types';

import { SimpleLabel } from './SimpleLabel';

/**
 * Simple layout component render sequential SimpleLabel components
 * in a row.
 *
 * Each SimpleLabel has a label and some other text.
 *
 * @prop {Array} details Array of objects in the form {label, text}
 */
export const DetailRow = ({ details }) => {
  const Details = React.useCallback(
    () =>
      details.map(({ label, text }) => (
        <View>
          <SimpleLabel label={label} size="small" text={text} />
        </View>
      )),
    [details]
  );

  return (
    <View style={localStyles.rowContainer}>
      <Details />
    </View>
  );
};

const localStyles = {
  rowContainer: {
    flexDirection: 'row',
    alignItems: 'center',
  },
};

DetailRow.propTypes = { details: PropTypes.array.isRequired };

/* eslint-disable react/forbid-prop-types */
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2016
 */

import React from 'react';
import { View } from 'react-native';
import PropTypes from 'prop-types';

import { SimpleLabel } from './SimpleLabel';

/**
 * Simple layout component render sequential SimpleLabel components
 * in a row.
 *
 * Each SimpleLabel has a label and some other text.
 *
 * @prop {Array} details Array of objects in the form {label, text}
 */
export const DetailRow = ({ details }) => {
  const Details = React.useCallback(
    () =>
      details.map(({ label, text }) => (
        <View style={{ flex: 1 }}>
          <SimpleLabel label={label} size="small" text={text} />
        </View>
      )),
    [details]
  );

  return (
    <View style={localStyles.rowContainer}>
      <Details />
    </View>
  );
};

const localStyles = {
  rowContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'flex-start',
    flex: 1,
  },
};

DetailRow.propTypes = { details: PropTypes.array.isRequired };

Add detail row style adjustments


//setup backbone message model
var Message = Backbone.Model.extend({
  defaults:{
    username:'',
    text: '',
    room:'',
    lang: 'en',
    translations: {}
  }
});

//setup backbone message model
var Message = Backbone.Model.extend({
  defaults:{
    username:'',
    text: '',
    room:'',
    lang: 'en',
    avatar: 'A01',
    translations: {}
  }
});

Add default avatar to model defaults


'use strict';

var testKey = 'test-key';
var testOptions = {
  publishNavigationTimingMetrics: true,
  publishUserActive: true,
  application: 'ui'
};

var testConfig = {
  apiKey: testKey,
  options: testOptions
};

angular.module('test', ['weblogng'])
    .constant('weblogngApiKey', testKey)
    .constant('weblogngOptions', testOptions)
    .constant('weblogngConfig', testConfig)
    ;

describe('Module: weblogng', function() {

  beforeEach(module('test'));

  it('should be configurable', function () {
    inject(function (_$weblogng_) {
      expect(_$weblogng_.apiKey).toEqual(testConfig.apiKey);
      expect(_$weblogng_.options).toEqual(testConfig.options);
    });

  });

});

'use strict';

var testKey = 'test-api-key';
var testOptions = {
  publishNavigationTimingMetrics: true,
  publishUserActive: true,
  application: 'ui'
};

var testConfig = {
  apiKey: testKey,
  options: testOptions
};

angular.module('test', ['weblogng'])
    .constant('weblogngConfig', testConfig)
    ;

describe('Module: weblogng', function() {

  beforeEach(module('test'));

  it('should be configurable', function () {
    inject(function (_$weblogng_) {
      expect(_$weblogng_.apiKey).toEqual(testConfig.apiKey);
      expect(_$weblogng_.options).toEqual(testConfig.options);
    });

  });

});

Remove constants that were used in 'old' weblogng module configuration approach.


$(document).ready(function() {
 $('#history_tab').on('click', function(event) {
    event.preventDefault();
    event.stopPropagation();
    var $details = $(event.target);
    console.log($details[0].href)
    $.ajax({
      url: $details[0].href,
      type: 'GET',
      }).done(function (response) {
          $('.profile-content').children().hide();
          $('.profile-content').append(response);
      }).fail(function (response) {
        alert("Can Not Render Your Parcels Due to Error")
      })

   $('#parcels_tab').on('click', function(event) {
      $('.profile-content').children().show();
      $('#history_show').hide();
    });
 });
})

$(document).ready(function() {
 $('#history_tab').on('click', function(event) {
    event.preventDefault();
    event.stopPropagation();
    var $details = $(event.target);
    console.log($details[0].href)
    $.ajax({
      url: $details[0].href,
      type: 'GET',
      }).done(function (response) {
          $('.profile-content').children().hide();
          $('.profile-content').append(response);
      }).fail(function (response) {
        alert("Can Not Render Your Parcels Due to Error")
      })

   $('#parcels_tab').on('click', function(event) {
      $('.profile-content').children().show();
      $('#history_show').hide();
    });

   $('#trips_tab').on('click', function(event) {
       $('.profile-content').children().show();
      $('#history_show').hide();
    });
 });
})

Use jquery to send parcels tab and trips tab html back into profile in DOM


'use strict';

(function() {
	var WHEREABOUTS = [
		'working on his personal brand',
		'at home writing medium posts',
		'fuck knows!',
		'project managing',
		'having a cigarette',
		'Having a coffee and quinoa in a startup restaurant',
		'Writing Ruby'
	];

	function getRandomInt(min, max) {
        return Math.floor(Math.random() * (max - min)) + min;
    }

    function getRandomLocation() {
        return WHEREABOUTS[getRandomInt(1, WHEREABOUTS.length) - 1];
    }

	document.querySelector('.js-whereabouts').innerHTML = getRandomLocation();
})();

'use strict';

(function() {
	var WHEREABOUTS = [
		'working on his personal brand',
		'at home writing medium posts',
		'fuck knows!',
		'project managing',
		'having a cigarette',
		'Having a coffee and quinoa in a startup restaurant',
		'Writing Ruby'
	];

	function getRandomInt(min, max) {
        return Math.floor(Math.random() * (max - min)) + min;
    }

    function getRandomLocation() {
        return WHEREABOUTS[getRandomInt(0, WHEREABOUTS.length)];
    }

	document.querySelector('.js-whereabouts').innerHTML = getRandomLocation();
})();

Include final item, upper bound is exclusive


'use strict';

var compiler = require('../lib/compiler');
var mocha = require('mocha');
var assert = require('../lib/assert');
var datasets = require('./lib/datasets');

mocha.suite('serialize', function () {

    mocha.test('print;parse = id', function () {
        var examples = [
            'VAR x',
            'QUOTE APP LAMBDA CURSOR VAR x VAR x HOLE',
            'LETREC VAR i LAMBDA VAR x VAR x APP VAR i VAR i',
        ];
        assert.inverses(compiler.parse, compiler.print, examples);
    });

    mocha.test('works on datasets.codes', function () {
        assert.inverses(compiler.parse, compiler.print, datasets.codes);
    });

    mocha.test('works on datasets.curryTerms', function () {
        assert.inverses(compiler.print, compiler.parse, datasets.curryTerms);
    });
});

'use strict';

var compiler = require('../lib/compiler');
var mocha = require('mocha');
var assert = require('../lib/assert');
var datasets = require('./lib/datasets');

mocha.suite('serialize', function () {

    mocha.test('print;parse = id', function () {
        var examples = [
            'VAR x',
            'QUOTE APP LAMBDA CURSOR VAR x VAR x HOLE',
            'LETREC VAR i LAMBDA VAR x VAR x APP VAR i VAR i',
        ];
        assert.inverses(compiler.parse, compiler.print, examples);
    });

    mocha.test('works on datasets.codes', function () {
        assert.inverses(compiler.parse, compiler.print, datasets.codes);
    });

    mocha.test('works on datasets.curryTerms', function () {
        assert.inverses(compiler.print, compiler.parse, datasets.curryTerms);
    });

    mocha.test('works on datasets.churchTerms', function () {
        assert.inverses(compiler.print, compiler.parse, datasets.churchTerms);
    });
});

Add test for parse;print of church terms


/* global SVGElement */

import { setStyle } from './setstyle';
import { isFunction, getEl } from './util';

const xlinkns = 'http://www.w3.org/1999/xlink';

export const setAttr = (view, arg1, arg2) => {
  const el = getEl(view);
  let isSVG = el instanceof SVGElement;

  if (arg2 !== undefined) {
    if (arg1 === 'style') {
      setStyle(el, arg2);
    } else if (isSVG && isFunction(arg2)) {
      el[arg1] = arg2;
    } else if (!isSVG && (arg1 in el || isFunction(arg2))) {
      el[arg1] = arg2;
    } else {
      if (isSVG && (arg1 === 'xlink')) {
        setXlink(el, arg2);
        return;
      }
      el.setAttribute(arg1, arg2);
    }
  } else {
    for (const key in arg1) {
      setAttr(el, key, arg1[key]);
    }
  }
};

function setXlink (el, obj) {
  for (const key in obj) {
    el.setAttributeNS(xlinkns, key, obj[key]);
  }
}

/* global SVGElement */

import { setStyle } from './setstyle';
import { isFunction, getEl } from './util';

const xlinkns = 'http://www.w3.org/1999/xlink';

export const setAttr = (view, arg1, arg2) => {
  const el = getEl(view);
  let isSVG = el instanceof SVGElement;

  if (arg2 !== undefined) {
    if (arg1 === 'style') {
      setStyle(el, arg2);
    } else if (isSVG && isFunction(arg2)) {
      el[arg1] = arg2;
    } else if (arg1 === 'dataset') {
      setData(el, arg2);
    } else if (!isSVG && (arg1 in el || isFunction(arg2))) {
      el[arg1] = arg2;
    } else {
      if (isSVG && (arg1 === 'xlink')) {
        setXlink(el, arg2);
        return;
      }
      el.setAttribute(arg1, arg2);
    }
  } else {
    for (const key in arg1) {
      setAttr(el, key, arg1[key]);
    }
  }
};

function setXlink (el, obj) {
  for (const key in obj) {
    el.setAttributeNS(xlinkns, key, obj[key]);
  }
}

function setData (el, obj) {
  for (const key in obj) {
    el.dataset[key] = obj[key];
  }
}

Add support for dataset Object


const About = () => {
  return (

      Gå til hovedsiden

Velkommen til Online, linjeforeningen for informatikkstudenter ved NTNU.

Det er vi som sørger for at studietiden blir den beste tiden i ditt liv! Vi i Online arrangerer utflukter, turer, fester, holder kurs og bedriftspresentasjoner gjennom hele året.

      <button className="skipToCalendar">Hopp til program</button>

  );
};

export default About;

Add prefix to header-logo selector


(function(){
	"use strict";

	var defaults = {
		Selector : null,
		Threshold: 30
	}

	var SwipeDetector = function(options) {
		if (typeof options !== "object") {
			options = {
				Selector: options
			};
		}

		var config = $.extend(defaults,options, { Start: null });

		var clear = function () {
			config.Start = null;
			$(config.Selector).off("mousemove", mouseMove);
		};

		var mouseMove = function (data) {
			if (!config.Start) {
				config.Start = data.pageX;
			}

			var dx = data.pageX - config.Start;
			if (dx > config.Threshold) {
				clear();
				$(this).trigger("swipe-right");
			} else if (dx < -config.Threshold) {
				clear();
				$(this).trigger("swipe-left");
			}
		};

		$(config.Selector).on("mousedown", function(data) {
			$(this).on("mousemove", mouseMove);
		});
		$(config.Selector).on("mouseup", function(data) {
			clear();
		});
	};

	window.SwipeDetector = SwipeDetector;
})();

(function(){
	"use strict";

	var defaults = {
		Selector : null,
		Threshold: 30
	}

	var SwipeDetector = function(options) {
		if (typeof options !== "object") {
			options = {
				Selector: options
			};
		}

		var config = $.extend(defaults,options, { Start: null });

		var handlers = {};

		var clear = function (type) {
			config.Start = null;
			$(config.Selector).off(type, handlers[type]);
		};
		var handleMove = function(x, type) {
			if (!config.Start) {
				config.Start = x;
			}

			var dx = x - config.Start;

			if (dx > config.Threshold) {
				clear(type);
				$(config.Selector).trigger("swipe-right");
			} else if (dx < -config.Threshold) {
				clear(type);
				$(config.Selector).trigger("swipe-left");
			}
		};

		handlers["mousemove"] = function(e) {
			handleMove(e.pageX, e.type);
		};
		handlers["touchmove"] = function (e) {
			e.preventDefault();
			var touch = e.originalEvent.touches[0] || e.originalEvent.changedTouches[0];
			handleMove(touch.pageX, e.type);
		};

		$(config.Selector).on({
			touchstart: function(e) {
				e.preventDefault();
				$(this).on("touchmove", handlers["touchmove"]);
			}, mousedown: function() {
				$(this).on("mousemove", handlers["mousemove"]);
			}, touchend: function(e) {
				e.preventDefault();
				clear("touchmove");
			}, mouseup: function() {
				clear("mousemove");
			}
		});
	};

	window.SwipeDetector = SwipeDetector;
})();

Fix for touch vs mouse events


const quotes = {
	leftDoubleQuote : 				"„",
	rightDoubleQuote : 				"“",
	leftSingleQuote : 				"‚",
	rightSingleQuote : 				"‘",
};

const numbers = {
	ordinalIndicator :				"\\.",
	romanOrdinalIndicator :		"\\.",
}

const abbreviationsForNbsp = [
	"S.",
	"z."
]

export default {
	quotes,
	numbers,
	abbreviationsForNbsp
}

// b. w.
// d. h.
// d. i.
// n. Chr.
// s. a.
// s. o.
// s. u.
// u. a.
// u. ä.
// u. Ä.
// u. U.
// u. z.
// u. zw.
// v. a.
// v. Chr.
// z. B.
// z. T.
// z. Zt.
// e. V.
// v. u. Z.
// n. u. Z.

Include German abbreviations for later work


/**
 * A new or existing user file attachment.
 *
 * Model Attributes:
 *     caption (string):
 *         The file attachment's caption.
 *
 *     userName (string):
 *         The username of the owner of the file attachment.
 *
 *     downloadURL (string):
 *         The URL to download the file, for existing file attachments.
 *
 *     file (file):
 *         The file to upload. Only works for newly created
 *         UserFileAttachments.
 *
 *     filename (string):
 *         The name of the file, for existing file attachments.
 */
RB.UserFileAttachment = RB.BaseResource.extend({
    defaults() {
        return _.defaults({
            caption: null,
            userName: '',
            downloadURL: null,
            file: null,
            filename: null
        }, RB.BaseResource.prototype.defaults());
    },

    rspNamespace: 'file_attachment',
    payloadFileKeys: ['path'],

    attrToJsonMap: {
        downloadURL: 'absolute_url',
        file: 'path'
    },

    serializedAttrs: [
        'caption',
        'file'
    ],

    deserializedAttrs: [
        'caption',
        'downloadURL',
        'filename'
    ],

    serializers: {
        file: RB.JSONSerializers.onlyIfValue
    },

    /**
     * Return the URL to use for syncing the model.
     *
     * Returns:
     *     string:
     *     The URL for the resource.
     */
    url() {
        const username = this.get('userName');
        const url = `${SITE_ROOT}api/users/${username}/file-attachments/`;

        return this.isNew() ? url : `${url}${this.id}/`;
    }
});

/**
 * A new or existing user file attachment.
 *
 * Model Attributes:
 *     caption (string):
 *         The file attachment's caption.
 *
 *     userName (string):
 *         The username of the owner of the file attachment.
 *
 *     downloadURL (string):
 *         The URL to download the file, for existing file attachments.
 *
 *     file (file):
 *         The file to upload. Only works for newly created
 *         UserFileAttachments.
 *
 *     filename (string):
 *         The name of the file, for existing file attachments.
 */
RB.UserFileAttachment = RB.BaseResource.extend({
    defaults() {
        return _.defaults({
            caption: null,
            userName: '',
            downloadURL: null,
            file: null,
            filename: null
        }, RB.BaseResource.prototype.defaults());
    },

    rspNamespace: 'user_file_attachment',
    payloadFileKeys: ['path'],

    attrToJsonMap: {
        downloadURL: 'absolute_url',
        file: 'path'
    },

    serializedAttrs: [
        'caption',
        'file'
    ],

    deserializedAttrs: [
        'caption',
        'downloadURL',
        'filename'
    ],

    serializers: {
        file: RB.JSONSerializers.onlyIfValue
    },

    /**
     * Return the URL to use for syncing the model.
     *
     * Returns:
     *     string:
     *     The URL for the resource.
     */
    url() {
        const username = this.get('userName');
        const url = `${SITE_ROOT}api/users/${username}/user-file-attachments/`;

        return this.isNew() ? url : `${url}${this.id}/`;
    }
});

Fix resource paths for user file attachments.


export default {
  givenName: {
    required: true,
    validateOnBlur: true,
  },
  familyName: {
    required: true,
    validateOnBlur: true,
  },
  nickname: {
    required: false,
    validateOnBlur: true,
  },
  email: {
    required: true,
    email: true,
    validateOnBlur: true,
  },
};

export default {
  givenName: {
    required: true,
    validateOnBlur: true,
  },
  familyName: {
    required: true,
    validateOnBlur: true,
  },
  nickname: {
    required: false,
    validateOnBlur: true,
  },
  email: {
    required: true,
    email: true,
    validateOnBlur: true,
  },
  emailVerified: {
    required: true,
    validateOnBlur: true,
  },
  gender: {
    required: true,
    validateOnBlur: true,
  },
  locale: {
    required: true,
    validateOnBlur: true,
  },
  notes: {
    required: false,
    validateOnBlur: true,
  },
};

Fix bug: need complete list of validation fields


'use strict';

var expect = require('chai').expect;
var angular = require('angular');

var calculatorModule = require('./calculator.service');

describe('calculator', function() {
	var calculator;

	beforeEach(function() {
		angular.mock.module(calculatorModule.moduleName);
		angular.mock.inject([calculatorModule.factoryName, function(_calculator_) {
			calculator = _calculator_;
		}]);
	});

	it ('should add two numbers', function() {
		var result = calculator.calculate('+', 5, 3);
		expect(result).to.equal(8);
	});

	it ('should subtract two numbers', function() {
		var result = calculator.calculate('-', 5, 3);
		expect(result).to.equal(2);
	});

	it ('should multiply two numbers', function() {
		var result = calculator.calculate('*', 5, 3);
		expect(result).to.equal(15);

		var result = calculator.calculate('x', 10, 3);
		expect(result).to.equal(30);
	});

	it ('should divide two numbers', function() {
		var result = calculator.calculate('/', 6, 3);
		expect(result).to.equal(2);
	});

	it ('should raise a number to a power', function() {
		var result = calculator.calculate('^', 2, 8);
		expect(result).to.equal(256);
	});
});

'use strict';

var expect = require('chai').expect;
var angular = require('angular');

var calculatorModule = require('./calculator.service');

describe('calculator', function() {
	var calculator;

	beforeEach(function() {
		angular.mock.module(calculatorModule.moduleName);
		angular.mock.inject([calculatorModule.factoryName, function(_calculator_) {
			calculator = _calculator_;
		}]);
	});

	it ('should add two numbers', function() {
		var result = calculator.calculate('+', 5, 3);
		expect(result).to.equal(8);
	});

	it ('should subtract two numbers', function() {
		var result = calculator.calculate('-', 5, 3);
		expect(result).to.equal(2);
	});

	it ('should multiply two numbers', function() {
		var result = calculator.calculate('*', 5, 3);
		expect(result).to.equal(15);

		result = calculator.calculate('x', 10, 3);
		expect(result).to.equal(30);
	});

	it ('should divide two numbers', function() {
		var result = calculator.calculate('/', 6, 3);
		expect(result).to.equal(2);
	});

	it ('should raise a number to a power', function() {
		var result = calculator.calculate('^', 2, 8);
		expect(result).to.equal(256);
	});
});

Fix redundant declaration of the same variable.


'use strict';

module.exports = function(grunt){
  grunt.initConfig({
    uglify: {
      cobra: {
        files: {
          'build/js/cobra.min.js': 'public/js/cobra.js'
        }
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-uglify');
};

'use strict';

module.exports = function(grunt){
  grunt.initConfig({
    uglify: {
      js: {
        files: {
          'build/js/foo.min.js': 'public/js/foo.js'
        }
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-uglify');
};

Correct task target name and file names


define([
	"use!underscore",
	"use!backbone",
	"baseView",
	"settings",
	"./instrumentView",
], function(_, Backbone, BaseView, settings, InstrumentView) {
	var InstrumentManagerView = BaseView.extend({
		modelEvents: {
			"instrumentAdded": function (instrument) {
				this.addInstrumentView(instrument);
			},

			"instrumentRemoved": function (instrument) {
				this.resizeNewInstrumentArea();
			}
		},

		render: function () {
			this.removeAllChildViews();

			_.each(this.model.instruments, function (instrument) {
				this.addInstrumentView(instrument, true);
			}, this);

			this.trigger("resize");

			return this;
		},

		addInstrumentView: function (instrument, preventResizeEvent) {
			var instrumentView = this.addChildView(InstrumentView, {
				model: instrument
			});

			instrumentView.on("removeInstrument", this.removeInstrument, this);

			instrumentView.render();

			this.$el.append(instrumentView.$el);

			if (!preventResizeEvent)
				this.trigger("resize");
		},

		removeInstrument: function (instrument) {
			this.eventBus.trigger("removeInstrument", this.model, instrument);
		},
	});

	return InstrumentManagerView;
});

define([
	"use!underscore",
	"use!backbone",
	"baseView",
	"settings",
	"./instrumentView",
], function(_, Backbone, BaseView, settings, InstrumentView) {
	var InstrumentManagerView = BaseView.extend({
		modelEvents: {
			"instrumentAdded": function (instrument) {
				this.addInstrumentView(instrument);
			}
		},

		render: function () {
			this.removeAllChildViews();

			_.each(this.model.instruments, function (instrument) {
				this.addInstrumentView(instrument, true);
			}, this);

			this.trigger("resize");

			return this;
		},

		addInstrumentView: function (instrument, preventResizeEvent) {
			var instrumentView = this.addChildView(InstrumentView, {
				model: instrument
			});

			instrumentView.on("removeInstrument", this.removeInstrument, this);

			instrumentView.render();

			this.$el.append(instrumentView.$el);

			if (!preventResizeEvent)
				this.trigger("resize");
		},

		removeInstrument: function (instrument) {
			this.eventBus.trigger("removeInstrument", this.model, instrument);
		},
	});

	return InstrumentManagerView;
});

Remove InstrumentManagerView's unnecessary 'instrumentRemoved' model event listener.


'use strict';

var fake = require('../../../helpers/fake');
var isGuestCheckout = require('../../../../src/lib/is-guest-checkout');

describe('isGuestCheckout', function () {
  it('returns true when given a tokenization key', function () {
    var auth = 'fake_tokenization_key';

    expect(isGuestCheckout(auth)).to.be.true;
  });

  it('returns true when given a client token without a customer ID', function () {
    var auth = fake.clientToken;

    expect(isGuestCheckout(auth)).to.be.true;
  });

  it('returns false when given a client token with a customer ID', function () {
    var fakeClientToken = fake.configuration().gatewayConfiguration;

    fakeClientToken.authorizationFingerprint = 'auth_fingerprint&customer_id=abc123';
    fakeClientToken = btoa(JSON.stringify(fakeClientToken));

    expect(isGuestCheckout(fakeClientToken)).to.be.false;
  });
});

'use strict';

var fake = require('../../../helpers/fake');
var isGuestCheckout = require('../../../../src/lib/is-guest-checkout');

describe('isGuestCheckout', function () {
  it('returns true when given a tokenization key', function () {
    var auth = fake.tokenizationKey;

    expect(isGuestCheckout(auth)).to.be.true;
  });

  it('returns true when given a client token without a customer ID', function () {
    var auth = fake.clientToken;

    expect(isGuestCheckout(auth)).to.be.true;
  });

  it('returns false when given a client token with a customer ID', function () {
    var auth = fake.clientTokenWithCustomerID;

    expect(isGuestCheckout(auth)).to.be.false;
  });
});

Use authentication from helpers in isGuestCheckout tests


import BabelPolyFill from 'babel-polyfill';

var testsContext = require.context('./unit/', true, /.js$/);
testsContext.keys().forEach(testsContext);

Add `babel-polyfill` to test environment