'use strict';

var assign = require('object-assign');
var chalk = require('chalk');
var ProgressBar = require('progress');

/**
 * Progress bar download plugin
 *
 * @param {Object} opts
 * @api public
 */

module.exports = function (opts) {
	return function (res, url, cb) {
		opts = opts || { info: 'cyan' };

		if (res.headers['content-length']) {
			var msg = chalk[opts.info]('  downloading') + ' : ' + url;
			var info = chalk[opts.info]('     progress') + ' : [:bar] :percent :etas';
			var len = parseInt(res.headers['content-length'], 10);

			var bar = new ProgressBar(info, assign({
				complete: '=',
				incomplete: ' ',
				width: 20,
				total: len
			}, opts));

			console.log(msg);

			res.on('data', function (data) {
				bar.tick(data.length);
			});

			res.on('end', function () {
				console.log('\n');
				cb();
			});
		}
	};
};

'use strict';

var assign = require('object-assign');
var chalk = require('chalk');
var ProgressBar = require('progress');

/**
 * Progress bar download plugin
 *
 * @param {Object} opts
 * @api public
 */

module.exports = function (opts) {
	return function (res, url, cb) {
		opts = opts || { info: 'cyan' };
		opts.stream = opts.stream || process.stderr;

		if (res.headers['content-length']) {
			var msg = chalk[opts.info]('  downloading') + ' : ' + url;
			var info = chalk[opts.info]('     progress') + ' : [:bar] :percent :etas';
			var len = parseInt(res.headers['content-length'], 10);

			var bar = new ProgressBar(info, assign({
				complete: '=',
				incomplete: ' ',
				width: 20,
				total: len
			}, opts));

			opts.stream.write(msg);

			res.on('data', function (data) {
				bar.tick(data.length);
			});

			res.on('end', function () {
				opts.stream.write('\n');
				cb();
			});
		}
	};
};

Use stream.write() instead of console.log()


/*eslint-disable no-console*/
const logsColorScheme = require("./logsColorScheme");

function success(...args){
  console.log(...args.map(arg => logsColorScheme.success(arg)));
}

function info(...args){
  console.log(...args.map(arg => logsColorScheme.info(arg)));
}

function warn(...args) {
  console.log(...args.map(arg => logsColorScheme.warn(arg)));
}

function error(...args){
  warn("ERROR:", ...args);
}

module.exports = {
  success,
  info,
  warn,
  error
};

/*eslint-disable no-console*/
import logsColorScheme from "./logsColorScheme";

const PREFIX = "[Gluestick]";

function success(...args){
  console.log(PREFIX, ...args.map(arg => logsColorScheme.success(arg)));
}

function info(...args){
  console.log(PREFIX, ...args.map(arg => logsColorScheme.info(arg)));
}

function warn(...args) {
  console.log(PREFIX, ...args.map(arg => logsColorScheme.warn(arg)));
}

function error(...args){
  warn("ERROR:", ...args);
}

module.exports = {
  success,
  info,
  warn,
  error
};

Add Gluestick prefix to logging statements


import connectionManager from './connectionManager';
import mapEvents from './utilities/mapEvents';
import client from 'node-eventstore-client';
import debugModule from 'debug';
import assert from 'assert';

const debug = debugModule('geteventstore:subscribeToStream');
const baseErr = 'Subscribe to Stream - ';

export default (config) => (
	streamName,
	onEventAppeared,
	onDropped,
	resolveLinkTos = false
) => new Promise(async (resolve, reject) => {
	assert(streamName, `${baseErr}Stream Name not provided`);

	let connection;
	const onEvent = (sub, ev) => {
		const mappedEvent = mapEvents([ev])[0];
		if (mappedEvent) onEventAppeared(mappedEvent);
	};

	const onConnected = async () => {
		try {
			const subscription = await connection.subscribeToStream(streamName, resolveLinkTos, onEvent, onDropped, new client.UserCredentials(config.credentials.username, config.credentials.password));
			debug('', 'Subscription: %j', subscription);
			resolve(subscription);
		} catch (ex) {
			reject(ex);
		}
	};

	try {
		connection = await connectionManager.create(config, onConnected, true);
	} catch (ex) {
		reject(ex);
	}
});

import connectionManager from './connectionManager';
import mapEvents from './utilities/mapEvents';
import client from 'node-eventstore-client';
import debugModule from 'debug';
import assert from 'assert';

const debug = debugModule('geteventstore:subscribeToStream');
const baseErr = 'Subscribe to Stream - ';

export default (config) => (
	streamName,
	onEventAppeared,
	onDropped,
	resolveLinkTos = false
) => new Promise(async (resolve, reject) => {
	assert(streamName, `${baseErr}Stream Name not provided`);

	let connection;
	const onEvent = (sub, ev) => {
		const mappedEvent = mapEvents([ev])[0];
		if (mappedEvent) onEventAppeared(sub, mappedEvent);
	};

	const onConnected = async () => {
		try {
			const subscription = await connection.subscribeToStream(streamName, resolveLinkTos, onEvent, onDropped, new client.UserCredentials(config.credentials.username, config.credentials.password));
			debug('', 'Subscription: %j', subscription);
			resolve(subscription);
		} catch (ex) {
			reject(ex);
		}
	};

	try {
		connection = await connectionManager.create(config, onConnected, true);
	} catch (ex) {
		reject(ex);
	}
});

Unify 'onEventAppeared' and 'onEvent' signatures.


'use strict';

function prepareQuery(sql) {
  var affectedTableRegexCache = {
      bbox: /!bbox!/g,
      scale_denominator: /!scale_denominator!/g,
      pixel_width: /!pixel_width!/g,
      pixel_height: /!pixel_height!/g
  };

  return sql
      .replace(affectedTableRegexCache.bbox, 'ST_MakeEnvelope(0,0,0,0)')
      .replace(affectedTableRegexCache.scale_denominator, '0')
      .replace(affectedTableRegexCache.pixel_width, '1')
      .replace(affectedTableRegexCache.pixel_height, '1');
}

module.exports.extractTableNames = function extractTableNames(query) {
    return [
        'SELECT * FROM CDB_QueryTablesText($windshaft$',
        prepareQuery(query),
        '$windshaft$) as tablenames'
    ].join('');
};

module.exports.getColumnNamesFromQuery = function extractTableNames(query) {
    return [
        'SELECT * FROM (',
        prepareQuery(query),
        ') as columnNames limit 0'
    ].join('');
};

module.exports.getTableStats = function getTableStats(table, geomColumn) {
    return [
        'SELECT row_to_json(s) as result FROM(',
        'select _postgis_stats(\'',
        table,
        '\'::regclass, \'',
        (geomColumn || 'the_geom'),
        '\') as stats) as s',
    ].join('');
};

'use strict';

function prepareQuery(sql) {
  var affectedTableRegexCache = {
      bbox: /!bbox!/g,
      scale_denominator: /!scale_denominator!/g,
      pixel_width: /!pixel_width!/g,
      pixel_height: /!pixel_height!/g
  };

  return sql
      .replace(affectedTableRegexCache.bbox, 'ST_MakeEnvelope(0,0,0,0)')
      .replace(affectedTableRegexCache.scale_denominator, '0')
      .replace(affectedTableRegexCache.pixel_width, '1')
      .replace(affectedTableRegexCache.pixel_height, '1');
}

module.exports.extractTableNames = function extractTableNames(query) {
    return [
        'SELECT * FROM CDB_QueryTablesText($windshaft$',
        prepareQuery(query),
        '$windshaft$) as tablenames'
    ].join('');
};

module.exports.getColumnNamesFromQuery = function extractTableNames(query) {
    return [
        'SELECT * FROM (',
        prepareQuery(query),
        ') as columnNames limit 0'
    ].join('');
};

module.exports.getTableStats = function getTableStats(table, geomColumn) {
    geomColumn = geomColumn || 'the_geom';

    return [
        'with feature_stats AS (',
        '    select (_postgis_stats(',
        '        \'' + table + '\'::regclass, \'' + geomColumn + '\'',
        '    )::json ->> \'table_features\')::numeric as features',
        '),',
        'vertex_stats as (',
        '    select sum(ST_NPoints(' + geomColumn + ')) as vertexes from ' + table,
        ')',
        'select * from feature_stats, vertex_stats'
    ].join('\n');
};

Modify query to return vertex count


import classnames from 'classnames';
import Form from './Item/Form';
import React, { Component } from 'react';

class EditColumn extends Component {
  constructor() {
    super();

    this.state = { isEditing: false };

    this.onDelete = this.onDelete.bind(this);
    this.onEdit = this.onEdit.bind(this);
    this.toggleEdit = this.toggleEdit.bind(this);
  }

  onDelete() {
    this.props.onDelete(this.props.row.id);
  }

  toggleEdit() {
    this.setState({ isEditing: true });
  }

  onEdit(item) {
    this.setState({ isEditing: false });

    this.props.onEdit(item);
  }

  render() {
    const editName = classnames('btn-group', {
      'open': this.state.isEditing,
    });

    return (

          <button
            className="btn btn-primary"
            onClick={this.toggleEdit}
            type="button"
          >
            Edit
          </button>

            <Form
              item={this.props.row}
              onSubmit={this.onEdit}
              isEditing={this.state.isEditing}
            />

        <button
          className="btn btn-primary"
          onClick={this.onDelete}
          type="button"
        >
          Delete
        </button>

    );
  }
}

export default (EditColumn);

import classnames from 'classnames';
import Form from './Item/Form';
import Modal from 'react-modal';
import React, { Component } from 'react';

class EditColumn extends Component {
  constructor() {
    super();

    this.state = { isEditing: false };

    this.onDelete = this.onDelete.bind(this);
    this.onEdit = this.onEdit.bind(this);
    this.toggleEdit = this.toggleEdit.bind(this);
  }

  onDelete() {
    this.props.onDelete(this.props.row.id);
  }

  toggleEdit() {
    this.setState({ isEditing: true });
  }

  onEdit(item) {
    this.setState({ isEditing: false });

    this.props.onEdit(item);
  }

	render() {
		return (
			<div>
        <Modal
          contentLabel="Edit Item Modal"
          isOpen={this.state.isEditing}
          overlayClassName="modal-overlay"
          onRequestClose={this.onEdit}
          shouldCloseOnOverlayClick={true}
        >
          <Form
              item={this.props.row}
              onSubmit={this.onEdit}
              isEditing={this.state.isEditing}
            />
        </Modal>
        <button
          className="btn btn-primary"
          onClick={this.toggleEdit}
        >
          Edit
        </button>
	      <button
	      	className="btn btn-primary"
	      	onClick={this.onDelete}
	      >
	      	Delete
	      </button>
      </div>
    );
  }
}

export default (EditColumn);

Convert column edit to react-modal


import React, { PropTypes } from 'react';
import { List } from 'immutable';
import Polyline from './Polyline';
import Marker from './Marker';

function TaskPlot(props) {
  const { task } = props;
  const positions = task.map(waypoint => waypoint.get('position'));

  let markers = positions.map((pos, index) =>
    <Marker googlemaps={props.googlemaps} map={props.map} position={pos} key={index} />)
    .toArray();

  return (
    <span>
      <Polyline googlemaps={props.googlemaps} map={props.map} path={positions} />

      {markers}
    </span>
  );

}

TaskPlot.propTypes = {
  task: PropTypes.instanceOf(List),
  map: PropTypes.object,
  googlemaps: PropTypes.object
};

export default TaskPlot;

import React, { PropTypes } from 'react';
import { List } from 'immutable';
import Polyline from './Polyline';
import Marker from './Marker';

class TaskPlot extends React.Component {

  shouldComponentUpdate(nextProps) {
    return (this.props.map !== nextProps.map) ||
      (this.props.googlemaps !== nextProps.googlemaps) ||
      (this.props.task !== nextProps.task);
  }

  render() {
    const { task, googlemaps, map } = this.props;
    const positions = task.map(waypoint => waypoint.get('position'));

    let markers = positions.map((pos, index) =>
      <Marker googlemaps={googlemaps} map={map} position={pos} key={index} />)
      .toArray();

    return (
      <span>
        <Polyline googlemaps={googlemaps} map={map} path={positions} />

        {markers}
      </span>
    );
  }
}

TaskPlot.propTypes = {
  task: PropTypes.instanceOf(List),
  map: PropTypes.object,
  googlemaps: PropTypes.object
};

export default TaskPlot;

Improve performance of task plotting.


/**
 * Data handler for a user form builder field in an Ajax form.
 *
 * @author	Matthias Schmidt
 * @copyright	2001-2019 WoltLab GmbH
 * @license	GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 * @module	WoltLabSuite/Core/Form/Builder/Field/User
 * @since	5.2
 */
define(['Core', './Field', 'WoltLabSuite/Core/Ui/ItemList'], function(Core, FormBuilderField, UiItemList) {
	"use strict";

	/**
	 * @constructor
	 */
	function FormBuilderFieldUser(fieldId) {
		this.init(fieldId);
	};
	Core.inherit(FormBuilderFieldUser, FormBuilderField, {
		/**
		 * @see	WoltLabSuite/Core/Form/Builder/Field/Field#_getData
		 */
		_getData: function() {
			var values = UiItemList.getValues(this._fieldId);
			var usernames = [];
			for (var i = 0, length = values.length; i < length; i++) {
				if (values[i].objectId) {
					usernames.push(values[i].value);
				}
			}

			var data = {};
			data[this._fieldId] = usernames.join(',');

			return data;
		}
	});

	return FormBuilderFieldUser;
});

/**
 * Data handler for a user form builder field in an Ajax form.
 *
 * @author	Matthias Schmidt
 * @copyright	2001-2019 WoltLab GmbH
 * @license	GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 * @module	WoltLabSuite/Core/Form/Builder/Field/User
 * @since	5.2
 */
define(['Core', './Field', 'WoltLabSuite/Core/Ui/ItemList'], function(Core, FormBuilderField, UiItemList) {
	"use strict";

	/**
	 * @constructor
	 */
	function FormBuilderFieldUser(fieldId) {
		this.init(fieldId);
	};
	Core.inherit(FormBuilderFieldUser, FormBuilderField, {
		/**
		 * @see	WoltLabSuite/Core/Form/Builder/Field/Field#_getData
		 */
		_getData: function() {
			var values = UiItemList.getValues(this._fieldId);
			var usernames = [];
			for (var i = 0, length = values.length; i < length; i++) {
				usernames.push(values[i].value);
			}

			var data = {};
			data[this._fieldId] = usernames.join(',');

			return data;
		}
	});

	return FormBuilderFieldUser;
});

Fix Ajax user form fields with pre-set values


'use strict';

const express = require('express');
const socketIO = require('socket.io');
const path = require('path');

const PORT = process.env.PORT || 3000;
const INDEX = path.join(__dirname, 'index.html');

const server = express()
  .use((req, res) => res.sendFile(INDEX) )
  .listen(PORT, () => console.log(`Listening on ${ PORT }`));

const io = socketIO(server);

io.on('connection', function(socket){

  console.log('a user connected');

  socket.on('disconnect', function(){
    console.log('user disconnected');
  });

  socket.on('chat message', function(msg){
    console.log('message: ' + msg);
  });

  socket.on('incrementor', function(msg){
      if (val != msg) {
          console.log('Incrementor: ' + msg);
          val = msg;
      }
  });

});

'use strict';

const express = require('express');
const socketIO = require('socket.io');
const path = require('path');

const PORT = process.env.PORT || 3000;
const INDEX = path.join(__dirname, 'index.html');

var val = 42

const server = express()
  .use((req, res) => res.sendFile(INDEX) )
  .listen(PORT, () => console.log(`Listening on ${ PORT }`));

const io = socketIO(server);

io.on('connection', function(socket){

  console.log('a user connected');

  socket.on('disconnect', function(){
    console.log('user disconnected');
  });

  socket.on('chat message', function(msg){
    console.log('message: ' + msg);
  });

  socket.on('incrementor', function(msg){
      if (val != msg) {
          console.log('Incrementor: ' + msg);
          val = msg;
      }
  });

});

setInterval(() => io.emit('time', val), 1000);

Set to broadcast val over a socket


var DEBUG_PORT = 8000;

var express = require('express');
var sys = require('sys');
var exec = require('child_process').exec;
var app = express();

app.use(express.bodyParser());

app.get('/', function(req, res) {
	res.send("Welcome to radiof.at! <form action='/' method='post' enctype='multipart/form-data'><input type='file' name='song' /><input type='submit' /></form>");
});

app.post('/', function(req, res) {
	if (req.files.song.type == 'audio/mp3') {
		// Convert an MP3
		exec('./PROCESSMP3 ' + req.files.song.path, { timeout: 0, maxBuffer: 0 }, function(err, stdout, stderr) {
			console.log("We're done!!!");
			console.log(stdout);
			res.sendfile(req.files.song.path + ".mp3");
		});
	}
});

app.listen(process.env.PORT || DEBUG_PORT);

var DEBUG_PORT = 8000;

var express = require('express');
var sys = require('sys');
var exec = require('child_process').exec;
var app = express();

app.use(express.bodyParser());

app.get('/', function(req, res) {
	res.send("Welcome to radiof.at! <form action='/' method='post' enctype='multipart/form-data'><input type='file' name='song' /><input type='submit' /></form>");
});

app.post('/', function(req, res) {
	if (req.files.song.type == 'audio/mp3') {
		// Convert an MP3
		exec('./PROCESSMP3 ' + req.files.song.path, { timeout: 0, maxBuffer: 10485760 }, function(err, stdout, stderr) {
			console.log("We're done!!!");
			console.log(stdout);
			res.sendfile(req.files.song.path + ".mp3");
		});
	}
});

app.listen(process.env.PORT || DEBUG_PORT);

Set buffer size to 10mb


const { describe, it, before, after, beforeEach, expect, sinon } = require('../../../test-helper');
const Hapi = require('hapi');

const snapshotController = require('../../../../lib/application/snapshots/snapshot-controller');

describe('Unit | Router | snapshot-router', () => {

  let server;

  beforeEach(() => {
    server = new Hapi.Server();
    server.connection({ port: null });
    server.register({ register: require('../../../../lib/application/snapshots') });
  });

  describe('POST /api/snapshots', _ => {

    before(() => {
      sinon.stub(snapshotController, 'create').callsFake((request, reply) => reply('ok'));
    });

    after(() => {
      snapshotController.create.restore();
    });

    it('should exist', (done) => {
      return server.inject({ method: 'POST', url: '/api/snapshots' }, res => {
        expect(res.statusCode).to.equal(200);
        done();
      });
    });
  });

});

Add unit test to snapshot index route


/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * @format
 * @flow
 */

import React, {Component} from 'react';
import {Platform, StyleSheet, Text, View} from 'react-native';

const instructions = Platform.select({
  ios: 'Press Cmd+R to reload,\n' + 'Cmd+D or shake for dev menu',
  android:
    'Double tap R on your keyboard to reload,\n' +
    'Shake or press menu button for dev menu',
});

type Props = {};
export default class App extends Component<Props> {
  render() {
    return (
      <View>
        <Text>Hello world!</Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
});

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * @format
 * @flow
 */

import React, {Component} from 'react';
import {Platform, StyleSheet, Text, View, Image} from 'react-native';

const instructions = Platform.select({
  ios: 'Press Cmd+R to reload,\n' + 'Cmd+D or shake for dev menu',
  android:
    'Double tap R on your keyboard to reload,\n' +
    'Shake or press menu button for dev menu',
});

type Props = {};

class Greeting extends Component {
  render() {
    return (
      <Text>Hello {this.props.name}!</Text>
    );
  }
}

export default class App extends Component<Props> {
  render() {
    let pic = {
      uri: 'https://upload.wikimedia.org/wikipedia/commons/d/de/Bananavarieties.jpg'
    };
    return (
      <View>
        <Text>Hello world!</Text>
        <Image source={pic} style={{width: 193, height: 110}}/>
        <Greeting name='Rexxar' />
        <Greeting name='Jaina' />
        <Greeting name='Valeera' />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
});

Add banana image and greeting class


'use strict';

window.addEventListener('load', function (){

// WebGPIO LED Blink
  navigator.requestGPIOAccess().then(
    function(gpioAccess) {
        console.log("GPIO ready!");
        return gpioAccess;
    }).then(gpio=>{
      var port = gpio.ports.get(256);
      var v = 0;
      return port.export("out").then(()=>{
        setInterval(function(){
          v = v ? 0 : 1;
          port.write(v);
        },5000);
      });
  }).catch(error=>{
    console.log("Failed to get GPIO access catch: " + error.message);
  });
}, false);

'use strict';

window.addEventListener('load', function (){

// WebGPIO LED Blink
  navigator.requestGPIOAccess().then(
    function(gpioAccess) {
        console.log("GPIO ready!");
        return gpioAccess;
    }).then(gpio=>{
      var port = gpio.ports.get(198);
      var v = 0;
      return port.export("out").then(()=>{
        setInterval(function(){
          v = v ? 0 : 1;
          port.write(v);
        },1000);
      });
  }).catch(error=>{
    console.log("Failed to get GPIO access catch: " + error.message);
  });
}, false);

Change gpio port number and blink interval.


import NativeMarkerIndex from '../../build/Release/marker_index'

NativeMarkerIndex.prototype.getRange = function (id) {
  return [this.getStart(id), this.getEnd(id)]
}

NativeMarkerIndex.prototype.findIntersecting = function (start, end) {
  return new Set(this._findIntersecting(start, end))
}

NativeMarkerIndex.prototype.findContaining = function (start, end) {
  return new Set(this._findContaining(start, end))
}

NativeMarkerIndex.prototype.findContainedIn = function (start, end) {
  return new Set(this._findContainedIn(start, end))
}

NativeMarkerIndex.prototype.findStartingIn = function (start, end) {
  return new Set(this._findStartingIn(start, end))
}

NativeMarkerIndex.prototype.findEndingIn = function (start, end) {
  return new Set(this._findEndingIn(start, end))
}

export default NativeMarkerIndex

import NativeMarkerIndex from '../../build/Release/marker_index'

NativeMarkerIndex.prototype.getRange = function (id) {
  return [this.getStart(id), this.getEnd(id)]
}

NativeMarkerIndex.prototype.findIntersecting = function (start, end) {
  return new Set(this._findIntersecting(start, end))
}

NativeMarkerIndex.prototype.findContaining = function (start, end) {
  return new Set(this._findContaining(start, end))
}

NativeMarkerIndex.prototype.findContainedIn = function (start, end) {
  return new Set(this._findContainedIn(start, end))
}

NativeMarkerIndex.prototype.findStartingIn = function (start, end) {
  return new Set(this._findStartingIn(start, end))
}

NativeMarkerIndex.prototype.findEndingIn = function (start, end) {
  return new Set(this._findEndingIn(start, end))
}

NativeMarkerIndex.prototype.findStartingAt = function (offset) {
  return new Set(this._findStartingIn(offset, offset))
}

NativeMarkerIndex.prototype.findEndingAt = function (offset) {
  return new Set(this._findEndingIn(offset, offset))
}

export default NativeMarkerIndex

Add findStarting/EndingAt to native MarkerIndex


define(function() {
  /**
   * If the browsers follow the spec, then they would expose vendor-specific style as:
   *   elem.style.WebkitBorderRadius
   * instead of something like the following, which would be technically incorrect:
   *   elem.style.webkitBorderRadius

   * Webkit ghosts their properties in lowercase but Opera & Moz do not.
   * Microsoft uses a lowercase `ms` instead of the correct `Ms` in IE8+
   *   erik.eae.net/archives/2008/03/10/21.48.10/

   * More here: github.com/Modernizr/Modernizr/issues/issue/21
   *
   * @access private
   * @returns {string} The string representing the vendor-specific style properties
   */

  var omPrefixes = 'Moz O ms Webkit';
  return omPrefixes;
});

define(function() {
  /**
   * If the browsers follow the spec, then they would expose vendor-specific styles as:
   *   elem.style.WebkitBorderRadius
   * instead of something like the following (which is technically incorrect):
   *   elem.style.webkitBorderRadius

   * WebKit ghosts their properties in lowercase but Opera & Moz do not.
   * Microsoft uses a lowercase `ms` instead of the correct `Ms` in IE8+
   *   erik.eae.net/archives/2008/03/10/21.48.10/

   * More here: github.com/Modernizr/Modernizr/issues/issue/21
   *
   * @access private
   * @returns {string} The string representing the vendor-specific style properties
   */

  var omPrefixes = 'Moz O ms Webkit';
  return omPrefixes;
});

Fix minor typos in comment block


var _ = require('lodash')
var fs = require('fs')
var gzip = require('gzip-size')
var filesize = require('filesize')
var postcss = require('postcss')
var cssstats = require('cssstats')

var display = require('tachyons-display/package.json')
var displayCss = fs.readFileSync('node_modules/tachyons-display/tachyons-display.min.css', 'utf8')
var displayObj = cssstats(displayCss)
var displaySize = filesize(displayObj.gzipSize)

var srcCSS = fs.readFileSync('./src/_display.css', 'utf8')
var navDocs = fs.readFileSync('./templates/nav_docs.html', 'utf8')

var template = fs.readFileSync('./templates/docs/display/index.html', 'utf8')
var tpl = _.template(template)
var html = tpl({
  displayVersion: display.version,
  displaySize: displaySize,
  displayObj: displayObj,
  srcCSS: srcCSS,
  navDocs: navDocs
})

fs.writeFileSync('./docs/layout/display/index.html', html)

var _ = require('lodash')
var fs = require('fs')
var gzip = require('gzip-size')
var filesize = require('filesize')
var postcss = require('postcss')
var cssstats = require('cssstats')

var module = require('tachyons-display/package.json')
var moduleCss = fs.readFileSync('node_modules/tachyons-display/tachyons-display.min.css', 'utf8')
var moduleObj = cssstats(moduleCss)
var moduleSize = filesize(moduleObj.gzipSize)

var srcCSS = fs.readFileSync('./src/_display.css', 'utf8')
var navDocs = fs.readFileSync('./templates/nav_docs.html', 'utf8')
var siteFooter = fs.readFileSync('./templates/footer.html', 'utf8')

var template = fs.readFileSync('./templates/docs/display/index.html', 'utf8')
var tpl = _.template(template)
var html = tpl({
  moduleVersion: module.version,
  moduleSize: moduleSize,
  moduleObj: moduleObj,
  srcCSS: srcCSS,
  navDocs: navDocs,
  siteFooter: siteFooter
})

fs.writeFileSync('./docs/layout/display/index.html', html)

Add site footer to each documentation generator


module.exports = new AppUtil();

var util = require("util");
var FrameInputStream = require("./frame_input_stream");

function AppUtil(){

}

AppUtil.prototype.init = function(argv){
    this.argv = argv;
    return this;
};

AppUtil.prototype.log = function(message){
    if(this.argv.verbose){
        util.print(message + "\n");
    }
};

AppUtil.prototype.fatalError = function(message){
    process.stderr.write(message + "\n");
    process.exit(1);
};

AppUtil.prototype.fatalErrorEvent = function(format){
    var self = this;
    return function(error){
        self.fatalError(util.format(format, error.message));
    };
};

AppUtil.prototype.parseHeaderLines = function(){

    var headers = {};

    var headerPattern = /([^:]+):(.*)/;

    for(var i = 0; i < arguments.length; i++){

        var arg = arguments[i];

        if(!(arg instanceof Array)){
            arg = [arg];
        }

        for(var j = 0; j < arg.length; j++){

            var lines = ("" + arg[j]).split("\n");

            for(var k = 0; k < lines.length; k++){

                var line = lines[k];

                var match = line.match(headerPattern);

                if(match){
                    headers[match[1]] = FrameInputStream.prototype.decodeHeaderValue(match[2]);
                }
            }
        }
    }

    return headers;
};

module.exports = {
    init: init
};

var util = require("util");
var FrameInputStream = require("./frame_input_stream");

function AppUtil(argv){
    this.argv = argv;
}

function init(argv){
    return new AppUtil(argv);
}

AppUtil.prototype.log = function(message){
    if(this.argv.verbose){
        util.print(message + "\n");
    }
};

AppUtil.prototype.fatalError = function(message){
    process.stderr.write(message + "\n");
    process.exit(1);
};

AppUtil.prototype.fatalErrorEvent = function(format){
    var self = this;
    return function(error){
        self.fatalError(util.format(format, error.message));
    };
};

AppUtil.prototype.parseHeaderLines = function(){

    var headers = {};

    var headerPattern = /([^:]+):(.*)/;

    for(var i = 0; i < arguments.length; i++){

        var arg = arguments[i];

        if(!(arg instanceof Array)){
            arg = [arg];
        }

        for(var j = 0; j < arg.length; j++){

            var lines = ("" + arg[j]).split("\n");

            for(var k = 0; k < lines.length; k++){

                var line = lines[k];

                var match = line.match(headerPattern);

                if(match){
                    headers[match[1]] = FrameInputStream.prototype.decodeHeaderValue(match[2]);
                }
            }
        }
    }

    return headers;
};

Return new AppUtil instance from init call


#!/usr/bin/env node
var prerender = require('./lib');
process.env.PORT = process.env.PORT || 4000

var server = prerender({
    workers: process.env.PHANTOM_CLUSTER_NUM_WORKERS,
    iterations: process.env.PHANTOM_WORKER_ITERATIONS || 10,
    phantomBasePort: process.env.PHANTOM_CLUSTER_BASE_PORT || 12300,
    messageTimeout: process.env.PHANTOM_CLUSTER_MESSAGE_TIMEOUT
});

// server.use(prerender.basicAuth());
// server.use(prerender.whitelist());
server.use(prerender.blacklist());
// server.use(prerender.logger());
server.use(prerender.removeScriptTags());
server.use(prerender.httpHeaders());
// server.use(prerender.inMemoryHtmlCache());
// server.use(prerender.s3HtmlCache());
server.use(prerender.redisHtmlCache());

server.start();

#!/usr/bin/env node
var prerender = require('./lib');
process.env.PORT = process.env.PORT || 4000

var server = prerender({
    workers: process.env.PHANTOM_CLUSTER_NUM_WORKERS || 10,
    iterations: process.env.PHANTOM_WORKER_ITERATIONS || 10,
    phantomBasePort: process.env.PHANTOM_CLUSTER_BASE_PORT || 12300,
    messageTimeout: process.env.PHANTOM_CLUSTER_MESSAGE_TIMEOUT
});

// server.use(prerender.basicAuth());
// server.use(prerender.whitelist());
server.use(prerender.blacklist());
// server.use(prerender.logger());
server.use(prerender.removeScriptTags());
server.use(prerender.httpHeaders());
// server.use(prerender.inMemoryHtmlCache());
// server.use(prerender.s3HtmlCache());
server.use(prerender.redisHtmlCache());

server.start();

Set default workers to 10


import { Screen } from '../screen';

export class LoadScreen extends Screen {

  // also try deploying and seeing if extended browsersync time is the reason for the memory leak

  static enter(/* display */) {
    // this.flicker = 0;

    // const dotStatus = ['.  ', '.. ', '...', ' ..', '  .'];

    /* this.interval = setInterval(() => {
      display.clear();
      this.render(display);

      this.drawCenterText(display, 12, `Generating ${dotStatus[this.flicker]}`);

      this.flicker = ++this.flicker % dotStatus.length;
    }, 500); */
  }

  static exit() {
    // window.clearInterval(this.interval);
  }

  static render(display) {
    this.drawCenterText(display,  11, '-Welcome to Roguathia-');
    this.drawCenterText(display,  12, 'Generating...');
  }
}

import { Screen } from '../screen';

export class LoadScreen extends Screen {
  static enter(display) {
    this.flicker = 0;

    const dotStatus = ['.  ', '.. ', '...', ' ..', '  .'];

    this.interval = setInterval(() => {
      display.clear();
      this.render(display);

      this.drawCenterText(display, 12, `Generating ${dotStatus[this.flicker]}`);

      this.flicker = ++this.flicker % dotStatus.length;
    }, 500);
  }

  static exit() {
    window.clearInterval(this.interval);
  }

  static render(display) {
    this.drawCenterText(display,  11, '-Welcome to Roguathia-');
  }
}

Revert "attempt to address memory leak"


import gql from 'graphql-tag';
import { graphql } from 'react-apollo';

import ProjectList from '../components/ProjectList';

const MyAppsQuery = gql`
  query MyApps($limit: Int!, $offset: Int!){
    viewer {
      me {
        id
        appCount
        apps(limit: $limit, offset: $offset) {
          id
          fullName
          iconUrl
          packageName
          description
          lastPublishedTime
        }
      }
    }
  }

`

export default graphql(MyAppsQuery, {
  props: (props) => {
    let { data } = props;
    let apps, appCount;
    if (data.viewer && data.viewer.me) {
      apps = data.viewer.me.apps;
      appCount = data.viewer.me.appCount;
    }

    return {
      ...props,
      data: {
        ...data,
        appCount,
        apps,
      },
      loadMoreAsync() {
        return data.fetchMore({
          variables: {
            offset: apps.length,
          },
          updateQuery: (previousData, { fetchMoreResult }) => {
            if (!fetchMoreResult.data) {
              return previousResult;
            }

            return Object.assign({}, previousData, {
              viewer: {
                me: {
                  ...fetchMoreResult.data.viewer.me,
                  apps: [
                    ...previousData.viewer.me.apps,
                    ...fetchMoreResult.data.viewer.me.apps
                  ],
                }
              },
            });
          },
        });
      }
    };
  },
  options: {
    variables: {
      limit: 15,
      offset: 0,
    },
  },
})(ProjectList);

import gql from 'graphql-tag';
import { graphql } from 'react-apollo';

import ProjectList from '../components/ProjectList';

const MyAppsQuery = gql`
  query MyApps($limit: Int!, $offset: Int!){
    viewer {
      me {
        id
        appCount
        apps(limit: $limit, offset: $offset) {
          id
          fullName
          iconUrl
          packageName
          description
          lastPublishedTime
          likeCount
        }
      }
    }
  }

`

export default graphql(MyAppsQuery, {
  props: (props) => {
    let { data } = props;
    let apps, appCount;
    if (data.viewer && data.viewer.me) {
      apps = data.viewer.me.apps;
      appCount = data.viewer.me.appCount;
    }

    return {
      ...props,
      data: {
        ...data,
        appCount,
        apps,
      },
      loadMoreAsync() {
        return data.fetchMore({
          variables: {
            offset: apps.length,
          },
          updateQuery: (previousData, { fetchMoreResult }) => {
            if (!fetchMoreResult.data) {
              return previousResult;
            }

            return Object.assign({}, previousData, {
              viewer: {
                me: {
                  ...fetchMoreResult.data.viewer.me,
                  apps: [
                    ...previousData.viewer.me.apps,
                    ...fetchMoreResult.data.viewer.me.apps
                  ],
                }
              },
            });
          },
        });
      }
    };
  },
  options: {
    variables: {
      limit: 15,
      offset: 0,
    },
  },
})(ProjectList);

Add missing like count from my projects list query


import {assert} from 'chai';

import {jsdom} from 'jsdom';

import {ImageTransformer} from '../src/imagetransformer';

describe('image transformer', function() {
    beforeEach(function() {
        this.document = jsdom();
    });

    it('should accept a mimetype', function() {
        var imt = new ImageTransformer('image/png');
        assert.equal(imt.mimetype, "image/png");
        var imt2 = new ImageTransformer('image/jpeg');
        assert.equal(imt2.mimetype, "image/jpeg");
    });
    describe('#transform', function() {
        it('should create an <img> with the right mimetype', function() {
            var imt = new ImageTransformer('image/png');
            var imageData = "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
            var el = imt.transform(imageData, this.document);

            assert.equal(el.src, "data:image/png;base64," + imageData);
            assert.equal(el.localName, "img");
            assert.equal(el.innerHTML, "");

            var imt2 = new ImageTransformer('image/gif');
            var el2 = imt2.transform(imageData, this.document);
            assert.equal(el2.src, "data:image/gif;base64," + imageData);
            assert.equal(el2.localName, "img");
            assert.equal(el2.innerHTML, "");
        });
    });
});

import {assert} from 'chai';

import {jsdom} from 'jsdom';

import {ImageTransformer} from '../src/imagetransformer';

describe('image transformer', function() {
    beforeEach(function() {
        this.document = jsdom();
    });

    it('should accept a mimetype', function() {
        var imt = new ImageTransformer('image/png');
        assert.equal(imt.mimetype, "image/png");
        var imt2 = new ImageTransformer('image/jpeg');
        assert.equal(imt2.mimetype, "image/jpeg");
    });
    describe('#transform', function() {
        it('should create an <img> with the right mimetype', function() {
            var imt = new ImageTransformer('image/png');
            var imageData = "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
            var el = imt.transform(imageData, this.document);

            assert.equal(el.src, "data:image/png;base64," + imageData);
            assert.equal(el.localName, "img");
            assert.equal(el.innerHTML, "");

            var imt2 = new ImageTransformer('image/gif');
            imageData = "R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=";
            var el2 = imt2.transform(imageData, this.document);
            assert.equal(el2.src, "data:image/gif;base64," + imageData);
            assert.equal(el2.localName, "img");
            assert.equal(el2.innerHTML, "");
        });
    });
});

Switch to small gif image.


const {
  FuseBox,
  SVGPlugin,
  CSSPlugin,
  BabelPlugin,
  WebIndexPlugin,
} = require("fuse-box");

const path = require('path')

let fuse, app, vendor, isProduction;

fuse = FuseBox.init({
  homeDir: "src",
  output: "dist/$name.js",
  sourceMaps: {project: true, vendor: true},
  plugins: [
    SVGPlugin(), CSSPlugin(), BabelPlugin(),
    WebIndexPlugin({
      title: "Todolab",
      template: path.resolve(__dirname, 'src', 'index.html')
    })
  ]
});

// vendor
vendor = fuse.bundle("vendor").instructions("~ index.jsx")

// bundle app
app = fuse.bundle("app").instructions("> [index.jsx]")

fuse.dev({
  port: process.env.PORT
});

app.watch().hmr()

// detect file changes on a mounted volume
fuse.run({chokidar: {usePolling: true}})

const {
    FuseBox,
    SVGPlugin,
    CSSPlugin,
    BabelPlugin,
    QuantumPlugin,
    WebIndexPlugin,
    Sparky
} = require("fuse-box");

const path = require('path')

let fuse, app, vendor, isProduction;
Sparky.task("config", () => {
    fuse = new FuseBox({
        homeDir: "src/",
        sourceMaps: true,
        hash: isProduction,
        output: "dist/$name.js",
        plugins: [
            SVGPlugin(), CSSPlugin(), BabelPlugin(),
            WebIndexPlugin({
              title: "Todolab",
              template: path.resolve(__dirname, 'src', 'index.html')
            }),
            isProduction && QuantumPlugin({
                removeExportsInterop: false,
                uglify: true
            })
        ]
    });
    // vendor
    vendor = fuse.bundle("vendor").instructions("~ index.jsx")

    // bundle app
    app = fuse.bundle("app").instructions("> [index.jsx]")
});

Sparky.task("default", ["clean", "config"], () => {
    fuse.dev({
      port: process.env.PORT
    });
    // add dev instructions
    app.watch().hmr()

    return fuse.run({chokidar: {usePolling: true}})
});

Sparky.task("clean", () => Sparky.src("dist/").clean("dist/"));
Sparky.task("prod-env", ["clean"], () => { isProduction = true })
Sparky.task("dist", ["prod-env", "config"], () => {
    // comment out to prevent dev server from running (left for the demo)
    fuse.dev();
    return fuse.run();
});

Add develop and production task


// @flow

import fs from 'fs'
import path from 'path'

import chalk from 'chalk'
import stableStringify from 'json-stable-stringify-without-jsonify'
import { table } from 'table'

import { messages as englishMessages } from '../../src/intl/messages'

const SOURCE = path.join('extra', 'from-zanata', 'translated-messages.json')
const DESTINATION = path.join('src', 'intl', 'translated-messages.json')

function main() {
  const stringContent = fs.readFileSync(SOURCE, { encoding: 'utf8' })
  const parsedContent = JSON.parse(stringContent)
  removeEmptyMessages(parsedContent)
  const serializedContent = stableStringify(parsedContent, { space: 2 }) + '\n'
  fs.writeFileSync(DESTINATION, serializedContent)
  console.log(chalk.green(`[normalize-messages.js] ${DESTINATION} written ✔`))
}

/**
 * Sometimes Zanata returns empty string. These are removed for the app to be able to
 * rendered the message in default language (English).
 *
 * @param translations object from zanata
 */
function removeEmptyMessages(translations) {
  Object.keys(translations).forEach(langKey => {
    const languageMessages = translations[langKey]
    Object.keys(languageMessages).forEach(messageKey => {
      const messageValue = languageMessages[messageKey]
      if (messageValue === '') {
        delete languageMessages[messageKey]
      }
    })
  })
}

main()

// @flow

import fs from 'fs'
import path from 'path'

import chalk from 'chalk'
import stableStringify from 'json-stable-stringify-without-jsonify'
import { table } from 'table'

import { messages as englishMessages } from '../../src/intl/messages'

const SOURCE = path.join('extra', 'from-zanata', 'translated-messages.json')
const DESTINATION = path.join('src', 'intl', 'translated-messages.json')

function main() {
  const stringContent = fs.readFileSync(SOURCE, { encoding: 'utf8' })
  const parsedContent = JSON.parse(stringContent)
  removeEmptyMessages(parsedContent)

  const pretty = stableStringify(parsedContent, {
    space: '  ',
    cmp: (a, b) => { return a.key > b.key ? 1 : -1 }
  }) + '\n'
  fs.writeFileSync(DESTINATION, pretty)
  console.log(chalk.green(`[normalize-messages.js] ${DESTINATION} written ✔`))
}

/**
 * Sometimes Zanata returns empty string. These are removed for the app to be able to
 * rendered the message in default language (English).
 *
 * @param translations object from zanata
 */
function removeEmptyMessages(translations) {
  Object.keys(translations).forEach(langKey => {
    const languageMessages = translations[langKey]
    Object.keys(languageMessages).forEach(messageKey => {
      const messageValue = languageMessages[messageKey]
      if (messageValue === '') {
        delete languageMessages[messageKey]
      }
    })
  })
}

main()

Refactor i18n normalize script to explicitly sort


define(function(require) {
    'use strict';

    var TransitionDialogWidget;
    var DialogWidget = require('oro/dialog-widget');
    var TransitionEventHandlers = require('oroworkflow/js/transition-event-handlers');
    var $ = require('jquery');

    TransitionDialogWidget = DialogWidget.extend({
        listen: {
            'transitionSuccess': 'onTransitionSuccess',
            'transitionFailure': 'onTransitionFailure'
        },
        onTransitionSuccess: function(response) {
            var element = $('<div>');
            this.hide();
            TransitionEventHandlers.getOnSuccess(element)(response);
        },
        onTransitionFailure: function(jqxhr) {
            var element = $('<div>');
            this.hide();
            TransitionEventHandlers.getOnFailure(element)(jqxhr);
        }
    });

    return TransitionDialogWidget;
});

define(function(require) {
    'use strict';

    var TransitionDialogWidget;
    var DialogWidget = require('oro/dialog-widget');
    var mediator = require('oroui/js/mediator');
    var TransitionEventHandlers = require('oroworkflow/js/transition-event-handlers');
    var $ = require('jquery');
    var _ = require('underscore');

    TransitionDialogWidget = DialogWidget.extend({
        listen: {
            'transitionSuccess': 'onTransitionSuccess',
            'transitionFailure': 'onTransitionFailure'
        },
        onTransitionSuccess: function(response) {
            var element = $('<div>');
            this.hide();
            TransitionEventHandlers.getOnSuccess(element)(response);
        },
        onTransitionFailure: function(jqxhr) {
            var element = $('<div>');
            this.hide();
            TransitionEventHandlers.getOnFailure(element)(jqxhr);
        },
        _onContentLoad: function(content) {
            if (_.has(content, 'workflowItem')) {
                var element = $('<div>');
                this.hide();
                mediator.execute('showLoading');
                TransitionEventHandlers.getOnSuccess(element)(content);
            }

            return TransitionDialogWidget.__super__._onContentLoad.apply(this, arguments);
        }
    });

    return TransitionDialogWidget;
});

Create controller actions with layout renderer for transition form - Updated JS for Transition Dialog - Updated Controllers


// @flow

export { default as Col } from './Col';
export { default as Row } from './Row';

// @flow

import Col from './Col';
import Row from './Row';

export default {
  Col,
  Row,
};

Revert "chore(general): expose flex grid row and column as separate components"


/*!
 * hackerone.reports.comment.create (https://github.com/xc0d3rz/hackerone)
 * Copyright 2016-2017 xc0d3rz(x.c0d3rz000@gmail.com)
 * Licensed under the MIT license
 */

/**
 *
 * @param id
 * @param param
 * @param callback
 */
module.exports = function (id, param, callback) {
    this.post('reports/' + id + '/close_comments', {
        data: {
            type: 'activity-comments-closed',
            attributes: {
                id: param.id
            }
        }
    }, callback);
};

/*!
 * hackerone.reports.comment.create (https://github.com/xc0d3rz/hackerone)
 * Copyright 2016-2017 xc0d3rz(x.c0d3rz000@gmail.com)
 * Licensed under the MIT license
 */

/**
 *
 * @param id
 * @param param
 * @param callback
 */
module.exports = function (id, param, callback) {
    this.put('reports/' + id + '/close_comments', {
        data: {
            type: 'activity-comments-closed',
            attributes: {
                id: param.id
            }
        }
    }, callback);
};

Change method from `post` to `put`


import debounce from 'lodash.debounce'

class ResizeHandler {
  constructor() {
    this._queue = []
    this.update = debounce(this.update.bind(this), 150)
    window.addEventListener('resize', this.update)
  }

  destroy() {
    window.removeEventListener('resize', this.update)
  }

  add(component) {
    this._queue.push(component)
  }

  remove(component) {
    const pos = this._queue.indexOf(component)
    if (pos > -1) {
      this._queue.splice(pos, 1)
    }
  }

  update() {
    for (let i = this._queue.length; i--;) {
      this._queue[i]._measure()
    }
  }
}

export default ResizeHandler

import debounce from 'lodash.debounce'

class ResizeHandler {
  constructor() {
    this._queue = []
    this.update = debounce(this.update.bind(this), 150)
    if (typeof window !== 'undefined') {
      window.addEventListener('resize', this.update)
    }
  }

  destroy() {
    if (typeof window !== 'undefined') {
      window.removeEventListener('resize', this.update)
    }
  }

  add(component) {
    this._queue.push(component)
  }

  remove(component) {
    const pos = this._queue.indexOf(component)
    if (pos > -1) {
      this._queue.splice(pos, 1)
    }
  }

  update() {
    for (let i = this._queue.length; i--;) {
      this._queue[i]._measure()
    }
  }
}

export default ResizeHandler

Make it work when rendered in server


'use strict'; // eslint-disable-line

const compiler = require('marko/compiler');

module.exports = {
  process(src, filePath) {
    return compiler.compileFile(filePath);
  },
};

// 'use strict'; // eslint-disable-line

// const compiler = require('marko/compiler');
// // const marko = require('marko');

// module.exports = {
//   process(src, filename, config, options) {
//     // console.log('WAS RUNNNNNN SRC', src);
//     console.log('WAS RUNNNNNN FLN', filename);
//     // console.log('WAS RUNNNNNN CNF', config);
//     // console.log('WAS RUNNNNNN OPT', options);

//     const compilerOpts = {
//       writeToDisk: false,
//       writeVersionComment: false,
//       preserveWhitespace: true,
//       autoKeyEnabled: false,
//     };

//     // return compiler.compileFile(filename, compilerOpts, (err) => {
//     //   if (err) throw err;
//     // });

//     const result = compiler.compileFile(filename, compilerOpts);

//     // const result = compiler.createInlineCompiler(filename, {});

//     // const result = marko.load(filename);

//     console.log('!!!!!!!!!!! result', result);

//     return result;
//   },
// };

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

// // module.exports = {
// //   process(src, filename, config, options) {
// //     return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';';
// //   },
// // };

// // // // // // // // // // // // // // // // // // // // // // // // // // //

'use strict'; // eslint-disable-line

const compiler = require('marko/compiler');

module.exports = {
  process(src, filename) {
    return compiler.compileFile(filename, {
      writeToDisk: false,
      writeVersionComment: false,
      preserveWhitespace: true,
      autoKeyEnabled: false,
    });
  },
};

Add options to marko compiler


// Back-end

// Front-end
$(document).ready(function () {
    // Make about section clickable and open about overlay
  $(".about").on('click', function (event) {
    event.preventDefault();
    $('.overlay').addClass('open');
      // Test
      // console.log("open");
  });

    // Close the about overlay
  $('.closeNav').on('click', function (event) {
    event.preventDefault();
    $('.overlay').removeClass('open');
  });

    // Store and Display player name in the console
    $('form.player').submit( function (event) {
        event.preventDefault();
        // Navigate to play console
        window.location.assign("play.html");

    });

    // Click the start button for the timer to start
  $('#start').click(function (event) {
    event.preventDefault();

    var min = 0;
    var sec = 30;

    var myVar = setInterval( function() { countDownTimer() } ,1000 );

    function countDownTimer() {

    var displayCountdown = $('.timer').text( min +" : " + sec );
    sec--;
    if(sec === -1) {
       clearInterval(myVar);
    };
    };
  });
});

// Back-end

// Front-end
$(document).ready(function () {
  // Make about section clickable and open about overlay
  $(".about").on('click', function (event) {
    event.preventDefault();
    $('.overlay').addClass('open');
    // Test
    // console.log("open");
  });

    // Close the about overlay
  $('.closeNav').on('click', function (event) {
    event.preventDefault();
    $('.overlay').removeClass('open');
  });

  // Navigate to play console
  $('form.player').submit( function (event) {
    event.preventDefault();
    window.location.assign("play.html");

  });

    // Click the start button for the timer to start
  $('#start').click(function (event) {
    event.preventDefault();

    var min = 0;
    var sec = 30;

    var myVar = setInterval( function() { countDownTimer() } ,1000 );

    function countDownTimer() {

    var displayCountdown = $('.timer').text( min +" : " + sec );
    sec--;
    if(sec === -1) {
       clearInterval(myVar);
    };
    };
  });
});

Add comments Comment out test sections of the work


/* Global Includes */
var testCase   = require('mocha').describe;
var pre        = require('mocha').before;
var preEach    = require('mocha').beforeEach;
var post       = require('mocha').after;
var postEach   = require('mocha').afterEach;
var assertions = require('mocha').it;
var assert     = require('chai').assert;
var validator  = require('validator');
var exec = require('child_process').execSync;
var artik      = require('../lib/artik-sdk');

/* Test Specific Includes */
var media = artik.media();
var sound_file = '/usr/share/sounds/alsa/Front_Center.wav';

/* Test Case Module */
testCase('Media', function() {

	pre(function() {
	});

	testCase('#play_sound_file', function() {

		assertions('Play the sound file', function() {
			media.play_sound_file(sound_file, function(response, status) {
				console.log('Finished playing');
			});
		});

	});

	post(function() {
	});

});

/* Global Includes */
var testCase   = require('mocha').describe;
var pre        = require('mocha').before;
var preEach    = require('mocha').beforeEach;
var post       = require('mocha').after;
var postEach   = require('mocha').afterEach;
var assertions = require('mocha').it;
var assert     = require('chai').assert;
var validator  = require('validator');
var exec = require('child_process').execSync;
var artik      = require('../lib/artik-sdk');

/* Test Specific Includes */
var media = artik.media();
var sound_file = '/usr/share/sounds/alsa/Front_Center.wav';
var start, end;

/* Test Case Module */
testCase('Media', function() {

	pre(function() {
	});

	testCase('#play_sound_file', function() {
		this.timeout(5000);
		start = new Date();

		assertions('Play the sound file', function(done) {

			media.play_sound_file(sound_file, function(response, status) {
				end = new Date();
				var timeOfPlay = (end.getTime() - start.getTime())/1000;
				console.log('Finished playing. Seconds ' + timeOfPlay);
				assert.isAtLeast(timeOfPlay, 1);
				done();
			});
		});

	});

	post(function() {
	});

});

Use time duration of test to measure the success


import path from "path";
import chalk from "chalk";
import mergeOptions from "./mergeOptions"
import findOptionsInFiles from "./findOptionsInFiles"
import defaultOptions from "./defaultOptions"
import ConfigurationError from "./ConfigurationError"

/* eslint-disable consistent-return */
export default function run(options = {}) {

	// for the option properties that weren't sent in, look for a config file
	// (either .reactserverrc or a reactServer section in a package.json). for
	// options neither passed in nor in a config file, use the defaults.
	options = mergeOptions(defaultOptions, findOptionsInFiles() || {}, options);

	const {
		routesFile,
		jsUrl,
		jsPort,
		host,
		httpsOptions,
	} = options;

	options.routesPath = path.resolve(process.cwd(), routesFile);
	options.routesDir = path.dirname(options.routesPath);
	options.routes = require(options.routesPath);

	options.outputUrl = jsUrl || `${httpsOptions ? "https" : "http"}://${host}:${jsPort}/`;

	try {
		return require("./" + path.join("commands", options.command))(options);
	} catch (e) {
		if (e instanceof ConfigurationError) {
			console.error(chalk.red(e.message));
		} else {
			throw e;
		}
	}
}

import path from "path";
import chalk from "chalk";
import mergeOptions from "./mergeOptions"
import findOptionsInFiles from "./findOptionsInFiles"
import defaultOptions from "./defaultOptions"
import ConfigurationError from "./ConfigurationError"

/* eslint-disable consistent-return */
export default function run(options = {}) {

	// for the option properties that weren't sent in, look for a config file
	// (either .reactserverrc or a reactServer section in a package.json). for
	// options neither passed in nor in a config file, use the defaults.
	options = mergeOptions(defaultOptions, findOptionsInFiles() || {}, options);

	const {
		routesFile,
		jsUrl,
		jsPort,
		host,
		httpsOptions,
	} = options;

	options.routesPath = path.resolve(process.cwd(), routesFile);
	options.routesDir = path.dirname(options.routesPath);
	if (options.command !== 'init') options.routes = require(options.routesPath);

	options.outputUrl = jsUrl || `${httpsOptions ? "https" : "http"}://${host}:${jsPort}/`;

	try {
		return require("./" + path.join("commands", options.command))(options);
	} catch (e) {
		if (e instanceof ConfigurationError) {
			console.error(chalk.red(e.message));
		} else {
			throw e;
		}
	}
}

Disable routes require when command is 'init'


// @flow
import {isEmpty, isArray, isNil, get, map, filter, includes} from "lodash";

import type {RuleOptions} from "../types";

function duplicateValuesExist(array): boolean {
  return !isEmpty(filter(array, (value, index, item) => includes(item, value, index + 1)));
}

export default function uniqueInArray(
  field: string,
  array: ?Array<mixed>,
  options?: RuleOptions,
): ?string {
  if (!isArray(array)) {
    return "uniqueInArray";
  }

  const byUniqueObjectKey = get(options, "byKey");
  if (!isNil(byUniqueObjectKey) && duplicateValuesExist(map(array, byUniqueObjectKey))) {
    return "uniqueInArray.byKey";
  }

  if (duplicateValuesExist(array)) {
    return "uniqueInArray";
  }

  return null;
}

// @flow
import {isArray, isNil, get, map} from "lodash/fp";

import type {RuleOptions} from "../types";

function duplicateValuesExist<T>(array: Array<T>): boolean {
  const seen: {[key: T]: boolean} = {};
  for (let i = 0; i < array.length; i += 1) {
    if (seen[array[i]]) {
      return true;
    }
    seen[array[i]] = true;
  }
  return false;
}

export default function uniqueInArray(
  field: string,
  array: ?Array<mixed>,
  options?: RuleOptions,
): ?string {
  if (!isArray(array)) {
    return "uniqueInArray";
  }

  const byUniqueObjectKey: ?string = get("byKey")(options);
  if (!isNil(byUniqueObjectKey)) {
    if (duplicateValuesExist(map(byUniqueObjectKey)(array))) {
      return "uniqueInArray.byKey";
    }
    // $FlowFixMe
  } else if (duplicateValuesExist(array)) {
    return "uniqueInArray";
  }

  return null;
}

Use seen object pattern in unique check instead of double filtering


// Karma configuration file, see link for more information
// https://karma-runner.github.io/0.13/config/configuration-file.html

module.exports = function (config) {
  var configuration = {
    basePath: '',
    frameworks: ['jasmine', 'angular-cli'],
    plugins: [
      require('karma-jasmine'),
      require('karma-chrome-launcher'),
      require('karma-remap-istanbul'),
      require('angular-cli/plugins/karma')
    ],
    files: [
      {pattern: './src/test.ts', watched: false}
    ],
    preprocessors: {
      './src/test.ts': ['angular-cli']
    },
    remapIstanbulReporter: {
      reports: {
        html: 'coverage',
        lcovonly: './coverage/coverage.lcov',
        json: './coverage/coverage.json'
      },
      remapOptions: {
        exclude: /(util|test|polyfills).ts$/
      }
    },
    angularCli: {
      config: './angular-cli.json',
      environment: 'dev'
    },
    reporters: ['progress', 'karma-remap-istanbul'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['Chrome'],
    singleRun: false,

    // browser for travis-ci
    customLaunchers: {
      Chrome_travis_ci: {
        base: 'Chrome',
        flags: ['--no-sandbox']
      }
    }
  };

  if (process.env.TRAVIS) {
    configuration.browsers = ['Chrome_travis_ci'];
  }

  config.set(configuration);
};

// Karma configuration file, see link for more information
// https://karma-runner.github.io/0.13/config/configuration-file.html

module.exports = function (config) {
  var configuration = {
    basePath: '',
    frameworks: ['jasmine', 'angular-cli'],
    plugins: [
      require('karma-jasmine'),
      require('karma-chrome-launcher'),
      require('karma-remap-istanbul'),
      require('angular-cli/plugins/karma')
    ],
    files: [
      {pattern: './src/test.ts', watched: false}
    ],
    preprocessors: {
      './src/test.ts': ['angular-cli']
    },
    remapIstanbulReporter: {
      reports: {
        html: 'coverage',
        lcovonly: './coverage/coverage.lcov',
        json: './coverage/coverage.json'
      },
      remapOptions: {
        exclude: /(util|test|polyfills).ts$/
      }
    },
    angularCli: {
      config: './angular-cli.json',
      environment: 'dev'
    },
    reporters: ['progress', 'karma-remap-istanbul'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['Chrome'],
    singleRun: false,

    // browser for travis-ci
    customLaunchers: {
      Chrome_travis_ci: {
        base: 'ChromeCanary',
        flags: ['--no-sandbox']
      }
    }
  };

  if (process.env.TRAVIS) {
    configuration.browsers = ['Chrome_travis_ci'];
  }

  config.set(configuration);
};

Set tests to run on Crome Canary on CI


/**
 * @package assets
 */

(function($) {

	/**
	 * This plugin shows and hides elements based on the value of a select box.
	 * If there is only one option, the select box will be hidden and
	 * the single element will be shown.
	 *
	 * @param {Object} custom_settings
	 *  An object specifying the elements that are pickable
	 */
	$.fn.symphonyPickable = function(custom_settings) {
		var objects = $(this);
		var settings = {
			pickables: '.pickable'
		};

		$.extend(settings, custom_settings);

	/*-----------------------------------------------------------------------*/

		// Pickables
		var pickables = $(settings.pickables).addClass('.pickable');

		// Process pickers
		return objects.each(function() {
			var picker = $(this);
			var select = picker.find('select');
			var options = select.find('option');

			// Multiple items
			if(options.size() > 1) {
				options.each(function() {
					pickables.filter('#' + $(this).val()).hide();
				});
				select.click(function() {
					pickables.hide().filter('#' + $(this).val()).show();
				}).click();
			}

			// Single item
			else {
				picker.hide();
				pickables.filter('#' + select.val()).removeClass('.pickable');
			}
		});
	}

})(jQuery.noConflict());

/**
 * @package assets
 */

(function($) {

	/**
	 * This plugin shows and hides elements based on the value of a select box.
	 * If there is only one option, the select box will be hidden and
	 * the single element will be shown.
	 *
	 * @param {Object} custom_settings
	 *  An object specifying the elements that are pickable
	 */
	$.fn.symphonyPickable = function(custom_settings) {
		var objects = $(this);
		var settings = {
			pickables: '.pickable'
		};

		$.extend(settings, custom_settings);

	/*-----------------------------------------------------------------------*/

		// Pickables
		var pickables = $(settings.pickables).addClass('.pickable');

		// Process pickers
		return objects.each(function() {
			var picker = $(this);
			var select = picker.find('select');
			var options = select.find('option');

			// Multiple items
			if(options.size() > 1) {
				options.each(function() {
					pickables.filter('#' + $(this).val()).hide();
				});
				select.change(function() {
					pickables.hide().filter('#' + $(this).val()).show();
				}).change();
			}

			// Single item
			else {
				picker.hide();
				pickables.filter('#' + select.val()).removeClass('.pickable');
			}
		});
	}

})(jQuery.noConflict());

Fix Pickable issue in Google Chrome


/* eslint-env node */
module.exports = {
  framework: 'qunit',
  test_page: 'tests/index.html?hidepassed',
  disable_watching: true,
  reporter: 'dot',
  launch_in_ci: ['Chrome'],
  launch_in_dev: ['Chrome'],
  browser_args: {
    Chrome: {
      mode: 'ci',
      args: [
        '--headless',
        '--disable-gpu',
        '--disable-dev-shm-usage',
        '--disable-software-rasterizer',
        '--mute-audio',
        '--remote-debugging-port=0',
        '--window-size=1440,900',
        '--no-sandbox',
      ],
    },
  },

};

/* eslint-env node */
module.exports = {
  framework: 'qunit',
  test_page: 'tests/index.html?hidepassed',
  disable_watching: true,
  reporter: 'dot',
  launch_in_ci: ['Chrome'],
  launch_in_dev: ['Chrome'],
  browser_args: {
    Chrome: {
      mode: 'ci',
      args: [
        '--headless',
        '--disable-dev-shm-usage',
        '--disable-software-rasterizer',
        '--mute-audio',
        '--remote-debugging-port=0',
        '--window-size=1440,900',
        '--no-sandbox',
      ],
    },
  },

};

Remove --disable-gpu flag when starting headless chrome


import { createStore } from 'redux';
import Shogi from '../reducers';

let store = createStore(Shogi);

// debug
store.subscribe(
  () => console.log(store.getState())
);

export default store;

import { createStore, compose } from 'redux';
import DevTools from '../components/dev_tools';
import Shogi from '../reducers';

let enhancer = compose(
  DevTools.instrument()
);

let store = createStore(Shogi, undefined, enhancer);

// debug
store.subscribe(
  () => console.log(store.getState())
);

export default store;

Enhance store for using redux-devtools


var chai = require('chai');

chai.config.includeStack = true;

global.expect = chai.expect;
global.AssertionError = chai.AssertionError;
global.Assertion = chai.Assertion;
global.assert = chai.assert;

var styleguide = require("../lib/styleguide.js");

var gulp = require('gulp');
gulp.task("testStyleguide", function() {
  return gulp.src(["./demo/source/**/*.scss"])
    .pipe(styleguide({
        outputPath: "./demo/tmp",
        overviewPath: "./demo/source/overview.md",
        extraHead: [
            "<link rel=\"stylesheet\" type=\"text/css\" href=\"your/custom/style.css\">",
            "<script src=\"your/custom/script.js\"></script>"
        ],
        sass: {
            // Options passed to gulp-ruby-sass
        },
      }));
});

describe('structure', function() {

  it('test', function() {
    gulp.tasks.testStyleguide.fn();
  })

})

var chai = require('chai'),
    runSequence = require('run-sequence');

chai.config.includeStack = true;

global.expect = chai.expect;
global.AssertionError = chai.AssertionError;
global.Assertion = chai.Assertion;
global.assert = chai.assert;

var styleguide = require("../lib/styleguide.js");

var gulp = require('gulp');
gulp.task("testStyleguide", function(done, cb) {
  return gulp.src(["./demo/source/**/*.scss"])
    .pipe(styleguide({
        outputPath: "./demo/tmp",
        overviewPath: "./demo/source/overview.md",
        extraHead: [
            "<link rel=\"stylesheet\" type=\"text/css\" href=\"your/custom/style.css\">",
            "<script src=\"your/custom/script.js\"></script>"
        ],
        sass: {
            // Options passed to gulp-ruby-sass
        },
      }))
});

describe('structure', function() {

  it('test', function(done) {
    runSequence("testStyleguide", function() {
      done()
    });
  })

})

Use "done" helper to marke the process finished


(function(env){
    "use strict";

    env.ddg_spice_expand_url = function(api_result) {

	    // Get original query.
        var script = $('[src*="/js/spice/expand_url/"]')[0],
            source = $(script).attr("src"),
            query = source.match(/expand_url\/([^\/]+)/)[1];

        // Check if there are any errors.
        if (!api_result.org_url || api_result.error) {
            return Spice.failed('expand_url');
        }

        // Display the plug-in.
        Spice.add({
            id: "expand_url",
            name: "Answer",
            data: api_result,
            meta: {
                sourceUrl: "http://untiny.me/",
                sourceName: "Untiny"
            },
            normalize: function (item) {
                return {
                    title: item.org_url,
                    url: item.org_url,
                    description: "Expanded URL for: " + decodeURI(query)
                };
            },
            templates: {
                group: 'info'
            }
        });
    };
}(this));

(function(env){
    "use strict";

    env.ddg_spice_expand_url = function(api_result) {

	    // Get original query.
        var script = $('[src*="/js/spice/expand_url/"]')[0],
            source = $(script).attr("src"),
            query = source.match(/expand_url\/([^\/]+)/)[1];

        // Check if there are any errors.
        if (!api_result.org_url || api_result.error) {
            return Spice.failed('expand_url');
        }

        // Display the plug-in.
        Spice.add({
            id: "expand_url",
            name: "Answer",
            data: api_result,
            meta: {
                sourceUrl: "http://untiny.me/",
                sourceName: "Untiny"
            },
            normalize: function (item) {
                return {
                    title: item.org_url,
                    url: item.org_url,
                    description: "Expanded URL for: " + decodeURIComponent(query)
                };
            },
            templates: {
                group: 'info'
            }
        });
    };
}(this));

Fix URI component decode part


window.addEventListener("load", pageFullyLoaded, false);

function pageFullyLoaded(e) {

//reposition the close button
//var bar = document.getElementsByClassName("titleButton close");
//bar[0].style.backgroundPosition = "-33px 0px";

//hide the maximize button
var max = document.getElementsByClassName("titleButton maximize");
max[0].classList.add("hidden");

}

window.addEventListener("load", pageFullyLoaded, false);

function pageFullyLoaded(e) {

//reposition the close button
var bar = document.getElementsByClassName("titleButton close");
bar[0].style.backgroundPosition = "-33px 0px";

//hide the maximize button
var max = document.getElementsByClassName("titleButton maximize");
max[0].classList.add("hidden");

}

Revert "todays hot fix fixed the need to fix the close button on the situation map."


import documentFormatter from 'src/logic/documentPackager/documentFormatter';
import base64OfSimplePdf from './reference/base64OfSimplePdf';

describe('DocumentFormatter', () => {
  describe('format', () => {
    it('should returns the same data for \'html viewMode\'', () => {
      const viewMode = 'html';
      const inputData = '
This is a HTML content
';
      const outputData = documentFormatter.format(inputData, viewMode);

      expect(outputData).to.equal('
This is a HTML content
');
    });
  });

  describe('getPdfBase64', () => {
    it('should returns a pdfBase64 for \'pages viewMode\'', (done) => {
      const expectedOutput = base64OfSimplePdf;
      const inputData = '
This is a HTML content
';

      documentFormatter.getPdfBase64(inputData)
      .then((outputData) => {
        // jsPDF does not give deterministic outputData
        // around 5 characters will always be different in the output
        // therefore, an approximation method to use content length for
        // testing is used.

        expect(outputData.length).to.equal(expectedOutput.length);
        done();
      })
      .catch((error) => {
        done(error);
      });
    });
  });
});

import documentFormatter from 'src/logic/documentPackager/documentFormatter';

describe('DocumentFormatter', () => {
  describe('format', () => {
    it('should returns the same data for \'html viewMode\'', () => {
      const viewMode = 'html';
      const inputData = '
This is a HTML content
';
      const outputData = documentFormatter.format(inputData, viewMode);

      expect(outputData).to.equal('
This is a HTML content
');
    });
  });

  describe('getPdfBase64', () => {
    it('should returns a pdfBase64 for \'pages viewMode\'', (done) => {
      const inputData = '
This is a HTML content
';

      documentFormatter.getPdfBase64(inputData)
      .then((outputData) => {
        // jsPDF does not give deterministic outputData
        // around 5 characters will always be different in the output
        // therefore, an approximation method to use content length for
        // testing is used.

        expect(outputData.length).to.equal(3333);
        done();
      })
      .catch((error) => {
        done(error);
      });
    });
  });
});

Fix unit test to temporarily pass Travis


/**
 * Copy all properties from `props` onto `obj`.
 * @param {object} obj Object onto which properties should be copied.
 * @param {object} props Object from which to copy properties.
 * @returns {object}
 * @private
 */
export function extend(obj, props) {
	for (let i in props) obj[i] = props[i];
	return obj;
}

/** Invoke or update a ref, depending on whether it is a function or object ref.
 *  @param {object|function} [ref=null]
 *  @param {any} [value]
 */
export function applyRef(ref, value) {
	if (ref!=null&&ref!==false) {
		if (typeof ref=='function') ref(value);
		else ref.current = value;
	}
}

/**
 * Call a function asynchronously, as soon as possible. Makes
 * use of HTML Promise to schedule the callback if available,
 * otherwise falling back to `setTimeout` (mainly for IE<11).
 * @type {(callback: function) => void}
 */
export const defer = typeof Promise=='function' ? Promise.resolve().then.bind(Promise.resolve()) : setTimeout;

/**
 * Copy all properties from `props` onto `obj`.
 * @param {object} obj Object onto which properties should be copied.
 * @param {object} props Object from which to copy properties.
 * @returns {object}
 * @private
 */
export function extend(obj, props) {
	for (let i in props) obj[i] = props[i];
	return obj;
}

/** Invoke or update a ref, depending on whether it is a function or object ref.
 *  @param {object|function} [ref=null]
 *  @param {any} [value]
 */
export function applyRef(ref, value) {
	if (ref) {
		if (typeof ref=='function') ref(value);
		else ref.current = value;
	}
}

/**
 * Call a function asynchronously, as soon as possible. Makes
 * use of HTML Promise to schedule the callback if available,
 * otherwise falling back to `setTimeout` (mainly for IE<11).
 * @type {(callback: function) => void}
 */
export const defer = typeof Promise=='function' ? Promise.resolve().then.bind(Promise.resolve()) : setTimeout;

Change the if condition to check if ref is defined, rather than explicitly null or false.


import Controller from '@ember/controller';
import { computed } from '@ember/object';

export default Controller.extend({
  isUpcoming: computed('model.endsAt', function() {
    let endAt = this.get('model.endsAt');
    if (endAt < moment()) {
      return false;
    }
    return true;
  })
});

import Controller from '@ember/controller';
import { computed } from '@ember/object';
import moment from 'moment';

export default Controller.extend({
  isUpcoming: computed('model.endsAt', function() {
    let endAt = this.get('model.endsAt');
    if (endAt < moment()) {
      return false;
    }
    return true;
  })
});

Add missing moment import to my session controller


function runTest()
{
    FBTest.sysout("issue5504.START");

    FBTest.openNewTab(basePath + "html/5504/issue5504.html", function(win)
    {
        FBTest.openFirebug();
        var panel = FBTest.selectPanel("html");

        // Get the selected elemetn and execute "New Attribute" action on it.
        var nodeBox = getSelectedNodeBox();
        FBTest.executeContextMenuCommand(nodeBox, "htmlNewAttribute", function()
        {
            var editor = panel.panelNode.getElementsByClassName("textEditorInner").item(0);
            FBTest.compare("", editor.value, "The default value must be an empty string");
            FBTest.testDone("issue5504.DONE");
        });
    });
}

// xxxHonza: use the one from FBTest
function getSelectedNodeBox()
{
    var panel = FBTest.getPanel("html");
    return panel.panelNode.querySelector(".nodeBox.selected");
}

function runTest()
{
    FBTest.sysout("issue5504.START");

    FBTest.openNewTab(basePath + "html/5504/issue5504.html", function(win)
    {
        FBTest.openFirebug();
        var panel = FBTest.selectPanel("html");

        // Get the selected element and execute "New Attribute" action on it.
        var nodeBox = getSelectedNodeBox();
        FBTest.executeContextMenuCommand(nodeBox, "htmlNewAttribute", function()
        {
            var editor = panel.panelNode.getElementsByClassName("textEditorInner").item(0);
            FBTest.compare("", editor.value, "The default value must be an empty string");
            FBTest.testDone("issue5504.DONE");
        });
    });
}

// xxxHonza: use the one from FBTest (should be in FBTest 1.10b5)
function getSelectedNodeBox()
{
    var panel = FBTest.getPanel("html");
    return panel.panelNode.querySelector(".nodeBox.selected");
}

Update comment, use FBTest.getSelectedNodeBox as soon as FBTest 1.10b5 is out


'use strict'

const CountdownTimer = (props) => {

  const dayString = props.days
  const hourString = props.hours

  return (

      {dayString} days
      {hourString} hrs

  )
}

export default CountdownTimer

'use strict'

const CountdownTimer = (props) => {

  const dayString = Math.max(0, props.days)
  const hourString = Math.max(0, props.hours)
  const minuteString = Math.max(0, props.minutes)

  return (

    	{props.days > 0 && {dayString} days}
      {hourString} hrs
      {props.days <= 0 && {minuteString} mins}

  )
}

export default CountdownTimer

Update countdown timer to show minutes.


// gerber file output component
'use strict'

const {h} = require('deku')

const GerberSettings = require('./gerber-settings')
const {LayerDefs} = require('../../layer/component')

module.exports = function renderGerberOutput({props}) {
  const {layers, layerDisplayStates, renders, units} = props
  const {
    toggleVisibility,
    toggleSettings,
    remove,
    setType,
    setConversionOpts,
    setColor
  } = props

  const children = layers.map((layer) => {
    const id = layer.id

    return h(GerberSettings, {
      layer,
      showSettings: layerDisplayStates[id].showSettings,
      toggleVisibility: toggleVisibility(id),
      toggleSettings: toggleSettings(id),
      remove: remove(id),
      setType: setType(id),
      setConversionOpts: setConversionOpts,
      setColor: setColor(id)
    })
  })

  return h('output', {class: 'fx fx-d-c bg-white-90 clickable max-app-ht'}, [
    h('ol', {
      class: 'list ma0 ph0 pv2 fx-1-1 overflow-scroll'
    }, children),
    h('p', {class: 'ma0 pa1 tc fx-0-0'}, [`${children.length} files`]),
    h('svg', {class: 'clip'}, [
      h(LayerDefs, {renders, units})
    ])
  ])
}

// gerber file output component
'use strict'

const {h} = require('deku')

const GerberSettings = require('./gerber-settings')
const {LayerDefs} = require('../../layer/component')

module.exports = function renderGerberOutput({props}) {
  const {layers, layerDisplayStates, renders, units} = props
  const {
    toggleVisibility,
    toggleSettings,
    remove,
    setType,
    setConversionOpts,
    setColor
  } = props

  const children = layers.map((layer) => {
    const id = layer.id

    return h(GerberSettings, {
      layer,
      showSettings: layerDisplayStates[id].showSettings,
      toggleVisibility: toggleVisibility(id),
      toggleSettings: toggleSettings(id),
      remove: remove(id),
      setType: setType(id),
      setConversionOpts: setConversionOpts,
      setColor: setColor(id)
    })
  })

  return h('output', {class: 'fx fx-d-c bg-white-90 clickable max-app-ht'}, [
    h('ol', {
      class: 'list ma0 ph0 pv2 fx-1-1 overflow-y-auto'
    }, children),
    h('p', {class: 'ma0 pa1 tc fx-0-0'}, [`${children.length} files`]),
    h('svg', {class: 'clip'}, [
      h(LayerDefs, {renders, units})
    ])
  ])
}

Tweak layer list overflow settings to auto


const {Command} = require('./index.js');
const {wotblitz: {default_region}} = require('../../blitzbot.json');
const options = {
	argCount: 1,
	description: 'Update your personal default region.',
	signatures: ['@BOTNAME set-region [region]']
};
const command = new Command(setRegion, options, 'set-region');

module.exports = command;

function setRegion(msg, region) {
	switch (region && region.toLowerCase()) {
	case 'asia':
	case 'a':
		region = 'asia';
		break;
	case 'eu':
	case 'e':
		region = 'eu';
		break;
	case 'ru':
	case 'r':
		region = 'ru';
		break;
	case 'na':
	case 'n':
		region = 'na';
		break;
	default:
		region = default_region;
		break;
	}

	return msg.reply('Region set to "' + region + '".').then(sent => {
		return {
			sentMsg: sent,
			master: true,
			updateFields: {
				region: region
			}
		};
	});
}

const {Command} = require('./index.js');
const {wotblitz: {default_region}} = require('../../blitzbot.json');
const options = {
	argCount: 1,
	description: 'Update your personal default region.',
	signatures: ['@BOTNAME set-region [region]']
};
const command = new Command(setRegion, options, 'set-region');

module.exports = command;

async function setRegion(msg, region) {
	switch (region && region.toLowerCase()) {
	case 'asia':
	case 'a':
		region = 'asia';
		break;
	case 'eu':
	case 'e':
		region = 'eu';
		break;
	case 'ru':
	case 'r':
		region = 'ru';
		break;
	case 'na':
	case 'n':
		region = 'na';
		break;
	default:
		region = default_region;
		break;
	}

	const sent = await msg.reply('Region set to "' + region + '".');

	return {
		sentMsg: sent,
		master: true,
		updateFields: {
			region: region
		}
	};
}

Use async patterns for set-region command.


// These URL paths will be transformed to CN mirrors.
var mirrors = {
  "//developers.google.com" : "//developers.google.cn",
  "//firebase.google.com"   : "//firebase.google.cn",
  "//developer.android.com" : "//developer.android.google.cn",
  "//angular.io"            : "//angular.cn",
}

// These URL paths are not available on CN mirrors, therefore won't be transformed.
var whitelist = [
  "//developers.google.com/groups",
  "//developers.google.com/events",
]

function mirrorUrl(url) {
  // Check for whitelisting.
  for (var key in whitelist) {
    if (url.includes(whitelist[key])) {
      return url;
    }
  }

  // Check for mapping.
  for (var key in mirrors) {
  	if (url.includes(key)) {
  		url = url.replace(key, mirrors[key]);
  		break;
  	}
  }
  return url;
}

// These URL paths will be transformed to CN mirrors.
var mirrors = {
  "//developers.google.com" : "//developers.google.cn",
  "//firebase.google.com"   : "//firebase.google.cn",
  "//developer.android.com" : "//developer.android.google.cn",
  "//angular.io"            : "//angular.cn",
}

// These URL paths are not available on CN mirrors, therefore won't be transformed.
var whitelist = [
  "//developers.google.com/groups",
  "//developers.google.com/events",
  "//firebase.google.com/support/contact/",
]

function mirrorUrl(url) {
  // Check for whitelisting.
  for (var key in whitelist) {
    if (url.includes(whitelist[key])) {
      return url;
    }
  }

  // Check for mapping.
  for (var key in mirrors) {
  	if (url.includes(key)) {
  		url = url.replace(key, mirrors[key]);
  		break;
  	}
  }
  return url;
}

Add whitelisted pages which require google login.


import React from 'react';
import { View } from 'react-native';

import { Wizard } from '../../widgets';

import { NewSensorStepOne } from './NewSensorStepOne';
import { NewSensorStepTwo } from './NewSensorStepTwo';

const TabThree = () => (
  <View style={{ backgroundColor: 'blue', flex: 1, width: 500, height: 500 }} />
);

const tabs = [
  { component: NewSensorStepOne, name: 'newSensorStepOne', title: '' },
  { component: NewSensorStepTwo, name: 'newSensorStepTwo', title: '' },
  { component: TabThree, name: 'prescription', title: '' },
];

export const NewSensorPage = () => (
  <Wizard captureUncaughtGestures={false} tabs={tabs} useNewStepper />
);

import React from 'react';

import { Wizard } from '../../widgets';

import { NewSensorStepOne } from './NewSensorStepOne';
import { NewSensorStepThree } from './NewSensorStepThree';
import { NewSensorStepTwo } from './NewSensorStepTwo';

const tabs = [
  { component: NewSensorStepOne, name: 'newSensorStepOne', title: '' },
  { component: NewSensorStepTwo, name: 'newSensorStepTwo', title: '' },
  { component: NewSensorStepThree, name: 'newSensorStepThree', title: '' },
];

export const NewSensorPage = () => (
  <Wizard captureUncaughtGestures={false} tabs={tabs} useNewStepper />
);

Add step three to new sensor page


'use strict';

var sync = require('./lib/sync.js');
var cron = require('cron');

var cronJob = cron.job("0 */5 * * * *", function(){
  console.log("START!");
  sync.synchronizeCrmToUmas();
  console.info('cron job completed');
});

cronJob.start();

'use strict';

var sync = require('./lib/sync.js');
var cron = require('cron');

var cronJob = cron.job("0 0 */2 * * *", function(){
  console.log("START!");
  sync.synchronizeCrmToUmas();
  console.info('cron job completed');
});

cronJob.start();

Modify cron for 2 hours


//= require jquery
//= require dataTables/jquery.dataTables
//= require_tree .

$(function() {
  $(document).on('click', '.mail a.preview', function() {
    console.log(this);
    $(this).colorbox({width:"80%", height:"80%", iframe:true});
  });

  $(document).on('click', '.mail a.raw', function(){
    $.get($(this).attr('href'), function(data){
      $('#raw div').text(data);
    });
    return false;
  });

  $("table").dataTable({
    "aaSorting": [[3, "desc"]],
    "aoColumns": [
      null,
      null,
      null,
      null,
      { "bSortable": false }
    ]
  });

});

//= require jquery
//= require dataTables/jquery.dataTables
//= require_tree .

$(function() {
  $('body').on('click', 'a.preview', function() {
    $(this).colorbox({width:"80%", height:"80%", iframe:true});
  });

  $('body').on('click', 'a.raw', function(){
    $.get($(this).attr('href'), function(data){
      $('#raw div').text(data);
    });
    return false;
  });

  $("table").dataTable({
    "aaSorting": [[3, "desc"]],
    "aoColumns": [
      null,
      null,
      null,
      null,
      { "bSortable": false }
    ]
  });

});

Use proper $.on delegate binding. This ensures anything added to the dom later (i.e. rows of the next page) get the events. Also removed console.log call.


function runTest()
{
    FBTest.sysout("issue5755.START");
    FBTest.openNewTab(basePath + "html/5755/issue5755.html", function (win)
    {
        FBTest.openFirebug();
        var panel = FBTest.selectPanel("html");
        if (FBTest.ok(panel, "Firebug must be opened and switched to HTML panel now."))
        {
            // to make sure displayedAttributeValueLimit hasn't a value of 0
            FBTest.setPref("displayedAttributeValueLimit", 10);
            var longOnclickValue = win.document.getElementById("long-onclick").
                getAttribute("onclick");
            FBTest.selectElementInHtmlPanel("long-onclick", function (nodes)
            {
                // getting onclick attribute's value
                var onclickValue = nodes.getElementsByClassName("nodeValue").item(1);
                FBTest.synthesizeMouse(onclickValue);
                var texteditor = panel.panelNode.getElementsByClassName("textEditorInner").item(0);
                if (FBTest.ok(texteditor, "Editor must be loaded now."))
                {
                    FBTest.compare(longOnclickValue, texteditor.value,
                        "Inline editor must contain whole string of onclick value.");
                }
                FBTest.testDone("issue5755.DONE");
            });
        }
        else
        {
            FBTest.testDone("issue5755.FAILED");
        }
    });
}

function runTest()
{
    FBTest.sysout("issue5755.START");
    FBTest.openNewTab(basePath + "html/5755/issue5755.html", function (win)
    {
        FBTest.openFirebug();
        var panel = FBTest.selectPanel("html");
        if (FBTest.ok(panel, "Firebug must be opened and switched to HTML panel now."))
        {
            // to make sure displayedAttributeValueLimit hasn't a value of 0
            FBTest.setPref("displayedAttributeValueLimit", 10);
            var longOnclickValue = win.document.getElementById("long-onclick").
                getAttribute("onclick");

            FBTest.selectElementInHtmlPanel("long-onclick", function (nodes)
            {
                // getting onclick attribute's value
                var onclickValue = nodes.getElementsByClassName("nodeValue").item(1);
                FBTest.synthesizeMouse(onclickValue);

                // Wait till the inline editor is available.
                var config = {tagName: "input", classes: "textEditorInner"};
                FBTest.waitForDisplayedElement("html", config, function(texteditor)
                {
                    if (FBTest.ok(texteditor, "Editor must be loaded now."))
                    {
                        FBTest.compare(longOnclickValue, texteditor.value,
                            "Inline editor must contain whole string of onclick value.");
                    }
                    FBTest.testDone("issue5755.DONE");
                });
            });
        }
        else
        {
            FBTest.testDone("issue5755.FAILED");
        }
    });
}

Fix FBTest: again the inline editor can be async on Mac


const $ = require('jquery');
import { getPlayer } from './player/player';
import { insertTimestamp, convertTimestampToSeconds, formatMilliseconds } from './timestamps';

let timeSelectionModalActive = false;
const $timeSelection = $('.controls .time-selection');

const hide = () => {
    timeSelectionModalActive = false;
    $('.controls .time-selection').removeClass('active');
}

const show = () => {
    timeSelectionModalActive = true;
    const player = getPlayer();

    if (timeSelectionModalActive === true) {
        $timeSelection.addClass('active');
        $timeSelection.find('input')
            .off()
            .val(formatMilliseconds(player.getTime()))
            .keyup(onTimeSelectionModalSubmit)
            .focus()
            .select();
    } else {
        $('.controls .time-selection').removeClass('active');
    }

    function onTimeSelectionModalSubmit(ev) {
        if (event.keyCode === 13) { // return key
            const time = $(this).val();
            if (time.indexOf(':') > -1) {
                player.setTime(convertTimestampToSeconds(time));
            } else {
                // assume user is thinking in minutes
                player.setTime(parseFloat(time) * 60);
            }
            hide();
        }
    }
}

const toggle = () => {
    if (timeSelectionModalActive) {
        hide();
    } else {
        show();
    }
}

export default {
    toggle, show, hide
}

const $ = require('jquery');
import { getPlayer } from './player/player';
import { insertTimestamp, convertTimestampToSeconds, formatMilliseconds } from './timestamps';

let timeSelectionModalActive = false;
const $timeSelection = $('.controls .time-selection');

const hide = () => {
    timeSelectionModalActive = false;
    $('.controls .time-selection').removeClass('active');
}

const show = () => {
    timeSelectionModalActive = true;
    const player = getPlayer();

    if (timeSelectionModalActive === true) {
        $timeSelection.addClass('active');
        $timeSelection.find('input')
            .off()
            .val(formatMilliseconds(player.getTime()))
            .keyup(onTimeSelectionModalSubmit)
            .focus()
            .select();
    } else {
        $('.controls .time-selection').removeClass('active');
    }

    function onTimeSelectionModalSubmit(ev) {
        if (ev.keyCode === 13) { // return key
            const time = $(this).val();
            if (time.indexOf(':') > -1) {
                player.setTime(convertTimestampToSeconds(time));
            } else {
                // assume user is thinking in minutes
                player.setTime(parseFloat(time) * 60);
            }
            hide();
        }
    }
}

const toggle = () => {
    if (timeSelectionModalActive) {
        hide();
    } else {
        show();
    }
}

export default {
    toggle, show, hide
}

Fix time selection modal bug


function validateJoin (username, email, password) {
    return validateUsername(username, "join") && validateEmail(email, "join") && validatePassword(password, "join");
}

function validateLogin (usernameOrEmail, password) {
    return (validateUsername(usernameOrEmail, "login") || validateEmail(usernameOrEmail, "login")) && validatePassword(password, "login");
}

function validateEmail (email, context) {
    var message = {
        join: "Please enter a valid email address",
        login: "That email doesn't look quite right"
    };
    if (validator.isEmail(email)) {
        return true;
    }
    else {
        alert(message[context]);
        return false;
    }
}

function validateUsername (username, context) {
    var message = {
        join: "Please enter a username with at least 4 characters",
        login: "That username doesn't look quite right"
    };
    if (username.length >= 4) {
        return true;
    }
    else {
        alert(message[context]);
        return false;
    }
}

function validatePassword (password, context) {
    var message = {
        join: "Please enter a password with at least 6 characters",
        login: "That password doesn't look quite right"
    };
    if (password.length >= 6) {
        return true;
    }
    else {
        alert(message[context]);
        return false;
    }
}

function validateJoin (username, email, password) {
    return validateUsername(username, "join") && validateEmail(email, "join") && validatePassword(password, "join");
}

function validateLogin (usernameOrEmail, password) {
    return (validateUsername(usernameOrEmail, "login") || validateEmail(usernameOrEmail, "login")) && validatePassword(password, "login");
}

function validateEmail (email, context) {
    var message = {
        join: "Please enter a valid email address",
        login: "That email doesn't look quite right"
    };
    if (validator.isEmail(email) && !containsSpace(email)) {
        return true;
    }
    else {
        alert(message[context]);
        return false;
    }
}

function validateUsername (username, context) {
    var message = {
        join: "Please enter a username with at least 4 characters",
        login: "That username doesn't look quite right"
    };
    if (username.length >= 4 && !containsSpace(username)) {
        return true;
    }
    else {
        alert(message[context]);
        return false;
    }
}

function validatePassword (password, context) {
    var message = {
        join: "Please enter a password with at least 6 characters",
        login: "That password doesn't look quite right"
    };
    if (password.length >= 6 && !containsSpace(password)) {
        return true;
    }
    else {
        alert(message[context]);
        return false;
    }
}

function containsSpace(value) {
    return value.indexOf(" ") !== -1;
}

Add form validation for spaces


import Abstract from './Abstract'
import { STRING, TEXT } from 'substance'
import { RICH_TEXT_ANNOS } from './modelConstants'

export default class CustomAbstract extends Abstract {
  static getTemplate () {
    return {
      type: 'custom-abstract',
      content: [
        { type: 'paragraph' }
      ]
    }
  }

  render (options = {}) {
    return this.title || ''
  }
}

CustomAbstract.schema = {
  type: 'custom-abstract',
  abstractType: STRING,
  title: TEXT(RICH_TEXT_ANNOS)
}

import Abstract from './Abstract'
import { ENUM, TEXT } from 'substance'
import { RICH_TEXT_ANNOS } from './modelConstants'

export default class CustomAbstract extends Abstract {
  static getTemplate () {
    return {
      type: 'custom-abstract',
      content: [
        { type: 'paragraph' }
      ]
    }
  }

  render (options = {}) {
    return this.title || ''
  }
}

CustomAbstract.schema = {
  type: 'custom-abstract',
  abstractType: ENUM(['executive-summary', 'web-summary'], { default: '' }),
  title: TEXT(RICH_TEXT_ANNOS)
}

Use new enum type for abstractType


"use strict";

angular.module('arethusa.core').directive('arethusaGrid', [
  'arethusaGrid',
  'plugins',
  function(arethusaGrid, plugins) {
    return {
      restrict: 'A',
      scope: {},
      link: function(scope, element, attrs) {
        angular.element(document.body).css('overflow', 'auto');
        scope.grid = arethusaGrid;
      },
      templateUrl: 'templates/arethusa.core/arethusa_grid.html'
    };
  }
]);

"use strict";

angular.module('arethusa.core').directive('arethusaGrid', [
  'arethusaGrid',
  'plugins',
  'globalSettings',
  '$timeout',
  function(arethusaGrid, plugins, globalSettings, $timeout) {
    return {
      restrict: 'A',
      scope: {},
      link: function(scope, element, attrs) {
        angular.element(document.body).css('overflow', 'auto');

        scope.grid = arethusaGrid;

        function addSettings() {
          globalSettings.defineSetting('grid', 'custom', 'grid-setting');
        }

        function removeSettings() {
          globalSettings.removeSetting('grid');
        }

        // We need to timeout this, so that we can give globalSettings
        // time to define its own default settings - only afterwards
        // we add our own grid settings
        $timeout(addSettings);

        scope.$on('$destroy', removeSettings);
      },
      templateUrl: 'templates/arethusa.core/arethusa_grid.html'
    };
  }
]);

Add gridSettings when arethusaGrid is active


var _ = require("lodash")
var publicRoutes = require("./public");
var authenticated = require("./authenticated");
var routes = [];

publicRoutes.concat(authenticated).forEach(function(route) {

  // If route defines an array of paths,
  // register each as an individual route
  if (route.paths) {
    route.paths.forEach(function(path) {
      var r = _.cloneDeep(route)
      delete r.paths
      r.path = path
      routes.push(r)
    })
  } else {
    routes.push(route);
  }
});

// Convenience method for tests
routes.at = function(name) {
  return _.find(this, function(route) {
    return name === route.method + " " + route.path
  })
}

module.exports = routes;

var _ = require("lodash");
var publicRoutes = require("./public");
var authenticated = require("./authenticated");
var feature = require('../lib/feature-flags');

var routes = publicRoutes.concat(authenticated).filter(function(route) {
  if (route.feature) {
    return [].concat(route.feature).some(function(feat) {
      if (feat[0] == '!') {
        return !feature(feat.slice(1));
      } else {
        return feature(feat);
      }
    });
  } else {
    return true;
  }
}).reduce(function(routes, route) {

  delete route.feature;

  // If route defines an array of paths,
  // register each as an individual route
  if (route.paths) {
    route.paths.forEach(function(path) {
      var r = _.cloneDeep(route);
      delete r.paths;
      r.path = path;
      routes.push(r);
    });
  } else {
    routes.push(route);
  }

  return routes;
}, []);

// Convenience method for tests
routes.at = function(name) {
  return _.find(this, function(route) {
    return name === route.method + " " + route.path;
  });
};

module.exports = routes;

Add feature filter to routes