'use strict';

angular.module('core').controller('MusicalBackgroundController', ['$scope', 'TrialData',
  function($scope, TrialData) {

    // Expose TrialData on scope
    $scope.trialData = TrialData;

    // Save data to Trial Data

    $scope.musicianChanged = function(isMusician) {
      TrialData.data.answers.musical_background = isMusician.toLowerCase() === "true" ? true : false;
    };

    $scope.hearingImpairmentsChanged = function(hasHearingImpairments) {
      TrialData.data.answers.hearing_impairments = hasHearingImpairments.toLowerCase() === "true" ? true : false;
    };

    $scope.visualImpairmentsChanged = function(hasVisualImpairments) {
      TrialData.data.answers.visual_impairments = hasVisualImpairments.toLowerCase() === "true" ? true : false;
    };

    $scope.$watch('musicalExpertise', function musicalExpertiseChanged(musicalExpertise) {
      TrialData.data.answers.musical_expertise = musicalExpertise;
    });

    $scope.stylesChanged = function() {
      var newStyles = [];
      for (var prop in $scope.subject.styles) {
        var style = prop.toString();
        if ($scope.subject.styles[style]) {
          newStyles.push(style);
        }
      }
      TrialData.data.answers.music_styles = newStyles.sort();
    };

    $scope.trialDataJson = function() {
      return TrialData.toJson();
    };
  }
]);

'use strict';

angular.module('core').controller('MusicalBackgroundController', ['$scope', 'TrialData',
  function($scope, TrialData) {

    // Expose TrialData on scope
    $scope.trialData = TrialData;

    // Save data to Trial Data

    $scope.musicianChanged = function(isMusician) {
      TrialData.data.answers.musical_background = isMusician.toLowerCase() === 'true' ? true : false;
    };

    $scope.hearingImpairmentsChanged = function(hasHearingImpairments) {
      TrialData.data.answers.hearing_impairments = hasHearingImpairments.toLowerCase() === 'true' ? true : false;
    };

    $scope.visualImpairmentsChanged = function(hasVisualImpairments) {
      TrialData.data.answers.visual_impairments = hasVisualImpairments.toLowerCase() === 'true' ? true : false;
    };

    $scope.$watch('musicalExpertise', function musicalExpertiseChanged(musicalExpertise) {
      TrialData.data.answers.musical_expertise = musicalExpertise;
    });

    $scope.stylesChanged = function() {
      var newStyles = [];
      for (var prop in $scope.subject.styles) {
        var style = prop.toString();
        if ($scope.subject.styles[style]) {
          newStyles.push(style);
        }
      }
      TrialData.data.answers.music_styles = newStyles.sort();
    };

    $scope.trialDataJson = function() {
      return TrialData.toJson();
    };
  }
]);

Change double quotes to single quotes


'use strict';
/**
 * angular-select-text directive
 */
angular.module('angular-select-text', []).
  directive('selectText', ['$window', function ($window) {
    var selectElement;

    if ($window.document.selection) {
      selectElement = function(element) {
        var range = $window.document.body.createTextRange();
        range.moveToElementText(element[0]);
        range.select();
      };
    } else if ($window.getSelection) {
      selectElement = function(element) {
        var range = $window.document.createRange();
        range.selectNode(element[0]);
        $window.getSelection().addRange(range);
      };
    }

    return {
      restrict: 'A',
      link: function(scope, element, attrs){
        element.bind('click', function(){
          selectElement(element);
        });
      }
    };
  }]);

'use strict';
/**
 * angular-select-text directive
 */
angular.module('angular-select-text', []).
  directive('selectText', function ($window) {
    var selectElement;

    if ($window.document.selection) {
      selectElement = function(element) {
        var range = $window.document.body.createTextRange();
        range.moveToElementText(element[0]);
        range.select();
      };
    } else if ($window.getSelection) {
      selectElement = function(element) {
        var range = $window.document.createRange();
        range.selectNode(element[0]);
        $window.getSelection().addRange(range);
      };
    }

    return {
      restrict: 'A',
      link: function(scope, element, attrs){
        element.bind('click', function(){
          selectElement(element);
        });
      }
    };
  });

Revert "use strict dependency injection"


var Class = require('./Class'),
	invokeWith = require('./invokeWith'),
	slice = require('./slice'),
	each = require('./each'),
	bind = require('./bind')

module.exports = Class(function() {
	this.init = function() {
		this._dependants = []
		this._fulfillment = null
	}

	this.add = function(callback) {
		if (this._fulfillment) { callback.apply(this, this._fulfillment) }
		else { this._dependants.push(callback) }
	}

	this.fulfill = function(/* arg1, arg2, ...*/) {
		if (this._fulfillment) { throw new Error('Promise fulfilled twice') }
		this._fulfillment = slice(arguments)
		each(this._dependants, invokeWith.apply(this, this._fulfillment))
		delete this._dependants
	}

	this.nextTickAdd = function(callback) {
		setTimeout(bind(this, this.add, callback), 0)
	}
})

var Class = require('./Class'),
	invokeWith = require('./invokeWith'),
	slice = require('./slice'),
	each = require('./each'),
	bind = require('./bind')

module.exports = Class(function() {
	this.init = function(callback) {
		this._dependants = []
		this._fulfillment = null
		if (callback) { this.add(callback) }
	}

	this.add = function(callback) {
		if (this._fulfillment) { callback.apply(this, this._fulfillment) }
		else { this._dependants.push(callback) }
	}

	this.fulfill = function(/* arg1, arg2, ...*/) {
		if (this._fulfillment) { throw new Error('Promise fulfilled twice') }
		this._fulfillment = slice(arguments)
		each(this._dependants, invokeWith.apply(this, this._fulfillment))
		delete this._dependants
	}

	this.nextTickAdd = function(callback) {
		setTimeout(bind(this, this.add, callback), 0)
	}
})

Enable passing in a callback in promise constructors


import React, { Component } from 'react'
import {
  Col,
  Dropdown,
  Form,
  Row
} from 'react-bootstrap'

class DropdownOption extends Component {
  render() {
    const option = this.props.options[this.props.optionKey]
    const model = this.props.model

    return (
      <Row className="align-items-center pb-2" key={this.props.index}>
        <Col sm={5}>
          <Form.Label htmlFor="options-dropdown">
            {option.title}
          </Form.Label>
        </Col>

        <Col sm={7}>
          <Dropdown
            id="options-dropdown"
            onSelect={(value) => {
              let attrs = {}
              attrs[this.props.optionKey] = value
              this.props.onChange(attrs)
            }}>
            <Dropdown.Toggle variant="secondary">
              {model[this.props.optionKey]}
             </Dropdown.Toggle>

            <Dropdown.Menu>
              {option.choices.map((choice) => {
                 return <Dropdown.Item key={choice} eventKey={choice}>{choice}</Dropdown.Item>
              })}
            </Dropdown.Menu>
          </Dropdown>
        </Col>
      </Row>
    )
  }
}

export default DropdownOption

import React, { Component } from 'react'
import {
  Col,
  Form,
  Row
} from 'react-bootstrap'
import Select from 'react-select'

class DropdownOption extends Component {
  render() {
    const option = this.props.options[this.props.optionKey]
    const model = this.props.model
    const currentChoice = model[this.props.optionKey]

    return (
      <Row className="align-items-center pb-2" key={this.props.index}>
        <Col sm={5}>
          <Form.Label htmlFor="options-dropdown">
            {option.title}
          </Form.Label>
        </Col>

        <Col sm={7}>
          <Select
            value={{value: currentChoice, label: currentChoice}}
            onChange={(choice) => {
              const value = choice.value
              let attrs = {}
              attrs[this.props.optionKey] = value
              this.props.onChange(attrs)
            }}
            options={option.choices.map((choice) => {
              return { value: choice, label: choice}
            })}
            />
        </Col>
      </Row>
    )
  }
}

export default DropdownOption

Use select instead of React Dropdown.


import { polyfill } from 'smoothscroll-polyfill';

polyfill();

const previousScrolls = {};
window.history.scrollRestoration = 'manual';

window.addEventListener('scroll', (e) => {
  const path = document.location.pathname
  previousScrolls[path] = window.scrollY;
});

export default function initScrolling(port) {
  port.subscribe(shouldScroll => {
    const path = document.location.pathname
    if (shouldScroll) {
      previousScrolls[path] = 0;
      window.scroll({
        top: 0,
        behavior: 'smooth'
      });
    } else {
      setTimeout(() => {
        window.scroll({
          top: previousScrolls[path]
        });
      }, 0)
    }
  });
}

import { polyfill } from 'smoothscroll-polyfill';

polyfill();

const previousScrolls = {};
window.history.scrollRestoration = 'manual';

window.addEventListener('scroll', (e) => {
  const path = document.location.pathname
  previousScrolls[path] = window.scrollY;
});

export default function initScrolling(port) {
  port.subscribe(shouldScroll => {
    const path = document.location.pathname
    if (shouldScroll) {
      previousScrolls[path] = 0;
      window.scroll({
        top: 0,
        behavior: 'smooth'
      });
    } else {
      setTimeout(() => {
        window.scroll({
          top: previousScrolls[path]
        });
      }, 50)
    }
  });
}

Add a bit of timeout to make it work better


import prompt from 'prompt';
import { getPrompt } from '../libs/prompt';
import chalk from 'chalk';
import Table from 'cli-table';

import config from '../config';
import * as specActions from '../actions/spec-actions';

const prompts = [{
  name: 'confirm',
  description: 'Are you sure you want to remove this project? [Y/n]'
}];

export default async function({getState, dispatch}, uuid) {

	prompt.start();

	prompt.message = '';
	prompt.delimiter = '';

	const { confirm } = await getPrompt(prompts);

	if (confirm == 'Yes' || confirm == 'Y') {

		console.log(chalk.gray(`Removing project with id '${uuid}'`));

		await dispatch(specActions.remove(uuid));

		console.log(chalk.red(`Project '${uuid}' removed`));

	}
}

import prompt from 'prompt';
import { getPrompt } from '../libs/prompt';
import chalk from 'chalk';
import Table from 'cli-table';

import config from '../config';
import * as specActions from '../actions/spec-actions';

const prompts = [{
  name: 'confirm',
  description: 'Are you sure you want to remove this project? [Y/n]'
}];

export default async function({getState, dispatch}, uuid) {

	prompt.start();

	prompt.message = '';
	prompt.delimiter = '';

	const { confirm } = await getPrompt(prompts);

	if (confirm == 'Yes' || confirm == 'Y') {

		console.log(chalk.gray(`Removing project with id '${uuid}'`));

		const ret = await dispatch(specActions.remove(uuid));
		const projectName = ret.data.spec.name;

		console.log(chalk.red(`Project ${projectName} (id: ${uuid}) removed`));

	}
}

Remove project: returns deleted project name


/*
  Shims global timers to behave like node,
  returning values with ref() and unref().
  This is on by default; you can disable with
  --no-node-timers or --no-nt
 */

var timers = require('timers');

module.exports = function timerHook () {
  global.setTimeout = wrapWithActivateUvLoop(timers.setTimeout);
  global.setInterval = wrapWithActivateUvLoop(timers.setInterval);
};

function wrapWithActivateUvLoop (func) {
  return function updateTimerLoop () {
    process.activateUvLoop();
    return func.apply(this, arguments);
  };
}

/*
  Shims global timers to behave like node,
  returning values with ref() and unref().
  This is on by default; you can disable with
  --no-node-timers or --no-nt
 */

var timers = require('timers');

module.exports = function timerHook () {
  global.setTimeout = wrapWithActivateUvLoop(timers.setTimeout);
  global.setInterval = wrapWithActivateUvLoop(timers.setInterval);
  global.clearTimeout = timers.clearTimeout;
  global.clearInterval = timers.clearInterval;
};

function wrapWithActivateUvLoop (func) {
  return function updateTimerLoop () {
    process.activateUvLoop();
    return func.apply(this, arguments);
  };
}

Fix for mixed setTimeout/clearTimeout methods


import {
  REQUEST_MARATHON_GROUPS_SUCCESS,
  REQUEST_MARATHON_GROUPS_ERROR,
  REQUEST_MARATHON_GROUPS_ONGOING
} from '../constants/ActionTypes';
var AppDispatcher = require('./AppDispatcher');
var Config = require('../config/Config');
var RequestUtil = require('../utils/RequestUtil');

module.exports = {

  fetchGroups: RequestUtil.debounceOnError(
    Config.getRefreshRate(),
    function (resolve, reject) {
      return function () {
        const embed = 'embed=group.groups&embed=group.apps&' +
          'embed=group.apps.deployments&embed=group.apps.counts';
        let url = `${Config.rootUrl}/marathon/v2/groups?${embed}`;

        RequestUtil.json({
          url: url,
          success: function (response) {
            AppDispatcher.handleServerAction({
              type: REQUEST_MARATHON_GROUPS_SUCCESS,
              data: response
            });
            resolve();
          },
          error: function (e) {
            AppDispatcher.handleServerAction({
              type: REQUEST_MARATHON_GROUPS_ERROR,
              data: e.message
            });
            reject();
          },
          hangingRequestCallback: function () {
            AppDispatcher.handleServerAction({
              type: REQUEST_MARATHON_GROUPS_ONGOING
            });
          }
        });
      };
    },
    {delayAfterCount: Config.delayAfterErrorCount}
  )

};

import {
  REQUEST_MARATHON_GROUPS_SUCCESS,
  REQUEST_MARATHON_GROUPS_ERROR,
  REQUEST_MARATHON_GROUPS_ONGOING
} from '../constants/ActionTypes';
var AppDispatcher = require('./AppDispatcher');
var Config = require('../config/Config');
var RequestUtil = require('../utils/RequestUtil');

module.exports = {

  fetchGroups: RequestUtil.debounceOnError(
    Config.getRefreshRate(),
    function (resolve, reject) {
      return function () {
        const url = `${Config.rootUrl}/marathon/v2/groups`;
        const embed = [
          {name: 'embed', value: 'group.groups'},
          {name: 'embed', value: 'group.apps'},
          {name: 'embed', value: 'group.apps.deployments'},
          {name: 'embed', value: 'group.apps.counts'}
        ];

        RequestUtil.json({
          url: url,
          data: embed,
          success: function (response) {
            AppDispatcher.handleServerAction({
              type: REQUEST_MARATHON_GROUPS_SUCCESS,
              data: response
            });
            resolve();
          },
          error: function (e) {
            AppDispatcher.handleServerAction({
              type: REQUEST_MARATHON_GROUPS_ERROR,
              data: e.message
            });
            reject();
          },
          hangingRequestCallback: function () {
            AppDispatcher.handleServerAction({
              type: REQUEST_MARATHON_GROUPS_ONGOING
            });
          }
        });
      };
    },
    {delayAfterCount: Config.delayAfterErrorCount}
  )

};

Use embed object instead of parameter string


/**
 * Incident Theatres
 * An example movie cinema guide API written in NodeJS
 * @copyright 2013 Jeremy Worboys
 */

var orm = require('orm');
var express = require('express');
var appConfig = require('./config/application');
var dbConfig = require('./config/database');
var cinemas = require('./controllers/cinemas');

// Create application
var app = module.exports = express();

// Configure application
appConfig(app);

// Define models
var defineModels = function(db, models) {
    models.cinema = require('./models/cinema')(db);
};

// Define middleware stack
app.use(orm.express(dbConfig, { define: defineModels }));
app.use(app.router);

// Define routes
app.get('/cinemas',    cinemas.list);
app.get('/cinema/:id', cinemas.single);

/**
 * Incident Theatres
 * An example movie cinema guide API written in NodeJS
 * @copyright 2013 Jeremy Worboys
 */

var orm = require('orm');
var express = require('express');
var appConfig = require('./config/application');
var dbConfig = require('./config/database');
var cinemas = require('./controllers/cinemas');

// Create application
var app = module.exports = express();

// Configure application
appConfig(app);

// Define models
app.models = {};
orm.connect(dbConfig, function(err, db) {
    app.models.cinema = require('./models/cinema')(db);
});
var modelsMiddleware = function(req, res, next) {
    req.models = app.models;
    next();
};

// Define middleware stack
app.use(modelsMiddleware);
app.use(app.router);

// Define routes
app.get('/cinemas',    cinemas.list);
app.get('/cinema/:id', cinemas.single);

Create custom model loader so models can be mocked when testing


import numbers from './number-iterator.js';

function findPrimes(total) {
	const primes = [ ...numbers(total) ].map((number) => {
		// Logic for finding next prime goes here
		return number * 2;
	});
	return primes;
}

export default findPrimes;

import numbers from './number-iterator.js';

function findPrimes(total) {
	const primes = [ ...numbers(total) ].map((number) => {
		// Logic for finding next prime goes here
		return number * 2;
	});
	return primes;
}

export function isPrime(number) {
	if (number < 2) {
		return false;
	}
	// Quick check for even numbers
	if (number !== 2 && number % 2 === 0) {
		return false;
	}
	// We start checking at 3, factor can be maximum 1/3 the number, and we will only check odd numbers
	// Number of factors to check is number / 3 (max 1/3) - 3 (for offset of start: 3) / 2 (odd only)
	const length = Math.round((number / 3 - 2) / 2);
	if (length <= 0) {
		return true;
	}
	// Check all possible odd factors
	return [ ...numbers({start : 3, increment : 2, length})].every(function(possibleFactor) {
		return number % possibleFactor !== 0;
	});
}

export default findPrimes;

Add isPrime helper function (tests :white_check_mark:)


/* eslint no-shadow: 0 */
'use strict';

var test = require('tap').test;
var bisect = require('../src/bisect');

test('bisect', function(t) {
    t.test('can find root of sin and cos', function(t) {
        t.equal(Number(bisect(Math.sin, 1, 4, 100, 0.003).toFixed(4)), 3.1416);
        t.equal(Number(bisect(Math.cos, 0, 4, 100, 0.003).toFixed(4)), 1.5723);
        t.throws(function() {
            bisect(0);
        }, 'Throws with syntax error f must be a function');
        t.end();
    });
    t.end();
});

/* eslint no-shadow: 0 */
'use strict';

var test = require('tap').test;
var bisect = require('../src/bisect');

test('bisect', function(t) {
    t.test('can find root of sin and cos', function(t) {
        t.equal(Number(bisect(Math.sin, 1, 4, 100, 0.003).toFixed(4)), 3.1416);
        t.equal(Number(bisect(Math.cos, 0, 4, 100, 0.003).toFixed(4)), 1.5723);
        t.throws(function() {
            t.equal(Number(bisect(Math.cos, 0, 4, 1, 0.003).toFixed(4)), 1.5723);
        }, 'Throws if it exceeds the number of iterations allowed');
        t.throws(function() {
            bisect(0);
        }, 'Throws with syntax error f must be a function');
        t.end();
    });
    t.end();
});

Cover bisect iteration error line


describe('Events', function () {
  var component, called;

  var TestUtils = React.addons.TestUtils;

  before(function () {
    called = false;
    component = TestUtils.renderIntoDocument(React.createElement('a', {
      onClick: function () {
        called = true;
      }
    }));
    this.$r = $R(component);
  });

  describe('#click', function () {
    before(function () {
      this.$r.click();
    });

    it('calls the onClick handler', function () {
      expect(called).to.be.true;
    });
  });
});

describe('Events', function () {
  var component, stubs;

  var TestUtils = React.addons.TestUtils;

  function stub (name) {
    return stubs[name] = sinon.stub();
  }

  beforeEach(function () {
    stubs = {};

    component = TestUtils.renderIntoDocument(React.createElement(
      'div',
      {
        onClick: stub('div')
      },
      React.createElement('a', {
        onClick: stubs.a = sinon.spy(function (event) {
          event.stopPropagation();
        })
      }),
      React.createElement('button', {
        className: 'first-button',
        onClick: stub('button1')
      }),
      React.createElement('button', {
        className: 'second-button',
        onClick: stub('button2')
      })
    ));
  });

  describe('#click on top-level component', function () {
    beforeEach(function () {
      $R(component).click();
    });

    it('calls the div onClick handler', function () {
      expect(stubs.div).to.have.been.called;
    });

    it('does not call any other handler', function () {
      expect(stubs.a).to.not.have.been.called;
      expect(stubs.button1).to.not.have.been.called;
      expect(stubs.button2).to.not.have.been.called;
    });
  });

  describe('#click on a tag component', function () {
    beforeEach(function () {
      $R(component, 'button').click();
    });

    it('calls the button1 onClick handler', function () {
      expect(stubs.button1).to.have.been.called;
    });

    it('calls the button2 onClick handler', function () {
      expect(stubs.button2).to.have.been.called;
    });

    it('bubbles up to the div onClick handler', function () {
      expect(stubs.div).to.have.been.called;
    });
  });

  describe('#click that stops bubbling', function () {
    beforeEach(function () {
      $R(component, 'a').click();
    });

    it('calls the a onClick handler', function () {
      expect(stubs.a).to.have.been.called;
    });

    it('does not bubble up to the div onClick handler', function () {
      expect(stubs.div).to.not.have.been.called;
    });
  });
});

Test selectors and events together.


'use strict';
/* global before, after */
var app = require('../server')
  , mongoose = require('mongoose')
  ;

before(function (done) {
  if (app.get('env') === 'test') {
    done();
  } else {
    done(new Error('Not using test env! Bailing out.'));
  }
});

after(function (done) {
  var connection = mongoose.connection;
  if (!connection.name.match(/test/i)) {
    done(new Error('Database name didn\'t contain test. Not dropping it.'));
    return;
  }
  connection.db.dropDatabase(function (err) {
    if (err) {
      done(new Error('Error when dropping test db. ' + err));
    } else {
      done();
    }
  });
});

'use strict';
/* global before, afterEach */
var app = require('../server')
  , mongoose = require('mongoose')
  ;

before(function (done) {
  if (app.get('env') === 'test') {
    done();
  } else {
    done(new Error('Not using test env! Bailing out.'));
  }
});

afterEach(function (done) {
  mongoose.connection.db.dropDatabase(done);
});

Simplify test DB dropping and drop it after every test


/*global CodeMirror*/
import mobileUtils from 'ghost/utils/mobile-utils';
import createTouchEditor from 'ghost/assets/lib/touch-editor';

var setupMobileCodeMirror,
    TouchEditor,
    init;

setupMobileCodeMirror = function setupMobileCodeMirror() {
    var noop = function () {},
        key;

    for (key in CodeMirror) {
        if (CodeMirror.hasOwnProperty(key)) {
            CodeMirror[key] = noop;
        }
    }

    CodeMirror.fromTextArea = function (el, options) {
        return new TouchEditor(el, options);
    };

    CodeMirror.keyMap = { basic: {} };
};

init = function init() {
    if (mobileUtils.hasTouchScreen()) {
        $('body').addClass('touch-editor');

        // make editor tabs touch-to-toggle in portrait mode
        $('.floatingheader').on('touchstart', function () {
            $('.entry-markdown').toggleClass('active');
            $('.entry-preview').toggleClass('active');
        });

        Ember.touchEditor = true;
        mobileUtils.initFastClick();
        TouchEditor = createTouchEditor();
        setupMobileCodeMirror();
    }
};

export default {
    createIfMobile: init
};

/*global CodeMirror, device*/
import mobileUtils from 'ghost/utils/mobile-utils';
import createTouchEditor from 'ghost/assets/lib/touch-editor';

var setupMobileCodeMirror,
    TouchEditor,
    init;

setupMobileCodeMirror = function setupMobileCodeMirror() {
    var noop = function () {},
        key;

    for (key in CodeMirror) {
        if (CodeMirror.hasOwnProperty(key)) {
            CodeMirror[key] = noop;
        }
    }

    CodeMirror.fromTextArea = function (el, options) {
        return new TouchEditor(el, options);
    };

    CodeMirror.keyMap = { basic: {} };
};

init = function init() {
    //Codemirror does not function on mobile devices,
    // nor on any iDevice.
    if (device.mobile() || (device.tablet() && device.ios())) {
        $('body').addClass('touch-editor');

        // make editor tabs touch-to-toggle in portrait mode
        $('.floatingheader').on('touchstart', function () {
            $('.entry-markdown').toggleClass('active');
            $('.entry-preview').toggleClass('active');
        });

        Ember.touchEditor = true;
        mobileUtils.initFastClick();
        TouchEditor = createTouchEditor();
        setupMobileCodeMirror();
    }
};

export default {
    createIfMobile: init
};

Use Device.js to determine mobile editor use


var gulp = require('gulp'),
    browserify = require('gulp-browserify'),
    uglify = require('gulp-uglify'),
    gzip = require('gulp-gzip'),
    size = require('gulp-size'),
    concat = require('gulp-concat');

gulp.task('default', ['test']);

gulp.task('test', function(cb) {
  var spawn = require('child_process').spawn;
  var child = spawn('node_modules/.bin/lab', ['-v', '-t', 90], {stdio: 'inherit'});
  child.on('exit', function(code) {
    cb(code ? new Error('Tests Failed') : null);
  });
});

gulp.task('build', function() {
  gulp.src('lib/template.js')
  .pipe(browserify())
  .pipe(concat('slm.js'))
  .pipe(size({showFiles: true}))
  .pipe(gulp.dest('dist'))
  .pipe(uglify())
  .pipe(concat('slm.min.js'))
  .pipe(size({showFiles: true}))
  .pipe(gulp.dest('dist'))
  .pipe(gzip())
  .pipe(size({showFiles: true}))
  .pipe(gulp.dest('dist'))
});

var gulp = require('gulp'),
    browserify = require('gulp-browserify'),
    uglify = require('gulp-uglify'),
    gzip = require('gulp-gzip'),
    size = require('gulp-size'),
    concat = require('gulp-concat');

gulp.task('default', ['test']);

gulp.task('test', function(cb) {
  var spawn = require('child_process').spawn;
  var child = spawn('node_modules/.bin/lab', ['-v', '-t', 90], {stdio: 'inherit'});
  child.on('exit', function(code) {
    cb(code ? new Error('Tests Failed') : null);
  });
});

gulp.task('build', ['full', 'runtime']);

gulp.task('full', function() {
  gulp.src('lib/template.js')
  .pipe(browserify())
  .pipe(concat('slm.js'))
  .pipe(size({showFiles: true}))
  .pipe(gulp.dest('dist'))
  .pipe(uglify())
  .pipe(concat('slm.min.js'))
  .pipe(size({showFiles: true}))
  .pipe(gulp.dest('dist'))
  .pipe(gzip())
  .pipe(size({showFiles: true}))
  .pipe(gulp.dest('dist'))
});

gulp.task('runtime', function() {
  gulp.src('lib/runtime.js')
  .pipe(browserify())
  .pipe(concat('slm-runtime.js'))
  .pipe(size({showFiles: true}))
  .pipe(gulp.dest('dist'))
  .pipe(uglify())
  .pipe(concat('slm-runtime.min.js'))
  .pipe(size({showFiles: true}))
  .pipe(gulp.dest('dist'))
  .pipe(gzip())
  .pipe(size({showFiles: true}))
  .pipe(gulp.dest('dist'))
});

Build runtime as separate file


$(document).ready(function() {
    $(document).keyup(function(e) {
        var tag = e.target.tagName.toLowerCase();
        if (tag != 'input' && tag != 'textarea' && tag != 'select' && !e.ctrlKey) {
            if (e.keyCode==78 || e.keyCode==77) {
                $('.nav-menu-icon').trigger('click');
            } else if (e.keyCode==65) {
                $('.search-menu-icon').trigger('click');
            } else if (e.keyCode==73 && e.shiftKey) {
                $( "#new-ip" ).click();
            } else if (e.keyCode==73) {
                $( "#new-indicator" ).click();
            } else if (e.keyCode==69 && e.shiftKey) {
                $( "#new-event" ).click();
            } else if (e.keyCode==69) {
                $( "#new-email-yaml" ).click();
            } else if (e.keyCode==68) {
                $( "#new-domain" ).click();
            } else if (e.keyCode==80) {
                $( "#new-pcap" ).click();
            } else if (e.keyCode==83) {
                $( "#new-sample" ).click();
            } else if (e.keyCode==71) {
                $( "#new-signature" ).click();
            } else if (e.keyCode==27) {
                $( ".mm-opened").trigger('close');
            } else if (e.shiftKey && e.keyCode==191) {
                $( "#shortcut-keys").click();
            }
        }
    });
});

$(document).ready(function() {
    $(document).keyup(function(e) {
        var tag = e.target.tagName.toLowerCase();
        if (tag != 'input' && tag != 'textarea' && tag != 'select' && !e.ctrlKey) {
            if (e.keyCode==78 || e.keyCode==77) {
                $('.nav-menu-icon').trigger('click');
            } else if (e.keyCode==65) {
                $('.search-menu-icon').trigger('click');
            } else if (e.keyCode==73 && e.shiftKey) {
                $( "#new-ip" ).click();
            } else if (e.keyCode==73) {
                $( "#new-indicator" ).click();
            } else if (e.keyCode==69 && e.shiftKey) {
                $( "#new-event" ).click();
            } else if (e.keyCode==69) {
                $( "#new-email-yaml" ).click();
            } else if (e.keyCode==68) {
                $( "#new-domain" ).click();
            } else if (e.keyCode==80) {
                $( "#new-pcap" ).click();
            } else if (e.keyCode==83 && e.shiftKey) {
                $( "#new-signature" ).click();
            } else if (e.keyCode==83) {
                $( "#new-sample" ).click();
            } else if (e.keyCode==27) {
                $( ".mm-opened").trigger('close');
            } else if (e.shiftKey && e.keyCode==191) {
                $( "#shortcut-keys").click();
            }
        }
    });
});

Update shortcut keys for new signature


var express = require('express');
var app = express();
var request = require('request');

var adviceArray = ["I am God's vessel. But my greatest pain in life is that I will never be able to see myself perform live.",
"Would you believe in what you believe in if you were the only one who believed it?"];
var currentAdvice = 0;
var adviceMax = adviceArray.length;

function getAdvice() {
    var message = adviceArray[currentAdvice];
    currentAdvice++;
    if (currentAdvice >= adviceMax) {
        currentAdvice = 0;
    }
    return message;
}

// The main app can hit this when an SMS is received
app.get('/sms', function(req, res) {
    var message = getAdvice();
	request("http://localhost:80/sendsms?message=" +
			message +
			"&number=" +
			encodeURIComponent(req.query.number));
	res.status(200).end()
});

app.listen(3000);

var express = require('express');
var app = express();
var request = require('request');

var adviceArray = ["I am God's vessel. But my greatest pain in life is that I will never be able to see myself perform live.",
"Would you believe in what you believe in if you were the only one who believed it?"];
var currentAdvice = 0;
var adviceMax = adviceArray.length;

function getAdvice() {
    var message = adviceArray[currentAdvice];
    currentAdvice++;
    if (currentAdvice >= adviceMax) {
        currentAdvice = 0;
    }
    return message;
}

// The main app can hit this when an SMS is received
app.get('/sms', function(req, res) {
	if (req.query.message.toLowerCase() != "advice") {
		res.status(400).end();
	}

    var message = getAdvice();
	request("http://localhost:80/sendsms?message=" +
			message +
			"&number=" +
			encodeURIComponent(req.query.number));
	res.status(200).end()
});

app.listen(3000);

Check the message in advice


/*global XMLHttpRequest, XDomainRequest*/
var ArgumentInvalidException = require('../helpers/custom_exception').ArgumentInvalidException;
/**
 * Create a cors http request.
 * @param method - Type of method yopu want to reach.
 * @param url - Url to reach.
 * @param options - The cors options.
 * @returns {XMLHttpRequest}
 */
module.exports = function createCORSRequest(method, url, options) {
  options = options || {};
  var isCORS = options.cors || require("../config").get().CORS;
  if (typeof method !== "string") {
    throw new ArgumentInvalidException('The method should be a string in GET/POST/PUT/DELETE', method);
  }
  if (typeof url !== "string") {
    throw new ArgumentInvalidException('The url should be a string', url);
  }
  var xhr = new XMLHttpRequest();
  xhr.setRequestHeader("Content-Type", "application/json");
  // xhr.overrideMimeType("application/json");

  //If CORS is not needed.
  if (!isCORS) {
    xhr.open(method, url, true);

  } else {
    if ("withCredentials" in xhr) {
      // XHR for Chrome/Firefox/Opera/Safari.
      xhr.open(method, url, true);
    } else if (typeof XDomainRequest != "undefined") {
      // XDomainRequest for IE.
      xhr = new XDomainRequest();
      xhr.open(method, url);
    } else {
      // CORS not supported.
      xhr = null;
    }
  }

  return xhr;
}

/*global XMLHttpRequest, XDomainRequest*/
var ArgumentInvalidException = require('../helpers/custom_exception').ArgumentInvalidException;
/**
 * Create a cors http request.
 * @param method - Type of method yopu want to reach.
 * @param url - Url to reach.
 * @param options - The cors options.
 * @returns {XMLHttpRequest}
 */
module.exports = function createCORSRequest(method, url, options) {
  options = options || {};
  var isCORS = options.cors || require("../config").get().CORS;
  if (typeof method !== "string") {
    throw new ArgumentInvalidException('The method should be a string in GET/POST/PUT/DELETE', method);
  }
  if (typeof url !== "string") {
    throw new ArgumentInvalidException('The url should be a string', url);
  }
  var xhr = new XMLHttpRequest();
  // xhr.overrideMimeType("application/json");

  //If CORS is not needed.
  if (!isCORS) {
    xhr.open(method, url, true);
  } else {
    if ("withCredentials" in xhr) {
      // XHR for Chrome/Firefox/Opera/Safari.
      xhr.open(method, url, true);
    } else if (typeof XDomainRequest != "undefined") {
      // XDomainRequest for IE.
      xhr = new XDomainRequest();
      xhr.open(method, url);
    } else {
      // CORS not supported.
      xhr = null;
    }
  }
  xhr.setRequestHeader("Content-Type", "application/json");
  return xhr;
}

Fix setRequestHeader called too soon.


'use strict';

var forEach = require('es5-ext/object/for-each')
  , memoize = require('memoizee/weak')
  , create  = Object.create;

module.exports = memoize(function (rules) {
	var map = {};

	forEach(rules, function (rule, key) {
		var currentMap = map, tokens, token;
		if (key === '/') {
			map.rule = rule;
			return;
		}
		tokens = key.split('/');
		while ((token = tokens.shift())) {
			if (!currentMap.children) currentMap.children = create(null);
			if (!currentMap.children[token]) currentMap.children[token] = {};
			currentMap = currentMap.children[token];
		}
		currentMap.rule = rule;
	});
	return map;
});

'use strict';

var forEach = require('es5-ext/object/for-each')
  , memoize = require('memoizee/weak')

  , create  = Object.create, stringify = JSON.stringify;

module.exports = memoize(function (rules) {
	var map = {};

	forEach(rules, function (rule, key) {
		var currentMap = map, tokens, token;
		if (key === '/') {
			map.rule = rule;
			return;
		}
		tokens = key.split('/');
		while ((token = tokens.shift()) !== undefined) {
			if (!token) throw new Error("Invalid rules map key " + stringify(key));
			if (!currentMap.children) currentMap.children = create(null);
			if (!currentMap.children[token]) currentMap.children[token] = {};
			currentMap = currentMap.children[token];
		}
		currentMap.rule = rule;
	});
	return map;
});

Introduce some rules map validation


module.exports = {
    extends: [
        'onelint'
    ],
    env: {
        es6: true
    },
    parserOptions: null
};

module.exports = {
    extends: [
        'onelint'
    ],
    env: {
        es6: true
    },
    parserOptions: {
        ecmaVersion: 2017
    }
};

Configure the parser to allow ES2017 features (async/await)


// http://eslint.org/
module.exports = {
    "extends": "eslint:recommended",

    // http://eslint.org/docs/user-guide/configuring#specifying-environments
    "env": {
        "browser": true,
    },

    // http://eslint.org/docs/user-guide/configuring#specifying-globals
    "globals": {
        // false means it shouldn't be overwritten
        "define": false,
        "hqDefine": false,
        "hqImport": false,
        "requirejs": false,
        "gettext": false,
        "ngettext": false,
        "assert": false,
        "sinon": false,
        "$": false,
        "ko": false,
        "_": false,
        "L": true,
        "it": false,
        "describe": false,
        "beforeEach": false,
    },

    // http://eslint.org/docs/rules/
    // http://eslint.org/docs/user-guide/configuring#configuring-rules
    "rules": {
        // First option can be off, warn, or error
        "comma-dangle": ["warn", "always-multiline"],
        "eqeqeq": ["error"],
        "indent": ["warn", 4, {"SwitchCase":1}],
        "linebreak-style": ["error", "unix"],
        "semi": ["error", "always"],
    }
};

// http://eslint.org/
module.exports = {
    "extends": "eslint:recommended",

    // http://eslint.org/docs/user-guide/configuring#specifying-environments
    "env": {
        "browser": true,
    },

    // http://eslint.org/docs/user-guide/configuring#specifying-globals
    "globals": {
        // false means it shouldn't be overwritten
        "define": false,
        "hqDefine": false,
        "hqImport": false,
        "requirejs": false,
        "gettext": false,
        "ngettext": false,
        "assert": false,
        "sinon": false,
        "$": false,
        "ko": false,
        "_": false,
        "L": true,
        "it": false,
        "describe": false,
        "beforeEach": false,
    },

    // http://eslint.org/docs/rules/
    // http://eslint.org/docs/user-guide/configuring#configuring-rules
    "rules": {
        // First option can be off, warn, or error
        "comma-dangle": ["warn", "always-multiline"],
        "eqeqeq": ["error"],
        "indent": ["warn", 4, {"SwitchCase":1}],
        "linebreak-style": ["error", "unix"],
        "semi": ["error", "always"],
        "no-new-object": ["error"],
        "no-unneeded-ternary": ["error"],
        "no-throw-literal": ["error"],
        "no-use-before-define": ["error"],
    }
};

Add rules to eslint config


module.exports = {
  env: {
    'jest/globals': true,
  },
  root: true,
  extends: ['@react-native-community'],
  plugins: ['jest'],
  rules: {
    "array-bracket-spacing": 0,
    "no-console": 0,
    'comma-dangle': 0,
    'object-curly-spacing': 0,
    'no-unused-vars': 2,
    "semi": 0,
    "quotes": [2, "single", "avoid-escape"],
    "eqeqeq": 0,
    "keyword-spacing": 0,
    "no-extra-semi": 0,
    "one-line": 0,
    "curly": 0,
    "no-shadow": 0,
    "prettier/prettier": 0,
    "no-trailing-spaces": 0,
    "dot-notation": 0
  }
}

Add new line at EOF


module.exports = {
  presets: [
    ['env', {
      targets: {
        node: 'current'
      }
    }]
  ];
}

module.exports = {
  parserOptions: {
    ecmaVersion: 8,
    sourceType: 'module'
  },
  env: {
    node: true
  }
};

Fix eslint config (copied babel conf on accident)


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

export default Ember.Component.extend({
	didInsertElement: function() {
		this._super();
		this.set("error_message", undefined);
	  	this.resizeBody();
	  	var self = this;
	  	Ember.$(window).on("window:resize", function() {
	  		self.resizeBody();
	  	});
	},
	actions: {
		logout: function() {
			Ember.$.ajax({
                url: config.API_LOC + "/logout",
                data: {},
                contentType: "application/json",
                type: 'GET'
            });
			this.get('session').invalidate();
		}
	},
	resizeBody: function() {
		if(this.get("session.current_user.admin")) {
			console.log("Admin!");
	     	Ember.$('body').css("padding-top", Ember.$("#navbar").height() + 150);
	  	}
	  	else {
	  		console.log("User!");
	  		Ember.$('body').css("padding-top", Ember.$("#navbar").height()+30);
	  	}
	}.observes("session.current_user.admin")
});

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

export default Ember.Component.extend({
	didInsertElement: function() {
		this._super();
		this.set("error_message", undefined);
	  	this.resizeBody();
	  	var self = this;
	  	Ember.$(window).on("window:resize", function() {
	  		self.resizeBody();
	  	});
	},
	actions: {
		logout: function() {
			var self = this;
			Ember.$.ajax({
                url: config.API_LOC + "/logout",
                data: {},
                contentType: "application/json",
                type: 'GET',
                success: function() {
                	self.get("session").invalidate();
                },
                error: function() {
                	self.get("session").invalidate();
                }
            });
		}
	},
	resizeBody: function() {
		if(this.get("session.current_user.admin")) {
			console.log("Admin!");
	     	Ember.$('body').css("padding-top", Ember.$("#navbar").height() + 150);
	  	}
	  	else {
	  		console.log("User!");
	  		Ember.$('body').css("padding-top", Ember.$("#navbar").height()+30);
	  	}
	}.observes("session.current_user.admin")
});

Change sending of the logout request


// /////////////////////////////////////////////////////// //
// Load content via window.fetch in a background script,   //
// from a content script                                   //
// /////////////////////////////////////////////////////// //
// Load this file as background and content scripts
// Use in content_script as:
// backgroundFetch(url).then(doSomething)

if (location.protocol === 'chrome-extension:' || location.protocol === 'moz-extension:') {
	// setup in background page
	chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
		if (message.action === 'fetch') {
			fetch(...message.arguments)
			.then(response => response.json())
			.then(sendResponse)
			.catch(err => sendResponse(String(err)));
		}
		return true; // tell browser to await response
	});
} else {
	// setup in content script
	window.backgroundFetch = function () {
		const args = [...arguments];
		return new Promise((resolve, reject) => {
			chrome.runtime.sendMessage({
				action: 'fetch',
				arguments: args
			}, response => {
				if (/^Error/.test(response)) {
					reject(new Error(response.replace(/Error:? ?/, '')));
				} else {
					resolve(response);
				}
			});
		});
	};
}

// /////////////////////////////////////////////////////// //
// Load content via window.fetch in a background script,   //
// from a content script                                   //
// /////////////////////////////////////////////////////// //
// Load this file as background and content scripts
// Use in content_script as:
// backgroundFetch(url).then(doSomething)

if (location.protocol === 'chrome-extension:' || location.protocol === 'moz-extension:') {
	// setup in background page
	chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
		if (message.action === 'fetch') {
			fetch(...message.arguments)
			.then(response => response.json())
			.then(sendResponse)
			.catch(err => sendResponse(String(err)));
		}
		return true; // tell browser to await response
	});
} else {
	// setup in content script
	window.backgroundFetch = function (...args) {
		return new Promise((resolve, reject) => {
			chrome.runtime.sendMessage({
				action: 'fetch',
				arguments: args
			}, response => {
				if (/^Error/.test(response)) {
					reject(new Error(response.replace(/Error:? ?/, '')));
				} else {
					resolve(response);
				}
			});
		});
	};
}

Remove a redundant line in background page


'use strict';

var assert = require('assert');
var ControllerInterface = require('../lib/controller-interface');

describe('controller-interface', function() {
    beforeEach(function() {
        this.controllerInterface = new ControllerInterface(
            './test-controller.fcl');
    });

    it('it should give an expected output on input', function(done) {
        this.controllerInterface.runController([-0.4, 3.1],
            function(err, result) {
                assertFloatEqual(result, 0.5);
                done();
        });
    });

    it('it should give an expected output on input', function(done) {
        this.controllerInterface.runController([0.2, 1.1],
            function(err, result) {
                assertFloatEqual(result, 1.5);
                done();
        });
    });
});

function assertFloatEqual(actual, expected) {
    assert(Math.abs(actual - expected) < 1e-5, 'expected ' + expected + ', got ' + actual);
}

'use strict';

var assert = require('assert');
var ControllerInterface = require('../lib/controller-interface');

describe('controller-interface', function() {
    beforeEach(function() {
        this.controllerInterface = new ControllerInterface(
            './test-controller.fcl');
    });

    it('it should give an expected output on input', function(done) {
        this.controllerInterface.runController([-0.4, 3.1],
            function(err, result) {
                assert.ifError(err);
                assertFloatEqual(result, 0.5);
                done();
        });
    });

    it('it should give an expected output on input', function(done) {
        this.controllerInterface.runController([0.2, 1.1],
            function(err, result) {
                assert.ifError(err);
                assertFloatEqual(result, 1.5);
                done();
        });
    });
});

function assertFloatEqual(actual, expected) {
    assert(Math.abs(actual - expected) < 1e-5, 'expected ' + expected + ', got ' + actual);
}

Add more asserts to tests for error checking


import React from 'react';

import PropTypes from 'prop-types';

import { ShareButtons, generateShareIcon } from 'react-share';

// import { addthis } from '../../../data/config';

// import { refreshToolBox } from '../../api/addthis';

const { FacebookShareButton, TwitterShareButton } = ShareButtons;
const FacebookIcon = generateShareIcon('facebook');
const TwitterIcon = generateShareIcon('twitter');

const ShareBox = ({ url }) => (
  <div>
    <FacebookShareButton
      url={url}
      className="d-inline-block"
    >
      <FacebookIcon
        size={32}
        round
      />
    </FacebookShareButton>
    <TwitterShareButton
      url={url}
      className="d-inline-block"
    >
      <TwitterIcon
        size={32}
        round
      />
    </TwitterShareButton>
  </div>
);

ShareBox.propTypes = {
  url: PropTypes.string.isRequired,
};

export default ShareBox;

import React from 'react';

import PropTypes from 'prop-types';

import { ShareButtons, generateShareIcon } from 'react-share';

// import { addthis } from '../../../data/config';

// import { refreshToolBox } from '../../api/addthis';

const {
  FacebookShareButton,
  TwitterShareButton,
  TelegramShareButton,
  WhatsappShareButton,
} = ShareButtons;

const FacebookIcon = generateShareIcon('facebook');
const TwitterIcon = generateShareIcon('twitter');
const TelegramIcon = generateShareIcon('telegram');
const WhatsappIcon = generateShareIcon('whatsapp');

const ShareBox = ({ url }) => (
  <div>
    <FacebookShareButton
      url={url}
      className="d-inline-block"
    >
      <FacebookIcon
        size={32}
        round
      />
    </FacebookShareButton>

    <TwitterShareButton
      url={url}
      className="d-inline-block"
    >
      <TwitterIcon
        size={32}
        round
      />
    </TwitterShareButton>
    <TelegramShareButton
      url={url}
      className="d-inline-block"
    >
      <TelegramIcon
        size={32}
        round
      />
    </TelegramShareButton>
    <WhatsappShareButton
      url={url}
      className="d-inline-block"
    >
      <WhatsappIcon
        size={32}
        round
      />
    </WhatsappShareButton>
  </div>
);

ShareBox.propTypes = {
  url: PropTypes.string.isRequired,
};

export default ShareBox;

Add Telegram and Whatsapp Share Button


/* See license.txt for terms of usage */

// ********************************************************************************************* //
// Dialog Implementation

var UserMessage =
{
    onLoad: function()
    {
        this.params = window.arguments[0];
    },

    onOK: function()
    {
        UserMessage.saveMessage();
    },

    onCancel: function()
    {
        this.params.cancel = true;
    },

    onKeyDown: function(event)
    {
        if (event.keyCode == KeyEvent.DOM_VK_RETURN && (event.metaKey || event.ctrlKey) &&
            !event.shiftKey && !event.altKey)
        {
            UserMessage.saveMessage();
            $("fbTestUserMessage", window).acceptDialog();
        }
    },

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

    saveMessage: function()
    {
        var textBox = $("message", window);
        this.params.message = textBox.value;
    }
}

// ********************************************************************************************* //
// Helpers

function $(id, win)
{
    return win.document.getElementById(id);
}

// ********************************************************************************************* //

window.document.addEventListener("keydown", UserMessage.onKeyDown, true);

Allow submitting the user message via Ctrl+Enter


/*global require, module*/
'use strict';

// bundled styling for app-header
require('./main.scss');

var AppHeader = require('./src/js/AppHeader');

var instance;

var getOrCreateInstance = function (element, options) {
	if (!instance) {
		instance = new AppHeader(element, options);
		return instance;
	}

	return instance.setMode(options.mode, options);
};

var construct = function (e) {

	if (!e.detail || !e.detail.element || !e.detail.config) {
		console.error('Missing element or configuration for app header.');
	}
	getOrCreateInstance(e.detail.element, e.detail.config);
};

document.addEventListener('o.DOMContentLoaded', construct);

module.exports = function (element, options) {
	return getOrCreateInstance(element, options);
};

module.exports.init = AppHeader.init;

/*global require, module*/
'use strict';

// bundled styling for app-header
require('./main.scss');

var AppHeader = require('./src/js/AppHeader');

var instance;

var getOrCreateInstance = function (element, options) {
	if (!instance) {
		instance = new AppHeader(element, options);
		return instance;
	}

	return instance.setMode(options.mode, options);
};

var construct = function (e) {

	if (!e.detail || !e.detail.element || !e.detail.config) {
		window.console && console.warn('AppHeader received an incorrectly structured event or missing e.detail.element and/or e.detail.config');
	}

	getOrCreateInstance(e.detail.element, e.detail.config);
};

document.addEventListener('o.DOMContentLoaded', construct);

module.exports = function (element, options) {
	return getOrCreateInstance(element, options);
};

module.exports.init = AppHeader.init;

Change event error to event warning.


const browsers = ['Firefox'];

if (!process.env.CI) {
    browsers.push('Chrome');
    switch (process.platform) {
        case 'darwin':
            browsers.push('Safari');
            break;
        case 'win32':
            browsers.push('IE');
            browsers.push('Edge');
            break;
    }
}

module.exports = (config) => {
    config.set({
        basePath: '../',
        frameworks: ['mocha', 'chai'],
        files: [
            'dist/blowfish.js',
            'test/common.js',
            'test/browser.js'
        ],
        reporters: ['dots'],
        browsers: browsers,
        singleRun: true
    });
};

const browsers = ['Firefox'];

if (!process.env.CI) {
    browsers.push('Chrome');
    switch (process.platform) {
        case 'darwin':
            browsers.push('Safari');
            break;
        case 'win32':
            browsers.push('IE');
            browsers.push('Edge');
            break;
    }
}

module.exports = (config) => {
    config.set({
        basePath: '../',
        frameworks: ['mocha', 'chai'],
        files: [
            'dist/blowfish.js',
            'test/common.js',
            'test/browser.js'
        ],
        reporters: ['dots'],
        browsers: browsers,
        singleRun: true,
        logLevel: config.LOG_WARN
    });
};

Change karma log lvl to WARN


import Sequelize from 'sequelize';
import database from '../';
import User from './user';

const URI = Sequelize.STRING(2048);

const blogPostDatabaseDefinition = database.define('blog_post', {
  id: {
    type: Sequelize.INTEGER,
    autoIncrement: true,
    primaryKey: true,
    field: 'id',
    allowNull: false
  },
  title: {
    type: Sequelize.STRING,
    field: 'title',
    allowNull: false
  },
  link: {
    type: URI,
    field: 'link',
    allowNull: false
  },
  imageURI: {
    type: URI,
    field: 'image_uri',
    allowNull: true
  },
  description: {
    type: Sequelize.TEXT,
    field: 'description',
    allowNull: false
  },
  dateUpdated: {
    type: Sequelize.DATE,
    field: 'date_updated',
    allowNull: false
  },
  datePublished: {
    type: Sequelize.DATE,
    field: 'date_published',
    allowNull: false
  }
}, {
  underscored: true
});

blogPostDatabaseDefinition.belongsTo(User, { as: 'author', foreignKey: { name: 'author_id', allowNull: false } });

export default blogPostDatabaseDefinition;

import Sequelize from 'sequelize';
import database from '../';
import User from './user';

const URI = Sequelize.STRING(2048);

const blogPostDatabaseDefinition = database.define('blog_post', {
  id: {
    type: Sequelize.INTEGER,
    autoIncrement: true,
    primaryKey: true,
    field: 'id',
    allowNull: false
  },
  title: {
    type: Sequelize.STRING,
    field: 'title',
    allowNull: false
  },
  link: {
    type: URI,
    field: 'link',
    allowNull: false
  },
  imageURI: {
    type: URI,
    field: 'image_uri',
    allowNull: true
  },
  description: {
    type: Sequelize.TEXT,
    field: 'description',
    allowNull: false
  },
  dateUpdated: {
    type: Sequelize.DATE,
    field: 'date_updated',
    allowNull: false
  },
  datePublished: {
    type: Sequelize.DATE,
    field: 'date_published',
    allowNull: false
  }
});

blogPostDatabaseDefinition.belongsTo(User, { as: 'author', foreignKey: { name: 'authorId', field: 'author_id', allowNull: false } });

export default blogPostDatabaseDefinition;

Update blog model to match API camelCase changes


var mongoose = require('mongoose');
var db = {};

mongoose.connect('mongodb://localhost/brainstormer');
var userModel = require('./users/user.server.model.js');
var ideaModel = require('./ideas/idea.server.model.js');
var roomModel = require('./rooms/room.server.model.js');
var interestModel = require('./interests/interest.server.model.js');
db.User = mongoose.model('User');
db.Idea = mongoose.model('Idea');
db.Room = mongoose.model('Room');
db.Interests = mongoose.model('Interest');

module.exports = db;

var mongoose = require('mongoose');
var db = {};

mongoose.connect('mongodb://localhost/brainstormer');
var userModel = require('./users/user.server.model.js');
var ideaModel = require('./ideas/idea.server.model.js');
var roomModel = require('./rooms/room.server.model.js');
var interestModel = require('./interests/interest.server.model.js');
db.User = mongoose.model('User');
db.Idea = mongoose.model('Idea');
db.Room = mongoose.model('Room');
db.Interest = mongoose.model('Interest');

module.exports = db;

Fix Interests to be Interest.


function buildMediaQuery(lowerBound) {
  return `@media only screen and (min-width:${lowerBound})`
}

export const mediaQueries = {
  smallUp: buildMediaQuery("0px"),
  mediumUp: buildMediaQuery("640px"),
  largeUp: buildMediaQuery("1024px"),
  extraLargeUp: buildMediaQuery("1440px"),
  extraExtraLargeUp: buildMediaQuery("1920px")
}

export default {
  ...mediaQueries
}

const mq = lowerBound => `@media only screen and (min-width: ${lowerBound}em)`

export const mediaQueries = {
  smallUp: mq(0),
  mediumUp: mq(52),
  largeUp: mq(64),
  extraLargeUp: mq(90),
  extraExtraLargeUp: mq(120)
}

export default {
  ...mediaQueries
}

Switch to em-based media queries


/*
  Created by 'myth' on June 30. 2015

  CognacTime is licenced under the MIT licence.
*/

var fs =            require('fs')
,   express =       require('express')
,   expresshbs =    require('express-handlebars')
,   io =            require('socket.io')
,   log =           require('./logging')
,   pkg =           require('../package.json')
,   config =        require('../config.json')
,   routes =        require('../routes/core')

// Initialize the express app
ct = express()

// Set logging middleware
ct.use(require('morgan')('combined', {'stream': log.stream}))

// Set static
ct.use('/static', express.static('public'))

// Set view engine
ct.engine('hbs', expresshbs({
  defaultLayout: 'main',
  extname: '.hbs'
}))
ct.set('views', './views')
ct.set('view engine', 'hbs')

// Set core routes
ct.use('/', routes)

// Lets drink some Cognac and watch documentaries...
server = ct.listen(config.port, config.host, function () {

  var host = server.address().address
  var port = server.address().port

  log.info("CognacTime v%s running at %s:%s", pkg.version, host, port)

})

// Enable socket.io
io = io(server)
require('./socket')(io)

/*
  Created by 'myth' on June 30. 2015

  CognacTime is licenced under the MIT licence.
*/

var fs =            require('fs')
,   express =       require('express')
,   expresshbs =    require('express-handlebars')
,   io =            require('socket.io')
,   log =           require('./logging')
,   pkg =           require('../package.json')
,   config =        require('../config.json')
,   routes =        require('../routes/core')
,   path =          require('path')

// Initialize the express app
ct = express()

// Set logging middleware
ct.use(require('morgan')('combined', {'stream': log.stream}))

// Set static
ct.use('/static', express.static('public'))

// Set view engine
ct.engine('hbs', expresshbs({
  defaultLayout: 'main',
  extname: '.hbs'
}))
ct.set('views', path.join(__dirname, '../views'))
ct.set('view engine', 'hbs')

// Set core routes
ct.use('/', routes)

// Lets drink some Cognac and watch documentaries...
server = ct.listen(config.port, config.host, function () {

  var host = server.address().address
  var port = server.address().port

  log.info("CognacTime v%s running at %s:%s", pkg.version, host, port)

})

// Enable socket.io
io = io(server)
require('./socket')(io)

Test fix for errors caused by CWD being different when using a systemd initiated service as compared to npm start or node ./


const config = require('./config');
const request = require('./request');
const debug = require('./debug');
const _template = require('lodash/template');
const cache = {};

module.exports = {
	get(name) {

		return request.get(config.templates+name+'.html')
		.then((tmp) => {
			let compiled = _template(tmp, {variable: 'data'});
			cache[name] = compiled;
			return compiled;
		})
		.catch(err => err);

	}
};

Add semicolon to the right place


const parser = require("../lib/parser");

const methodMap = {
  parse: parser.parse
};

function output(obj) {
  process.stdout.write(JSON.stringify(obj));
}

const input = [];
process.stdin.on("data", data => input.push(data));
process.stdin.on("end", () => {
  const request = JSON.parse(input.join(""));
  const method = methodMap[request.method];
  if (!method) {
    output({ error: `Invalid method ${request.method}` });
    return;
  }

  method
    .apply(null, request.args)
    .then(result => {
      output({ result: result });
    })
    .catch(error => {
      console.log(error.message);
    });
});

const parser = require("../lib/parser");

const methodMap = {
  parse: parser.parse
};

function output(obj) {
  process.stdout.write(JSON.stringify(obj));
}

const input = [];
process.stdin.on("data", data => input.push(data));
process.stdin.on("end", () => {
  const request = JSON.parse(input.join(""));
  const method = methodMap[request.method];
  if (!method) {
    output({ error: `Invalid method ${request.method}` });
    process.exit(1);
  }

  method
    .apply(null, request.args)
    .then(result => {
      output({ result: result });
    })
    .catch(error => {
      console.log(error.message);
      process.exit(1);
    });
});

Exit 1 on failure in js helpers


//= require_directory ./AbstractDocument
//= require_directory ./BrowserWidget
//= require_directory ./ComponentStateManager
//= require_directory ./Desktop
//= require_directory ./DiagramWidget
//= require_directory ./DocumentEditor
//= require_directory ./EventWidget
//= require_directory ./FundamentalTypes
//= require_directory ./HierarchicalMenuWidget
//= require_directory ./HtmlDocument
//= require_directory ./Internationalization
//= require_directory ./jStorage
//= require_directory ./LanguageSelectorWidget
//= require_directory ./NewsReaderWidget
//= require_directory ./PhotoGaleryWidget
//= require_directory ./ResourceManager
//= require_directory ./ScrollingBehaviour
//= require_directory ./Singleton
//= require_directory ./SkinSelectorWidget
//= require_directory ./SmartDocument
//= require_directory ./SplashForm
//= require_directory ./TabWidget
//= require_directory ./TextAreaEditor
//= require_directory ./ToolBarWidget
//= require_directory ./VideoPlayerWidget
//= require_directory ./WebUIConfiguration
//= require_directory ./WebUIController
//= require_directory ./WebUILogger
//= require_directory ./WebUIMessageBus

//= require_directory ./AbstractDocument
//= require_directory ./BrowserWidget
//= require_directory ./ComponentStateManager
//= require_directory ./Desktop
//= require_directory ./DiagramWidget
//= require_directory ./DocumentEditor
//= require_directory ./EventWidget
//= require_directory ./FundamentalTypes
//= require_directory ./HierarchicalMenuWidget
//= require_directory ./HtmlDocument
//= require_directory ./Internationalization
//= require_directory ./jStorage
//= require_directory ./LanguageSelectorWidget
//= require_directory ./NewsReaderWidget
//= require_directory ./PhotoGaleryWidget
//= require_directory ./ResourceManager
//= require_directory ./ScrollingBehaviour
//= require_directory ./Singleton
//= require_directory ./SkinSelectorWidget
//= require_directory ./SmartDocument
//= require_directory ./SplashForm
//= require_directory ./TabWidget
//= require_directory ./TextAreaEditor
//= require_directory ./ToolBarWidget
//= require_directory ./TreeWidget
//= require_directory ./VideoPlayerWidget
//= require_directory ./WebUIConfiguration
//= require_directory ./WebUIController
//= require_directory ./WebUILogger
//= require_directory ./WebUIMessageBus

Add TreeWidget to the concatenated JavaScript output.


'use strict';

var Types = {
  paragraphInsert: 1,
  paragraphUpdate: 2,
  paragraphDelete: 3,
  sectionInsert: 4,
  sectionUpdate: 5,
  sectionDelete: 6
}

/**
 * Delta constructor. Type should be one of the types above.
 *
 * @param {Int} index
 * @param {*} data
 * @param {String} type
 */
function Delta (index, data, type) {
  this.index = index
  this.data = data
  this.type = Types[type]
}

/**
 * Delta.compress(deltas) takes an array of Deltas and reduces it to
 * the minimum number of deltas necessary to represent the same changes
 * the original represented. For example, if there are two update
 * operations on the same paragraph, the first will get discarded.
 *
 * @param {[Deltas]}
 */
Delta.compress = function (deltas) {

}

Delta.types = Types

module.exports = Delta

'use strict';

var Types = {
  paragraphInsert: 1,
  paragraphUpdate: 2,
  paragraphDelete: 3,
  sectionInsert: 4,
  sectionUpdate: 5,
  sectionDelete: 6
}

// We should also be able to look up the string from the number.
Object.keys(Types).forEach(function (key) {
  Types[Types[key]] = key
})

/**
 * Delta constructor. Type should be one of the types above.
 *
 * @param {String || Number} type
 * @param {Int} index
 * @param {*} data
 */
function Delta (type, index, data) {
  if (!(this instanceof Delta))
    return new Delta(type, index, data)

  this.index = index
  this.type = typeof type !== 'number' ? Types[type] : type

  if (data && this.type <= 3)
    this.paragraph = data
  else if (data)
    this.section = data
}

/**
 * Delta.reduce(deltas) takes an array of Deltas and reduces it to
 * the minimum number of deltas necessary to represent the same changes
 * the original represented. For example, if there are two update
 * operations on the same paragraph, the first will get discarded.
 *
 * FIXME: actually do something.
 *
 * @param {[Deltas]}
 */
Delta.reduce = function (deltas) {

}

// Expose types.
Delta.types = Types

module.exports = Delta

Allow reverse lookup of Delta types.


var shell   = require('shelljs'),
    fs      = require('fs'),
    plugins = require('./util/plugins'),
    xml_helpers = require('./util/xml-helpers'),
    path    = require('path');

module.exports = function fetchPlugin(plugin_dir, plugins_dir, link, subdir, callback) {
    // Ensure the containing directory exists.
    shell.mkdir('-p', plugins_dir);

    subdir = subdir || '.';

    // clone from git repository
    if(plugin_dir.indexOf('https://') == 0 || plugin_dir.indexOf('git://') == 0) {
        if (link) {
            var err = new Error('--link is not supported for git URLs');
            if (callback) callback(err);
            else throw err;
        } else {
            plugins.clonePluginGitRepo(plugin_dir, plugins_dir, subdir, callback);
        }
    } else {
        // Copy from the local filesystem.
        // First, read the plugin.xml and grab the ID.
        plugin_dir = path.join(plugin_dir, subdir);
        var xml = xml_helpers.parseElementtreeSync(path.join(plugin_dir, 'plugin.xml'));
        var plugin_id = xml.getroot().attrib.id;

        var dest = path.join(plugins_dir, plugin_id);

        shell.rm('-rf', dest);
        if (link) {
            fs.symlinkSync(plugin_dir, dest, 'dir');
        } else {
            shell.cp('-R', plugin_dir, dest);
        }

        if (callback) callback(null, dest);
    }
};

var shell   = require('shelljs'),
    fs      = require('fs'),
    plugins = require('./util/plugins'),
    xml_helpers = require('./util/xml-helpers'),
    path    = require('path');

module.exports = function fetchPlugin(plugin_dir, plugins_dir, link, subdir, callback) {
    // Ensure the containing directory exists.
    shell.mkdir('-p', plugins_dir);

    subdir = subdir || '.';

    // clone from git repository
    if(plugin_dir.indexOf('https://') == 0 || plugin_dir.indexOf('git://') == 0) {
        if (link) {
            var err = new Error('--link is not supported for git URLs');
            if (callback) callback(err);
            else throw err;
        } else {
            plugins.clonePluginGitRepo(plugin_dir, plugins_dir, subdir, callback);
        }
    } else {
        // Copy from the local filesystem.
        // First, read the plugin.xml and grab the ID.
        plugin_dir = path.join(plugin_dir, subdir);
        var xml = xml_helpers.parseElementtreeSync(path.join(plugin_dir, 'plugin.xml'));
        var plugin_id = xml.getroot().attrib.id;

        var dest = path.join(plugins_dir, plugin_id);

        shell.rm('-rf', dest);
        if (link) {
            fs.symlinkSync(plugin_dir, dest, 'dir');
        } else {
            shell.mkdir('-p', dest);
            shell.cp('-R', path.join(plugin_dir, '*') , dest);
        }

        if (callback) callback(null, dest);
    }
};

Fix source and destination paths for installing plugins from local disk


const axios = require('axios')
const path = require('path')

let GraphQL
try {
  // do to GraphQL schema issue [see](https://github.com/graphql/graphiql/issues/58)
  GraphQL = require(path.join(process.cwd(), './node_modules/graphql'))
} catch (e) {
  // fallback if graphql is not installed locally
  GraphQL = require('graphql')
}

const { graphql } = GraphQL
const { correctURL, encode, DEFAULT_CONFIG } = require('./util')

function Gest (schema, config = {}) {
  const { baseURL, headers, timeout } = Object.assign(DEFAULT_CONFIG, config)
  return function (query) {
    if (baseURL) {
      const instance = axios.create({
        timeout,
        headers
      })

      const corrected = correctURL(baseURL)
      if (config.verbose) console.log(`${query} -> ${corrected}`)

      return instance.post(corrected, encode(query))
                     .then(res => res.data)
    }

    if (config.verbose) console.log(query)
    return graphql(schema, query)
  }
}

module.exports = exports.default = Gest

const axios = require('axios')
const path = require('path')

let GraphQL
try {
  // do to GraphQL schema issue [see](https://github.com/graphql/graphiql/issues/58)
  GraphQL = require(path.join(process.cwd(), './node_modules/graphql'))
} catch (e) {
  // fallback if graphql is not installed locally
  GraphQL = require('graphql')
}

const { graphql } = GraphQL
const { correctURL, encode, DEFAULT_CONFIG } = require('./util')

function Gest (schema, config = {}) {
  const { baseURL, headers, timeout } = Object.assign(DEFAULT_CONFIG, config)
  return function (query) {
    if (baseURL) {
      const instance = axios.create({
        timeout,
        headers
      })

      const corrected = correctURL(baseURL)
      if (config.verbose) console.log(`${query} -> ${corrected}`)

      return instance.post(corrected, encode(query))
                     .then(res => res.data)
    }

    if (config.verbose) console.log(query)
    return graphql(schema, query, null, { headers })
  }
}

module.exports = exports.default = Gest

Send headers with local queries


'use strict'

/* @flow */

function promisify(callback: Function, throwError: boolean = true): Function {
  return function promisified(){
    const parameters = Array.from ? Array.from(arguments) : Array.prototype.slice.call(arguments)
    const parametersLength = parameters.length + 1
    let promise = new Promise((resolve, reject) => {
      parameters.push(function(error, data) {
        if (error) {
          reject(error)
        } else resolve(data)
      })
      if (parametersLength === 1) {
        callback.call(this, parameters[0])
      } else if (parametersLength === 2) {
        callback.call(this, parameters[0], parameters[1])
      } else if (parametersLength === 3) {
        callback.call(this, parameters[0], parameters[1], parameters[2])
      } else if (parametersLength === 4) {
        callback.call(this, parameters[0], parameters[1], parameters[2], parameters[3])
      } else {
        callback.apply(this, parameters)
      }
    })
    if (!throwError) {
      promise = promise.catch(function() {
        return null
      })
    }
    return promise
  }
}

function promisifyAll(object: Object, throwError: boolean = true): Object {
  const duplicate = Object.assign({}, object)
  for (const item in duplicate) {
    if (duplicate.hasOwnProperty(item)) {
      const value = duplicate[item]
      if (typeof value === 'function') {
        duplicate[item] = promisify(value, throwError)
      }
    }
  }
  return duplicate
}

module.exports = promisify
module.exports.promisifyAll = promisifyAll

/* @flow */

function promisify(callback: Function, throwError: boolean = true): Function {
  return function promisified(...parameters) {
    let promise = new Promise((resolve, reject) => {
      parameters.push(function(error, data) {
        if (error) {
          reject(error)
        } else resolve(data)
      })
      callback.apply(this, parameters)
    })
    if (!throwError) {
      promise = promise.then(function(result) {
        return typeof result === 'undefined' ? true : result
      }).catch(function() {
        return false
      })
    }
    return promise
  }
}

function promisifyAll(object: Object, throwError: boolean = true): Object {
  const duplicate = Object.assign({}, object)
  for (const item in duplicate) {
    if (
      !{}.hasOwnProperty.call(duplicate, item) ||
      typeof duplicate[item] !== 'function'
    ) {
      continue
    }
    duplicate[`${item}Async`] = promisify(duplicate[item], throwError)
  }
  return duplicate
}

export default promisify
export { promisify, promisifyAll }

Enhance main module - Make promisifyAll work bluebird style - Return null instead of false in case of error when throwError is false - Return true instead of undefined in case of success when throwError is false - Make module exports babel styled


module AST from "AST.js";

import { Parser } from "Parser.js";
import { Scanner } from "Scanner.js";

export { Parser, Scanner, AST };

export function parseModule(input, options) {

    return new Parser(input, options).Module();
}

export function parseScript(input, options) {

    return new Parser(input, options).Script();
}

export function forEachChild(node, fn) {

    var keys = Object.keys(node), val, i, j;

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

        // Don't iterate over backlink to parent
        if (keys[i] === "parentNode")
            continue;

        val = node[keys[i]];

        // Skip non-objects
        if (val !== Object(val))
            continue;

        if (typeof val.type === "string") {

            // Nodes have a "type" property
            fn(val);

        } else {

            // Iterate arrays
            for (j = 0; j < (val.length >>> 0); ++j)
                if (val[j] && typeof val[j].type === "string")
                    fn(val[j]);
        }
    }
}

Remove exported forEachNode (now a method of AST.Node).


const emotionHash = require('@emotion/hash')

let cache = {}

function hash (string) {
  if (!cache[string]) {
    cache[string] = emotionHash(string)
  }

  return cache[string]
}

module.exports = hash

const emotionHash = require('@emotion/hash/dist/hash.cjs.js')

let cache = {}

function hash (string) {
  if (!cache[string]) {
    cache[string] = emotionHash(string)
  }

  return cache[string]
}

module.exports = hash

Fix emotion import assuming ES2015 imports intead of commonjs with webpack


// @echo header
(function (moduleName, factory) {
    'use strict';

    if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {
        // CommonJS/Node.js
        factory(require('knockout'), require('jquery'));
    } else if (typeof define === 'function' && define.amd) {
        // AMD
        define(moduleName, ['knockout', 'jquery'], factory);
    } else {
        factory(ko, $);
    }

})('knockstrap', function (ko, $) {
    'use strict';

    // @include utils.js

    // @include templates.js

    // @include bindings.js
});

// @echo header
(function (factory) {
    'use strict';

    if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {
        // CommonJS/Node.js
        factory(require('knockout'), require('jquery'));
    } else if (typeof define === 'function' && define.amd) {
        // AMD
        define(['knockout', 'jquery'], factory);
    } else {
        factory(ko, $);
    }

})(function (ko, $) {
    'use strict';

    // @include utils.js

    // @include templates.js

    // @include bindings.js
});

Use anonymous module when exporting via AMD


export function isNumber(v) {
  return typeof v === "number";
}

export function isString(v) {
  return typeof v === "string";
}

export function isObject(v) {
  return typeof v === "object";
}

export function isArray(v) {
  return Array.isArray(v);
}

export function isFunction(v) {
  return typeof v === "function";
}

export function isNumber(v) {
  return typeof v === "number";
}

export function isString(v) {
  return typeof v === "string";
}

export function isObject(v) {
  return typeof v === "object" && v !== null;
}

export function isArray(v) {
  return Array.isArray(v);
}

export function isFunction(v) {
  return typeof v === "function";
}

Check if value is not null


'use strict';

const clientManager = require('./clientManager');
const dbg = require('debug')('anamorph:stateManager');

module.exports = new class StateManager {
  getState(id) {
    this.clientId = id;
    const client = clientManager.getClient(id);

    return client.authManager.getAuthData()
      .then(authData => ({
        syncId: client.syncId,
        status: client.isRegistered() ? 'connected' : 'disconnected',
        remoteStatus: client.remoteIsRegistered() ? 'connected' : 'disconnected',
        auth: {
          clientId: this.clientId,
          syncId: client.syncId,
          rootUrl: 'http://127.0.0.1:3000', //todo: get the root base url here
          facebookUrl: authData.facebook.authUrl,
          twitterUrl: authData.twitter.authUrl,
          linkedinUrl: authData.linkedin.authUrl,
          instagramUrl: authData.instagram.authUrl,
        }
      }));
  }

  onStateChange() {

  }
};

'use strict';
const ip = require('ip');
const clientManager = require('./clientManager');
const dbg = require('debug')('anamorph:stateManager');

module.exports = new class StateManager {
  getState(id) {
    this.clientId = id;
    const client = clientManager.getClient(id);

    return client.authManager.getAuthData()
      .then(authData => ({
        syncId: client.syncId,
        status: client.isRegistered() ? 'connected' : 'disconnected',
        remoteStatus: client.remoteIsRegistered() ? 'connected' : 'disconnected',
        auth: {
          clientId: this.clientId,
          syncId: client.syncId,
          rootUrl: 'http://127.0.0.1:8080', //todo: get the root base url here
          facebookUrl: authData.facebook.authUrl,
          twitterUrl: authData.twitter.authUrl,
          linkedinUrl: authData.linkedin.authUrl,
          instagramUrl: authData.instagram.authUrl,
        },
        localIp: ip.address(),
      }));
  }

  onStateChange() {

  }
};

Add IP address to state


// Configuration
var browserSync = require('browser-sync');

// Init browserSync manually
module.exports = function(grunt) {
    grunt.registerTask('bs-init', function () {
        var done = this.async();

        browserSync({
            server: 'src/',
            files: [
                'src/*.html',
                'src/scripts/*.js'
            ]
        }, function (err, bs) {
            done();
        });
    });
};

// Configuration
var browserSync = require('browser-sync');

// Init browserSync manually
module.exports = function(grunt) {
    grunt.registerTask('bs-init', function () {
        var done = this.async();

        browserSync({
            server: 'src/',
            files: [
                'src/*.html',
                // 'src/icons/*.svg',
                'src/scripts/*.js'
            ]
        }, function (err, bs) {
            done();
        });
    });
};

Add option to watch for changes in svg icons sources


//
chrome.webNavigation.onBeforeNavigate.addListener((details) => {
  console.log(details);

  if(detectYTURL(details.url) && details.url.indexOf("disable_polymer=true") === -1){
    if(details.url.indexOf("?") !== -1){
      chrome.tabs.update(details.tabId, {
        url: details.url + "&disable_polymer=true"
      });
    }else{
      chrome.tabs.update(details.tabId, {
        url: details.url + "?disable_polymer=true"
      });
    }
  }
});

function detectYTURL(url){
  return (url.indexOf("www.youtube.com") !== -1 && url.indexOf("/js") === -1);
}

//
chrome.webNavigation.onBeforeNavigate.addListener((details) => {
  console.log(details);

  if(detectYTURL(details.url) && details.url.indexOf("disable_polymer=true") === -1){
    if(details.url.indexOf("?") !== -1){
      chrome.tabs.update(details.tabId, {
        url: details.url + "&disable_polymer=true"
      });
    }else{
      chrome.tabs.update(details.tabId, {
        url: details.url + "?disable_polymer=true"
      });
    }
  }
});

function detectYTURL(url){
  return (
          (url.indexOf("www.youtube.com") !== -1 || url.indexOf("https://youtube.com" !== -1))
          && url.indexOf("/js") === -1
          );
}

Cover case of entering via plain youtube.com


import Select from 'antd/lib/select';
import React from 'react';
import { ConnectionsContext } from '../connections/ConnectionsStore';

const { Option } = Select;

function ConnectionDropdown() {
  return (
    <ConnectionsContext.Consumer>
      {context => (
        <Select
          showSearch
          placeholder="Choose a connection"
          // TODO className is overridden by antdesign css?
          // className="w5"
          style={{ width: 260 }}
          optionFilterProp="children"
          value={context.selectedConnectionId}
          onChange={id => context.selectConnection(id)}
          filterOption={(input, option) =>
            option.props.value &&
            option.props.children.toLowerCase().indexOf(input.toLowerCase()) >=
              0
          }
        >
          <Option value="">Choose a connection...</Option>
          {context.connections.map(conn => {
            return (
              <Option key={conn._id} value={conn._id}>
                {conn.name}
              </Option>
            );
          })}
        </Select>
      )}
    </ConnectionsContext.Consumer>
  );
}

export default ConnectionDropdown;

import Select from 'antd/lib/select';
import Icon from 'antd/lib/icon';
import React, { useContext, useState } from 'react';
import { ConnectionsContext } from '../connections/ConnectionsStore';
import ConnectionEditDrawer from '../connections/ConnectionEditDrawer';

const { Option } = Select;

function ConnectionDropdown() {
  const connectionsContext = useContext(ConnectionsContext);
  const [showEdit, setShowEdit] = useState(false);

  const handleChange = id => {
    if (id === 'new') {
      return setShowEdit(true);
    }
    connectionsContext.selectConnection(id);
  };

  const handleConnectionSaved = connection => {
    connectionsContext.addUpdateConnection(connection);
    connectionsContext.selectConnection(connection._id);
    setShowEdit(false);
  };

  return (
    <>
      <Select
        showSearch
        placeholder="Choose a connection"
        // TODO className is overridden by antdesign css?
        // className="w5"
        style={{ width: 260 }}
        optionFilterProp="children"
        value={connectionsContext.selectedConnectionId}
        onChange={handleChange}
        filterOption={(input, option) =>
          option.props.value &&
          option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
        }
      >
        <Option value="">
          Choose a connection...
        </Option>
        {connectionsContext.connections.map(conn => {
          return (
            <Option key={conn._id} value={conn._id}>
              {conn.name}
            </Option>
          );
        })}
        <Option value="new">
          <Icon type="plus-circle" /> New connection
        </Option>
      </Select>
      <ConnectionEditDrawer
        visible={showEdit}
        placement="left"
        onClose={() => setShowEdit(false)}
        onConnectionSaved={handleConnectionSaved}
      />
    </>
  );
}

export default ConnectionDropdown;

Allow adding new connection from connection picker


Template.byte.helpers({
  score: function () {
    return Session.get('score');
  },
  binary: function () {
    return Session.get('score').toString(2);
  }
});

Template.byte.events({
  'click button': function () {
    Session.setPersistent('score', Session.get('score') + Session.get('clickrate'));
  }
});

Template.byte.helpers({
  score: function () {
    return Session.get('score');
  },
  binary: function () {
    return Session.get('score').toString(2);
  },
  text: function () {
    var score = Session.get('score');
    var conversionFactor, byteType;
    if (score >= 1099511627776) {
      conversionFactor = 1099511627776;
      byteType = 'Terabyte(s)';
    } else if (score >= 1073741824) {
      conversionFactor = 1073741824;
      byteType = 'Gigabyte(s)';
    } else if (score >= 1048576) {
      conversionFactor = 1048576;
      byteType = 'Megabyte(s)';
    } else if (score >= 1024) {
      conversionFactor = 1024;
      byteType = 'Kilobyte(s)';
    } else {
      conversionFactor = 1;
      byteType = 'Byte(s)';
    }

    var num = (score / conversionFactor).toFixed(2)
    if (num.slice(-2) == '00')
      num = num.slice(0, -3);
    return num + ' ' + byteType;
  }
});

Template.byte.events({
  'click button': function () {
    Session.setPersistent('score', Session.get('score') + Session.get('clickrate'));
  }
});

Add switch for byte size


import Shape from 'kittik-shape-basic';

/**
 * Implements Text shape which is rendering the text at specified point.
 *
 * @since 1.0.0
 * @version 1.0.0
 */
export default class Text extends Shape {
  render(cursor) {
    let text = this.getText();
    let {x, y} = this.getPosition();
    let background = this.getBackground();
    let foreground = this.getForeground();

    if (background) cursor.background(background);
    if (foreground) cursor.foreground(foreground);

    cursor.setPosition(x, y).write(text);

    return this;
  }
}

import Shape from 'kittik-shape-basic';

/**
 * Implements Text shape which is rendering the text at specified point.
 *
 * @since 1.0.0
 */
export default class Text extends Shape {
  render(cursor) {
    let text = this.getText();
    let x = this.getX();
    let y = this.getY();
    let background = this.getBackground();
    let foreground = this.getForeground();

    if (background !== undefined) cursor.background(background);
    if (foreground !== undefined) cursor.foreground(foreground);

    cursor.moveTo(x, y).write(text);

    return this;
  }
}

Migrate to new Basic shape package


/**
 * @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
 *
 * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
 *
 * @license GNU AGPL version 3 or any later version
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 */

import FlowPostToConversation from './views/FlowPostToConversation'

window.OCA.WorkflowEngine.registerOperator({
	id: 'OCA\\Talk\\Flow\\Operation',
	color: 'tomato',
	operation: '',
	options: FlowPostToConversation,
})

/**
 * @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
 *
 * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
 *
 * @license GNU AGPL version 3 or any later version
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 */

import FlowPostToConversation from './views/FlowPostToConversation'

window.OCA.WorkflowEngine.registerOperator({
	id: 'OCA\\Talk\\Flow\\Operation',
	color: '#0082c9',
	operation: '',
	options: FlowPostToConversation,
})

Update Talk action to Nextcloud blue


import './main.sass'
import 'babel-core/polyfill'

import React from 'react'
import thunk from 'redux-thunk'
import createLogger from 'redux-logger'
import { Router } from 'react-router'
import createBrowserHistory from 'history/lib/createBrowserHistory'
import { createStore, applyMiddleware, combineReducers } from 'redux'
import { Provider } from 'react-redux'
import * as reducers from './reducers'
import { analytics, uploader, requester } from './middleware'
import App from './containers/App'

const logger = createLogger({ collapsed: true })
const createStoreWithMiddleware = applyMiddleware(thunk, uploader, requester, analytics, logger)(createStore)
const reducer = combineReducers(reducers)
const store = createStoreWithMiddleware(reducer)

function createRedirect(from, to) {
  return {
    path: from,
    onEnter(nextState, replaceState) {
      replaceState(nextState, to)
    },
  }
}

const rootRoute = {
  path: '/',
  component: App,
  childRoutes: [
    createRedirect('onboarding', '/onboarding/communities'),
    require('./routes/Onboarding'),
  ],
}

const element = (
  <Provider store={store}>
    {() =>
      <Router history={createBrowserHistory()} routes={rootRoute} />
    }
  </Provider>
)

React.render(element, document.getElementById('root'))

import './main.sass'
import 'babel-core/polyfill'

import React from 'react'
import thunk from 'redux-thunk'
import createLogger from 'redux-logger'
import { Router } from 'react-router'
import createBrowserHistory from 'history/lib/createBrowserHistory'
import { createStore, applyMiddleware, combineReducers } from 'redux'
import { Provider } from 'react-redux'
import * as reducers from './reducers'
import { analytics, uploader, requester } from './middleware'
import App from './containers/App'

const logger = createLogger({ collapsed: true })
const createStoreWithMiddleware = applyMiddleware(thunk, uploader, requester, analytics, logger)(createStore)
const reducer = combineReducers(reducers)
const store = createStoreWithMiddleware(reducer)

function createRedirect(from, to) {
  return {
    path: from,
    onEnter(nextState, replaceState) {
      replaceState(nextState, to)
    },
  }
}

const rootRoute = {
  path: '/',
  component: App,
  childRoutes: [
    createRedirect('onboarding', '/onboarding/communities'),
    require('./routes/onboarding'),
  ],
}

const element = (
  <Provider store={store}>
    {() =>
      <Router history={createBrowserHistory()} routes={rootRoute} />
    }
  </Provider>
)

React.render(element, document.getElementById('root'))

Set the right case for directory lookup


'use strict';

togglbutton.render('.ha:not(.toggl)', { observe: true }, function (elem) {
  const description = $('h2', elem);
  const project = $('.hX:last-of-type .hN', elem);

  if (!description) {
    return;
  }

  const link = togglbutton.createTimerLink({
    className: 'google-mail',
    description: description.textContent,
    projectName: !!project && project.textContent.split('/').pop()
  });

  elem.appendChild(link);
});

// This enables support for popup gmail view used by dragapp.com extension
togglbutton.render('div.subject:not(.toggl)', { observe: true }, function (elem) {
  const description = $('span', elem);

  if (!description) {
    return;
  }

  const link = togglbutton.createTimerLink({
    className: 'google-mail',
    description: description.textContent
  });

  elem.children[0].insertBefore(link, elem.children[0].children[1]);
});

Enable gmail integration to work with Drag-app popup view


// @flow

import * as React from 'react'
import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom'

import AppFooter from './App/AppFooter'
import AppHeader from './App/AppHeader'
import type { ReducersStateT } from '../ducks'
import * as SessionAction from '../ducks/session'
import type { SessionT } from '../ducks/session'

const App = ({
  children,
  session
}: {
  children: React.Node,
  session: ?SessionT
}) => (

    <AppHeader session={session} />
    {children}
    <AppFooter />

)

export default App

export const AppContainer = withRouter(
  connect<_, _, *, _, *, _>(({ session }: ReducersStateT) => ({
    session
  }))(
    class $App extends React.Component<*, { isLoading: boolean }> {
      state = { isLoading: false }

      async componentDidMount() {
        const { dispatch } = this.props
        this.setState({ isLoading: true })
        dispatch(await SessionAction.getCurrentUser())
        this.setState({ isLoading: false })
      }

      render() {
        return this.state.isLoading ? (

Loading...

        ) : (
          <App {...this.props} />
        )
      }
    }
  )
)

// @flow

import * as React from 'react'
import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom'

import AppFooter from './App/AppFooter'
import AppHeader from './App/AppHeader'
import type { ReducersStateT } from '../ducks'
import * as SessionAction from '../ducks/session'
import type { SessionT } from '../ducks/session'

const App = ({
  children,
  session
}: {
  children: React.Node,
  session: ?SessionT
}) => (

    <AppHeader session={session} />
    {children}
    <AppFooter />

)

export default App

export const AppContainer = withRouter(
  connect<_, _, *, _, *, _>(({ session }: ReducersStateT) => ({
    session
  }))(
    class $App extends React.Component<*, { isLoading: boolean }> {
      state = { isLoading: true }

      async componentDidMount() {
        const { dispatch } = this.props
        this.setState({ isLoading: true })
        dispatch(await SessionAction.getCurrentUser())
        this.setState({ isLoading: false })
      }

      render() {
        return this.state.isLoading ? (

Loading...

        ) : (
          <App {...this.props} />
        )
      }
    }
  )
)

Change initial state value to prevent fire didMount 2-times


'use strict';

var _ = require('lodash');
var path = require('path');
var fs = require('fs');
var Crawler = require('./crawler');

(function() {
	var Instance = function(options) {
		var defaults = {
			skipDuplicates: true,
		};

		if (fs.existsSync(options || '')) {
			this.options = require(options);
		} else {
			this.options = options || {};
		}

		this.options = _.merge(defaults, this.options);
	};

	Instance.prototype.setConfigFrom = function(configFile) {
		try {
			var optionFile = require(configFile);
			if (optionFile) this.options = _.merge(this.options, optionFile);
		} catch (e) {
			console.log(e);
		}
	};

	Instance.prototype.getConfig = function() {
		return this.options || {};
	};

	Instance.prototype.start = function() {
	};

	module.exports = Instance;
}).call(this);

'use strict';

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

var _ = require('lodash');
var mongoose = require('mongoose');

var Crawler = require('./crawler');
var db = require('../config/db');

(function() {
	var Instance = function(options) {
		var defaults = {
			skipDuplicates: true,

			product: '',
			model: 'RawData',
			urls: [],
			source: '',
			rules: {
				navigator: [],
				content: []
			}
		};

		if (fs.existsSync(options || '')) {
			this.options = require(options);
		} else {
			this.options = options || {};
		}

		this.options = _.merge(defaults, this.options);

		// Model
		this.models = {};
		this.models.queue = require('../models/queue');
		this.models.contents = require('../models/contents');
	};

	Instance.prototype.setConfigFrom = function(configFile) {
		try {
			var optionFile = require(configFile);
			if (optionFile) this.options = _.merge(this.options, optionFile);
		} catch (e) {
			console.log(e);
		}
	};

	Instance.prototype.getConfig = function() {
		return this.options || {};
	};

	Instance.prototype.updateNavigatorRules = function(rule) {
		if (!_.isArray(rule)) rule = [rule];

		this.options.rules.navigator = rule;
	};

	Instance.prototype.updateContentRules = function(content) {
		if (!_.isArray(content)) content = [content];

		this.options.rules.content = content;
	};

	Instance.prototype.start = function() {
	};

	module.exports = Instance;
}).call(this);

Add some default options, add prototype for update these options


var ack = require('ac-koa').require('hipchat'),
  pkg = require('./package.json'),
  app = ack(pkg),
  _ = require('lodash');

var addon = app.addon()
      .hipchat()
      .allowRoom(true)
      .scopes(['send_notification','admin_room']),
    msgOps = require('./lib/msg_operations.js');

  addon.webhook('room_message', /^--.+/, function *() {
    var msg = this.content,
        cmdMatch = _.find(msgOps.commands, function(cmd) {
          return cmd.regex.test(msg);
        });

    if (cmdMatch) {
      var matches = msg.match(cmdMatch.regex);
      yield cmdMatch.handler.apply(this, matches.slice(1));
    }
  });

app.listen();

var ack = require('ac-koa').require('hipchat'),
  pkg = require('./package.json'),
  app = ack(pkg),
  _ = require('lodash');

var addon = app.addon()
      .hipchat()
      .allowRoom(true)
      .scopes(['send_notification','view_room']),
    msgOps = require('./lib/msg_operations.js');

  addon.webhook('room_message', /^--.+/, function *() {
    var msg = this.content,
        cmdMatch = _.find(msgOps.commands, function(cmd) {
          return cmd.regex.test(msg);
        });

    if (cmdMatch) {
      var matches = msg.match(cmdMatch.regex);
      yield cmdMatch.handler.apply(this, matches.slice(1));
    }
  });

app.listen();

Change admin_room scope to view_room


import React from 'react'

class Input extends React.Component {
  constructor() {
    super()
  }

  render() {
    return (
      <input onKeyUp={this.props.onType} type="text"></input>
    )
  }
}

export default Input;

import React from 'react'

class Input extends React.Component {
  render() {
    return (
      <textarea onKeyUp={this.props.onType}></textarea>
    )
  }
}

export default Input;

Use textarea to handle linebreaks in text.


describe('caniuseYarn', () => {
  const caniuseYarn = require('../../lib'); // eslint-disable-line global-require
  const shelljs = require('shelljs'); // eslint-disable-line global-require

  beforeEach(() => {
    shelljs.which = jest.fn();
  });

  it('returns true if yarn is available', () => {
    shelljs.which.mockReturnValue(true);
    expect(caniuseYarn()).toBeTruthy();
  });

  it('returns false if yarn is not available', () => {
    shelljs.which.mockReturnValue(false);
    expect(caniuseYarn()).toBeFalsy();
  });
});

const shelljs = require('shelljs');
const caniuseYarn = require('../../lib');

describe('caniuseYarn', () => {
  beforeEach(() => {
    shelljs.which = jest.fn();
  });

  test('returns true if yarn is available', () => {
    shelljs.which.mockReturnValue(true);
    expect(caniuseYarn()).toBeTruthy();
  });

  test('returns false if yarn is not available', () => {
    shelljs.which.mockReturnValue(false);
    expect(caniuseYarn()).toBeFalsy();
  });
});

Remove eslint-ignore and use test() instead of it()