import Service from '@ember/service';
import { computed, get, getProperties, set } from '@ember/object';
import { getOwner } from '@ember/application';
import { isBlank } from '@ember/utils';
import isMobile from 'ismobilejs';

export default Service.extend({

  fastboot: computed(function() {
    return getOwner(this).lookup('service:fastboot');
  }),

  init() {
    this._super(...arguments);

    let queries = [];
    if (get(this, 'fastboot.isFastBoot')) {
      const headers = get(this, 'fastboot.request.headers');
      let userAgent = get(headers, 'headers.user-agent')[0];

      if (isBlank(userAgent)) { return; }

      queries = getProperties(
        isMobile(userAgent),
        ['any', 'phone', 'tablet', 'apple', 'android', 'amazon', 'windows', 'seven_inch', 'other']
      );
    } else {
      queries = isMobile;
    }

    for (let media in queries) {
      set(this, media, queries[media]);
    }
  }
});

import Service from '@ember/service';
import { computed, get, set } from '@ember/object';
import { getOwner } from '@ember/application';
import { isBlank } from '@ember/utils';
import isMobile from 'ismobilejs';

/**
 * The attributes returned by isMobile are accessible. However, they should be
 * accessed using the `get` helper, since they may be undefined if the user
 * agent header is blank.
 */
export default Service.extend({
  fastboot: computed(function() {
    return getOwner(this).lookup('service:fastboot');
  }),

  init() {
    this._super(...arguments);
    let queries = [];
    if (get(this, 'fastboot.isFastBoot')) {
      const headers = get(this, 'fastboot.request.headers');
      let userAgent = get(headers, 'headers.user-agent');

      // isMobile tries to fetch `navigator` if we give it a blank user agent.
      if (isBlank(userAgent)) { return; }

      userAgent = userAgent[0];

      // Call with the current context to avoid leaking the node global space!
      queries = isMobile.call(this, userAgent);
    } else {
      queries = isMobile;
    }

    for (let media in queries) {
      set(this, media, queries[media]);
    }
  }
});

Fix edge-case with blank user agent in FastBoot


/*
 * Implement some helpers methods for interacting with the DOM,
 * be it Fastboot's SimpleDOM or a browser's version.
 */

export function getActiveElement() {
  if (typeof document === 'undefined') {
    return null;
  } else {
    return document.activeElement;
  }
}

function childNodesOfElement(element) {
  let children = [];
  let child = element.firstChild;
  while (child) {
    children.push(child);
    child = child.nextSibling;
  }
  return children;
}

export function findElementById(doc, id) {
  let nodes = childNodesOfElement(doc);
  let node;

  while (nodes.length) {
    node = nodes.shift();

    if (node.getAttribute && node.getAttribute('id') === id) {
      return node;
    }

    nodes = childNodesOfElement(node).concat(nodes);
  }
}

/*
 * Implement some helpers methods for interacting with the DOM,
 * be it Fastboot's SimpleDOM or a browser's version.
 */

export function getActiveElement() {
  if (typeof document === 'undefined') {
    return null;
  } else {
    return document.activeElement;
  }
}

function childNodesOfElement(element) {
  let children = [];
  let child = element.firstChild;
  while (child) {
    children.push(child);
    child = child.nextSibling;
  }
  return children;
}

export function findElementById(doc, id) {
  if (doc.getElementById) {
    return doc.getElementById(id);
  }

  let nodes = childNodesOfElement(doc);
  let node;

  while (nodes.length) {
    node = nodes.shift();

    if (node.getAttribute && node.getAttribute('id') === id) {
      return node;
    }

    nodes = childNodesOfElement(node).concat(nodes);
  }
}

Use native doc.getElementById when available


import React, {Component, PropTypes} from 'react';
import ReactDOM from 'react-dom';
import {component as ConfirmationModal} from '../../application/confirmation-popin';
import Connect from '../../behaviours/store/connect';
import {application} from 'focus-core';
const {builtInStore: applicationStore} = application;
const propTypes = {
    isVisible: PropTypes.bool,
    ConfirmContentComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
    handleCancel: PropTypes.func.isRequired,
    handleConfirm: PropTypes.func.isRequired
};

const defaultProps = {
    isVisible: false,
    ConfirmContentComponent: null
};

@Connect(
    [{store: applicationStore, properties: ['confirmConfig']}],
    () => {
        const {isVisible = false, Content: ConfirmContentComponent = null, handleCancel, handleConfirm} = applicationStore.getConfirmConfig() || {};

        const exp = {isVisible, ConfirmContentComponent, handleCancel, handleConfirm};
        return exp;
    }
)
class ConfirmWrapper extends Component {
    render() {
        console.log('confirm wrapper')
        const {isVisible, ConfirmContentComponent, handleCancel, handleConfirm} = this.props;
        return isVisible ? <ConfirmationModal open={true} handleCancel={handleCancel} handleConfirm={handleConfirm}>{ConfirmContentComponent ? <ConfirmContentComponent /> :
Hello
}</ConfirmationModal> : null;
    }
}

ConfirmWrapper.propTypes = propTypes;
ConfirmWrapper.defaultProps = defaultProps;
ConfirmWrapper.displayName = 'ConfirmWrapper';

export default ConfirmWrapper;

import React, {Component, PropTypes} from 'react';
import ReactDOM from 'react-dom';
import {component as ConfirmationModal} from '../../application/confirmation-popin';
import Connect from '../../behaviours/store/connect';
import {application} from 'focus-core';
const {builtInStore: applicationStore} = application;
const propTypes = {
    isVisible: PropTypes.bool,
    ConfirmContentComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
    handleCancel: PropTypes.func.isRequired,
    handleConfirm: PropTypes.func.isRequired
};

const defaultProps = {
    isVisible: false,
    ConfirmContentComponent: null
};

@Connect(
    [{store: applicationStore, properties: ['confirmConfig']}],
    () => {
        const {isVisible = false, Content: ConfirmContentComponent = null, handleCancel: cancelHandler, handleConfirm: confirmHandler} = applicationStore.getConfirmConfig() || {};
        return {isVisible, ConfirmContentComponent, cancelHandler, confirmHandler};
    }
)
class ConfirmWrapper extends Component {
    render() {
        console.log('confirm wrapper', this.props);
        const {isVisible, ConfirmContentComponent, cancelHandler, confirmHandler} = this.props;
        return isVisible ? <ConfirmationModal open={true} cancelHandler={cancelHandler} confirmHandler={confirmHandler}>{ConfirmContentComponent ? <ConfirmContentComponent /> : null}</ConfirmationModal> : null;
    }
}

ConfirmWrapper.propTypes = propTypes;
ConfirmWrapper.defaultProps = defaultProps;
ConfirmWrapper.displayName = 'ConfirmWrapper';

export default ConfirmWrapper;

Rename handlers to math confirmation popin props.


//= require fingerprint2

(function(global) {
   'use strict';

    // based on jQuery's param implementation https://github.com/jquery/jquery/blob/master/src/serialize.js
    function serialiseComponents(components) {
        var componentsToExclude = ['webgl'];
        var r = [];
        global.jQuery.each(components, function() {
            if(componentsToExclude.indexOf(this.key) == -1) {
                r[r.length] = encodeURIComponent(this.key) + "=" + encodeURIComponent(this.value);
            }
        });
        return r.join("&");
    }

    function reportFingerprint(path, epoch, imageElement) {
        // disabling javascript font parsing, this is very slow, and the canvas because it yields a different fingerprint for the first page loaded
        var options = {excludeJsFonts: true, excludeCanvas: true};
        new Fingerprint2(options).get(function(result, components){
            imageElement.src = path + '?hash='+epoch+'-'+result+'&cache_bust='+(new Date().getTime())+'&'+serialiseComponents(components);
        });
    }

    global.reportFingerprint = reportFingerprint;
})(window);

//= require fingerprint2

(function(global) {
   'use strict';

    // By default Fingerprint2 logs things to the console. This setting disables logging:
    global.NODEBUG = null;

    // based on jQuery's param implementation https://github.com/jquery/jquery/blob/master/src/serialize.js
    function serialiseComponents(components) {
        var componentsToExclude = ['webgl'];
        var r = [];
        global.jQuery.each(components, function() {
            if(componentsToExclude.indexOf(this.key) == -1) {
                r[r.length] = encodeURIComponent(this.key) + "=" + encodeURIComponent(this.value);
            }
        });
        return r.join("&");
    }

    function reportFingerprint(path, epoch, imageElement) {
        // disabling javascript font parsing, this is very slow, and the canvas because it yields a different fingerprint for the first page loaded
        var options = {excludeJsFonts: true, excludeCanvas: true};
        new Fingerprint2(options).get(function(result, components){
            imageElement.src = path + '?hash='+epoch+'-'+result+'&cache_bust='+(new Date().getTime())+'&'+serialiseComponents(components);
        });
    }

    global.reportFingerprint = reportFingerprint;
})(window);

Disable console logging of fingerprint diagnostics


import React from 'react';
import { Link } from 'react-router';
import Cycle from 'cycle-react';
let Rx = Cycle.Rx;

class Testink extends React.Component {
	render() {
		return <div> Cheers { this.props.test } </div>;
	}
}

let Clicker = Cycle.component( 'Counter', ( interactions ) => {
	return interactions.get( 'plus' )
		.startWith( 0 )
		.scan( 0, acc => acc + 1 ) //old Rx style. remove first argument (0) in rx 3+
		.map( i =>
			<button onClick={ interactions.listener( 'plus' ) }> plus one { i } </button>
		);
} );

let Counter = Cycle.component( 'Counter', ( ) => {
	return Rx.Observable.interval( 1000 ).map( i =>
		<Testink test={ i } />
	);
} );

export default class Home extends React.Component {
	render() {
		return (
			<div>
				<div> <Link to="/about"> About </Link> </div>
				<div> <Link to="/map"> Map </Link> </div>
				<Clicker/>
				<Counter/>
			</div>
		);
	}
}

import React from 'react';
import { Link } from 'react-router';
import Cycle from 'cycle-react';
let Rx = Cycle.Rx;

class Testink extends React.Component {
	render() {
		return
 Cheers { this.props.test }
;
	}
}

let Clicker = Cycle.component( 'Counter', ( interactions ) => {
	return Rx.Observable.merge(
		interactions.get( 'plus' )
		.map( () => 1 ),

		interactions.get( 'minus' )
		.map( () => -1 )
	)
	.scan( 0, ( acc, i ) => acc + i )
	.startWith( 0 )
	.map( i => {
		return

 { i }

			<button onClick={ interactions.listener( 'plus' ) }> plus one </button>
			<button onClick={ interactions.listener( 'minus' ) }> minus one </button>

	} );
} );

let Counter = Cycle.component( 'Counter', ( ) => {
	return Rx.Observable.interval( 1000 ).map( i =>
		<Testink test={ i } />
	);
} );

export default class Home extends React.Component {
	render() {
		return (

 <Link to="/about"> About </Link>

 <Link to="/map"> Map </Link>

				<Clicker/>

				<Counter/>

		);
	}
}

Add minus button as well and combine streams


'use strict';

// native runtime support for AWS
// https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html

module.exports = new Set([
  // .net
  // dotnetcore2.1
  // dotnetcore1.0

  // go
  'go1.x',

  // java
  // java8

  // node.js
  'nodejs10.x',
  'nodejs8.10',

  // provided
  'provided',

  // python
  'python2.7',
  'python3.6',
  'python3.7',

  // ruby
  'ruby2.5',
]);

'use strict';

// native runtime support for AWS
// https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html

module.exports = new Set([
  // .net
  // dotnetcore2.1
  // dotnetcore1.0

  // go
  'go1.x',

  // java
  // java8

  // node.js
  'nodejs8.10',
  'nodejs10.x',

  // provided
  'provided',

  // python
  'python2.7',
  'python3.6',
  'python3.7',

  // ruby
  'ruby2.5',
]);

Switch order of supported node versions


"use strict";

module.exports = function(sequelize, DataTypes) {
  var Task = sequelize.define("Task", {
    name:          { type: DataTypes.STRING, allowNull: false },
    task_type:     { type: DataTypes.ENUM('todo', 'exercise', 'meal', 'meeting', 'other'), allowNull: false, defaultValue: 'other' },
    date:          { type: DataTypes.DATE, defaultValue: DataTypes.NOW },
    moveable:      { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: 1 },
    auto_moveable: { type: DataTypes.BOOLEAN, allowNull: true, defaultValue: null }
  }, {
    classMethods: {
      associate: function(models) {
        Task.belongsTo(models.User, {
          onDelete: "CASCADE",
          foreignKey: {
            allowNull: false
          }
        });
      }
    }
  });

  return Task;
}

"use strict";

module.exports = function(sequelize, DataTypes) {
  var Task = sequelize.define("Task", {
    taskID:       { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true },
    name:         { type: DataTypes.STRING, allowNull: false },
    taskType:     { type: DataTypes.ENUM('todo', 'exercise', 'meal', 'meeting', 'other'), allowNull: false, defaultValue: 'other' },
    date:         { type: DataTypes.DATE, defaultValue: DataTypes.NOW },
    moveable:     { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: 1 },
    autoMoveable: { type: DataTypes.BOOLEAN, allowNull: true, defaultValue: null }
  }, {
    classMethods: {
      associate: function(models) {
        Task.belongsTo(models.User, {
          onDelete: "CASCADE",
          foreignKey: {
            allowNull: false
          }
        });
      }
    }
  });

  return Task;
}

Use more consistent attribute naming


import React from "react";
import { connect } from "react-redux";
import { Switch, Route } from "react-router-dom";

import { findJobs } from "../actions";
import JobsList from "./List";
import JobDetail from "./Detail";
import JobsResources from "./Resources";
import Resources from "../../administration/components/Jobs/Resources";
import Tasks from "../../administration/components/Jobs/Tasks";
import { LoadingPlaceholder } from "../../base";

const JobsSettings = () => (

                Job Settings

        <Resources />
        <Tasks />

);

const Jobs = (props) => {
    if (props.settings === null) {
        return <LoadingPlaceholder />;
    }

    return (

            <Switch>
                <Route path="/jobs" component={JobsList} exact />
                <Route path="/jobs/resources" component={JobsResources} />
                <Route path="/jobs/settings" component={JobsSettings} />
                <Route path="/jobs/:jobId" component={JobDetail} />
            </Switch>

    );
};

const mapStateToProps = (state) => ({
    documents: state.jobs.list,
    settings: state.settings.data
});

const mapDispatchToProps = (dispatch) => ({

    onFind: () => {
        dispatch(findJobs());
    }

});

export default connect(mapStateToProps, mapDispatchToProps)(Jobs);

import React from "react";
import { connect } from "react-redux";
import { Switch, Route } from "react-router-dom";
import JobsList from "./List";
import JobDetail from "./Detail";
import JobsResources from "./Resources";
import Resources from "../../administration/components/Jobs/Resources";
import Tasks from "../../administration/components/Jobs/Tasks";
import { LoadingPlaceholder } from "../../base";

const JobsSettings = () => (

                Job Settings

        <Resources />
        <Tasks />

);

const Jobs = (props) => {
    if (props.settings === null) {
        return <LoadingPlaceholder />;
    }

    return (

            <Switch>
                <Route path="/jobs" component={JobsList} exact />
                <Route path="/jobs/resources" component={JobsResources} />
                <Route path="/jobs/settings" component={JobsSettings} />
                <Route path="/jobs/:jobId" component={JobDetail} />
            </Switch>

    );
};

const mapStateToProps = (state) => ({
    settings: state.settings.data
});

export default connect(mapStateToProps, null)(Jobs);

Remove unused statements from jobs


import React from 'react'
import PropTypes from 'prop-types'
import moment from 'moment'
import { translate } from 'react-i18next'

import Dropdown from '../Dropdown'

export class LanguageSelection extends React.PureComponent {
  static propTypes = {
    i18n: PropTypes.shape({
      changeLanguage: PropTypes.func.isRequired
    }).isRequired
  }

  changeLanguage(language) {
    const { i18n } = this.props

    moment.locale(language)
    i18n.changeLanguage(language)
  }

  render() {
    const { i18n } = this.props

    return (
      <Dropdown title={i18n.language === 'en' ? '🇬🇧' : '🇫🇷'}>

 this.changeLanguage('en')}>English 🇬🇧

 this.changeLanguage('fr')}>Français 🇫🇷

      </Dropdown>
    )
  }
}

export default translate('Common')(LanguageSelection)

Use unbreakable spaces in lang dropdown


const assert = require("assert");
const csrf = require("csurf");
const mozlog = require("../logging").mozlog("csrf-middleware");
const { captureRavenException } = require("../ravenclient");
const { simpleResponse } = require("../responses");

const config = require("../config").getProperties();

const useSecureCsrfCookie = (config.expectProtocol && /^https$/.test(config.expectProtocol));

exports.csrfProtection = csrf({cookie: {httpOnly: true, secure: useSecureCsrfCookie}});

exports.csrf = function(req, res, next) {
  // The cookies library doesn't detect duplicates; check manually
  let rawCookies = req.get("cookie") || "";
  let pairs = rawCookies.split(";");
  let csrfTokens = pairs.filter(item => item.match(/_csrf=/));
  if (csrfTokens.length > 1) {
    let exc = new Error("Duplicate CSRF cookies");
    exc.headerValue = rawCookies;
    captureRavenException(exc);
    return simpleResponse(res, "Bad request", 400);
  }
  req.cookies._csrf = req.cookies.get("_csrf"); // csurf expects a property
  next();
};

exports.csrfErrorResponse = function(err, req, res) {
  assert(err.code === "EBADCSRFTOKEN", "Returning csrf response for non-csrf error code.");
  mozlog.info("bad-csrf", {id: req.ip, url: req.url});
  res.status(403);
  res.type("text");
  res.send("Bad CSRF Token");
};

const assert = require("assert");
const csrf = require("csurf");
const mozlog = require("../logging").mozlog("csrf-middleware");
const { captureRavenException } = require("../ravenclient");
const { simpleResponse } = require("../responses");

const config = require("../config").getProperties();

const useSecureCsrfCookie = (config.expectProtocol && /^https$/.test(config.expectProtocol));

exports.csrfProtection = csrf({cookie: {httpOnly: true, secure: useSecureCsrfCookie}});

exports.csrf = function(req, res, next) {
  // The cookies library doesn't detect duplicates; check manually
  let rawCookies = req.get("cookie") || "";
  let pairs = rawCookies.split(";");
  let csrfTokens = pairs.filter(item => item.match(/_csrf=/));
  if (csrfTokens.length > 1) {
    let exc = new Error("Duplicate CSRF cookies");
    exc.headerValue = rawCookies;
    captureRavenException(exc);
    return simpleResponse(res, "Bad request", 400);
  }
  req.cookies._csrf = req.cookies.get("_csrf"); // csurf expects a property
  next();
};

exports.csrfErrorResponse = function(err, req, res) {
  assert(err.code === "EBADCSRFTOKEN", "Returning csrf response for non-csrf error code.");
  mozlog.info("bad-csrf", {ip: req.ip, url: req.url});
  res.status(403);
  res.type("text");
  res.send("Bad CSRF Token");
};

Change mozlog use to ip from id


import React from 'react';
import { connect } from 'react-redux'
import find from 'lodash/find';
import ChannelName from './ChannelName'
import SlackActions from '../actions/SlackActions';
import firebase from 'firebase/app';

const ChannelMessagesHeader = ({ channels, ims, currentChannelId, openSidebar }) => {
  const allChannels = Object.assign({}, channels, ims);
  const channel = find(allChannels, (c) => c.id === currentChannelId);

  if (!channel) {
    return null;
  }

  const handleToggleSidebar = (event) => {
    event.stopPropagation();
    openSidebar();
  };

  const handleSignOut = () => {
    firebase.auth().signOut();
  }

  return (

        <ChannelName channel={channel} />

      <button onClick={handleSignOut}> Sign Out </button>

  );
};

const mapDispatchToProps = dispatch => {
  return {
    openSidebar: () => {
      dispatch(SlackActions.openSidebar());
    },
  };
};

const mapStateToProps = state => {
  return {
    channels: state.channels.channels,
    ims: state.channels.ims,
    router: state.router
  };
};

export default connect(mapStateToProps, mapDispatchToProps)(ChannelMessagesHeader);

import React from 'react';
import { connect } from 'react-redux'
import find from 'lodash/find';
import ChannelName from './ChannelName'
import SlackActions from '../actions/SlackActions';

const ChannelMessagesHeader = ({ channels, ims, currentChannelId, openSidebar }) => {
  const allChannels = Object.assign({}, channels, ims);
  const channel = find(allChannels, (c) => c.id === currentChannelId);

  if (!channel) {
    return null;
  }

  const handleToggleSidebar = (event) => {
    event.stopPropagation();
    openSidebar();
  };

  return (

        <ChannelName channel={channel} />

  );
};

const mapDispatchToProps = dispatch => {
  return {
    openSidebar: () => {
      dispatch(SlackActions.openSidebar());
    },
  };
};

const mapStateToProps = state => {
  return {
    channels: state.channels.channels,
    ims: state.channels.ims,
    router: state.router
  };
};

export default connect(mapStateToProps, mapDispatchToProps)(ChannelMessagesHeader);

Revert "implement sign out button"


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

export default class TimeColumn extends Component {
  // static propTypes = {
  //   times: PropTypes.array.isRequired
  // };

  render() {
    const hours = ['12', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11'];
    const minutes = ['00', '15', '30', '45']
    const times = _.flatten(hours.map((hour) => {
      return minutes.map((min) => {
        return `${hour}:${min}`;
      });
    }));
    const timesToRender = times.map((ts, i) => {
      return
{ts}
;
    });

    return

Times

        {timesToRender}

;
  }
}

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

export default class TimeColumn extends Component {
  // static propTypes = {
  //   times: PropTypes.array.isRequired
  // };

  render() {
    const hours = ['12', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
    const minutes = ['00', '15', '30', '45']
    const times = _.flatten(hours.map((hour) => {
      return minutes.map((min) => {
        return `${hour}:${min}`;
      });
    }));
    const timesToRender = times.map((ts, i) => {
      return
{ts}
;
    });

    return

Times

        {timesToRender}

;
  }
}

Update length of Times to go to 10pm


const Cloudevent = require("../lib/cloudevent.js");
const Spec = require("../lib/specs/spec_0_3.js");
const StructuredHTTPEmitter = require("../lib/bindings/http/emitter_binary.js");
const BinaryHTTPEmitter = require("../lib/bindings/http/emitter_binary_0_3.js");

function event() {
  return new Cloudevent(Spec);
}

module.exports = {
  Spec,
  StructuredHTTPEmitter,
  BinaryHTTPEmitter,
  event
};

const Cloudevent = require("../lib/cloudevent.js");
const Spec = require("../lib/specs/spec_0_3.js");
const StructuredHTTPEmitter =
  require("../lib/bindings/http/emitter_structured.js");
const BinaryHTTPEmitter = require("../lib/bindings/http/emitter_binary_0_3.js");

function event() {
  return new Cloudevent(Spec);
}

module.exports = {
  Spec,
  StructuredHTTPEmitter,
  BinaryHTTPEmitter,
  event
};

Fix the import of structured emitter


"use strict";

var ManifestValidator = (function() {

  function _check(string) {
    var json = "";
    var logs = [];
    try {
      json = JSON.parse(string);
    } catch (e) {
      logs.push("File isn't valid JSON.");
      return [ false, logs ];
    }

    logs.push("JSON parsed successfuly.");

    return [ true, logs ];
  }

  return {
    check: _check,
  };
})();

"use strict";

var ManifestValidator = (function() {

  var _manifest = {};
  var _logs = [];

  function _checkName(manifest) {
    if (!_manifest.name) {
      _logs.push('Manifest has no `name` property.')
      return;
    }

    if (typeof _manifest.name != "string") {
      _logs.push('ERROR: Manifest\'s `name` property type is invalid.');
      return;
    }

    _manifest.name = _manifest.name.trim();
    _logs.push('Parsed `name` property is: ' + _manifest.name);
  }

  function _checkShortName(manifest) {
    if (!_manifest.short_name) {
      _logs.push('Manifest has no `short_name` property.')
      return;
    }

    if (typeof _manifest.short_name != "string") {
      _logs.push('ERROR: Manifest\'s `short_name` property type is invalid.');
      return;
    }

    _manifest.short_name = _manifest.short_name.trim();
    _logs.push('Parsed `short_name` property is: ' + _manifest.short_name);
  }

  function _check(string) {
    try {
      _manifest = JSON.parse(string);
    } catch (e) {
      _logs.push("File isn't valid JSON: " + e);
      return [ false, _logs ];
    }

    _logs.push("JSON parsed successfuly.");

    _checkName();
    _checkShortName();

    return [ true, _logs ];
  }

  return {
    check: _check,
  };
})();

Add checks for 'name' and 'short_name'.


import request from 'request';
import config from '../config/config';

export default {
    getFromConfig(callback) {
        let now = new Date();
        let later = new Date().setDate(now.getDate() + 2);

        let url = `https://www.googleapis.com/calendar/v3/calendars/${config.google.calendarId}/events?key=${config.google.apiKey}&singleEvents=true&orderBy=startTime&timeMin=${now.toISOString()}&timeMax=${later.toISOString()}`;
        request(url, (error, response, body) => {
            if (!error && response.statusCode === 200) {
                if (body === null) {
                    console.error(`Error: ${response}`);
                } else {
                    callback(response.items);
                }
            } else {
                console.error(`Error: ${error}`);
            }
        });
    }
};

import request from 'request';
import config from '../config/config';

export default {
    getFromConfig(callback) {
        let now = new Date();
        let later = new Date().setDate(now.getDate() + 2);

        let url = 'https://www.googleapis.com/calendar/v3/calendars' +
            `/${config.google.calendarId}/events?key=${config.google.apiKey}` +
            `&timeMin=${now.toISOString()}&timeMax=${later.toISOString()}` +
            `&singleEvents=true&orderBy=startTime`;
        request(url, (error, response, body) => {
            if (!error && response.statusCode === 200) {
                if (body === null) {
                    console.error(`Error: ${response}`);
                } else {
                    callback(response.items);
                }
            } else {
                console.error(`Error: ${error}`);
            }
        });
    }
};

Break up long, long line


/*
 * Game core for Robots Order BreakOut Trepidation!
 *
 * @canvas = reference to an HTML5 canvas element.
 */

'use strict';
import Actors from 'Actors.js';

export default class {
  constructor(canvas) {
    this._canvas = canvas;
    this._ctx = this._canvas.getContext("2d");

    this._actors = new Actors(this._canvas.width, this._canvas.height);
  }

  clearScene() {
    this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
    this._ctx.fillStyle = "#9ea7b8";
    this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height);
  }

  drawScene() {
    this.clearScene();
    this._actors.getVisibleActors().forEach((e)=> this.drawElement(e));
    requestAnimationFrame(()=> this.drawScene());
  }

  drawElement(e) {
    this._ctx.beginPath();
    this._ctx[e.drawType].apply(this._ctx, e.params);
    this._ctx.fillStyle = e.color;
    this._ctx.fill();
    this._ctx.closePath();
  }

  startGameLoop() {
    requestAnimationFrame(()=> this.drawScene());
  }

  start() {
    this.startGameLoop();
  }
}

/*
 * Game core for Robots Order BreakOut Trepidation!
 *
 * @canvas = reference to an HTML5 canvas element.
 */

'use strict';
import Actors from 'Actors.js';

export default class {
  constructor(canvas) {
    this._canvas = canvas;
    this._ctx = this._canvas.getContext("2d");

    this._actors = new Actors(this._canvas.width, this._canvas.height);
  }

  clearScene() {
    this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
    this._ctx.fillStyle = "#9ea7b8";
    this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height);
  }

  drawScene() {
    this.clearScene();
    this._actors.moveActors();
    this._actors.getActors().forEach((e)=> this.drawElement(e));
    requestAnimationFrame(()=> this.drawScene());
  }

  drawElement(e) {
    if (e.visible) {
      this._ctx.beginPath();
      this._ctx[e.drawType].apply(this._ctx, e.params);
      this._ctx.fillStyle = e.color;
      this._ctx.fill();
      this._ctx.closePath();
    }
  }

  startGameLoop() {
    requestAnimationFrame(()=> this.drawScene());
  }

  start() {
    this.startGameLoop();
  }
}

Make it possible for visible actors to move


"use strict";

/**
 * @typedef {{
 *      input: string,
 *      output: string,
 *      ignoreLintFor: Array.<(string|RegExp)>,
 * }} JsTaskConfig
 */

let fs = require("fs");
let JsTask = require("./js/js-task");
const _ = require("lodash");

/**
 * Main task for Sass
 *
 * @param {JsTaskConfig} config
 *
 * @returns {Function}
 */
module.exports = function (config = {})
{
    config = _.assign({
        input: "src/**/Resources/assets/js/",
        output: "../../public/js",
        // list of file path paths (string or regex). If the file path matches one of these entries, the file won't be linted
        ignoreLintFor: ["/node_modules/", "/vendor/"]
    }, config);

    // build internal config
    config.input = config.input.replace(/\/+$/, "") + "/";

    return function (done, debug)
    {
        let task = new JsTask(config);
        task.run(debug);
    }
};

"use strict";

/**
 * @typedef {{
 *      input: string,
 *      output: string,
 *      ignoreLintFor: Array.<(string|RegExp)>,
 * }} JsTaskConfig
 */

let fs = require("fs");
let JsTask = require("./js/js-task");
const _ = require("lodash");

/**
 * Main task for Sass
 *
 * @param {JsTaskConfig} config
 *
 * @returns {Function}
 */
module.exports = function (config = {})
{
    config = _.assign({
        // input directory (can be a glob to multiple directories)
        input: "src/**/Resources/assets/js/",
        // output directory (relative to input directory)
        output: "../../public/js",
        // list of file path paths (string or regex). If the file path matches one of these entries, the file won't be linted
        ignoreLintFor: ["/node_modules/", "/vendor/"]
    }, config);

    // ensure one trailing slash
    config.input = config.input.replace(/\/+$/, "") + "/";

    return function (done, debug)
    {
        let task = new JsTask(config);
        task.run(debug);
    }
};

Add comments for the JS task configuration


const execFile = require('child_process').execFile;

exports.handler = function(event, context) {
  const chunkSize = process.env.CHUNK_SIZE || 10;

  var records = event.Records;
  var context = context;

  console.log("Received " + records.length + " records.")

  var chunkOffset = 0;
  var chunks = []

  while (chunkOffset < records.length) {
    chunks.push(records.slice(chunkOffset, chunkOffset + chunkSize));
    chunkOffset += chunkSize;
  }

  chunks.forEach(function(chunk, index) {
    const child = execFile('./ruby_wrapper', [JSON.stringify(chunk), JSON.stringify(context)], {}, (error, stdout, stderr) => {
      stdout.trim().split("\n").forEach(function(x) {
        log = x.trim();
        if (log !== "") {
          console.log(log);
        }
      });
      stderr.trim().split("\n").forEach(function(x) {
        log = x.trim();
        if (log !== "") {
          console.log(log);
        }
      });
      if (error) {
        console.error(`exec error: ${error}`);
        process.exit(1);
      }
    });
  });
};

const execFile = require('child_process').execFile;
const fs = require("fs");

exports.handler = function(event, context) {
  const chunkSize = process.env.CHUNK_SIZE || 10;

  var records = event.Records;
  var context = context;

  console.log("Received " + records.length + " records.")

  var fileName = "/tmp/" + context.invokeid + ".json";

  fs.writeFile(fileName, JSON.stringify(records), (err) => {
    if (err) throw err;

    execFile('./ruby_wrapper', [fileName, JSON.stringify(context)], {}, (error, stdout, stderr) => {
      stdout.trim().split("\n").forEach(function(x)
        log = x.trim();
        if (log !== "") {
          console.log(log);
        }
      });
      stderr.trim().split("\n").forEach(function(x) {
        log = x.trim();
        if (log !== "") {
          console.log(log);
        }
      });
      if (error) {
        console.error(`exec error: ${error}`);
        process.exit(1);
      }
    });
  });
};

Write records to a temp file that can be read by Ruby


'use strict';

var mongoose = require('mongoose');
var slack = require('./slack.js');

var Rejection = mongoose.model('Rejection');

exports.reject = reject;

function reject(args, message) {
  var companyName = args[0];
  var rejection = new Rejection({
    companyName: companyName,
    user: message.user,
    timestamp: { type: Date, default: Date.now }
  });

  rejection.save(function (err, rejection) {
    if (err) {
      return console.error(err);
    }
    var channel = slack.getChannelGroupOrDMByID(message.channel);
    channel.send('Sounds like you\'re to awesome for ' + rejection.companyName + ' anyways!');
  });
  console.log(message._client.users[message.user].real_name, " was rejected from ", companyName)
}

'use strict';

var mongoose = require('mongoose');
var slack = require('./slack.js');

var Rejection = mongoose.model('Rejection');

exports.reject = reject;

function reject(args, message) {
  var channel = slack.getChannelGroupOrDMByID(message.channel);

  var companyName = args[0];
  var rejection = new Rejection({
    companyName: companyName,
    user: message.user,
    timestamp: { type: Date, default: Date.now }
  });

  Rejection.find({
    companyName: companyName,
    user: message.user
  }).exec(function (err, rejections) {
    if(rejections.length > 0) {
      channel.send('This information is already in my database. Thanks for reminding me though.');
    } else {
      rejection.save(function (err, rejection) {
        if (err) {
          return console.error(err);
        }

        channel.send('Sounds like you\'re to awesome for ' + rejection.companyName + ' anyways!');
      });
    }
  });
}

Make sure rejection isn't duplicated


// Shamelessly pulled from Modernizr
// https://github.com/Modernizr/Modernizr/blob/master/test/js/server.js
var express = require('express'),
  path = require('path'),
  fs = require('fs'),
	args = process.argv.slice(2),
  root = path.join(__dirname, '..');
  folder = path.join(root, 'test'),
	port = args[1] || '80';

var server = express.createServer();
server.use(express.static(folder));

function serveFile(file) {
  server.get('/' + file, function(request, response) {
    response.sendfile(path.join(root, file));
  });
}

fs.readdirSync(path.join(root, 'lib')).forEach(function(item) {
  serveFile(path.join('lib', item));
});

fs.readdirSync(path.join(root, 'dist')).forEach(function(item) {
  serveFile(path.join('dist', item));
});

server.listen(port);

console.log("Server started on port %s in %s", port, folder);

// Shamelessly pulled from Modernizr
// https://github.com/Modernizr/Modernizr/blob/master/test/js/server.js
var express = require('express'),
  path = require('path'),
  fs = require('fs'),
	args = process.argv.slice(2),
  root = path.join(__dirname, '..'),
  folder = path.join(root, 'build'),
	port = args[1] || '80';

var server = express.createServer();
server.use(express.static(folder));

server.listen(port);

console.log("Server started on port %s in %s", port, folder);

Update test server for build directory


var buildVariable = require('./buildVariable');
var format        = require('util').format;
var repeat        = require('repeat-string');

/**
 * Builds a complete section of the settings file, which includes a title area, and a list of variables.
 * @param {string} name - The title of the section.
 * @param {integer} num - The number of the section, which is added before the title text.
 * @param {object[]} component - A set of SassDoc variable definitions, which contain info about each variable's name and value.
 * @param {boolean} shim - If `true`, a reference to the mixin `add-foundation-colors()` is added in the "Global Stlyes" section. This is needed for the Foundation for Sites settings file to work correctly.
 * @returns {string} A formatted section.
 */
module.exports = function(name, num, component, shim) {
  var output = '';
  var title = format('%d. %s', num, name);

  output += format('// %s\n// %s\n\n', title, repeat('-', title.length));

  // Iterate through each variable within the component
  for (var i in component) {
    output += buildVariable(component[i]);
  }

  if (shim && name == 'Global Styles') {
    output += '\n@include add-foundation-colors;';
  }

  output += '\n';

  return output;
}

var buildVariable = require('./buildVariable');
var format        = require('util').format;
var repeat        = require('repeat-string');

/**
 * Builds a complete section of the settings file, which includes a title area, and a list of variables.
 * @param {string} name - The title of the section.
 * @param {integer} num - The number of the section, which is added before the title text.
 * @param {object[]} component - A set of SassDoc variable definitions, which contain info about each variable's name and value.
 * @param {boolean} shim - If `true`, a reference to the mixin `add-foundation-colors()` is added in the "Global Stlyes" section. This is needed for the Foundation for Sites settings file to work correctly.
 * @returns {string} A formatted section.
 */
module.exports = function(name, num, component, shim) {
  var output = '';
  var title = format('%d. %s', num, name);

  output += format('// %s\n// %s\n\n', title, repeat('-', title.length));

  // Iterate through each variable within the component
  for (var i in component) {
    output += buildVariable(component[i]);
  }

  if (shim && name == 'Global') {
    output += '\n@include add-foundation-colors;';
  }

  output += '\n';

  return output;
}

Fix incorrect section name in Foundation shim


/*
  @method gitPull
  Takes a flightplan instance and transport
  @param remote {Object} Flightplan transport instance
  @param webRoot {string} path to run git pull on the remote server
   e.g. /var/www/project
*/
var gitPull = function (remote, webRoot) {
  // git pull
  remote.with('cd ' + webRoot, function() {
    remote.exec('git pull');
  });
};

/*
  @method addToPlan
  Takes a flightplan instance and adds a task called gitDeploy
  @param plan {Object} Flightplan instance
  @param plan.runtime.options.webRoot {String} Remote path to run git pull
*/
var addToPlan = function (plan) {
  // Task to git deploy via ssh remote
  plan.remote('gitDeploy', function(remote) {
    var webRoot = plan.runtime.options.webRoot;
    gitPull(remote, webRoot);
  });
};

module.exports = {
  addToPlan: addToPlan,
  gitPull: gitPull
};

/*
  @method gitPull
  Takes a flightplan instance and transport
  @param remote {Object} Flightplan transport instance
  @param webRoot {string} path to run git pull on the remote server
  @param [gitIdentifier=master] {string} branch/tag/commit identifier
   e.g. /var/www/project
*/
var gitPull = function (remote, webRoot, gitIdentifier) {
  // git pull
  var gitIdentifier = gitIdentifier || 'master';
  remote.with('cd ' + webRoot, function() {
    remote.exec('git pull');
    remote.exec('git checkout ' + gitIdentifier);
  });
};

/*
  @method addToPlan
  Takes a flightplan instance and adds a task called gitDeploy
  @param plan {Object} Flightplan instance
  @param plan.runtime.options.webRoot {String} Remote path to run git pull
*/
var addToPlan = function (plan) {
  // Task to git deploy via ssh remote
  plan.remote('gitDeploy', function(remote) {
    var webRoot = plan.runtime.options.webRoot;
    gitPull(remote, webRoot);
  });
};

module.exports = {
  addToPlan: addToPlan,
  gitPull: gitPull
};

Add option to deploy specific branch/tag which defaults to master


const babelify = require('babelify')
const envify = require('envify/custom')
const markdown = require('browserify-markdown')
const through = require('through2')
const YAML = require('yamljs')

const babelConfig = require('./babel-config')
const util = require('./util')

module.exports = function transform ({
  config,
  env
}) {
  util.configureEnvironment({config, env})

  return [
    htmlTransform,
    markdown({
      html: true
    }),
    yamlTransform,
    babelify.configure(babelConfig(env)),
    envify(process.env) // Envify needs to happen last...
  ]
}

function htmlTransform (filename) {
  if (!/\.html$/i.test(filename)) {
    return through()
  }

  return through(function (buf, enc, next) {
    this.push('module.exports=' + JSON.stringify(buf.toString('utf8')))
    next()
  })
}

function yamlTransform (filename) {
  if (!/\.yml|\.yaml$/i.test(filename)) {
    return through()
  }

  return through(function (buf, enc, next) {
    this.push('module.exports=' + JSON.stringify(YAML.parse(buf.toString('utf8'))))
    next()
  })
}

const babelify = require('babelify')
const envify = require('envify/custom')
const markdown = require('browserify-markdown')
const through = require('through2')
const YAML = require('yamljs')

const babelConfig = require('./babel-config')
const util = require('./util')

module.exports = function transform ({
  config,
  env
}) {
  util.configureEnvironment({config, env})

  return [
    htmlTransform,
    markdown({
      html: true
    }),
    yamlTransform,
    babelify.configure(babelConfig(env)),
    [envify(process.env), {global: true}] // Envify needs to happen last...
  ]
}

function htmlTransform (filename) {
  if (!/\.html$/i.test(filename)) {
    return through()
  }

  return through(function (buf, enc, next) {
    this.push('module.exports=' + JSON.stringify(buf.toString('utf8')))
    next()
  })
}

function yamlTransform (filename) {
  if (!/\.yml|\.yaml$/i.test(filename)) {
    return through()
  }

  return through(function (buf, enc, next) {
    this.push('module.exports=' + JSON.stringify(YAML.parse(buf.toString('utf8'))))
    next()
  })
}

Apply envify globally so that local react files get processed by it.


var _ = require('lodash');

// Split a page up into sections (lesson, exercises, ...)
function split(nodes) {
    var section = [];

    return _.chain(nodes)
    .reduce(function(sections, el) {
        if(el.type === 'hr') {
            sections.push(section);
            section = [];
            section.links = nodes.links || {};
        } else {
            section.push(el);
        }

        return sections;
    }, [])
     // Add remaining nodes
    .concat([section])
    // Exclude empty sections
    .filter(_.negate(_.isEmpty))
    // Spit out JS array
    .value();
}

function join(sections) {
    var nodes = _.reduce(sections, function(nodes, section) {
        return nodes.concat(section);
    }, []);
    nodes.links = (sections[0] && (sections[0].links) || {});
    return nodes;
}

module.exports = {
    split: split,
    join: split,
};

var _ = require('lodash');

// Split a page up into sections (lesson, exercises, ...)
function split(nodes) {
    var section = [];

    var sections = _.chain(nodes)
    .reduce(function(sections, el) {
        if(el.type === 'hr') {
            sections.push(section);
            section = [];
        } else {
            section.push(el);
        }

        return sections;
    }, [])
     // Add remaining nodes
    .concat([section])
    // Exclude empty sections
    .filter(_.negate(_.isEmpty))
    // Spit out JS array
    .value();

    // Copy over links (kramed needs them)
    sections.links = nodes.links || {};

    return sections;
}

function join(sections) {
    var nodes = _.reduce(sections, function(nodes, section) {
        return nodes.concat(section);
    }, []);

    // Restore links (needed by kramed)
    nodes.links = sections.links || {};

    return nodes;
}

module.exports = {
    split: split,
    join: split,
};

Fix carrying kramed links around


const logger = require('./utils/logger'),
      argsParser = require('./parsers/argsParser'),
      styleParser = require('./parsers/styleParser'),
      filesReader = require('./utils/filesReader'),
      filesWriter = require('./utils/filesWriter'),
      minificationReport = require('./utils/minificationReport'),
      replacer = require('./replacer');

function unbemify () {
  logger.log('Unbemify started ...');

  const startTime = process.hrtime(),
        options = argsParser(process.argv);

  if (options.mistake) {
    logger.error(options.mistake);
    return null;
  }
  else {
    const allPaths = [options.style.path]
            .concat(options.files.map(file => file.path)),
          readedFiles = filesReader(allPaths);

    Promise.all(readedFiles)
      .then(resources => {
        options.style.content = resources.splice(0, 1)[0];
        options.style.size = options.style.content.length;
        resources.forEach((resource, index) => {
          const file = options.files[index];

          file.content = resource;
          file.size = resource.length;
        });

        const selectors = styleParser(options.style.content);

        replacer(selectors, options);

        filesWriter(options)
          .then(() => minificationReport(options, selectors, startTime))
          .catch(e => logger.error(e));
      })
      .catch(e => logger.error(e));
  }
}

module.exports = unbemify;

const logger = require('./utils/logger'),
      argsParser = require('./parsers/argsParser'),
      styleParser = require('./parsers/styleParser'),
      filesReader = require('./utils/filesReader'),
      filesWriter = require('./utils/filesWriter'),
      minificationReport = require('./utils/minificationReport'),
      replacer = require('./replacer');

function unbemify () {
  logger.log('Unbemify started ...');

  const startTime = process.hrtime(),
        options = argsParser(process.argv);

  if (options.mistake) {
    logger.error(options.mistake);
  }
  else {
    const allPaths = [options.style.path]
            .concat(options.files.map(file => file.path)),
          readedFiles = filesReader(allPaths);

    Promise.all(readedFiles)
      .then(resources => {
        options.style.content = resources.splice(0, 1)[0];
        options.style.size = options.style.content.length;
        resources.forEach((resource, index) => {
          const file = options.files[index];

          file.content = resource;
          file.size = resource.length;
        });

        const selectors = styleParser(options.style.content);

        replacer(selectors, options);

        filesWriter(options)
          .then(() => minificationReport(selectors, options, startTime))
          .catch(e => logger.error(e));
      })
      .catch(e => logger.error(e));
  }
}

module.exports = unbemify;

Change args order for report function


const socket = require('socket.io-client')();

class Client {
	constructor(board, boardElement) {
		this.board = board;
		this.boardElement = boardElement;
		socket.on('login', ({id}) => {
			this.board.selfNumber = id;
			this.boardElement.update();
		});
		socket.on('update', (data) => {
			this.board.fromData(data);
			this.boardElement.update();
		});
		socket.on('move', (data) => {
			if (this.board.selfNumber !== data.player) {
				this.board.moveTo(data.direction);
			}
		});
		board.on('moving', (direction) => {
			if (this.board.isActive()) {
				socket.emit('move', {direction});
			}
		});
	}
}

module.exports = Client;

const socket = require('socket.io-client')();

class Client {
	constructor(board, boardElement) {
		this.board = board;
		this.boardElement = boardElement;
		socket.on('login', ({id}) => {
			this.board.selfNumber = id;
			this.boardElement.update();
		});
		socket.on('update', (data) => {
			this.board.fromData(data);
			this.boardElement.update();
		});
		socket.on('move', (data) => {
			if (this.board.selfNumber !== data.player) {
				this.board.moveTo(data.direction);
			}
		});
		board.on('moving', (direction) => {
			if (this.board.isActive()) {
				socket.emit('move', {direction});
			}
		});
		board.on('win', () => {
			this.board.selfNumber = 2;
		});
	}
}

module.exports = Client;

Disable moving after the game is over


import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

// Codesplit based on route
const Home = ()=> System.import('pages/Home')
const Test = ()=> System.import('pages/Test')
const NotFound = ()=> System.import('pages/NotFound')

const options = {
    mode: 'history',
    scrollBehavior(to, from, savedPosition) {
        let ret = { x: 0, y: 0 }
        if (to.hash) {
            ret = {selector: to.hash}
        } else if (savedPosition) {
            ret = savedPosition
        }
        return ret
    },
    routes: [{
        path: '/',
        name: 'Home',
        component: Home
    }, {
        path: '/test',
        name: 'Test',
        component: Test
    }, {
        path: '*',
        name: 'NotFound',
        component: NotFound
    }]
}

export default ()=> new Router(options)

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

const loadPage = name=> ()=> import(`pages/${name}`)

const pages = [
    'Home',
    'Test',
    'NotFound'
]
    .map(page=> ({ name: page, component: loadPage(page) }))
    .reduce((sum, { name, component })=> ({...sum, [name]: component}), {})

const options = {
    mode: 'history',
    scrollBehavior(to, from, savedPosition) {
        let ret = { x: 0, y: 0 }
        if (to.hash) {
            ret = {selector: to.hash}
        } else if (savedPosition) {
            ret = savedPosition
        }
        return ret
    },
    routes: [{
        path: '/',
        name: 'Home',
        component: pages.Home
    }, {
        path: '/test',
        name: 'Test',
        component: pages.Test
    }, {
        path: '*',
        name: 'NotFound',
        component: pages.NotFound
    }]
}

export default ()=> new Router(options)

Use import() instead of System.import()


var globalnotifier = require('../globalnotifier');
var notifier = require('../notifier');

function setupJSON() {
  var JSONObject = JSON;

  if (__USE_JSON__) {
    // This adds the script to this context. We need it since this library
    // is not a CommonJs or AMD module.
    require("script!../../vendor/json2.min.js");

    var customJSON = {};
    setupCustomJSON(customJSON);

    JSONObject = customJSON;
  }

  globalnotifier.setupJSON(JSONObject);
}

setupJSON();

window.Rollbar = globalnotifier.wrapper;

// We need to expose Notifier for the snippet
window.RollbarNotifier = notifier.Notifier;

module.exports = globalnotifier.wrapper;

var globalnotifier = require('../globalnotifier');
var notifier = require('../notifier');

function setupJSON() {
  var JSONObject = JSON;

  if (__USE_JSON__) {
    // This adds the script to this context. We need it since this library
    // is not a CommonJs or AMD module.
    require("script!../../vendor/json2.min.js");

    var customJSON = {};
    setupCustomJSON(customJSON);

    JSONObject = customJSON;
  }

  globalnotifier.setupJSON(JSONObject);
}

setupJSON();

window.Rollbar = globalnotifier.wrapper;

// We need to expose Notifier for the snippet
window.RollbarNotifier = notifier.Notifier;

var config = window._rollbarConfig;

if (config) {
  globalnotifier.wrapper.init(config);
}

module.exports = globalnotifier.wrapper;

Initialize notifier if window._rollbarConfig is found. @coryvirok.


(function (GOVUK) {
  'use strict'

  GOVUK.Analytics.load()
  GOVUK.analyticsVars = { primaryLinkedDomains: [document.domain] }
  GOVUK.analytics = new GOVUK.Analytics({
    universalId: 'UA-26179049-6', // GOVUK Apps GA ID
    cookieDomain: document.domain
  })

  GOVUK.analytics.trackPageview()
})(window.GOVUK)

(function (GOVUK) {
  'use strict'

  GOVUK.Analytics.load()
  GOVUK.analyticsVars = { primaryLinkedDomains: [document.domain] }
  GOVUK.analytics = new GOVUK.Analytics({
    universalId: 'UA-26179049-6', // GOVUK Apps GA ID
    cookieDomain: document.domain
  })

  GOVUK.analytics.trackPageview()
  GOVUK.analyticsPlugins.externalLinkTracker()
})(window.GOVUK)

Enable external link tracking plugin


'use strict';
var _ = require('lodash');
var debug = require('ghost-ignition').debug('format');
var Mapper = require('./vendor/jsonapi-mapper');

var defaultSerializerOptions = {};
var defaultMapperOptions = {enableLinks: true};

module.exports = function format(api, apiReq, apiRes) {
    if (!apiRes || _.isEmpty(apiRes.model)) {
        debug('Nothing to format :(');
        // We don't throw an error, but return empty
        // This allows us to support, empty delete responses etc
        return;
    }

    debug('mapping to JSONAPI');
    // Apply any defaults that weren't overridden
    _.defaults(apiRes.serializerOptions, defaultSerializerOptions);
    _.defaults(apiRes.mapperOptions, defaultMapperOptions);
    var mapper = Mapper(api.baseUrl, apiRes.serializerOptions);
    var json = mapper.map(apiRes.model, apiRes.type, apiRes.mapperOptions);

    debug('returning');
    return json;
};

'use strict';
var _ = require('lodash');
var debug = require('ghost-ignition').debug('format');
var Mapper = require('./vendor/jsonapi-mapper');

var defaultSerializerOptions = {};
var defaultMapperOptions = {enableLinks: true};

module.exports = function format(api, apiReq, apiRes) {
    if (!apiRes || _.isEmpty(apiRes.model)) {
        debug('Nothing to format :(');
        // We don't throw an error, but return empty
        // This allows us to support, empty delete responses etc
        return;
    }

    debug('mapping to JSONAPI');
    // Apply any defaults that weren't overridden
    _.defaults(apiRes.serializerOptions, defaultSerializerOptions);
    _.defaults(apiRes.mapperOptions, defaultMapperOptions);
    var mapper = Mapper(api.baseUrl, apiRes.serializerOptions);
    var json = mapper.map(apiRes.model, apiRes.type, apiRes.mapperOptions);
    var includes = apiReq.query.options.include;

    // Omit data attribute from related models that have not been included
    // This is a workaround for jsonapi-mapper bug - https://github.com/scoutforpets/jsonapi-mapper/issues/69
    _.each(json.data.relationships, function (v, k) {
        json.data.relationships[k] = !_.includes(includes, k) ? _.omit(v, 'data') : v;
    });

    debug('returning');
    return json;
};

Remove related model data attribute not included


'use strict'

const exec = require('./util').exec
const logger = require('./logging').logger

function getMergeCommits (revRange) {
  const gitLogCmd = `git log ${revRange} --format='%s' --grep='^Merge pull request #[0-9]\\+ from '`
  const commits = exec(gitLogCmd).toString().trimRight().split('\n')
  logger.debug('Commits: %s', commits)
  return commits
}

function getPRs (commits) {
  const prs = commits.map(function (line) {
    const match = /Merge pull request #(\d+) from /.exec(line)
    if (match) {
      return parseInt(match[1], 10)
    }
    return false
  }).filter(Boolean)
  logger.debug('PRs: %s', prs)
  return prs
}

module.exports = {
  getMergeCommits,
  getPRs
}

'use strict'

const exec = require('./util').exec
const logger = require('./logging').logger

function getMergeCommits (revRange) {
  const gitLogCmd = `git log ${revRange} --format='%s' --grep='^Merge pull request #[0-9]\\+ from '`
  const stdout = exec(gitLogCmd).toString().trimRight()
  const commits = stdout ? stdout.split('\n') : []
  logger.debug('Commits: %s', JSON.stringify(commits))
  return commits
}

function getPRs (commits) {
  const prs = commits.map(function (line) {
    const match = /Merge pull request #(\d+) from /.exec(line)
    if (match) {
      return parseInt(match[1], 10)
    }
    return false
  }).filter(Boolean)
  logger.debug('PRs: %s', JSON.stringify(prs))
  return prs
}

module.exports = {
  getMergeCommits,
  getPRs
}

Fix getMergeCommits to return empty array


var events  = require('events'),
    util    = require('util'),
    EventRegistry,
    EventRegistryInstance;

EventRegistry = function () {};
util.inherits(EventRegistry, events.EventEmitter);

EventRegistryInstance = new EventRegistry();
EventRegistryInstance.setMaxListeners(100);

module.exports = EventRegistryInstance;

var events  = require('events'),
    util    = require('util'),
    EventRegistry,
    EventRegistryInstance;

EventRegistry = function () {
    events.EventEmitter.call(this);
};
util.inherits(EventRegistry, events.EventEmitter);

EventRegistryInstance = new EventRegistry();
EventRegistryInstance.setMaxListeners(100);

module.exports = EventRegistryInstance;

Make sure events gets initialized


// We save the user language preference in the user profile, and use that to set
// the language reactively. If the user is not connected we use the language
// information provided by the browser, and default to english.

Tracker.autorun(() => {
  const currentUser = Meteor.user();
  let language;
  if (currentUser) {
    language = currentUser.profile && currentUser.profile.language;
  } else {
    language = navigator.language || navigator.userLanguage;
  }

  if (language) {
    TAPi18n.setLanguage(language);

    // XXX
    const shortLanguage = language.split('-')[0];
    T9n.setLanguage(shortLanguage);
  }
});

// We save the user language preference in the user profile, and use that to set
// the language reactively. If the user is not connected we use the language
// information provided by the browser, and default to english.

Tracker.autorun(() => {
  const currentUser = Meteor.user();
  let language;
  if (currentUser) {
    language = currentUser.profile && currentUser.profile.language;
  }

  if (!language) {
    if(navigator.languages) {
      language = navigator.languages[0];
    } else {
      language = navigator.language || navigator.userLanguage;
    }
  }

  if (language) {
    TAPi18n.setLanguage(language);

    // XXX
    const shortLanguage = language.split('-')[0];
    T9n.setLanguage(shortLanguage);
  }
});

Use the navigator's language properties even if a user exists but the user has not yet choosen a profile language. If the browser provides multiple preferred languages via `navigator.languages`, select the most-preferred one.


const fs = require('fs');
const {join} = require('path');
const {promisify} = require('util');

const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);

const mkdirp = promisify(require('mkdirp'));
const request = promisify(require('request'));

const checkCacheExpiry = require('../utils/check-cache-expiry');
const generateUUID = require('../utils/generate-uuid');

const DEFAULT_CACHE_TTL = 3600;

const MILLISECONDS = 1000;

const fetch = (url, {
    cacheDirectory = 'temp/cache/',
    requestOptions = {},
    ttl = DEFAULT_CACHE_TTL
} = {}) => {

    const uuid = generateUUID(url);

    const path = join(
        cacheDirectory,
        uuid
    );

    return checkCacheExpiry(
        path,
        ttl * MILLISECONDS
    )
        .then(() => readFile(
            path,
            'utf8'
        ))
        .catch(() =>
            request(
                url,
                requestOptions
            ).then(({body}) =>
                mkdirp(cacheDirectory).then(() =>
                    writeFile(
                        path,
                        body
                    ).then(() => body))));

};

module.exports = {fetch};

const fs = require('fs');
const {join} = require('path');
const {promisify} = require('util');

const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);

const mkdirp = promisify(require('mkdirp'));
const request = promisify(require('request'));

const checkCacheExpiry = require('../utils/check-cache-expiry');
const generateUUID = require('../utils/generate-uuid');

const DEFAULT_CACHE_TTL = 3600;

const MILLISECONDS = 1000;

const requestFromUrlorCache = (url, {
    cacheDirectory = 'temp/cache/',
    requestOptions = {},
    ttl = DEFAULT_CACHE_TTL
} = {}) => {

    const uuid = generateUUID(url);

    const path = join(
        cacheDirectory,
        uuid
    );

    return checkCacheExpiry(
        path,
        ttl * MILLISECONDS
    )
        .then(() => readFile(
            path,
            'utf8'
        ))
        .catch(() =>
            request(
                url,
                requestOptions
            ).then(({body}) =>
                mkdirp(cacheDirectory).then(() =>
                    writeFile(
                        path,
                        body
                    ).then(() => body))));

};

const fetch = (urls, options) => {

    if (Array.isArray(urls)) {

        return Promise.all(urls.map(url => requestFromUrlorCache(
            url,
            options
        )));

    }

    return requestFromUrlorCache(
        urls,
        options
    );

};

module.exports = {
    fetch,
    requestFromUrlorCache
};

Support passing an array of urls.


var subscriptions = new SubsManager();

Router.configure({
	layoutTemplate: 'layout',
	loadingTemplate: 'loading',
	notFoundTemplate: 'notFound',
});

Router.route('/', {
	name: 'home',
	waitOn: function() {
		return subscriptions.subscribe('allVolunteers');
	},
	fastRender: true
});

Router.route('/myProfile', {
	name: 'myProfile',
	fastRender: false,
	waitOn: function() {
		return subscriptions.subscribe('oneQuestionAtATime', Meteor.userId());
	},
	data: function() {
		return UserQuestions.findOne({
			userId: Meteor.userId(),
			answered: false,
			deprecated: false
		}, {
			sort: {
				level: 1,
				version: 1
			}
		});
	}
});

Router.route('/myDay', {
	name: 'myDay',
	fastRender: false
});

Router.route('/meeting', {
	name: 'meeting',
	fastRender: false
});

Router.route('/admin', {
	layoutTemplate: 'adminLayout',
	name: 'admin',
	waitOn: function() {
		return [subscriptions.subscribe('allUniverses'), subscriptions.subscribe('allWorkshops')];
	},
	fastRender: true
});

Router.route('/admin/universes/new', {
	layoutTemplate: 'adminLayout',
	name: 'newUniverse',
	fastRender: false
});

Add admin routes and layout management


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

var floats = require('tachyons-floats/package.json')
var floatsCss = fs.readFileSync('node_modules/tachyons-floats/tachyons-floats.min.css', 'utf8')
var floatsObj = cssstats(floatsCss)
var floatsSize = filesize(floatsObj.gzipSize)

var srcCSS = fs.readFileSync('./src/_floats.css', 'utf8')

var template = fs.readFileSync('./templates/docs/floats/index.html', 'utf8')
var tpl = _.template(template)
var html = tpl({
  floatsVersion: floats.version,
  floatsSize: floatsSize,
  floatsObj:floatsObj,
  srcCSS: srcCSS
})

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

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

var floats = require('tachyons-floats/package.json')
var floatsCss = fs.readFileSync('node_modules/tachyons-floats/tachyons-floats.min.css', 'utf8')
var floatsObj = cssstats(floatsCss)
var floatsSize = filesize(floatsObj.gzipSize)

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

var template = fs.readFileSync('./templates/docs/floats/index.html', 'utf8')
var tpl = _.template(template)
var html = tpl({
  floatsVersion: floats.version,
  floatsSize: floatsSize,
  floatsObj:floatsObj,
  srcCSS: srcCSS,
  navDocs: navDocs
})

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

Update with reference to global nav partial


const path = require('path')

class Loader {

  cwd() {
    return process.cwd()
  }

  moduleRepo(customPath = "") {
    if (customPath !== '') return customPath
    return process.env.BEATLAB_HOME || path.join(process.cwd(), "modules")
  }

}

module.exports = new Loader()

const path = require('path')

class Loader {
  cwd () {
    return process.cwd()
  }

  moduleRepo (customPath = '') {
    if (customPath !== '') return customPath
    return process.env.BEATLAB_HOME || path.join(process.cwd(), 'modules')
  }
}

module.exports = new Loader()

Clean up source based on linter suggestions.


/*
 * Optics / output.js
 * copyright (c) 2016 Susisu
 */

/**
 * @module output
 */

"use strict";

function endModule() {
    module.exports = Object.freeze({
        Output,
        std
    });
}

/**
 * The `Output` class represents pair of output and error output function.
 * An instance of this class is used as the output target of the error and help messages of a command.
 * @static
 */
class Output {
    /**
     * Creates a new `Output` instance.
     * @param {Function} out A function used as an output target.
     * @param {Function} err A function used as an error output target.
     */
    constructor(out, err) {
        this.out = out;
        this.err = err;
    }
}

let stdout = process && process.stdout && process.stdout.write
    ? x => process.stdout.write(x)
    : x => { throw new Error("no stdout"); }
let stderr = process && process.stderr && process.stderr.write
    ? x => process.stderr.write(x)
    : x => { throw new Error("no stderr"); }
/**
 * The standard output target (stdout and stderr).
 * @static
 * @type {module:output.Output}
 */
let std = new Output(stdout, stderr);

endModule();

/*
 * Optics / output.js
 * copyright (c) 2016 Susisu
 */

/**
 * @module output
 */

"use strict";

function endModule() {
    module.exports = Object.freeze({
        Output,
        std
    });
}

/**
 * The `Output` class represents pair of output and error output function.
 * An instance of this class is used as the output target of the error and help messages of a command.
 * @static
 */
class Output {
    /**
     * Creates a new `Output` instance.
     * @param {Function} out A function used as an output target.
     * @param {Function} err A function used as an error output target.
     * @throws {TypeError} `out` is not a function.
     * @throws {TypeError} `err` is not a function.
     */
    constructor(out, err) {
        if (typeof out !== "function") {
            throw new TypeError("out must be a function");
        }
        if (typeof err !== "function") {
            throw new TypeError("err must be a function");
        }
        this.out = out;
        this.err = err;
    }
}

let stdout = process && process.stdout && process.stdout.write
    ? x => process.stdout.write(x)
    : x => { throw new Error("no stdout"); }
let stderr = process && process.stderr && process.stderr.write
    ? x => process.stderr.write(x)
    : x => { throw new Error("no stderr"); }
/**
 * The standard output target (stdout and stderr).
 * @static
 * @type {module:output.Output}
 */
let std = new Output(stdout, stderr);

endModule();

Fix the Output constructor does not throw TypeError


(function(){
  'use strict';

  var parser = {};

  module.exports = exports = {
    'set': function(prop, imp){
      // normalize
      if (prop === 'stringify' || prop === 'serialise') prop = 'serialize';
      parser[prop] = imp;
      return this;
    },

    'parse': function(input){
      if (typeof parser.parse !== 'function') throw Error('The parser you set is not a function!');
      if (typeof input === 'object' && input instanceof Buffer) input = input.toString();
      if (typeof input !== 'string') throw Error('You can only parse strings or buffers!');
      return parser.parse(input);
    },

    'serialize': function(input, indent){
      if (typeof parser.serialize !== 'function') throw Error('The seralizer you set is not a function!');
      if (typeof input !== 'object') throw Error('You can only serialize objects!');
      if (typeof indent === 'undefined') indent = 2;
      return parser.serialize(input, indent);
    },

    'serialise': function(){return this.serialize();},
    'stringify': function(){return this.serialize();}
  };
})();

Add 'set', 'parse', 'serialize', 'serialise', and 'stringify'


import { observable, action } from 'mobx'

class ProfileStore {
  @observable profile

  @action
  updateProfile(profile) {
    this.profile = profile
  }
}

export default new ProfileStore()

import { action, observable, runInAction, useStrict } from 'mobx'

useStrict(true)

class ProfileStore {
  @observable profile

  @action
  updateProfile = async (accessToken) => {
    let userInfoResponse = await fetch(
      `https://graph.facebook.com/me?access_token=${accessToken}&fields=id,name,picture.type(large)`,
    )
    const profile = await userInfoResponse.json()
    runInAction(() => {
      this.profile = profile
    })
  }
}

export default new ProfileStore()

Update profile by passing accessToken, not profile data


/**
 * User Input Component Stories.
 *
 * 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 { storiesOf } from '@storybook/react';

/**
 * Internal dependencies
 */
import UserInputApp from '../assets/js/components/user-input/UserInputApp';
import { CORE_USER } from '../assets/js/googlesitekit/datastore/user/constants';
import { WithTestRegistry } from '../tests/js/utils';

storiesOf( 'User Input', module )
	.add( 'UserInputApp', () => {
		return (
			<WithTestRegistry callback={ ( registry ) => {
				// Don't mark the user input as completed in this story.
				registry.dispatch( CORE_USER ).receiveUserInputState( 'missing' );
			} } features={ [ 'userInput' ] }>
				<div className="-googlesitekit-plugin-preview">
					<UserInputApp />
				</div>
			</WithTestRegistry>
		);
	} );

/**
 * User Input Component Stories.
 *
 * 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 { storiesOf } from '@storybook/react';

/**
 * Internal dependencies
 */
import UserInputApp from '../assets/js/components/user-input/UserInputApp';
import { CORE_USER } from '../assets/js/googlesitekit/datastore/user/constants';
import { WithTestRegistry } from '../tests/js/utils';

storiesOf( 'User Input', module )
	.add( 'UserInputApp', () => {
		return (
			<WithTestRegistry callback={ ( registry ) => {
				// Don't mark the user input as completed in this story.
				registry.dispatch( CORE_USER ).receiveUserInputState( 'missing' );
			} } features={ [ 'userInput' ] }>
				<UserInputApp />
			</WithTestRegistry>
		);
	}, {
		padding: 0,
	} );

Refactor user input story with padding parameter.


import React, {
  PureComponent,
  createElement,
  Children,
  cloneElement
} from 'react';
import PropTypes from 'prop-types';

import eventManager from './../util/eventManager';

class ContextMenuProvider extends PureComponent {
  static propTypes = {
    id: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.number
    ]).isRequired,
    children: PropTypes.oneOfType([
      PropTypes.arrayOf(PropTypes.node),
      PropTypes.node
    ]).isRequired,
    renderTag: PropTypes.node,
    event: PropTypes.string,
    className: PropTypes.string,
    style: PropTypes.object
  };

  static defaultProps = {
    renderTag: 'div',
    event: 'onContextMenu',
    className: '',
    style: {}
  };

  handleEvent = e => {
    e.preventDefault();
    eventManager.emit(`display::${this.props.id}`, e.nativeEvent);
  };

  getChildren() {
    const {
      id,
      renderTag,
      event,
      children,
      className,
      style,
      ...rest
    } = this.props;
    return Children.map(this.props.children,
      child => cloneElement(child, { ...rest }));
  }

  render() {
    const { renderTag, event, className, style } = this.props;
    const attributes = Object.assign({}, {
      [event]: this.handleEvent,
      className,
      style
    });

    return createElement(
      renderTag,
      attributes,
      this.getChildren()
    );
  }
}

export default ContextMenuProvider;

import React, {
  PureComponent,
  createElement,
  Children,
  cloneElement,
  isValidElement
} from 'react';
import PropTypes from 'prop-types';

import eventManager from './../util/eventManager';

class ContextMenuProvider extends PureComponent {
  static propTypes = {
    id: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.number
    ]).isRequired,
    children: PropTypes.oneOfType([
      PropTypes.arrayOf(PropTypes.node),
      PropTypes.node
    ]).isRequired,
    renderTag: PropTypes.node,
    event: PropTypes.string,
    className: PropTypes.string,
    style: PropTypes.object
  };

  static defaultProps = {
    renderTag: 'div',
    event: 'onContextMenu',
    className: '',
    style: {}
  };

  handleEvent = e => {
    e.preventDefault();
    eventManager.emit(`display::${this.props.id}`, e.nativeEvent);
  };

  getChildren() {
    const {
      id,
      renderTag,
      event,
      children,
      className,
      style,
      ...rest
    } = this.props;
    return isValidElement(this.props.children) ? Children.map(this.props.children,
      child => cloneElement(child, { ...rest })) : this.props.children;
  }

  render() {
    const { renderTag, event, className, style } = this.props;
    const attributes = Object.assign({}, {
      [event]: this.handleEvent,
      className,
      style
    });

    return createElement(
      renderTag,
      attributes,
      this.getChildren()
    );
  }
}

export default ContextMenuProvider;

Clone only valid react element


import React, { Component } from 'react';
import ReactDom from 'react-dom';
import YTSearch from 'youtube-api-search';
import SearchBar from './components/search_bar';
import VideoList from './components/video_list';
import VideoDetail from './components/video_detail';
// const API_KEY = 'PLACE_YOUR_API_KEY_HERE';

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

		this.state = {
			videos: [],
			selectedVideo: null
		};

		YTSearch({key: API_KEY, term: 'dragonball super'}, (videos) => {
			console.log(videos);
			this.setState({
				videos: videos,
				selectedVideo: videos[0]
			});
		});
	}

	render () {
		return (
			<div>
				<SearchBar />
				<VideoDetail video={this.state.selectedVideo} />
				<VideoList videos={this.state.videos} />
			</div>
		);
	}
}

ReactDom.render(<App />, document.querySelector('.container'));

import React, { Component } from 'react';
import ReactDom from 'react-dom';
import YTSearch from 'youtube-api-search';
import SearchBar from './components/search_bar';
import VideoList from './components/video_list';
import VideoDetail from './components/video_detail';
// const API_KEY = 'PLACE_YOUR_API_KEY_HERE';

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

		this.state = {
			videos: [],
			selectedVideo: null
		};

		YTSearch({key: API_KEY, term: 'dragonball super'}, (videos) => {
			console.log(videos);
			this.setState({
				videos: videos,
				selectedVideo: videos[0]
			});
		});
	}

	render () {
		return (
			<div>
				<SearchBar />
				<VideoDetail video={this.state.selectedVideo} />
				<VideoList
					videos={this.state.videos}
					onVideoSelect={selectedVideo => this.setState({selectedVideo})}
				/>
			</div>
		);
	}
}

ReactDom.render(<App />, document.querySelector('.container'));

Add a property onto VideoList that calls a function which updates App state with a selectVideo.


export { PagePod } from "./page-pod";

export function configure(config) {
  config.globalResources("./page-pod");
}

Add export to fix bundle problem


var uniqueRandomArray = require('unique-random-array');
var starWarsNames = require('./starwars-names.json');

var getRandomItem = uniqueRandomArray(starWarsNames);

module.exports = {
  all: starWarsNames,
  random: random
};

function random(number) {
  if (number === undefined) {
    return getRandomItem();
  } else {
    return new Array(number).map(function() {
      return getRandomItem();
    });
  }
}

var uniqueRandomArray = require('unique-random-array');
var starWarsNames = require('./starwars-names.json');

var getRandomItem = uniqueRandomArray(starWarsNames);

module.exports = {
  all: starWarsNames,
  random: random
};

function random(number) {
  if (number === undefined) {
    return getRandomItem();
  } else {
    var randomItems = [];
    for (var i = 0; i < number; i++) {
      randomItems.push(getRandomItem());
    }
    return randomItems;
  }
}

Fix random with a number issue


(function () {
	'use strict';
	// Awesome Browser Shortcuts
	chrome.commands.onCommand.addListener(function (command) {
		console.log(command);
		if (command === 'toggle-pin') {
			// Get current tab in the active window
			chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
				var currentTab = tabs[0];
				chrome.tabs.update(currentTab.id, {'pinned': currentTab.pinned});
			});
		}
	});
})();

(function () {
	'use strict';
	// Awesome Browser Shortcuts
	chrome.commands.onCommand.addListener(function (command) {
		if (command === 'toggle-pin') {
			// Get current tab in the active window
			chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
				var currentTab = tabs[0];
				chrome.tabs.update(currentTab.id, {'pinned': !currentTab.pinned});
			});
		}
	});
})();

Fix toggle-pin and clean code


chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
    var queryStringIndex = tab.url.indexOf('?');
    if (tab.url.indexOf('utm_') > queryStringIndex) {
        var stripped = tab.url.replace(
            /([\?\&]utm_(source|medium|term|campaign|content|cid)=[^&#]+)/ig,
            '');
        if (stripped.charAt(queryStringIndex) === '&') {
            stripped = stripped.substr(0, queryStringIndex) + '?' +
                stripped.substr(queryStringIndex + 1)
        }
        if (stripped != tab.url) {
            chrome.tabs.update(tab.id, {url: stripped});
        }
    }
});

chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
    var queryStringIndex = tab.url.indexOf('?');
    if (tab.url.indexOf('utm_') > queryStringIndex) {
        var stripped = tab.url.replace(
            /([\?\&]utm_(source|medium|term|campaign|content|cid|reader)=[^&#]+)/ig,
            '');
        if (stripped.charAt(queryStringIndex) === '&') {
            stripped = stripped.substr(0, queryStringIndex) + '?' +
                stripped.substr(queryStringIndex + 1)
        }
        if (stripped != tab.url) {
            chrome.tabs.update(tab.id, {url: stripped});
        }
    }
});

Include utm_reader in stripped parameters


import Joi from 'joi'
import { graph } from 'helpers/sparql'

export default [
  {
    path: '/pipelines',
    query: `
      PREFIX core: <http://mu.semte.ch/vocabularies/core/>
      CONSTRUCT {
        ?pipeline ?p ?o .
        ?step ?x ?y
      }
      FROM <${graph}>
      WHERE {
        ?pipeline core:uuid ?uuid .
        ?pipeline ?p ?o .
        ?pipeline <http://purl.org/spar/pwo/hasStep> ?step .
        ?step ?x ?y
      }`,
    config: {
      validate: {
        query: {
          'uuid': Joi.string().required()
        }
      }
    }
  }
]

import Joi from 'joi'
import { graph } from 'helpers/sparql'

export default [
  {
    path: '/pipelines',
    query: `
      PREFIX mu: <http://mu.semte.ch/vocabularies/core/>
      PREFIX pwo: <http://purl.org/spar/pwo/>
      CONSTRUCT {
        ?pipeline ?p ?o .
        ?step ?x ?y
      }
      FROM <${graph}>
      WHERE {
        ?pipeline mu:uuid ?uuid .
        ?pipeline ?p ?o .
        ?pipeline pwo:hasStep ?step .
        ?step ?x ?y
      }`,
    config: {
      validate: {
        query: {
          'uuid': Joi.string().required()
        }
      }
    }
  }
]

Use prefixes in SPARQL query


import React, { Component } from 'react';

export default class AddProject extends Component {
  constructor() {
    super();

    this.state = {
      projectId: '',
    };

    this.submit = this.submit.bind(this);
  }

  submit(e) {
    e.preventDefault();
    this.props.onAdd(this.state.projectId);
  }

  render() {
    return (
      <form onSubmit={this.submit}>

Project ID

          <input
            id="projectId"
            onChange={e => this.setState({ projectId: e.target.value })}
            type="number"
            value={this.state.projectId}
            className="form-control"
          />

            <button type="submit" className="btn btn-primary">Add</button>

      </form>
    );
  }
}

import React, { Component } from 'react';

export default class AddProject extends Component {
  constructor() {
    super();

    this.state = {
      projectId: '',
    };

    this.submit = this.submit.bind(this);
  }

  submit(e) {
    e.preventDefault();
    this.props.onAdd(this.state.projectId);
  }

  render() {
    return (
      <form onSubmit={this.submit}>

Project ID

          <input
            id="projectId"
            onChange={e => this.setState({ projectId: e.target.value })}
            type="number"
            value={this.state.projectId}
            className="form-control"
            placeholder="123"
          />

            <button type="submit" className="btn btn-primary">Add</button>

      </form>
    );
  }
}

Add placeholder of example ID to manual project entry


export default ({
  defaultLocale,
  siteLocales,
} = {}) => (req, res, next) => {
  if (typeof defaultLocale !== 'string') {
    throw new Error(`Expected defaultLocale to be a locale string.`);
  }
  if (typeof siteLocales !== 'object' || siteLocales.constructor !== Array || siteLocales.length === 0) {
    throw new Error(`Expected siteLocales to be an array of locale string.`);
  }
  let [ language, country ] = defaultLocale.split('-');
  const [ url ] = req.url.substr(1).split('?');
  const [ localeSegment ] = url.split('/');
  if (localeSegment) {
    const [ languageSegment, countrySegment ] = localeSegment.split('-');
    language = languageSegment || language;
    country = countrySegment || country;
    const newLocale = [ language, country ].join('-');
    if (siteLocales.indexOf(newLocale) !== -1) {
      req.url = '/' + url.substr(localeSegment.length + 1);
    }
  }
  req.locale = {
    country,
    language,
  };
  next();
};

export default ({
  defaultLocale,
  siteLocales,
} = {}) => (req, res, next) => {
  if (typeof defaultLocale !== 'string') {
    throw new Error(`Expected defaultLocale to be a locale string.`);
  }
  if (typeof siteLocales !== 'object' || siteLocales.constructor !== Array || siteLocales.length === 0) {
    throw new Error(`Expected siteLocales to be an array of locale string.`);
  }
  let [ language, country ] = defaultLocale.split('-');
  const [ url ] = req.url.substr(1).split('?');
  const [ localeSegment ] = url.split('/');
  if (localeSegment) {
    const [ languageSegment = language, countrySegment = country ] = localeSegment.split('-');
    if (siteLocales.indexOf(`${language}-${country}`) !== -1) {
      language = languageSegment;
      country = countrySegment;
      const newLocale = [ language, country ].join('-');
      if (siteLocales.indexOf(newLocale) !== -1) {
        req.url = '/' + url.substr(localeSegment.length + 1);
      }
    }
  }
  req.locale = {
    country,
    language,
  };
  next();
};

Fix server-side locale matching logic


/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2020
 */

import AsyncStorage from '@react-native-community/async-storage';
import { persistStore, persistReducer } from 'redux-persist';
import { applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';

import { navigationMiddleware } from './navigation';
import reducers from './reducers';

const persistConfig = {
  keyPrefix: '',
  key: 'root',
  storage: AsyncStorage,
  blacklist: [
    'pages',
    'user',
    'prescription',
    'patient',
    'finalise',
    'form',
    'prescriber',
    'wizard',
    'payment',
    'insurance',
    'dashboard',
    'dispensary',
    'modules',
    'supplierCredit',
    'temperatureSync',
  ],
};

const persistedReducer = persistReducer(persistConfig, reducers);

const store = createStore(persistedReducer, {}, applyMiddleware(thunk, navigationMiddleware));

const persistedStore = persistStore(store);

export { store, persistedStore };

/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2020
 */

import AsyncStorage from '@react-native-community/async-storage';
import { persistStore, persistReducer } from 'redux-persist';
import { applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';

import { navigationMiddleware } from './navigation';
import reducers from './reducers';

const persistConfig = {
  keyPrefix: '',
  key: 'root',
  storage: AsyncStorage,
  blacklist: [
    'pages',
    'user',
    'prescription',
    'patient',
    'finalise',
    'form',
    'prescriber',
    'wizard',
    'payment',
    'insurance',
    'dashboard',
    'dispensary',
    'modules',
    'supplierCredit',
  ],
};

const persistedReducer = persistReducer(persistConfig, reducers);

const store = createStore(persistedReducer, {}, applyMiddleware(thunk, navigationMiddleware));

const persistedStore = persistStore(store);

export { store, persistedStore };

Add remove temperatureSync from blacklist


SimpleForm = {
  processForm: function(target) {
    var form = {};
    array = $(target).serializeArray();
    _.each(array, function(formItem) {
      type = $(target).find("input[name='" + formItem.name + "']").attr('type')
      if (type === 'date') {
        return form[formItem.name] = new Date(formItem.value);
      } else {
        return form[formItem.name] = formItem.value;
      }
    });
    return form;
  },
  resetForm: function(target){
    $(target).trigger('reset')
  }
};

SimpleForm = {
  processForm: function(target) {
    var form = {};
    array = $(target).serializeArray();
    _.each(array, function(formItem) {
      type = $(target).find("input[name='" + formItem.name + "']").attr('type')
      if (type === 'date') {
        return form[formItem.name] = new Date(formItem.value + " 00:00");
      } else {
        return form[formItem.name] = formItem.value;
      }
    });
    return form;
  },
  resetForm: function(target){
    $(target).trigger('reset')
  }
};

Add midnight time to date stamps.


/* cp to secrets.js and fill w/ yr api keys as needed */
export default function secrets() {
	return {
		echoNest: '',
	};
}

/* cp to secrets.js and fill w/ yr api keys as needed */
export default function secrets() {
	return {
		echoNest: '',
		'7digital': '',
	};
}

Add 7digital api key spot


var _curry = require('./internal/_curry');

var Regex = require('./internal/_primitives').Regex;

/**
 * Point-free, curried form of regular expression's test method.
 *
 * @sig String -> RegExp -> Boolean
 * @since 0.6.0
 */
Regex.test = _curry(function(str, re){
    return re.test(str);
});

/**
 * Point-free, curried form of a regular expression's exec method.
 *
 * @sig String -> RegExp -> null|Array
 * @since 0.6.0
 */
Regex.exec = _curry(function(str, re){
    return re.exec(str);
});

Regex.show = function(value) {
    return value.toString();
};

module.exports = Regex;

var _curry = require('./internal/_curry');

var Regex = require('./internal/_primitives').Regex;

/**
 * Point-free, curried form of regular expression's test method.
 *
 * @sig String -> RegExp -> Boolean
 * @since 0.6.0
 */
Regex.test = _curry(function(str, re){
    return re.test(str);
});

/**
 * Point-free, curried form of a regular expression's exec method.
 *
 * Unlike the built in Regex.prototype.exec function, this function will return
 * an empty array if no matches are found.
 *
 * @sig String -> RegExp -> [String]
 * @since 0.6.0
 */
Regex.exec = _curry(function(str, re){
    return re.exec(str) || [];
});

Regex.show = function(value) {
    return value.toString();
};

module.exports = Regex;

Update to regex exec so returns array if no match found


import Calendar from './Calendar';
import CenturyView from './CenturyView';
import DecadeView from './DecadeView';
import YearView from './YearView';
import MonthView from './MonthView';

export default Calendar;

export {
  CenturyView,
  DecadeView,
  YearView,
  MonthView,
};

import Calendar from './Calendar';
import CenturyView from './CenturyView';
import DecadeView from './DecadeView';
import YearView from './YearView';
import MonthView from './MonthView';

export default Calendar;

export {
  Calendar,
  CenturyView,
  DecadeView,
  YearView,
  MonthView,
};

Allow importing Calendar by destructuring


const TwitterPostBase = require('./TwitterPostBase');
const { captureScheduleScreenshot } = require('../screenshots');
const { readData } = require('../utilities');

class ScheduleTweet extends TwitterPostBase {
    getKey() { return 'schedule'; }
    getName() { return 'Schedule'; }

    getSchedules() {
        return readData('schedules.json');
    }

    getData() {
        return this.getSchedules().regular.find(s => s.start_time == this.getDataTime());
    }

    getTestData() {
        return this.getSchedules().regular[0];
    }

    getImage(data) {
        return captureScheduleScreenshot(data.start_time);
    }

    getText(data) {
        return 'Current Splatoon 2 map rotation';
    }
}

module.exports = ScheduleTweet;

const TwitterPostBase = require('./TwitterPostBase');
const { captureScheduleScreenshot } = require('../screenshots');
const { readData, readJson } = require('../utilities');
const path = require('path');

const stagesPath = path.resolve('storage/stages.json');

class ScheduleTweet extends TwitterPostBase {
    getKey() { return 'schedule'; }
    getName() { return 'Schedule'; }

    getSchedules() {
        return readData('schedules.json');
    }

    getData() {
        return this.getSchedules().regular.find(s => s.start_time == this.getDataTime());
    }

    getTestData() {
        return this.getSchedules().regular[0];
    }

    getImage(data) {
        return captureScheduleScreenshot(data.start_time);
    }

    getText(data) {
        // Load known stages
        let stages = readJson(stagesPath);

        for (let stage of [data.stage_a, data.stage_b]) {
            let stageInfo = stages.find(s => s.id == stage.id);

            // If this is the first time the stage has been available, return some different text
            if (stageInfo && stageInfo.first_available == data.start_time)
                return `New stage ${stage.name} is now open! #splatoon2`;
        }

        return 'Current Splatoon 2 map rotation';
    }
}

module.exports = ScheduleTweet;

Use alternate text for the schedule tweet when there's a new stage


const hash = require('object-hash');

const toSafeObject = obj => JSON.parse(JSON.stringify(obj || {}));

function cache(func, options) {
  return (root, args, context) => {
    if (!context.resolverCache) {
      throw new Error('Missing resolverCache property on the Graphql context.');
    }

    const key = options.key
      ? options.key(root, args, context)
      : `${hash(func)}:${hash(toSafeObject(root))}:${hash(toSafeObject(args))}`;

    const executeAndCache = () =>
      Promise.resolve(func(root, args, context)).then((value) => {
        context.resolverCache.set(key, value, options);
        return value;
      });

    return (
      context.resolverCache
        .get(key)
        .then(value => (value ? value : executeAndCache()))
        // Error
        .catch(() => executeAndCache())
    );
  };
}

module.exports = cache;

const hash = require('object-hash');

const toSafeObject = obj => JSON.parse(JSON.stringify(obj || {}));

function cache(func, options = {}) {
  return (root, args, context) => {
    if (!context.resolverCache) {
      throw new Error('Missing resolverCache property on the Graphql context.');
    }

    const key = options.key
      ? options.key(root, args, context)
      : `${hash(func)}:${hash(toSafeObject(root))}:${hash(toSafeObject(args))}`;

    const executeAndCache = () =>
      Promise.resolve(func(root, args, context)).then((value) => {
        context.resolverCache.set(key, value, options);
        return value;
      });

    return (
      context.resolverCache
        .get(key)
        .then(value => (value ? value : executeAndCache()))
        // Error
        .catch(() => executeAndCache())
    );
  };
}

module.exports = cache;

Set default value for options argument


const React = require('react');
const ReactDOM = require('react-dom');
const { Provider } = require('react-redux');

const { initializeApp } =  require('./actions');
const Root =  require('./components/Root');
const config =  require('./config');
const { configureStore } =  require('./store');

/**
 * @namespace Immutable
 */

/**
 * @namespace State
 */

window.document.addEventListener('DOMContentLoaded', () => {
  const store = configureStore();

  // For development on your browser
  window._config = config;
  window._store = store;

  const app = React.createElement(
    Provider,
    { store },
    React.createElement(Root)
  );

  const appContainer = window.document.querySelector('.js-dd-container');
  ReactDOM.render(app, appContainer, () => {
    store.dispatch(initializeApp());
  });
});

const React = require('react');
const ReactDOM = require('react-dom');
const { Provider } = require('react-redux');

const { initializeApp } =  require('./actions');
const Root =  require('./components/Root');
const config =  require('./config');
const acts =  require('./immutable/acts');
const constants =  require('./immutable/constants');
const boardAnimations =  require('./immutable/board-animations');
const jobs =  require('./immutable/jobs');
const { configureStore } =  require('./store');

/**
 * @namespace Immutable
 */

/**
 * @namespace State
 */

window.document.addEventListener('DOMContentLoaded', () => {
  const store = configureStore();

  // For development on your browser
  window._config = config;
  window._immutable = {
    acts,
    boardAnimations,
    constants,
    jobs,
  };
  window._store = store;

  const app = React.createElement(
    Provider,
    { store },
    React.createElement(Root)
  );

  const appContainer = window.document.querySelector('.js-dd-container');
  ReactDOM.render(app, appContainer, () => {
    store.dispatch(initializeApp());
  });
});

Define some variables for manual debugging


import {Transform} from 'stream';
import SVGOptim from 'svgo';
import {PluginError} from 'gulp-util';

const PLUGIN_NAME = 'gulp-svgmin';

module.exports = function (opts) {
    const stream = new Transform({objectMode: true});
    let svgo;

    if (typeof options !== 'function') {
        svgo = new SVGOptim(opts);
    }

    stream._transform = function (file, encoding, cb) {
        if (file.isNull()) {
            return cb(null, file);
        }

        if (file.isStream()) {
            return cb(new PluginError(PLUGIN_NAME, 'Streaming not supported'));
        }

        if (file.isBuffer()) {
            if (typeof opts === 'function') {
                svgo = new SVGOptim(opts(file));
            }

            svgo.optimize(String(file.contents), result => {
                if (result.error) {
                    return cb(new PluginError(PLUGIN_NAME, result.error));
                }
                file.contents = new Buffer(result.data);
                cb(null, file);
            });
        }
    };

    return stream;
};

import {Transform} from 'stream';
import SVGOptim from 'svgo';
import {PluginError} from 'gulp-util';

const PLUGIN_NAME = 'gulp-svgmin';

module.exports = function (opts) {
    const stream = new Transform({objectMode: true});
    let svgo;

    if (typeof opts !== 'function') {
        svgo = new SVGOptim(opts);
    }

    stream._transform = function (file, encoding, cb) {
        if (file.isNull()) {
            return cb(null, file);
        }

        if (file.isStream()) {
            return cb(new PluginError(PLUGIN_NAME, 'Streaming not supported'));
        }

        if (file.isBuffer()) {
            if (typeof opts === 'function') {
                svgo = new SVGOptim(opts(file));
            }

            svgo.optimize(String(file.contents), result => {
                if (result.error) {
                    return cb(new PluginError(PLUGIN_NAME, result.error));
                }
                file.contents = new Buffer(result.data);
                cb(null, file);
            });
        }
    };

    return stream;
};

Fix typo in variable name


const { response } = require('./util/lambda')

function handler(event, context, callback) {

    const { body } = event;
    const {
        SPOTIFY_USER_ACCESS_TOKEN,
        SPOTIFY_LOCAL_URL,
        SLACK_TOKEN
    } = process.env;

    let message = 'no token...';
    if (SPOTIFY_USER_ACCESS_TOKEN) {
        message = 'It works!'
    }
    callback(null, response({
        message,
        body
    }));
}

module.exports = {
    handler
};

const { response } = require('./util/lambda')

function handler(event, context, callback) {

    const { body } = event;
    const { SPOTIFY_CLIENT_ID, SPOTIFY_SECRET } = process.env;

    callback(null, response({
        message: 'It works!',
        body: body
    }));
}

module.exports = {
    handler
};

Revert "verifying token is present. deploy test"