import validate from '../../../utils/validate';
import { module, test } from 'qunit';

import Required from 'ember-simple-validate/lib/validators/required';

var validators;
var user;

module('validate', {
  beforeEach: function() {
    validators = {
      firstName: Required.create(),
      lastName: Required.create(),
    };

    user = {
      firstName: 'Mihai',
      lastName: 'Scurtu',
    };
  }
});

test('it validates a simple object', function(assert) {
  var result = validate(user, validators);

  assert.equal(result, true);
});

test('it fails on error', function(assert) {
  delete user.lastName;

  var result = validate(user, validators);

  assert.equal(result, false);
});

test('errors are retrievable', function(assert) {
  delete user.lastName;

  var result = validate(user, validators);

  assert.ok(validators.lastName.errors[0]);
});

import validate from '../../../utils/validate';
import { module, test } from 'qunit';

import Ember from 'ember';
import Required from 'ember-simple-validate/lib/validators/required';

var validators;
var user;

module('validate', {
  beforeEach: function() {
    validators = {
      firstName: Required.create(),
      lastName: Required.create(),
    };

    user = {
      firstName: 'Mihai',
      lastName: 'Scurtu',
    };
  }
});

test('it validates a simple object', function(assert) {
  var result = validate(user, validators);

  assert.equal(result, true);
});

test('it fails on error', function(assert) {
  delete user.lastName;

  var result = validate(user, validators);

  assert.equal(result, false);
});

test('errors are retrievable', function(assert) {
  delete user.lastName;

  var result = validate(user, validators);

  assert.ok(validators.lastName.errors[0]);
});

test('it works with ember objects', function(assert) {
  var userObject = Ember.Object.create(user);

  var result = validate(userObject, validators);

  assert.equal(result, true);
});

test('it works with computed properties', function(assert) {
  var UserClass = Ember.Object.extend({
    firstName: 'Mihai',
    lastName: 'Scurtu',

    fullName: Ember.computed('firstName', 'lastName', function(key, value) {
      return Ember.get(this, 'firstName') + ' ' + Ember.get(this, 'lastName');
    }),
  });

  var userObject = UserClass.create();

  validators = {
    fullName: Required.create(),
  };

  var result = validate(userObject, validators);
  assert.equal(result, true);
});

Add tests for validating ember object (which would be the most used case :/)


import React, { PropTypes } from 'react';

import { tile as tileConstants } from './constants';

const styles = {
  wrapper: {
    width: tileConstants.width,
    height: tileConstants.height,
    border: '1px solid #FFD1AA',
    backgroundColor: '#226666',
    position: 'absolute',
    display: 'flex',
    flexDirection: 'column',
    justifyContent: 'center',
    alignItems: 'center',
    cursor: 'pointer',
  },
  number: {
    color: '#FFD1AA',
    fontSize: 22,
  },
};

const Tile = ({ number, left, top, onClick }) => {
  const compStyles = Object.assign({}, styles.wrapper, {
    left,
    top,
  });

  return (

      {number}

  );
};

Tile.propTypes = {
  number: PropTypes.number,
  left: PropTypes.number.isRequired,
  top: PropTypes.number.isRequired,
  onClick: PropTypes.func.isRequired,
};

Tile.defaultProps = {
  number: 0,
};

export default Tile;

import React, { PropTypes } from 'react';

import { tile as tileConstants } from './constants';

const styles = {
  wrapper: {
    width: tileConstants.width,
    height: tileConstants.height,
    border: '1px solid #FFD1AA',
    position: 'absolute',
    display: 'flex',
    flexDirection: 'column',
    justifyContent: 'center',
    alignItems: 'center',
    cursor: 'pointer',
  },
  number: {
    color: '#FFD1AA',
    fontSize: 22,
  },
};

const Tile = ({ number, left, top, onClick, correct }) => {
  const compStyles = Object.assign({}, styles.wrapper, {
    left,
    top,
    backgroundColor: correct ? '#226666' : '#D4726A',
  });

  return (

      {number}

  );
};

Tile.propTypes = {
  number: PropTypes.number,
  left: PropTypes.number.isRequired,
  top: PropTypes.number.isRequired,
  onClick: PropTypes.func.isRequired,
  correct: PropTypes.bool.isRequired,
};

Tile.defaultProps = {
  number: 0,
  correct: false,
};

export default Tile;

Add correct flag to render different layout.


import _request from 'request-promise'

const jar = _request.jar()
const request = _request.defaults({
  headers: {'Content-Type': 'application/json'},
  jar,
})

const SCV='https://www.alza.sk/Services/EShopService.svc/'

export function* login(credentials) {
  return yield request.post(`${SCV}LoginUser`, {body: JSON.stringify(credentials)})
}

export function* addToCart(item, count) {
  return yield request.post(`${SCV}OrderCommodity`, {body: JSON.stringify(
    {id: item, count}
  )})
}

import _request from 'request-promise'

const jar = _request.jar()
const request = _request.defaults({
  headers: {'Content-Type': 'application/json'},
  jar,
})

const SCV='https://www.alza.sk/Services/EShopService.svc/'

export function* login(credentials) {
  return yield request.post(`${SCV}LoginUser`, {body: JSON.stringify(credentials)})
}

export function* addToCart(itemUrl, count) {
  const id = itemUrl.match(/d(\d*)\.htm$/)[1]
  return yield request.post(`${SCV}OrderCommodity`, {body: JSON.stringify(
    {id, count}
  )})
}

Add to cart from item url.


// ensure base DOM properties are already injected
import 'react-dom'
import DOMProperty from 'react-dom/lib/DOMProperty'

export { DOMProperty as _DOMProperty }

export const emailAttributes = {
  Properties: {
    'xmlns': 0,
    'align': 0,
    'valign': 0,
    'bgcolor': 0,
    'border': 0,
  },
}

let injected = false

export default function injectReactEmailAttributes() {
  if (injected) {
    return
  }

  if (DOMProperty.properties.hasOwnProperty('xmlns')) {
    // already exists in React 15.3.0
    delete emailAttributes.Properties.xmlns
  }

  // make React accept some HTML attributes useful to emails
  DOMProperty.injection.injectDOMPropertyConfig(emailAttributes)

  injected = true
}

// ensure base DOM properties are already injected
import 'react-dom'
import DOMProperty from 'react-dom/lib/DOMProperty'

export { DOMProperty as _DOMProperty }

export const emailAttributes = {
  Properties: {
    'align': 0,
    'valign': 0,
    'bgcolor': 0,
    'border': 0,
  },
}

let injected = false

export default function injectReactEmailAttributes() {
  if (injected) {
    return
  }

  // make React accept some HTML attributes useful to emails
  DOMProperty.injection.injectDOMPropertyConfig(emailAttributes)

  injected = true
}

Remove obsolete React 15.3.0 workaround


import React from 'react';
import ReactDOM from 'react-dom';
import events from '../utils/events';

const isDescendant = (el, target) => {
  if (target !== null) {
    return el === target || isDescendant(el, target.parentNode);
  }
  return false;
};

const clickAwayEvents = ['mousedown', 'touchstart'];
const bind = (callback) => clickAwayEvents.forEach((event) => events.on(document, event, callback));
const unbind = (callback) => clickAwayEvents.forEach((event) => events.off(document, event, callback));

export default class ClickAwayListener extends React.Component {

  static propTypes = {
    children: React.PropTypes.node,
    onClickAway: React.PropTypes.any,
  };

  componentDidMount() {
    if (this.props.onClickAway) {
      bind(this.handleClickAway);
    }
  }

  componentDidUpdate(prevProps) {
    if (prevProps.onClickAway !== this.props.onClickAway) {
      unbind(this.handleClickAway);
      if (this.props.onClickAway) {
        bind(this.handleClickAway);
      }
    }
  }

  componentWillUnmount() {
    unbind(this.handleClickAway);
  }

  handleClickAway = (event) => {
    if (event.defaultPrevented) {
      return;
    }

    const el = ReactDOM.findDOMNode(this);

    if (document.documentElement.contains(event.target) && !isDescendant(el, event.target)) {
      this.props.onClickAway(event);
    }
  };

  render() {
    return this.props.children;
  }
}

import React from 'react';
import ReactDOM from 'react-dom';
import events from '../utils/events';

const isDescendant = (el, target) => {
  if (target !== null) {
    return el === target || isDescendant(el, target.parentNode);
  }
  return false;
};

const clickAwayEvents = ['mouseup', 'touchend'];
const bind = (callback) => clickAwayEvents.forEach((event) => events.on(document, event, callback));
const unbind = (callback) => clickAwayEvents.forEach((event) => events.off(document, event, callback));

export default class ClickAwayListener extends React.Component {

  static propTypes = {
    children: React.PropTypes.node,
    onClickAway: React.PropTypes.any,
  };

  componentDidMount() {
    if (this.props.onClickAway) {
      bind(this.handleClickAway);
    }
  }

  componentDidUpdate(prevProps) {
    if (prevProps.onClickAway !== this.props.onClickAway) {
      unbind(this.handleClickAway);
      if (this.props.onClickAway) {
        bind(this.handleClickAway);
      }
    }
  }

  componentWillUnmount() {
    unbind(this.handleClickAway);
  }

  handleClickAway = (event) => {
    if (event.defaultPrevented) {
      return;
    }

    const el = ReactDOM.findDOMNode(this);

    if (document.documentElement.contains(event.target) && !isDescendant(el, event.target)) {
      this.props.onClickAway(event);
    }
  };

  render() {
    return this.props.children;
  }
}

Fix nested menus that can't be clicked issue


if (!process.env["CLOUDFRONT_DISTRIBUTION_ID"]) {
  throw new Error("CLOUDFRONT_DISTRIBUTION_ID env var must be set");
}

CLOUDFRONT_DISTRIBUTION_ID = process.env["CLOUDFRONT_DISTRIBUTION_ID"]

const AWS = require("aws-sdk");
const cloudfront = new AWS.CloudFront();

cloudfront
  .createInvalidation({
    DistributionId: CLOUDFRONT_DISTRIBUTION_ID,
    InvalidationBatch: {
      Paths: { Quantity: 1, Items: ["/*"] },
      CallerReference: new Date().toISOString()
    }
  })
  .promise()
  .then(console.log);

// This script is meant to be run from your own laptop, build environment,
// or some separate process (as opposed to the Lambda@Edge function)
// It expects CLOUDFRONT_DISTRIBUTION_ID env var
// and since it uses the aws-sdk lib, it assumes your AWS keys are in either:
//   * in the file: ~/.aws/credentials
//   * or in the env vars: AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
// see https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/setting-credentials-node.html

if (!process.env["CLOUDFRONT_DISTRIBUTION_ID"]) {
  throw new Error("CLOUDFRONT_DISTRIBUTION_ID env var must be set");
}

CLOUDFRONT_DISTRIBUTION_ID = process.env["CLOUDFRONT_DISTRIBUTION_ID"];

const AWS = require("aws-sdk");
const cloudfront = new AWS.CloudFront();
const util = require("./lib/util");

function createCloudfrontInvalidation(items = []) {
  return cloudfront
    .createInvalidation({
      DistributionId: CLOUDFRONT_DISTRIBUTION_ID,
      InvalidationBatch: {
        Paths: { Quantity: items.length, Items: items },
        CallerReference: new Date().toISOString()
      }
    })
    .promise()
    .then(console.log);
}

// e.g. invalidatePaths(["/docs", "/pricing"])
function invalidatePaths(paths) {
  // map the paths into the custom format this library uses: "/" + toBase64(JSON.stringify({ uri, shouldPrerender }));
  // before: ["/docs", "/pricing"]
  // after:  ["eyJ1cmkiOiIvZG9jcyIsInNob3VsZFByZXJlbmRlciI6dHJ1ZX0=", "/eyJ1cmkiOiIvcHJpY2luZyIsInNob3VsZFByZXJlbmRlciI6dHJ1ZX0="]
  const cloudFrontUrls = paths.map(path => util.createUri(path, true));

  return createCloudfrontInvalidation(cloudFrontUrls);
}

function invalidateEverything() {
  return createCloudfrontInvalidation(["/*"]);
}

invalidateEverything();

// This script calls `invalidateEverything` to invalidate all possible paths on your
// CloudFront distribution. If instead you want to invalidate a distinct set of paths,
// use invalidatePaths.
// invalidatePaths(["/docs", "/pricing"]);

Add example for invalidating specific paths


/*!
 * Simple Layout Rendering for Pattern Lab
 *
 * Copyright (c) 2014 Dave Olsen, http://dmolsen.com
 * Licensed under the MIT license
 */

try {

  /* load pattern nav */
  var template         = document.getElementById("pl-pattern-nav-template");
  var templateCompiled = Hogan.compile(template.innerHTML);
  var templateRendered = templateCompiled.render(navItems);
  document.querySelector(".pl-js-pattern-nav-target").innerHTML = templateRendered;

  /* load ish controls */
  var template         = document.getElementById("pl-ish-controls-template");
  var templateCompiled = Hogan.compile(template.innerHTML);
  var templateRendered = templateCompiled.render(ishControls);
  document.querySelector(".pl-js-controls").innerHTML = templateRendered;

} catch(e) {

  var message = "
Please generate your site before trying to view it.
";
  document.querySelector(".pl-js-pattern-nav-target").innerHTML = message;

}

/*!
 * Simple Layout Rendering for Pattern Lab
 *
 * Copyright (c) 2014 Dave Olsen, http://dmolsen.com
 * Licensed under the MIT license
 */

try {

  /* load pattern nav */
  var template         = document.querySelector(".pl-js-pattern-nav-template");
  var templateCompiled = Hogan.compile(template.innerHTML);
  var templateRendered = templateCompiled.render(navItems);
  document.querySelector(".pl-js-pattern-nav-target").innerHTML = templateRendered;

  /* load ish controls */
  var template         = document.querySelector(".pl-js-ish-controls-template");
  var templateCompiled = Hogan.compile(template.innerHTML);
  var templateRendered = templateCompiled.render(ishControls);
  document.querySelector(".pl-js-controls").innerHTML = templateRendered;

} catch(e) {

  var message = "
Please generate your site before trying to view it.
";
  document.querySelector(".pl-js-pattern-nav-target").innerHTML = message;

}

Change from IDs to pl-js- classes


import Calendar from 'date-picker/calendar';
import injectTheme from '../fixtures/inject-theme';
import React from 'react';
import CalendarToolbar from 'date-picker/calendar-toolbar';

const TestUtils = React.addons.TestUtils;

describe(`Calendar`, () => {
    let ThemedCalendar;

    beforeEach(() => {
        ThemedCalendar = injectTheme(Calendar);
    });

    describe(`Next Month Button`, () => {
        it(`should initially be disabled if the current month is the same as the month in the maxDate prop`, () => {
            let initialDate = new Date();
            let maxDate = new Date(initialDate.toDateString());

            let render = TestUtils.renderIntoDocument(
                <ThemedCalendar
                    initialDate={initialDate}
                    maxDate={maxDate}
                />
            );
            let renderedCalendarToolbar =
                TestUtils.findRenderedComponentWithType(render, CalendarToolbar);

            expect(renderedCalendarToolbar.props.nextMonth).to.be.false;
        });
    });
});

import Calendar from 'date-picker/calendar';
import injectTheme from '../fixtures/inject-theme';
import React from 'react';
import CalendarToolbar from 'date-picker/calendar-toolbar';

const TestUtils = React.addons.TestUtils;

describe(`Calendar`, () => {
    let ThemedCalendar;

    beforeEach(() => {
        ThemedCalendar = injectTheme(Calendar);
    });

    describe(`Next Month Button`, () => {
        it(`should initially be disabled if the current month is the same as the month in the maxDate prop`, () => {
            let initialDate = new Date();
            let maxDate = new Date(initialDate.toDateString());

            let render = TestUtils.renderIntoDocument(
                <ThemedCalendar
                    initialDate={initialDate}
                    maxDate={maxDate}
                />
            );
            let renderedCalendarToolbar =
                TestUtils.findRenderedComponentWithType(render, CalendarToolbar);

            expect(renderedCalendarToolbar.props.nextMonth).to.be.false;
        });

        it(`should initially be disabled if the current month is after the month in the maxDate prop`, () => {
            let initialDate = new Date();
            let maxDate = new Date(initialDate.toDateString());
            maxDate.setMonth(maxDate.getMonth() - 1);

            let render = TestUtils.renderIntoDocument(
                <ThemedCalendar
                    initialDate={initialDate}
                    maxDate={maxDate} />
            );
            let renderedCalendarToolbar =
                TestUtils.findRenderedComponentWithType(render, CalendarToolbar);

            expect(renderedCalendarToolbar.props.nextMonth).to.be.false;
        });
    });
});

Test if next month button is initially disabled if current month is after the maxDate prop


const util = require('../../lib/continuous-delivery/util');

describe("Continuous delivery", function() {
    describe("utils", function() {
        describe("makeStringArray", function() {
            it('works when list is a string', function() {
                const result = util.makeStringArray('gugus');
                expect(result).toEqual(['gugus']);
            });

            it('works when list is an array', function() {
                const result = util.makeStringArray(['first', 'second']);
                expect(result).toEqual(['first', 'second']);
            });

            it('works when list is empty', function() {
                const result = util.makeStringArray(undefined);
                expect(result).toEqual([]);
            });

            it('throws when list is weird', function() {
                expect(() => util.makeStringArray({}))
                    .toThrowError('expected list to be a string or an array but got object');
            });

            it('throws when list has weird element', function() {
                expect(() => util.makeStringArray([{}]))
                    .toThrowError('expected list element to be a string but got object');
            });
        });

        describe('unique', function() {
            it('removes duplicated strings only', function() {
                expect(util.unique(['a', 'b', 'c', 'd', 'b', 'a', 'd', 'd']))
                    .toEqual(['a', 'b', 'c', 'd']);
            });
        });
    });
});

const util = require('../../lib/util');

describe("Continuous delivery", function() {
    describe("utils", function() {
        describe("makeStringArray", function() {
            it('works when list is a string', function() {
                const result = util.makeStringArray('gugus');
                expect(result).toEqual(['gugus']);
            });

            it('works when list is an array', function() {
                const result = util.makeStringArray(['first', 'second']);
                expect(result).toEqual(['first', 'second']);
            });

            it('works when list is empty', function() {
                const result = util.makeStringArray(undefined);
                expect(result).toEqual([]);
            });

            it('throws when list is weird', function() {
                expect(() => util.makeStringArray({}))
                    .toThrowError('expected list to be a string or an array but got object');
            });

            it('throws when list has weird element', function() {
                expect(() => util.makeStringArray([{}]))
                    .toThrowError('expected list element to be a string but got object');
            });
        });

        describe('unique', function() {
            it('removes duplicated strings only', function() {
                expect(util.unique(['a', 'b', 'c', 'd', 'b', 'a', 'd', 'd']))
                    .toEqual(['a', 'b', 'c', 'd']);
            });
        });
    });
});

Fix path to "util.js" in test


$(document).ready(function () {
    let zone;
    let district;

    let getZone = $.get('/inputs/geojson/zone', function (data) {
        zone = data;
    });

    let getDistrict = $.get('/inputs/geojson/district', function (data) {
        district = data;
    });

    $.when(getZone, getDistrict).done(function () {
        let map = new MapClass('map-div');
        map.init({
            data:{
                zone: zone,
                district: district
            },
            extrude: true
        });
    });
});

$(document).ready(function () {
    let zone;
    let district;

    let getZone = $.get('/inputs/geojson/zone', function (data) {
        zone = data;
    });

    let getDistrict = $.get('/inputs/geojson/district', function (data) {
        district = data;
    });

    $.when(getZone, getDistrict).always(function () {
        let map = new MapClass('map-div');
        map.init({
            data:{
                zone: zone,
                district: district
            },
            extrude: true
        });
    });
});

Make sure function runs even if one of the promise fails


module.exports = {
    presenceUrl: 'http://localhost:9870',
    backendUrl: 'http://backend.unichat.eu/api',
};

module.exports = {
    presenceUrl: 'http://presence.unichat.eu',
    backendUrl: 'http://backend.unichat.eu/api',
};

Use presence subdomain in frontend


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

import App from './App'

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

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

/* Create a simple component without JSX
	<div>
		React Tutorial
	</div>
*/
const App = function() {
	return React.createElement('div', null, 'React Tutorial');
}

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

Create a simple component without JSX


/*

Copyright 2014 - 2016 Roland Bouman (roland.bouman@gmail.com)

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

*/
var showLoadingIndicator, hideLoadingIndicator;

if (iFun(window.top.showLoadingIndicator)) {
  showLoadingIndicator = function(){
    win.top.showLoadingIndicator();
  }

  hideLoadingIndicator = function(){
    win.top.hideLoadingIndicator();
  }
}
else {
  var spinner = new Spinner({
    delayHide: 125,
    useTransitions: typeof(useTransitions)==="undefined" ? true : Boolean(useTransitions)
  });
  win.top.showLoadingIndicator = function(){
    spinner.show();
  };
  win.top.hideLoadingIndicator = function(){
    spinner.hide();
  };
}

function busy(yes){
  if (yes) {
    showLoadingIndicator();
  }
  else {
    hideLoadingIndicator();
  }
}

/*

Copyright 2014 - 2016 Roland Bouman (roland.bouman@gmail.com)

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

*/
var showLoadingIndicator, hideLoadingIndicator;

if (iFun(window.top.showLoadingIndicator)) {
  showLoadingIndicator = function(){
    win.top.showLoadingIndicator();
  }

  hideLoadingIndicator = function(){
    win.top.hideLoadingIndicator();
  }
}
else {
  var spinner = new Spinner({
    delayHide: 125,
    useTransitions: typeof(useTransitions)==="undefined" ? true : Boolean(useTransitions)
  });
  showLoadingIndicator = function(){
    spinner.show();
  };
  hideLoadingIndicator = function(){
    spinner.hide();
  };
}

function busy(yes){
  if (yes) {
    showLoadingIndicator();
  }
  else {
    hideLoadingIndicator();
  }
}

Fix bug in how loading indicator was created. You can now run xavier inside a frame.


"use strict";

const express = require("express");
const bodyParser = require("body-parser");

const gutibot = require("./gutiBot");

const barelyBot = require("./barelyBot").bot;
const defineBot = require("./defineBot");
const windBot = require("./windBot");

const app = express();
const port = process.env.PORT || 3000;

app.use(bodyParser.urlencoded({ extended: true }));

app.get('/', (req, res) => {
  res.send('Hello! This is gutibot. Are you lost?');
});

app.post("/define", defineBot);
app.post("/doesheknower", gutibot.doSync(barelyBot));
app.post("/wind", gutibot.doSync(windBot));

app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(400).send(err.message);
});

app.listen(port, () => console.log("Listening on port " + port));

"use strict";

const express = require("express");
const bodyParser = require("body-parser");

const gutiBot = require("./gutiBot");

const barelyBot = require("./barelyBot").bot;
const defineBot = require("./defineBot");
const windBot = require("./windBot");

const app = express();
const port = process.env.PORT || 3000;

app.use(bodyParser.urlencoded({ extended: true }));

app.get('/', (req, res) => {
  res.send('Hello! This is gutibot. Are you lost?');
});

app.post("/barely", gutiBot.doSync(barelyBot));
app.post("/define", defineBot);
app.post("/wind", gutiBot.doSync(windBot));

app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(400).send(err.message);
});

app.listen(port, () => console.log("Listening on port " + port));

Use simpler endpoint for barelyBot


/*
 * Initializes the model/data layer.
 * @param app - The Scroll app instance
 */
module.exports = function(app) {
  // load the model layer module
  var data = require(app.config.model.module),
      appModels = app.util.getJSFiles(
        app.config.__path + '/' + app.config.model.folder);

  var model = null;
  for(var m in appModels) {
    model = require(appModels[m]);
    data.load(model.name || m, model);
  }

  data.connect(app.config.model.connection,
    function() {
      console.log('Connected to ' + app.config.model.connection);
    },
    function(err) {
      console.error(err);
    });

  // return the models for use
  return data.models;
};

/*
 * Initializes the model/data layer.
 * @param app - The Scroll app instance
 */
module.exports = function(app) {
  // load the model layer module
  var data = require(app.config.model.module),
      appModels = app.util.extend(
        app.util.getJSFiles(app.config.view.path + '/models'),
        app.util.getJSFiles(app.config.__path + '/' + app.config.model.folder)
      );

  var model = null;
  for(var m in appModels) {
    model = require(appModels[m]);

    if(typeof model === 'function') {
      data.load(m, model);
    } else if(typeof model === 'object') {
      data.load(model.name || m, model.load);
    }
  }

  data.connect(app.config.model.connection,
    function() {
      console.log('Connected to ' + app.config.model.connection);
    },
    function(err) {
      console.error(err);
    });

  // return the models for use
  return data.models;
};

Support for custom model names and view layers to add their own custom models


module.exports = {
  server: server,
  client: client
}

var escapeTextForBrowser = require('react/lib/escapeTextForBrowser')

function server(html, data = {}, meta = {}) {
  var xState = escapeTextForBrowser(JSON.stringify(data))
  var xMeta = escapeTextForBrowser(JSON.stringify(meta))

  return (
`<div class="node-iso-v3"
      data-state="${xState}"
      data-meta="${xMeta}">
  ${html}
</div>`
  )
}

function client(cb) {
  Array.prototype.forEach.call(
    document.querySelectorAll('.node-iso-v3'),
    node => cb(JSON.parse(node.dataset.state), JSON.parse(node.dataset.meta), node)
  )
}

module.exports = {
  server: server,
  client: client
}

var escapeTextForBrowser = require('react/lib/escapeTextForBrowser')

function server(html, data = {}, meta = {}) {
  var xState = escapeTextForBrowser(JSON.stringify(data))
  var xMeta = escapeTextForBrowser(JSON.stringify(meta))

  return (
`<div class="node-iso-v3"
      data-state="${xState}"
      data-meta="${xMeta}">
  ${html}
</div>`
  )
}

function client(cb) {
  Array.prototype.forEach.call(
    document.querySelectorAll('.node-iso-v3'),
    node => cb(JSON.parse(node.getAttribute('data-state')), JSON.parse(node.getAttribute('data-meta')), node)
  )
}

Use Element.getAttribute instead of `node.dataset`


let timer = null

function waitForRequests(mockedRequests) {
  return new Promise(resolve => {
    if (mockedRequests.length < 1) {
      resolve()
      return
    }

    timer = setInterval(() => {
      const notYetCalled = mockedRequests.map(req => req.called()).
        filter(isCalled => !isCalled)
      if (notYetCalled.length < 1) {
        clearInterval(timer)
        resolve()
      }
    }, 100)
  })
}

export default waitForRequests

let timer = null

function waitForRequests(mockedRequests) {
  return new Promise(resolve => {
    if (mockedRequests.length < 1) {
      resolve()
      return
    }

    const allRequestsFinished = () => {
      const unfinishedRequests = mockedRequests.
        map(req => req.called()).filter(isCalled => !isCalled)
      return unfinishedRequests.length < 1
    }

    timer = setInterval(() => {
      if (allRequestsFinished()) {
        clearInterval(timer)
        resolve()
      }
    }, 100)
  })
}

export default waitForRequests

Refactor waitForRequests to be more clear


'use strict';

angular.module('publicApp')
  .controller('LoginCtrl', ['$scope', '$http', '$location', 'UserService', function ($scope, $http, $location, $user) {
    $scope.user = {}
    $scope.userService = $user

    $scope.logout = function() {
      $user.logout()
    }

    $scope.login = function () {
			var name = $scope.user.name;
			var password = $scope.user.password;
      $user.login(name, password, function(err, session){
        if (!!session.username) {
					$location.path('/gateway/account');
				} else {
					$location.path('/admin/users/new');
				}
      })
		}
  }]);

'use strict';

angular.module('publicApp')
  .controller('LoginCtrl', ['$scope', '$http', '$location', 'UserService', function ($scope, $http, $location, $user) {
    function checkAdminExists() {
      $http.get("/api/v1/gateway/settings").success(function(resp){
        if (resp.success) {
          $scope.adminExists = resp.settings.adminExists
        }
        $scope.loading = false
      })
    }

    checkAdminExists()

    $scope.loading = true
    $scope.adminExists = false

    $scope.user = {}
    $scope.userService = $user

    $scope.logout = function() {
      $user.logout()
    }

    $scope.login = function () {
			var name = $scope.user.name;
			var password = $scope.user.password;
      $user.login(name, password, function(err, session){
        if (!!session.username) {
					$location.path('/gateway/account');
				} else {
					$location.path('/admin/users/new');
				}
      })
		}
  }]);

Add view, function for registering admin if doesnt exist.


import bootstrapper from 'public_site/bootstrapper.react';
import Raven from 'raven-js';

let sentryDSN = 'https://27643f06676048be96ad6df686c17da3@app.getsentry.com/73366';

Raven.config(sentryDSN, {release: '6a34f86c188811e698eb0242ac130001'}).install();

bootstrapper.run();

import bootstrapper from 'public_site/bootstrapper.react';
import Raven from 'raven-js';

let sentryDSN = 'https://27643f06676048be96ad6df686c17da3@app.getsentry.com/73366';

Raven.config(sentryDSN, {release: 'dfdfd4c7765c8aa5e2e4e34ac3baea92431ca511'}).install();

bootstrapper.run();

Make Sentry release for public-site consistent


const getMonth = function month(string) {
  let date = new Date(string);
  let index = date.getMonth();
  let months = ["January",
  "February",
  "March",
  "April",
  "May",
  "June",
  "July",
  "August",
  "September",
  "October",
  "November",
  "December"];

  return months[index];
};

module.exports = {
  getMonth
}

const getMonth = function month(string) {
  let date = new Date(string);
  let index = date.getMonth();
  let months = ["January",
  "February",
  "March",
  "April",
  "May",
  "June",
  "July",
  "August",
  "September",
  "October",
  "November",
  "December"];

  return months[index];
};

module.exports = {
  getMonth,
}

Make sure code is clean and readable.


'use strict';

var http = require('http');

var assert = require('assertive');

var fetch = require('../lib/fetch');

describe('fetch', function() {
  var server;

  before('start echo server', function(done) {
    server = http.createServer(function(req, res) {
      var chunks = [];
      req.on('data', function(chunk) {
        chunks.push(chunk);
      });
      req.on('end', function() {
        res.writeHead(200, {
          'Content-Type': 'application/json',
        });
        res.end(JSON.stringify({
          method: req.method,
          url: req.url,
          headers: req.headers,
          body: Buffer.concat(chunks).toString()
        }));
      });
    });
    server.on('error', done);
    server.listen(3000, function() { done(); });
  });

  after('close echo server', function(done) {
    server.close(done);
  });

  it('can fetch stuff', function() {
    var headers = { 'x-Fancy': 'stuff' };
    headers['X-Fancy'] = 'other stuff';
    return fetch('http://localhost:3000/foo', { headers: headers })
      .json()
      .then(function(echo) {
        assert.equal('GET', echo.method);
        assert.equal('/foo', echo.url);
        assert.equal('', echo.body);
        assert.equal('overriding headers works thanks to key ordering',
          'other stuff', echo.headers['x-fancy']);
      });
  });
});

'use strict';

var http = require('http');

var assert = require('assertive');

var fetch = require('../lib/fetch');

describe('fetch', function() {
  var server;

  before('start echo server', function(done) {
    server = http.createServer(function(req, res) {
      var chunks = [];
      req.on('data', function(chunk) {
        chunks.push(chunk);
      });
      req.on('end', function() {
        res.writeHead(200, {
          'Content-Type': 'application/json',
        });
        res.end(JSON.stringify({
          method: req.method,
          url: req.url,
          headers: req.headers,
          body: Buffer.concat(chunks).toString(),
        }));
      });
    });
    server.on('error', done);
    server.listen(3000, function() { done(); });
  });

  after('close echo server', function(done) {
    server.close(done);
  });

  it('can fetch stuff', function() {
    var headers = { 'x-Fancy': 'stuff' };
    headers['X-Fancy'] = 'other stuff';
    return fetch('http://localhost:3000/foo', { headers: headers })
      .json()
      .then(function(echo) {
        assert.equal('GET', echo.method);
        assert.equal('/foo', echo.url);
        assert.equal('', echo.body);
        assert.equal('overriding headers works thanks to key ordering',
          'other stuff', echo.headers['x-fancy']);
      });
  });
});

Fix lint error in test


import React from 'react';
import WeatherIcons from '../weather_icons/WeatherIcons'

const SevenHour = ({hourlyForecast}) => {
  if(!hourlyForecast){
    return(

    )
  }

  const icons = new WeatherIcons();
  const sevenHourForecast = hourlyForecast.slice(0, 7)

  return(

      {sevenHourForecast.map((hour, i) => {
        return(

{hour.temp.english}°F

{hour.FCTTIME.civil}

        );
      })}

  )
}

export default SevenHour;

import React from 'react';
import WeatherIcons from '../weather_icons/WeatherIcons'

const SevenHour = ({hourlyForecast}) => {
  if(!hourlyForecast){
    return(

    )
  }

  const icons = new WeatherIcons();
  const sevenHourForecast = hourlyForecast.slice(0, 8)

  return(

      {sevenHourForecast.map((hour, i) => {
        return(

{hour.FCTTIME.civil}

{hour.temp.english}°F

        );
      })}

  )
}

export default SevenHour;

Add render for seven hour.


var express = require('express');
var router = express.Router();

/* GET home page. */
router.post('/', function(req, res) {
	//var param = JSON.parse(req.body.payload);
	console.log(req.body);

	res.send('complete');
});

module.exports = router;

var express = require('express'),
	router = express.Router(),
	net = require('net'),
	fs = require('fs'),
	socketPath = '/tmp/haproxy';

/* GET home page. */
router.post('/', function(req, res) {
	var param = JSON.parse(req.body);
	console.log(req.body);

	fs.stat(socketPath, function (err) {
		if (!err) {
			fs.unlinkSync(socketPath);
		}

		var unixServer = net.createServer(function (sock) {
			sock.write('show health');

			sock.on('data', function (data) {
				winston.debug('DATA: '+data);
				sock.destroy();
			});

		}).listen(socketPath);
	});

	res.send('complete');
});

module.exports = router;

Write the code to read haproxy socket


import winston from "winston";
import _ from "underscore";

import Broadcast from "./classes/Broadcast";
import ENV from "./env";
import SK from "./sk";

const broadcasts = {};

winston.cli();
winston.info("Pipeland starting up.");

// Main loop. Watch for broadcasts that I'm supposed to manage.
SK.broadcasts.watch({}).then(function(docs) {
  const ids = _(docs).pluck("id");
  winston.info(`Got ${ids.length} broadcasts in the initial pull.`);
  ids.forEach(function(id) {
    broadcasts[id] = Broadcast.create({id});
  });
})
.catch(function(err) {
  winston.error("Error getting broadcasts", err);
});

import winston from "winston";
import _ from "underscore";

import Broadcast from "./classes/Broadcast";
import ENV from "./env";
import SK from "./sk";

const broadcasts = {};

winston.cli();
winston.info("Pipeland starting up.");

// Main loop. Watch for broadcasts that I'm supposed to manage.
SK.broadcasts.watch({}).then(function(docs) {
  const ids = _(docs).pluck("id");
  winston.info(`Got ${ids.length} broadcasts in the initial pull.`);
  ids.forEach(function(id) {
    broadcasts[id] = Broadcast.create({id});
  });
})
.on("created", (docs) => {
  docs.forEach((doc) => {
    winston.info(`Initializing broadcast ${doc.id}`);
    broadcasts[doc.id] = Broadcast.create({id: doc.id});
  });
})
.catch(function(err) {
  winston.error("Error getting broadcasts", err);
});

Add broadcasts to Pipeland automagically


import test from 'tape';
import computedStyles from '../src/';

document.body.innerHTML += `

`;

test('computedStyles', (t) => {
  t.plan(3);

  const node = document.querySelector('#node');
  const styles = computedStyles(node);

  t.equal(styles.color, 'rgb(255, 0, 0)', 'inline');
  t.equal(styles['font-family'], 'sans-serif', 'css');

  t.throws(function() {
    computedStyles(document.querySelector('#unknown'));
  }, null, 'Throws if unexpected type');

});

import test from 'tape';
import computedStyles from '../src/';

const html = `

`;

var node;

const t = document.createElement("div");
document.body.appendChild(t);

function resetDOM() {
  t.innerHTML = html;
  node = document.querySelector('#node');
}

test('computedStyles', (t) => {
  t.plan(2);

  resetDOM()
  const styles = computedStyles(node);

  t.equal(styles.color, 'rgb(255, 0, 0)', 'inline');
  t.equal(styles['font-family'], 'sans-serif', 'css');

});

test('throws if unexpected type', (t) => {
  t.plan(1);

  resetDOM()

  t.throws(function() {
    computedStyles(document.querySelector('#unknown'));
  }, null, 'Throws if unexpected type');

});

Fix for tests in zuul


var arrayUnion = require('array-union');

module.exports = function(el, targets, states) {
  var targetNames = [];
  var state;
  var nodes;
  var elTargetName;

  // first we want to find all targets and their names
  for(var stateName in states) {
    state = states[ stateName ];

    if(state) {
      targetNames = arrayUnion(targetNames, state.constructor === Object ? Object.keys(state)  : []  );
    }
  }

  // then we'll get all elements with the `data-f1` attribute
  nodes = el.querySelectorAll('[data-f1]');

  // then we'll cross reference and grab only the nodes we want to act on
  for(var i = 0; i < nodes.length; i++) {
    elTargetName = nodes[ i ].getAttribute('data-f1');

    if(targetNames.indexOf(elTargetName) !== -1) {
      targets[ elTargetName ] = nodes[ i ];
    }
  }
};

var arrayUnion = require('array-union');

module.exports = function(el, targets, states) {
  var targetNames = [];
  var state;
  var nodes;
  var elTargetName;

  // first we want to find all targets and their names
  for(var stateName in states) {
    state = states[ stateName ];

    if(state) {
      targetNames = arrayUnion(targetNames, state.constructor === Object ? Object.keys(state)  : []  );
    }
  }

  // then we'll get all elements with the `data-f1` attribute
  nodes = el.querySelectorAll('[data-f1]');

  // then we'll cross reference and grab only the nodes we want to act on
  for(var i = 0; i < nodes.length; i++) {
    elTargetName = nodes[ i ].getAttribute('data-f1');

    if(targetNames.indexOf(elTargetName) !== -1) {
      targets[ elTargetName ] = nodes[ i ];
    }
  }

  // Add the container to potential target list as well
  if (elTargetName = el.getAttribute('data-f1')) targets[ elTargetName ] = el;
};

Add the parent element to the list of potential targets


module.exports = {
    extends: ['eslint:recommended', 'prettier'],
    parser: 'babel-eslint',
    plugins: ['import', 'prettier'],
    env: {
        node: true,
        es6: true,
    },
    root: true,
    rules: {
        'prettier/prettier': [
            'error',
            { trailingComma: 'es5', singleQuote: true, printWidth: 90, tabWidth: 4 },
        ],

        'import/order': [
            'error',
            {
                groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
            },
        ],
        'no-unused-vars': ['error', { vars: 'all', args: 'none' }],
        'no-underscore-dangle': 'off',
        'no-param-reassign': 'off',
        'no-console': 'off',
        'no-warning-comments': ['warn', { terms: ['fixme'], location: 'start' }],
    },
};

module.exports = {
    extends: ['eslint:recommended', 'prettier'],
    parser: 'babel-eslint',
    plugins: ['import', 'prettier'],
    env: {
        node: true,
        es6: true,
    },
    root: true,
    rules: {
        'prettier/prettier': [
            'error',
            { trailingComma: 'es5', singleQuote: true, printWidth: 90, tabWidth: 4 },
        ],

        'import/order': [
            'error',
            {
                groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
            },
        ],
        'no-unused-vars': [
            'error',
            { vars: 'all', args: 'none', varsIgnorePattern: '^_' },
        ],
        'no-underscore-dangle': 'off',
        'no-param-reassign': 'off',
        'no-console': 'off',
        'no-warning-comments': ['warn', { terms: ['fixme'], location: 'start' }],
    },
};

Allow spreading with ununsed underscore-prefixed vars


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

db.knex.schema.hasTable('sessions').then(function(exists) {
  if (!exists) {
    db.knex.schema.createTable('sessions', function(session) {
      session.increments('id').primary();
      session.timestamps();
    }).then(function() {
      console.log('Session Table created');
    })
  }
});

var Session = db.Model.extend({
  tableName: 'sessions',
  hasTimestamps: true
});

module.exports(User);

var db = require('./db');
var User = require('./UserModel.js');
var Snapshot = require('./SnapshotModel.js');

db.knex.schema.hasTable('sessions').then(function(exists) {
  if (!exists) {
    db.knex.schema.createTable('sessions', function(session) {
      session.increments('id').primary();
      session.integer('userId');
      session.timestamps();
    }).then(function() {
      console.log('Session Table created');
    })
  }
});

var Session = db.Model.extend({
  tableName: 'sessions',
  hasTimestamps: true,
  user: function() {
    return this.belongTo(User);
  },
  snapshots: function() {
    return this.hasMany(Snapshot);
  }
});

module.exports(User);

Add foreign keys to User model


const os               = require('os');

module.exports = function getNetworkIp() {
  let interfaces = os.networkInterfaces();
  let addresses = [];

  for (let k in interfaces) {
    for (let k2 in interfaces[k]) {
      let address = interfaces[k][k2];
      if (address.family === 'IPv4' && !address.internal) {
        addresses.push(address.address);
      }
    }
  }

  return addresses[0];
};

const os               = require('os');

module.exports = function getNetworkIp() {
  let interfaces = os.networkInterfaces();
  let addresses = [];

  for (let k in interfaces) {
    for (let k2 in interfaces[k]) {
      let address = interfaces[k][k2];
      if (address.family === 'IPv4' && !address.internal) {
        addresses.push(address.address);
      }
    }
  }

  // Default to 'localhost' when a suitable IP address isn't found.
  // This will allow live-reload emulators builds to function when
  // working without a network connection.
  return addresses[0] || 'localhost';
};

Enable live-reload emulator builds to work without a network connection


// Verifies that Electron cannot play a video that uses proprietary codecs
//
// This application should be run with the ffmpeg that does not include
// proprietary codecs to ensure Electron uses it instead of the version
// that does include proprietary codecs.

const {app, BrowserWindow, ipcMain} = require('electron')
const path = require('path')
const url = require('url')

const MEDIA_ERR_SRC_NOT_SUPPORTED = 4

let window

app.once('ready', () => {
  window = new BrowserWindow({
    show: false
  })

  window.loadURL(url.format({
    protocol: 'file',
    slashed: true,
    pathname: path.resolve(__dirname, 'asar', 'video.asar', 'index.html')
  }))

  ipcMain.on('asar-video', function (event, message, error) {
    if (message === 'ended') {
      console.log('Video played, proprietary codecs are included')
      app.exit(1)
      return
    }

    if (message === 'error' && error === MEDIA_ERR_SRC_NOT_SUPPORTED) {
      console.log('Video format not supported, proprietary codecs are not included')
      app.exit(0)
      return
    }

    console.log(`Unexpected error: ${error}`)
    app.exit(1)
  })
})

Add comment to codecs app script


// website/static/js/growlBox.js
'use strict';
var $ = require('jquery');
require('bootstrap.growl');

/**
* Show a growl-style notification for messages. Defaults to an error type.
* @param {String} title Shows in bold at the top of the box. Required or it looks foolish.
* @param {String} message Shows a line below the title. This could be '' if there's nothing to say.
* @param {String} type One of 'success', 'info', 'warning', or 'danger'. Defaults to danger.
*/
class GrowlBox {
    constructor(title, message, type='danger') {
        this.title = title;
        this.message = message;
        this.type = type;
        this.show();
    }
    show() {
        $.growl({
            title: '' + this.title + '
',
            message: this.message
        },{
            type: this.type,
            delay: 0,
            animate: {
                enter: 'animated slideInDown',
                exit: 'animated slideOutRight'
            }
        });

    }
}

module.exports = GrowlBox;

// website/static/js/growlBox.js
'use strict';
var $ = require('jquery');
require('bootstrap.growl');

var oop = require('js/oop');

/**
* Show a growl-style notification for messages. Defaults to an error type.
* @param {String} title Shows in bold at the top of the box. Required or it looks foolish.
* @param {String} message Shows a line below the title. This could be '' if there's nothing to say.
* @param {String} type One of 'success', 'info', 'warning', or 'danger'. Defaults to danger.
*/
var GrowlBox = oop.defclass({
    constructor(title, message, type='danger') {
        this.title = title;
        this.message = message;
        this.type = type;
        this.show();
    },
    show() {
        $.growl({
            title: '' + this.title + '
',
            message: this.message
        },{
            type: this.type,
            delay: 0,
            animate: {
                enter: 'animated slideInDown',
                exit: 'animated slideOutRight'
            }
        });

    }
});

module.exports = GrowlBox;

Use oop helpers for GrowlBox class


$(document).ready(function() {
  var $join = $('#landing-join'),
      $landingEmailForm = $join.find('form'),
      $landingEmailInput = $join.find('.landing-email-input'),
      $landingEmailSubmit = $join.find('.landing-email-submit'),
      $landingEmailSpinner = $landingEmailSubmit.find('.fa-spinner'),
      $landingSubmitText = $landingEmailSubmit.find('.landing-submit-text'),
      $successMsg = $join.find('.alert-success'),
      $failureMsg = $join.find('.alert-danger'),
      subscribePath = window.config.subscribePath,
      authenticityToken = window.config.authenticityToken;

  $landingEmailForm.on('submit', function() {
    var email = $landingEmailInput.val(),
        data = { authenticity_token: authenticityToken, email: email };

    if (email) {
      $.post(subscribePath, data, success).fail(failure);
      $landingEmailSpinner.removeClass('hide');
      $landingSubmitText.addClass('hide');
      $landingEmailSubmit.prop('disabled', true);
    }

    function success() {
      $successMsg.removeClass('hide');
      $failureMsg.addClass('hide');
      $landingEmailSpinner.addClass('hide');
      $landingSubmitText.removeClass('hide');
    }

    function failure() {
      $failureMsg.removeClass('hide');
      $successMsg.addClass('hide');
      $landingEmailSubmit.prop('disabled', false);
      $landingEmailSpinner.addClass('hide');
      $landingSubmitText.removeClass('hide');
    }
  });
});

$(document).ready(function() {
  var $join = $('#landing-join'),
      $landingEmailForm = $join.find('form'),
      $landingEmailInput = $join.find('.landing-email-input'),
      $landingEmailSubmit = $join.find('.landing-email-submit'),
      $landingEmailSpinner = $landingEmailSubmit.find('.fa-spinner'),
      $landingSubmitText = $landingEmailSubmit.find('.landing-submit-text'),
      $successMsg = $join.find('.alert-success'),
      $failureMsg = $join.find('.alert-danger'),
      subscribePath = window.config.subscribePath,
      authenticityToken = window.config.authenticityToken;

  $landingEmailForm.on('submit', function(e) {
    e.preventDefault();

    var email = $landingEmailInput.val(),
        data = { authenticity_token: authenticityToken, email: email };

    if (email) {
      $.post(subscribePath, data, success).fail(failure);
      $landingEmailSpinner.removeClass('hide');
      $landingSubmitText.addClass('hide');
      $landingEmailSubmit.prop('disabled', true);
    }

    function success() {
      $successMsg.removeClass('hide');
      $failureMsg.addClass('hide');
      $landingEmailSpinner.addClass('hide');
      $landingSubmitText.removeClass('hide');
    }

    function failure() {
      $failureMsg.removeClass('hide');
      $successMsg.addClass('hide');
      $landingEmailSubmit.prop('disabled', false);
      $landingEmailSpinner.addClass('hide');
      $landingSubmitText.removeClass('hide');
    }
  });
});

Fix email collection js bug.


var path = require('path')

module.exports = function(config) {
  config.set({
    singleRun: true,

    files: [
      './node_modules/babel-polyfill/browser.js',
      'test/index.js',
      'test/eventing.js'
    ],

    frameworks: [ 'mocha' ],

    preprocessors: {
      'test/index.js': [ 'webpack', 'sourcemap' ],
      'test/eventing.js': [ 'webpack', 'sourcemap' ]
    },

    logLevel: config.LOG_INFO,

    reporters: [ 'spec' ],

    webpack: {
      devtool: 'inline-source-map',

      module: {
        loaders: [{
          test: /\.js$/,
          loader: 'babel',
          include: [ path.join(__dirname, 'test') ]
        }]
      },
      resolve: {
        modulesDirectories: [
          '',
          'node_modules'
        ],
        alias: {
          'stream-dom': path.join(__dirname, 'lib')
        },
        extensions: [ '', '.js' ]
      }
    },

    webpackMiddleware: {
            // Display no info to console (only warnings and errors)
      noInfo: true
    },

    plugins: [
      require('karma-webpack'),
      require('karma-sourcemap-loader'),
      require('karma-mocha'),
      require('karma-chrome-launcher'),
      require('karma-firefox-launcher'),
      require('karma-spec-reporter')
    ],

    browsers: [ 'Chrome', 'Firefox' ]
  })
}

var path = require('path')

module.exports = function(config) {
  config.set({
    singleRun: true,

    files: [
      'test/index.js',
      'test/eventing.js'
    ],

    frameworks: [ 'mocha' ],

    preprocessors: {
      'test/index.js': [ 'webpack', 'sourcemap' ],
      'test/eventing.js': [ 'webpack', 'sourcemap' ]
    },

    logLevel: config.LOG_INFO,

    reporters: [ 'spec' ],

    webpack: {
      devtool: 'inline-source-map',

      module: {
        loaders: [{
          test: /\.js$/,
          loader: 'babel',
          include: [ path.join(__dirname, 'test') ]
        }]
      },
      resolve: {
        modulesDirectories: [
          '',
          'node_modules'
        ],
        alias: {
          'stream-dom': path.join(__dirname, 'lib')
        },
        extensions: [ '', '.js' ]
      }
    },

    webpackMiddleware: {
            // Display no info to console (only warnings and errors)
      noInfo: true
    },

    plugins: [
      require('karma-webpack'),
      require('karma-sourcemap-loader'),
      require('karma-mocha'),
      require('karma-chrome-launcher'),
      require('karma-firefox-launcher'),
      require('karma-spec-reporter')
    ],

    browsers: [ 'Chrome', 'Firefox' ]
  })
}

Remove polyfill from unit test


(function () {
    var updateUrl = window.IVOAZ_CONTENT_EDITABLE_UPDATE_URL;
    var current, timeout;
    var htmlTags = document.getElementsByTagName('html');

    if (0 === htmlTags.length) {
        return;
    }

    var html = htmlTags[0];

    html.addEventListener('mousedown', onMouseDown);
    html.addEventListener('mouseup', onMouseUp);

    var contents = document.getElementsByClassName('ivoaz-content-editable');

    for (var i=0; i < contents.length; ++i) {
        contents[i].addEventListener('mousedown', onMouseDown);
    }

    function onMouseDown(event) {
        var target = event.currentTarget;

        if (current && current !== target) {
            save();
            current = undefined;
        }

        if (!target.classList.contains('ivoaz-content-editable')) {
            return;
        }

        event.stopPropagation();

        if (current) {
            return;
        }

        timeout = setTimeout(function () {
            current = target;
            edit();
        }, 1000);
    }

    function onMouseUp(event) {
        if (timeout) {
            clearTimeout(timeout);
            timeout = undefined;
        }
    }

    function edit() {
        current.contentEditable = true;
        current.focus();
    }

    function save() {
        var request = new XMLHttpRequest();
        request.open('PUT', updateUrl.replace(':id', current.dataset.ivoazContentEditableId), true);
        request.setRequestHeader('Content-Type', 'application/json');
        request.setRequestHeader('Accept', 'application/json');
        request.send(JSON.stringify({ text: current.innerHTML }));

        current.contentEditable = false;
    }
})();

(function () {
    var updateUrl = window.IVOAZ_CONTENT_EDITABLE_UPDATE_URL;
    var current, timeout;
    var htmlTags = document.getElementsByTagName('html');

    if (0 === htmlTags.length) {
        return;
    }

    document.addEventListener('mouseup', onMouseUp);
    document.addEventListener('mousedown', function (event) {
        var el = event.target
            , found;

        while (el && !(found = el.classList.contains('ivoaz-content-editable'))) {
            el = el.parentElement;
        }

        onMouseDown(event, found ? el : undefined);
    });

    function onMouseDown(event, target) {
        target = target || event.currentTarget;

        if (current && current !== target) {
            save();
            current = undefined;
        }

        if (!target.classList.contains('ivoaz-content-editable')) {
            return;
        }

        event.stopPropagation();

        if (current) {
            return;
        }

        timeout = setTimeout(function () {
            current = target;
            edit();
        }, 1000);
    }

    function onMouseUp(event) {
        if (timeout) {
            clearTimeout(timeout);
            timeout = undefined;
        }
    }

    function edit() {
        current.contentEditable = true;
        current.focus();
    }

    function save() {
        var request = new XMLHttpRequest();
        request.open('PUT', updateUrl.replace(':id', current.dataset.ivoazContentEditableId), true);
        request.setRequestHeader('Content-Type', 'application/json');
        request.setRequestHeader('Accept', 'application/json');
        request.send(JSON.stringify({ text: current.innerHTML }));

        current.contentEditable = false;
    }
})();

Fix default editor not working for dynamic content


import timeago from 'timeago.js';
import axios from 'axios';
import Vue from 'vue';
import VueMaterial from 'vue-material';
import 'vue-material/dist/vue-material.min.css';

import InstallerIssues from './components/InstallerIssues';

Vue.config.productionTip = false;
Vue.use(VueMaterial);

function formatTimeAgo(date) {
  if (!date) {
    return '';
  }
  return timeago().format(new Date(date));
}
Vue.filter('formatTimeAgo', formatTimeAgo);

axios.defaults.baseURL = 'http://localhost:8000';

/* eslint-disable no-new */
new Vue({
  el: '#issues',
  components: { InstallerIssues },
});

import timeago from 'timeago.js';
import axios from 'axios';
import Vue from 'vue';
import VueMaterial from 'vue-material';
import 'vue-material/dist/vue-material.min.css';

import InstallerIssues from './components/InstallerIssues';

Vue.config.productionTip = false;
Vue.use(VueMaterial);

function formatTimeAgo(date) {
  if (!date) {
    return '';
  }
  return timeago().format(new Date(date));
}
Vue.filter('formatTimeAgo', formatTimeAgo);

// axios.defaults.baseURL = 'http://localhost:8000';

/* eslint-disable no-new */
new Vue({
  el: '#issues',
  components: { InstallerIssues },
});

Remove localhost config for Axios


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

/**
 * External dependencies
 */
import PropTypes from 'prop-types';

/**
 * A single module. Keeps track of its own active state and settings.
 */

function Spinner( { isSaving, styles = {} } ) {
	return (
		<span
			className="spinner"
			style={ {
				display: isSaving ? 'inline-block' : 'none',
				float: 'none',
				marginTop: '0',
				visibility: 'visible',
				...styles,
			} }
		/>
	);
}

Spinner.propTypes = {
	isSaving: PropTypes.bool,
	styles: PropTypes.object,
};

export default Spinner;

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

/**
 * External dependencies
 */
import PropTypes from 'prop-types';

/**
 * A single module. Keeps track of its own active state and settings.
 */

function Spinner( { isSaving, style = {} } ) {
	return (
		<span
			className="spinner"
			style={ {
				display: isSaving ? 'inline-block' : 'none',
				float: 'none',
				marginTop: '0',
				visibility: 'visible',
				...style,
			} }
		/>
	);
}

Spinner.propTypes = {
	isSaving: PropTypes.bool,
	style: PropTypes.object,
};

export default Spinner;

Correct typo for style prop.


/* eslint-disable max-len */
exports.extensions = {
  supported: [
    { icon: 'dist', extensions: ['dist', 'out'] },
    { icon: 'git', extensions: ['git', 'github'] },
    { icon: 'node', extensions: ['node_modules'] },
    { icon: 'meteor', extensions: ['meteor'] },
    { icon: 'src', extensions: ['src'] },
    { icon: 'test', extensions: ['tests', 'test', '__tests__', '__test__'] },
    { icon: 'typings', extensions: ['typings'] },
    { icon: 'vscode', extensions: ['vscode'] }
  ],
  parse: function () {
    var s = this.replace(/\./g, '_');
    if ((/^\d/).test(s)) return 'n' + s;
    return s;
  }
};

/* eslint-disable max-len */
exports.extensions = {
  supported: [
    { icon: 'dist', extensions: ['dist', 'out', 'export'] },
    { icon: 'git', extensions: ['git', 'github'] },
    { icon: 'node', extensions: ['node_modules'] },
    { icon: 'meteor', extensions: ['meteor'] },
    { icon: 'src', extensions: ['src'] },
    { icon: 'test', extensions: ['tests', 'test', '__tests__', '__test__'] },
    { icon: 'typings', extensions: ['typings'] },
    { icon: 'vscode', extensions: ['vscode'] }
  ],
  parse: function () {
    var s = this.replace(/\./g, '_');
    if ((/^\d/).test(s)) return 'n' + s;
    return s;
  }
};

Use the dist folder icon for "export"


function initProgressMarkers() {
  var $formPage = $('.FormPage');
  var $progressMarkers = $('.Answer').map(function() {
    var $progressMarker = $("
");
    $progressMarker.addClass( $(this).hasClass('Answered') ? 'Answered' : 'Unanswered');
    $progressMarker.css('top', $(this).position().top);
    $formPage.append($progressMarker);
    return $progressMarker;
  });
  for (var i=1; i<$progressMarkers.length; i++) {
    var topMarkerY = $progressMarkers[i-1].position().top;
    var bottomMarkerY = $progressMarkers[i].position().top;
    var length = bottomMarkerY - topMarkerY;
    var isConnected = $progressMarkers[i-1].hasClass('Answered') &&
                      $progressMarkers[i].hasClass('Answered');

    var $progressLine = $("
|
");
    $progressLine.addClass( isConnected ? 'Connected' : 'Disconnected');
    $progressLine.css('top', topMarkerY);
    $progressLine.css('height', length);
    $formPage.append($progressLine);
    console.log(isConnected, length);
  }
}

function initProgressMarkers() {
  $('.ProgressMarker').remove();
  $('.ProgressLine').remove();

  var $formPage = $('.FormPage');
  var $progressMarkers = $('.Answer').map(function() {
    var $progressMarker = $("
");
    $progressMarker.addClass( $(this).hasClass('Answered') ? 'Answered' : 'Unanswered');
    $progressMarker.css('top', $(this).position().top);
    $formPage.append($progressMarker);
    return $progressMarker;
  });
  for (var i=1; i<$progressMarkers.length; i++) {
    var topMarkerY = $progressMarkers[i-1].position().top;
    var bottomMarkerY = $progressMarkers[i].position().top;
    var length = bottomMarkerY - topMarkerY;
    var isConnected = $progressMarkers[i-1].hasClass('Answered') &&
                      $progressMarkers[i].hasClass('Answered');

    var $progressLine = $("
|
");
    $progressLine.addClass( isConnected ? 'Connected' : 'Disconnected');
    $progressLine.css('top', topMarkerY);
    $progressLine.css('height', length);
    $formPage.append($progressLine);
    console.log(isConnected, length);
  }
}

Remove old progress stuff before creating new.


module.exports = {
  'root': true,

  'parser': 'babel-eslint',

  'extends': [
    'airbnb',
    'plugin:import/errors'
  ],

  'rules': {
    'space-before-function-paren': 0,
    'comma-dangle': [2, 'never'],
    'one-var': 0,
    'one-var-declaration-per-line': 0,
    'prefer-arrow-callback': 0,
    'strict': 0,
    'no-use-before-define': [2, {'functions': false}],
    'no-underscore-dangle': 0,

    'react/wrap-multilines': 0,
    'react/prefer-stateless-function': 0,
    'react/jsx-first-prop-new-line': 0,
    'react/jsx-no-bind': 0
  }
}

module.exports = {
  'root': true,

  'parser': 'babel-eslint',

  'extends': [
    'airbnb',
    'plugin:import/errors'
  ],

  'rules': {
    'space-before-function-paren': 0,
    'comma-dangle': [2, 'never'],
    'one-var': 0,
    'one-var-declaration-per-line': 0,
    'prefer-arrow-callback': 0,
    'strict': 0,
    'no-use-before-define': [2, {'functions': false}],
    'no-underscore-dangle': 0,

    'react/wrap-multilines': 0,
    'react/prefer-stateless-function': 0,
    'react/jsx-first-prop-new-line': 0,
    'react/jsx-no-bind': 0,
    'react/sort-comp': [2, {
      order: [
        'displayName',
        'propTypes',
        'mixins',
        'statics',
        'getDefaultProps',
        'getInitialState',
        'constructor',
        'render',
        '/^_render.+$/', // any auxiliary _render methods
        'componentWillMount',
        'componentDidMount',
        'componentWillReceiveProps',
        'shouldComponentUpdate',
        'componentWillUpdate',
        'componentDidUpdate',
        'componentWillUnmount',
        '/^on[A-Z].+$/', // event handlers
        'everything-else',
        '/^_.+$/' // private methods
      ]
    }]
  }
}

Update lint rule for component methods order


'use strict'

const { env, platform } = process

module.exports = {
  extends: [
    'eslint:recommended',
    'eslint-config-airbnb-base',
    'plugin:prettier/recommended',
  ],

  env: {
    jest: true,
  },

  parserOptions: {
    sourceType: 'script',
  },

  rules: {
    // overwrite airbnb-base options
    'no-underscore-dangle': 'off',
    // until we switch to ES6 modules (which use 'strict mode' implicitly)
    strict: ['error', 'global'],

    // TODO FIXME workaround for git + prettier line ending issue on Travis for Windows OS:
    ...(env.TRAVIS &&
      platform === 'win32' && {
        'prettier/prettier': ['error', { endOfLine: 'auto' }],
      }),

    // TODO FIXME turn off temporary, to make eslint pass
    'class-methods-use-this': 'off',
    'consistent-return': 'off',
    'no-console': 'off',
    'no-restricted-syntax': 'off',
    'no-shadow': 'off',
    'no-use-before-define': 'off',
  },
}

'use strict'

const { platform } = require('os')

const { env } = process

module.exports = {
  extends: [
    'eslint:recommended',
    'eslint-config-airbnb-base',
    'plugin:prettier/recommended',
  ],

  env: {
    jest: true,
  },

  parserOptions: {
    sourceType: 'script',
  },

  rules: {
    // overwrite airbnb-base options
    'no-underscore-dangle': 'off',
    // until we switch to ES6 modules (which use 'strict mode' implicitly)
    strict: ['error', 'global'],

    // TODO FIXME workaround for git + prettier line ending issue on Travis for Windows OS:
    ...(env.TRAVIS &&
      platform() === 'win32' && {
        'prettier/prettier': ['error', { endOfLine: 'auto' }],
      }),

    // TODO FIXME turn off temporary, to make eslint pass
    'class-methods-use-this': 'off',
    'consistent-return': 'off',
    'no-console': 'off',
    'no-restricted-syntax': 'off',
    'no-shadow': 'off',
    'no-use-before-define': 'off',
  },
}

Use platform from 'os' module


var http = require('http');
var util = require('./util');
var counter = 0;
var port = 1337;

var greeting = function (req, res) {
    var answer = util.helloWorld();
    counter = counter + 1;
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end(answer + '\nYou are user number ' + counter + '.');
    console.log('Current count: ' + counter);
};

http.createServer(greeting).listen(port, '127.0.0.1');
console.log('Server running at http://127.0.0.1:' + port + '/');

var http = require('http');
var util = require('./util');
var fs = require('fs');

var counter;
var tempCounter = 0;
var port = 1337;
var filename = __dirname + '/../counter.txt';

fs.readFile(filename, function (err, data) {
    if (err) {
        console.log('Error reading from ' + filename + ': ' + err);
        counter = 0;
    }
    else {
        counter = parseInt(data);
    }
});

var greeting = function (req, res) {
    if (req.url != '/favicon.ico') {
        var answer = util.helloWorld();
        if (counter !== undefined) {
            console.log(req.url);
            counter = counter + 1 + tempCounter;
            tempCounter = 0;
            res.writeHead(200, {'Content-Type': 'text/plain'});
            res.end(answer + '\nYou are user number ' + counter + '.');
            console.log('Current count: ' + counter);

            fs.writeFile(filename, counter, function (err) {
                if (err) {
                    console.log('Error writing file: ' + err);
                } else {
                    console.log('Writing to file OK.');
                }
            });
        } else {
            tempCounter = tempCounter + 1;
            res.writeHead(200, {'Content-Type': 'text/plain'});
            res.end(answer + '\n');
        }
    }
};

var server = http.createServer(greeting).listen(port, 'localhost');

server.on('close', function() {
    console.log('server closing');
});

process.on('SIGTERM', function () {
    server.close(function () {
        console.log('bye bye');
        process.exit(0);
    });
});

console.log('Server running at localhost:' + port + '/');

FIX Use localhost instead of IP


module.exports = {
  normalizeEntityName: function() {},

  afterInstall: function(options) {
    // We assume that handlebars, ember, and jquery already exist
    return this.addBowerPackagesToProject([
      {
        // Antiscroll seems to be abandoned by its original authors. We need
        // two things: (1) a version in package.json, and (2) the name of the
        // package must be "antiscroll" to satisfy ember-cli.
        'name': 'git://github.com/azirbel/antiscroll.git#90391fb371c7be769bc32e7287c5271981428356'
      },
      {
        'name': 'jquery-mousewheel',
        'target': '~3.1.4'
      },
      {
        'name': 'jquery-ui',
        'target': '~1.11.4'
      }
    ]);
  }
};

module.exports = {
  normalizeEntityName: function() {},

  afterInstall: function(options) {
    // We assume that handlebars, ember, and jquery already exist
    return this.addBowerPackagesToProject([
      {
        // Antiscroll seems to be abandoned by its original authors. We need
        // two things: (1) a version in package.json, and (2) the name of the
        // package must be "antiscroll" to satisfy ember-cli.
        'name': 'git://github.com/Addepar/antiscroll.git#e0d1538cf4f3fd61c5bedd6168df86d651f125da'
      },
      {
        'name': 'jquery-mousewheel',
        'target': '~3.1.4'
      },
      {
        'name': 'jquery-ui',
        'target': '~1.11.4'
      }
    ]);
  }
};

Use correct version of antiscroll in blueprint


import ActiveModelAdapter from 'active-model-adapter';
import ActiveModelSerializer from 'active-model-adapter/active-model-serializer';

export default {
  name: 'active-model-adapter',
  initialize: function(applicationOrRegistry) {
    var register;
    if (applicationOrRegistry.register) {
      // initializeStoreService was called by an initializer instead of
      // an instanceInitializer. The first argument is a registry for
      // Ember pre 1.12, or an application instance for Ember >2.1.
      register = applicationOrRegistry.register.bind(applicationOrRegistry);
    } else {
      // initializeStoreService was registered with an
      // instanceInitializer. The first argument is the application
      // instance.
      register = applicationOrRegistry.registry.register.bind(applicationOrRegistry);
    }

    register('adapter:-active-model', ActiveModelAdapter);
    register('serializer:-active-model', ActiveModelSerializer);
  }
};

import ActiveModelAdapter from 'active-model-adapter';
import ActiveModelSerializer from 'active-model-adapter/active-model-serializer';

export default {
  name: 'active-model-adapter',
  initialize: function(applicationOrRegistry) {
    var register;
    if (applicationOrRegistry.register) {
      // initializeStoreService was called by an initializer instead of
      // an instanceInitializer. The first argument is a registry for
      // Ember pre 1.12, or an application instance for Ember >2.1.
      register = applicationOrRegistry.register;
    } else {
      // initializeStoreService was registered with an
      // instanceInitializer. The first argument is the application
      // instance.
      register = applicationOrRegistry.registry.register;
    }

    register.call(applicationOrRegistry, 'adapter:-active-model', ActiveModelAdapter);
    register.call(applicationOrRegistry, 'serializer:-active-model', ActiveModelSerializer);
  }
};

Use call instead of bind to prevent phantomjs failures


// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file.
//
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require bootstrap-sprockets
//= require 'moment'
//= require bootstrap-datetimepicker
//= require_tree .

$('.container').on('click', 'tr.add-meal button.close', function() {
    $(this).closest('tr.add-meal').prev('tr.hidden').andSelf().toggleClass('hidden');
});

$('.js-filter-form :reset').click(function(e){
    e.preventDefault();

    this.form.reset();
    $(this.form).find(':submit').click();
});

// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file.
//
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require bootstrap-sprockets
//= require 'moment'
//= require bootstrap-datetimepicker
//= require_tree .

$('.container').on('click', 'tr.add-meal button.close', function() {
    $(this).closest('tr.add-meal').prev('tr.hidden').andSelf().toggleClass('hidden');
});

$('.container').on('click', '.js-filter-form :reset', function(e) {
    e.preventDefault();

    this.form.reset();
    $(this.form).find(':submit').click();
});

Fix lost grid reload callback after ajax grid reload.


var config;

config = require('../../lib/protractor.conf.js').config;

config.capabilities = {
  browserName: 'chrome'
};

config.framework = 'mocha';

config.specs = [
  '../generalStepDefs/support/initGlobals.js',
  '../**/*.spec.js'
];

config.allScriptsTimeout = 20000;

exports.config = config;

var config;

config = require('../../lib/protractor.conf.js').config;

config.capabilities = {
  browserName: 'firefox'
};

config.framework = 'mocha';

config.specs = [
  '../generalStepDefs/support/initGlobals.js',
  '../**/*.spec.js'
];

config.allScriptsTimeout = 20000;

exports.config = config;

Change test browser back to firefox


var fs = require('fs'),
	path = require('path'),
	traceurPath = require.resolve('traceur'),
	makeNode = require("../node/make_node"),
	minify = require("../graph/minify");

module.exports = function(bundle){
	var lastTraceur = traceurPath.indexOf("traceur/");
	var baseTraceur = traceurPath.substr(0, lastTraceur);

	// We have to product Steal's system.

	var traceurRuntime = fs.readFileSync( path.join(baseTraceur,"traceur/bin/traceur-runtime.js") );

	var node = makeNode("[traceur-runtime]", "this._System = this.System;\n"+traceurRuntime+";\nthis.System = this._System; delete this._System");
	minify([node]);
	bundle.nodes.unshift( node );
};

var fs = require('fs'),
	path = require('path'),
	traceurPath = require.resolve('traceur'),
	makeNode = require("../node/make_node"),
	minify = require("../graph/minify");

module.exports = function(bundle){
	var lastTraceur = traceurPath.indexOf("traceur" + path.sep);
	var baseTraceur = traceurPath.substr(0, lastTraceur);

	// We have to product Steal's system.

	var traceurRuntime = fs.readFileSync( path.join(baseTraceur,"traceur/bin/traceur-runtime.js") );

	var node = makeNode("[traceur-runtime]", "this._System = this.System;\n"+traceurRuntime+";\nthis.System = this._System; delete this._System");
	minify([node]);
	bundle.nodes.unshift( node );
};

Fix resloving traceurRuntime path on windows


(function ($) {

    var init = function ($element, options) {
        $element.select2(options);
    };

    var initHeavy = function ($element, options) {
        var settings = $.extend({
            ajax: {
                data: function (params) {
                    return {
                        term: params.term,
                        page: params.page,
                        field_id: $element.data('field_id')
                    };
                },
                processResults: function (data, page) {
                    return {
                        results: data.results,
                        pagination: {
                            more: data.more
                        }
                    };
                }
            }
        }, options);

        $element.select2(settings);
    };

    $.fn.djangoSelect2 = function (options) {
        var settings = $.extend({}, options);
        $.each(this, function (i, element) {
            var $element = $(element);
            if ($element.hasClass('django-select2-heavy')) {
                initHeavy($element, settings);
            } else {
                init($element, settings);
            }
        });
        return this;
    };

    $(function () {
        $('.django-select2').djangoSelect2({
            dropdownAutoWidth: true
        });
    });

}(this.jQuery));

(function ($) {

    var init = function ($element, options) {
        $element.select2(options);
    };

    var initHeavy = function ($element, options) {
        var settings = $.extend({
            ajax: {
                data: function (params) {
                    return {
                        term: params.term,
                        page: params.page,
                        field_id: $element.data('field_id')
                    };
                },
                processResults: function (data, page) {
                    return {
                        results: data.results,
                        pagination: {
                            more: data.more
                        }
                    };
                }
            }
        }, options);

        $element.select2(settings);
    };

    $.fn.djangoSelect2 = function (options) {
        var settings = $.extend({}, options);
        $.each(this, function (i, element) {
            var $element = $(element);
            if ($element.hasClass('django-select2-heavy')) {
                initHeavy($element, settings);
            } else {
                init($element, settings);
            }
        });
        return this;
    };

    $(function () {
        $('.django-select2:not([id*=__prefix__])').djangoSelect2({
            dropdownAutoWidth: true
        });
    });

    if ('django' in window && 'jQuery' in window.django)
        django.jQuery(document).on('formset:added', function(event, $row) {
            $row.find('.django-select2').each(function () {
                $(this).djangoSelect2({
                    dropdownAutoWidth: true
                })
            });
        });

}(jQuery));

Make select2 widgets work with Django admin's inline addition


import JSZip from "jszip";
import musicMetadataBrowser from "music-metadata-browser";
import WebampLazy from "./webampLazy";

class Winamp extends WebampLazy {
  constructor(options) {
    super({
      ...options,
      requireJSZip: () => JSZip,
      requireMusicMetadata: () => musicMetadataBrowser
    });
  }
}

export default Winamp;

import JSZip from "jszip";
import * as musicMetadataBrowser from "music-metadata-browser";
import WebampLazy from "./webampLazy";

class Winamp extends WebampLazy {
  constructor(options) {
    super({
      ...options,
      requireJSZip: () => JSZip,
      requireMusicMetadata: () => musicMetadataBrowser
    });
  }
}

export default Winamp;

Fix bad import of music-metadata-browser


'use strict';

const apiEngine = require('../index');

// eslint-disable-next-line fp/no-mutation
Error.stackTraceLimit = 100;

const startServer = async function () {
  try {
    const { options, servers, exit } = await apiEngine.run();
    return { options, servers, exit };
  } catch (error) {
    // eslint-disable-next-line no-console, no-restricted-globals
    console.log(error);
  }
};

startServer();

'use strict';

const apiEngine = require('../index');

// eslint-disable-next-line fp/no-mutation
Error.stackTraceLimit = 100;

const startServer = async function () {
  try {
    await apiEngine.compile();
    const { options, servers, exit } = await apiEngine.run();
    return { options, servers, exit };
  } catch (error) {
    // eslint-disable-next-line no-console, no-restricted-globals
    console.log(error);
  }
};

startServer();

Add compile-step to debug flow


// Load modules

var Fs = require('fs');
var Path = require('path');
var HeapDump = require('heapdump');
var Hoek = require('hoek');

// Declare internals

var internals = {
    initialized: false,
    defaults: {
        logPath: Path.join(__dirname, '..', 'poop.log')
    }
};

exports.register = function (plugin, options, next) {

    if (internals.initialized) {
        return next();
    }

    internals.initialized = true;

    var settings = Hoek.applyToDefaults(internals.defaults, options || {});

    process.once('uncaughtException', function (err) {

        HeapDump.writeSnapshot();

        var log = Fs.createWriteStream(settings.logPath);
        var formattedErr = {
            message: err.message,
            stack: err.stack,
            timestamp: Date.now()
        };

        log.write(JSON.stringify(formattedErr), function () {

            log.end();
            process.exit(1);
        });
    });

    process.on('SIGUSR1', function () {

        HeapDump.writeSnapshot();
    });

    return next();
};

exports.register.attributes = {
    pkg: require('../package.json')
};

Prepare for Hapi 6 migration.


var targetPages = ["https://www.youtube.com/watch*"];
var settings = {};

function openOriginal(info, tab) {
  browser.tabs.create({
    url: info.linkUrl
  });
}

function restoreSettings() {
  function setSettings(data) {
    settings = data;
  }

  var getting = browser.storage.local.get();
  getting.then(setSettings);
}

function openInMpv(requestDetails) {
  function closeTab(data) {
    if (!data.active) {
      browser.tabs.remove(data.id);
    }
  }

  if (requestDetails.type === "main_frame" && requestDetails.originUrl != undefined) {
    var command = `${requestDetails.url} --force-window=immediate ${settings.args}`;

    browser.runtime.sendNativeMessage("mpv", command);

    var querying = browser.tabs.get(requestDetails.tabId);
    querying.then(closeTab);

    return { cancel: true };
  }
}

chrome.contextMenus.create({
  id: "open_original",
  title: "Open without MPV",
  onclick: openOriginal,
  contexts: ["link"]
});

browser.storage.onChanged.addListener(restoreSettings);
browser.webRequest.onBeforeRequest.addListener(openInMpv, { urls: targetPages }, ["blocking"]);

restoreSettings();

var targetPages = [
  "*://*.youtube.com/watch*",
  "*://*.vimeo.com/*",
  "*://*.streamable.com/*",
  "*://*.liveleak.com/view*",
  "*://*.vid.me/*",
  "*://*.funnyordie.com/videos/*",
  "*://*.dailymotion.com/video/*"
];

var settings = {};

function openOriginal(info, tab) {
  browser.tabs.create({
    url: info.linkUrl
  });
}

function restoreSettings() {
  function setSettings(data) {
    settings = data;
  }

  var getting = browser.storage.local.get();
  getting.then(setSettings);
}

function openInMpv(requestDetails) {
  function closeTab(data) {
    if (!data.active) {
      browser.tabs.remove(data.id);
    }
  }

  if (requestDetails.type === "main_frame" && requestDetails.originUrl != undefined) {
    var command = `${requestDetails.url} --force-window=immediate ${settings.args}`;

    browser.runtime.sendNativeMessage("mpv", command);

    var querying = browser.tabs.get(requestDetails.tabId);
    querying.then(closeTab);

    return { cancel: true };
  }
}

chrome.contextMenus.create({
  id: "open_original",
  title: "Open without MPV",
  onclick: openOriginal,
  contexts: ["link"]
});

browser.storage.onChanged.addListener(restoreSettings);
browser.webRequest.onBeforeRequest.addListener(openInMpv, { urls: targetPages }, ["blocking"]);

restoreSettings();

Add some other famous streaming websites


/**
 * Copyright (c) 2015-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @providesModule ViewContext
 * @flow
 * @format
 */
'use strict';

const PropTypes = require('prop-types');

export type ViewChildContext = {|
  +isInAParentText: boolean,
|};

export const ViewContextTypes = {
  isInAParentText: PropTypes.bool,
};

/**
 * Copyright (c) 2015-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @providesModule ViewContext
 * @flow
 * @format
 */
'use strict';

const PropTypes = require('prop-types');

export type ViewChildContext = {|
  +isInAParentText: boolean,
|};

module.exports = {
  ViewContextTypes: {
    isInAParentText: PropTypes.bool,
  },
};

Change ES6 module syntax to module.exports


var gulp = require('gulp');
var babel = require('gulp-babel');
var jsdoc = require("gulp-jsdoc");

gulp.task("babel", function() {
  return gulp.src('src/**/*.js')
    .pipe(babel())
    .pipe(gulp.dest('lib'));
});

gulp.task("watch", function() {
  gulp.watch('src/**/*.js', ["babel"]);
});

gulp.task("default", ["babel"]);

var gulp = require('gulp');
var babel = require('gulp-babel');

gulp.task("babel", function() {
  return gulp.src('src/**/*.js')
    .pipe(babel())
    .pipe(gulp.dest('lib'));
});

gulp.task("watch", function() {
  gulp.watch('src/**/*.js', ["babel"]);
});

gulp.task("default", ["babel"]);

Remove accidental left in line


'use strict'

const paths = require('./config/paths.json')
const gulp = require('gulp')
const cssnano = require('gulp-cssnano')
const del = require('del')
const rename = require('gulp-rename')
const sass = require('gulp-sass')

// Clean task ----------------------------
// Deletes the /public directory
// ---------------------------------------

gulp.task('clean', () => {
  return del(paths.public)
})

// Styles build task ---------------------
// Compiles CSS from Sass
// Output both a minified and non-minified version into /public/stylesheets/
// ---------------------------------------

gulp.task('styles', () => {
  return gulp.src(paths.assetsScss + '**/*.scss')
    .pipe(sass({
      includePaths: [
        'node_modules/govuk_frontend_toolkit/stylesheets'
      ]
    }).on('error', sass.logError))
    .pipe(gulp.dest(paths.publicCss))
    .pipe(rename({ suffix: '.min' }))
    .pipe(cssnano())
    .pipe(gulp.dest(paths.publicCss))
})

// Images build task ---------------------
// Copies images to /public/images
// ---------------------------------------

gulp.task('images', () => {
  return gulp.src(paths.assetsImg + '**/*')
    .pipe(gulp.dest(paths.publicImg))
})

// Scripts build task ---------------------
// Copies JavaScript to /public/javascripts
// ---------------------------------------
gulp.task('scripts', () => {
  return gulp.src(paths.assetsJs + '**/*.js')
    .pipe(gulp.dest(paths.publicJs))
})

Add a Scripts build task


/*jslint onevar: false, eqeqeq: false*/
/*global require*/
if (typeof require != "undefined") {
    var sinon = require("sinon");

    var buster = {
        assert: require("buster-assert"),
        testRunner: require("buster-test").testRunner
    };

    try {
        buster.test = require("buster-test/util");
    } catch (e) {}
}

if (buster.test && buster.test.stackFilters) {
    buster.test.stackFilters.push("lib/sinon");
}

buster.testRunner.onCreate = function (runner) {
    runner.on("test:setUp", function (test) {
        var config = sinon.getConfig(sinon.config);
        config.useFakeServer = false;
        var sandbox = sinon.sandbox.create();
        sandbox.inject(test.testCase);

        test.testCase.useFakeTimers = function () {
            return sandbox.useFakeTimers.apply(sandbox, arguments);
        };

        test.testCase.sandbox = sandbox;
    });

    runner.on("test:tearDown", function (test) {
        test.testCase.sandbox.verifyAndRestore();
    });
};

for (var prop in sinon.assert) {
    if (typeof sinon.assert[prop] == "function" && prop != "fail" && prop != "pass") {
        buster.assert[prop] = sinon.assert[prop];
    }
}

sinon.assert.fail = buster.assert.fail;
sinon.assert.pass = buster.assert.pass;

/*jslint onevar: false, eqeqeq: false*/
/*global require*/
if (typeof require != "undefined") {
    var sinon = require("sinon");

    var buster = {
        assert: require("buster-assert"),
        testRunner: require("buster-test").testRunner
    };

    try {
        buster.test = require("buster-test/util");
    } catch (e) {}
}

if (buster.test && buster.test.stackFilters) {
    buster.test.stackFilters.push("lib/sinon");
}

buster.testRunner.onCreate(function (runner) {
    runner.on("test:setUp", function (test) {
        var config = sinon.getConfig(sinon.config);
        config.useFakeServer = false;
        var sandbox = sinon.sandbox.create();
        sandbox.inject(test.testCase);

        test.testCase.useFakeTimers = function () {
            return sandbox.useFakeTimers.apply(sandbox, arguments);
        };

        test.testCase.sandbox = sandbox;
    });

    runner.on("test:tearDown", function (test) {
        test.testCase.sandbox.verifyAndRestore();
    });
});

for (var prop in sinon.assert) {
    if (typeof sinon.assert[prop] == "function" && prop != "fail" && prop != "pass") {
        buster.assert[prop] = sinon.assert[prop];
    }
}

sinon.assert.fail = buster.assert.fail;
sinon.assert.pass = buster.assert.pass;

Use new onCreate callback for buster.testRunner


$(document).ready(function() {
  var editor = new Quill('#editor', {
    modules: {
      'toolbar': { container: '#toolbar' },
      'image-tooltip': true,
      'link-tooltip': true
    },
    theme: 'snow'
  });

  $('.quill-wrapper').tooltip({ trigger: 'manual' });
  var tooltipTimer = setTimeout(function() {
    $('.quill-wrapper').tooltip('show');
  }, 2500);

  editor.once('selection-change', function(hasFocus) {
    $('#editor').toggleClass('focus', hasFocus);
    // Hack for inability to scroll on mobile
    if (/mobile/i.test(navigator.userAgent)) {
      $('#editor').css('height', editor.root.scrollHeight + 30)   // 30 for padding
    }
    $('.quill-wrapper').tooltip('destroy');
    clearTimeout(tooltipTimer);
  });
});

var quill;   // Expose as global so people can easily try out the API

$(document).ready(function() {
  quill = new Quill('#editor', {
    modules: {
      'toolbar': { container: '#toolbar' },
      'image-tooltip': true,
      'link-tooltip': true
    },
    theme: 'snow'
  });

  $('.quill-wrapper').tooltip({ trigger: 'manual' });
  var tooltipTimer = setTimeout(function() {
    $('.quill-wrapper').tooltip('show');
  }, 2500);

  quill.once('selection-change', function(hasFocus) {
    $('#editor').toggleClass('focus', hasFocus);
    // Hack for inability to scroll on mobile
    if (/mobile/i.test(navigator.userAgent)) {
      $('#editor').css('height', quill.root.scrollHeight + 30)   // 30 for padding
    }
    $('.quill-wrapper').tooltip('destroy');
    clearTimeout(tooltipTimer);
  });
});

Make quill editor variable global


const wd = require('wd');
const Promise = require('bluebird');
const browser = require('./browser');
const normalise = require('./normalise-logs');

function test (opts) {
  return function () {
    return Promise.using(browser(opts.browser), (session) => {
      return session
        .get(opts.url)
        .then(() => {
          return Promise.resolve()
            .then(() => {
              if (opts.scroll || opts.reporter === 'fps') {
                return session.execute('function f () { window.scrollBy(0,5); window.requestAnimationFrame(f); } window.requestAnimationFrame(f);');
              }
            })
            .then(() => {
              if (typeof opts.inject === 'function') {
                return opts.inject(session);
              }
            })
            .then(() => {
              return session.waitFor(wd.asserters.jsCondition(`document.readyState==='complete'`), 120000)
                .catch(() => {
                  console.error('Page loading timed out after 120s');
                });
            })
            .then(() => {
              return session;
            });
        })
        .sleep(opts.sleep)
        .log('performance')
        .then((logs) => {
          return normalise(logs, opts.url);
        });
    });
  };
}

module.exports = test;

const wd = require('wd');
const Promise = require('bluebird');
const browser = require('./browser');
const normalise = require('./normalise-logs');

function test (opts) {
  return function () {
    return Promise.using(browser(opts.browser), (session) => {
      return session
        .setWindowSize(1024, 768)
        .get(opts.url)
        .then(() => {
          return Promise.resolve()
            .then(() => {
              if (opts.scroll || opts.reporter === 'fps') {
                return session.execute('function f () { window.scrollBy(0,5); window.requestAnimationFrame(f); } window.requestAnimationFrame(f);');
              }
            })
            .then(() => {
              if (typeof opts.inject === 'function') {
                return opts.inject(session);
              }
            })
            .then(() => {
              return session.waitFor(wd.asserters.jsCondition(`document.readyState==='complete'`), 120000)
                .catch(() => {
                  console.error('Page loading timed out after 120s');
                });
            })
            .then(() => {
              return session;
            });
        })
        .sleep(opts.sleep)
        .log('performance')
        .then((logs) => {
          return normalise(logs, opts.url);
        });
    });
  };
}

module.exports = test;

Fix window size before running tests


/**
*
* SearchInput
*
*/

import React from 'react';
import FontAwesome from 'react-fontawesome';
import { Button, Form, FormGroup, FormControl } from 'react-bootstrap';
import { withClientState } from '../../components/Apollo/LocalStateDecorator'

class SearchInput extends React.Component { // eslint-disable-line react/prefer-stateless-function

  constructor(props) {
    super(props);
    this.onSearchChange = this.onSearchChange.bind(this);
    this.onSubmit = this.onSubmit.bind(this);
  }

  onSearchChange(e) {
    this.query = e.target.value;
  }

  onSubmit(e) {
    e.preventDefault();
    this.props.updateClientState({
      searchQuery: this.query || this.props.clientState.searchQuery
    })
  }

  render() {
    return (
        <Form inline onSubmit={this.onSubmit} className="searchInput">
          <FormGroup controlId="formInlineEmail">
            <FormControl
              style={{ width: '80%' }}
              type="text"
              defaultValue={this.props.clientState && this.props.clientState.searchQuery}
              placeholder={(this.props.clientState && this.props.clientState.searchQuery) || 'search...'}
              onChange={this.onSearchChange}
            />
            <Button type="submit">
              <FontAwesome name="search" />
            </Button>
          </FormGroup>
        </Form>
      )
  }
}

export default withClientState(SearchInput);

/**
*
* SearchInput
*
*/

import React from 'react';
import FontAwesome from 'react-fontawesome';
import { Button, Form, FormGroup, FormControl } from 'react-bootstrap';
import { withClientState } from '../../components/Apollo/LocalStateDecorator'

class SearchInput extends React.Component { // eslint-disable-line react/prefer-stateless-function

  constructor(props) {
    super(props);
    this.onSearchChange = this.onSearchChange.bind(this);
    this.onSubmit = this.onSubmit.bind(this);
  }

  onSearchChange(e) {
    this.query = e.target.value;
  }

  onSubmit(e) {
    e.preventDefault();
    let searchQuery = this.query
    if (searchQuery == null && searchQuery == undefined) {
      searchQuery = this.props.clientState.searchQuery
    }
    this.props.updateClientState({ searchQuery })
  }

  render() {
    return (
        <Form inline onSubmit={this.onSubmit} className="searchInput">
          <FormGroup controlId="formInlineEmail">
            <FormControl
              style={{ width: '80%' }}
              type="text"
              defaultValue={this.props.clientState && this.props.clientState.searchQuery}
              placeholder={(this.props.clientState && this.props.clientState.searchQuery) || 'search...'}
              onChange={this.onSearchChange}
            />
            <Button type="submit">
              <FontAwesome name="search" />
            </Button>
          </FormGroup>
        </Form>
      )
  }
}

export default withClientState(SearchInput);

Enable deleting the search query from the box to clear it