import _ from 'lodash';

function extractHrefs(htmlBody) {
  const matchHref = /href="([^\"]*)"/g;
  let results = [];
  let match;

  while (match = matchHref.exec(htmlBody)) {
    results.push(match[1]);
  }

  return results;
}

function toMatchInfo(hrefs) {
  const matchHistoryUrl = _.find(hrefs, href => {
    return href.search(/matchhistory/i) !== -1;
  });

  const replayUrl = _.find(hrefs, href => {
    return href.search(/replay.gg\/\?r=/i) !== -1;
  });

  const replayUnsubscribeUrl = _.find(hrefs, href => {
    return href.search(/replay.gg\/\?remove=/i) !== -1;
  });

  if (matchHistoryUrl && replayUrl && replayUnsubscribeUrl) {
    const matchId = parseInt(matchHistoryUrl.match(/\/(\d+)\//)[1], 10);

    return {
      matchId,
      matchHistoryUrl,
      replayUrl,
      replayUnsubscribeUrl,
    };
  }
}

export default function parseReplaygg(emailBody) {
  const matchInfo = toMatchInfo(extractHrefs(emailBody));

  if (!matchInfo) {
    console.error(`ERROR, Could not parse email:\n${emailBody}`);
  }

  return matchInfo;
}

import _ from 'lodash';

function extractHrefs(htmlBody) {
  const matchHref = /href="([^\"]*)"/g;
  let results = [];
  let match;

  while (match = matchHref.exec(htmlBody)) {
    results.push(match[1]);
  }

  return results;
}

function toMatchInfo(hrefs) {
  const matchHistoryUrl = _.find(hrefs, href => {
    return href.search(/matchhistory/i) !== -1;
  });

  const replayUrl = _.find(hrefs, href => {
    return href.search(/replay.gg\/\?r=/i) !== -1;
  });

  const replayUnsubscribeUrl = _.find(hrefs, href => {
    return href.search(/replay.gg\/\?remove=/i) !== -1;
  });

  if (matchHistoryUrl && replayUrl && replayUnsubscribeUrl) {
    const matchId = parseInt(matchHistoryUrl.match(/\/(\d+)\//)[1], 10);

    return {
      matchId,
      matchHistoryUrl,
      replayUrl,
      replayUnsubscribeUrl: replayUnsubscribeUrl.replace('&', '&'),
    };
  }
}

export default function parseReplaygg(emailBody) {
  const matchInfo = toMatchInfo(extractHrefs(emailBody));

  if (!matchInfo) {
    console.error(`ERROR, Could not parse email:\n${emailBody}`);
  }

  return matchInfo;
}

Replace HTML-encoded & with &


import React, { Component, PropTypes } from 'react'
import { Container, Segment, Menu, Header } from 'semantic-ui-react'
import { Link } from 'react-router'
import moment from 'moment'

const propTypes = {
  user: PropTypes.object
}

class UserPage extends Component {
  render() {
    const { user } = this.props
    if (!user) return null

    return (
      <Container>
        <Segment>
          <Menu secondary>
            <Menu.Item>
              <Header>
                <Link to={`/user/${user._id}`}>{user.username}</Link>
                <Header.Subheader>
                  Joined {moment(user.createdAt).fromNow()}
                </Header.Subheader>
              </Header>
            </Menu.Item>
            <Menu.Item
              as={Link}
              to={`/user/${user._id}/favorites`}
              name="Favorites"
              activeClassName="active"
            />
            <Menu.Item
              as={Link}
              to={`/user/${user._id}/uploads`}
              name="Uploads"
              activeClassName="active"
            />
            {/* <Menu.Item name='Settings' activeClassName="active" /> */}
          </Menu>
        </Segment>

        <Segment basic vertical>
          {this.props.children}
        </Segment>

      </Container>
    )
  }
}

UserPage.propTypes = propTypes

export default UserPage

import React, { Component, PropTypes } from 'react'
import { Container, Segment, Menu, Header, Label } from 'semantic-ui-react'
import { Link } from 'react-router'
import moment from 'moment'

const propTypes = {
  user: PropTypes.object
}

class UserPage extends Component {
  render() {
    const { user } = this.props
    if (!user) return null

    return (
      <Container>
        <Segment>
          <Menu secondary>
            <Menu.Item>
              <Header>
                <Link to={`/user/${user._id}`}>{user.username}</Link>
                <Header.Subheader>
                  Joined {moment(user.createdAt).fromNow()}
                </Header.Subheader>
              </Header>
            </Menu.Item>
            <Menu.Item
              as={Link}
              to={`/user/${user._id}/favorites`}
              name="Favorites"
              activeClassName="active"
            >
              Favorites
              <Label color="yellow" content={user.favorites.length} floating />
            </Menu.Item>
            <Menu.Item
              as={Link}
              to={`/user/${user._id}/uploads`}
              name="Uploads"
              activeClassName="active"
            >
              Uploads
              <Label color="teal" content={user.screenshots.length} floating />

            </Menu.Item>
            {/* <Menu.Item name='Settings' activeClassName="active" /> */}
          </Menu>
        </Segment>

        <Segment basic vertical>
          {this.props.children}
        </Segment>

      </Container>
    )
  }
}

UserPage.propTypes = propTypes

export default UserPage

Add user page favorites and uploads number indicator


import { expect } from 'chai';

import { launchNewNotebook } from '../../build/main/launch';

var ipc = require('ipc');

describe('launch', () => {
  describe('launchNewNotebook', () => {
    it('launches a window', function() {
      // The render window may take a while to display on slow machines.
      // Set the timeout to 5s
      this.timeout(5000);

      return launchNewNotebook().then(win => {
        expect(win).to.not.be.undefined;
        return new Promise(resolve => {

          // Query selector all and make sure the length for .cells is
          // greater than 0.  Use IPC to return the results from the
          // render window.
          ipc.on('queryLength', function (event, value) {
            expect(value).to.be.greaterThan(0);
            resolve();
          });
          win.webContents.executeJavaScript(`
            var ipc = require('ipc');
            ipc.send('queryLength', document.querySelectorAll('div.cell').length);
          `);
        });
      });
    });
  });
});

import { expect } from 'chai';

import { launchNewNotebook } from '../../build/main/launch';
import {ipcMain as ipc} from 'electron';

describe('launch', () => {
  describe('launchNewNotebook', () => {
    it('launches a window', function() {
      // The render window may take a while to display on slow machines.
      // Set the timeout to 5s
      this.timeout(5000);

      return launchNewNotebook().then(win => {
        expect(win).to.not.be.undefined;
        return new Promise(resolve => {

          // Query selector all and make sure the length for .cells is
          // greater than 0.  Use IPC to return the results from the
          // render window.
          ipc.on('queryLength', function (event, value) {
            expect(value).to.be.greaterThan(0);
            resolve();
          });
          win.webContents.executeJavaScript(`
            var ipc = require('ipc');
            ipc.send('queryLength', document.querySelectorAll('div.cell').length);
          `);
        });
      });
    });
  });
});

Update import in response to deprecation warning


/**
 * Include this file _after_ the main backbone-forms file to override the default templates.
 * You only need to include templates you want to override.
 *
 * Requirements when customising templates:
 * - Each template must have one 'parent' element tag.
 * - "data-type" attributes are required.
 * - The main placeholder tags such as {{fieldsets}}, {{fields}}, {{editor}} are required.
 */
;(function() {
  var Form = Backbone.Form;

  {{body}}

})();

/**
 * Include this file _after_ the main backbone-forms file to override the default templates.
 * You only need to include templates you want to override.
 *
 * Requirements when customising templates:
 * - Each template must have one 'parent' element tag.
 * - "data-type" attributes are required.
 * - The main placeholder tags such as the following are required: fieldsets, fields
 */
;(function() {
  var Form = Backbone.Form;

  {{body}}

})();

Replace template tags that were interfering


/* eslint-disable max-len,func-names */
import { chai, expect } from './spec_helper'
import app from '../src/server-iso'
import { fetchOauthToken } from '../oauth'

describe('isomorphically rendering on the server', function () {
  this.timeout(5000)

  it('isomorphically renders the homepage', (done) => {
    fetchOauthToken(() => {
      chai
      .request(app)
      .get('/')
      .end((err, res) => {
        expect(err).to.be.null
        expect(res).to.have.status(200)
        expect(res).to.be.html
        // This gets rendered in with iso, not as part of the template
        expect(res.text).to.contain(' {
    fetchOauthToken(() => {
      chai
      .request(app)
      .get('/')
      .set('X-Skip-Prerender', true)
      .end((err, res) => {
        expect(err).to.be.null
        expect(res).to.have.status(200)
        expect(res).to.be.html
        expect(res.text).to.contain('

')
        done()
      })
    })
  })
})

/* eslint-disable max-len,func-names */
import { chai, expect } from './spec_helper'
import app from '../src/server-iso'
import { fetchOauthToken } from '../oauth'
import jsdom from 'jsdom'

describe('isomorphically rendering on the server', function () {
  this.timeout(5000)

  it('isomorphically renders the homepage', (done) => {
    fetchOauthToken(() => {
      chai
      .request(app)
      .get('/')
      .end((err, res) => {
        expect(err).to.be.null
        expect(res).to.have.status(200)
        expect(res).to.be.html
        const document = jsdom.jsdom(res.text)
        expect(document.querySelectorAll('main')).to.have.lengthOf(1)
        done()
      })
    })
  })

  it('does not isomorphically render when X-Skip-Prerender is true', (done) => {
    fetchOauthToken(() => {
      chai
      .request(app)
      .get('/')
      .set('X-Skip-Prerender', true)
      .end((err, res) => {
        expect(err).to.be.null
        expect(res).to.have.status(200)
        expect(res).to.be.html
        const document = jsdom.jsdom(res.text)
        expect(document.querySelectorAll('main')).to.have.lengthOf(0)
        done()
      })
    })
  })
})

Move iso tests to jsdom


module.exports = {

    handleResponseCode: function(response) {
        var status = parseInt(response.status, 10);
        if (status >= 400) {
            throw new Error(`Invalid response: ${status} ${response.statusText}`);
        }
        return response;
    }

};

module.exports = {

    handleResponseCode: function(response) {
        var status = parseInt(response.status, 10);
        if (status >= 400) {
            throw new Error("Invalid response: " + status + " " + response.statusText);
        }
        return response;
    }

};

Remove incompatible code for 0.12


var xbmc = require('xbmc');

exports.name = 'task.xbmc';
exports.version = '1.0.0';

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

  function run(job, done) {

    var connection = new xbmc.TCPConnection({
      host: '10.0.0.53',
      port: 9090,
      verbose: true
    });

    var api = new xbmc.XbmcApi();

    api.setConnection(connection);

    api.message(
      'RING RING RING!',
      'Doorbell - Side Gate',
      10000
    );

    return done();
  }

  app.task.define('Notify XBMC', options || {}, run);

  next();
};

var xbmc = require('xbmc');

exports.name = 'task.xbmc';
exports.version = '1.0.0';

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

  function run(job, done) {

    var connection = new xbmc.TCPConnection({
      host: '10.0.0.53',
      port: 9090,
      verbose: false
    });

    var api = new xbmc.XbmcApi();

    api.setConnection(connection);

    api.message(
      'RING RING RING!',
      'Doorbell - Side Gate',
      10000
    );

    return done();
  }

  app.task.define('Notify XBMC', options || {}, run);

  next();
};

Disable verbose mode on XBMC connection


import { PLUGIN_TYPE } from 'rotonde-plugin';

export const TYPE = PLUGIN_TYPE.ISOMORPHIC;
export { default as ChatClientPlugin } from './client';
export { default as ChatServerPlugin } from './server';

Set the type of rotonde-plugin-chat


/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 */
import _ from 'lodash';
import { connect } from 'react-redux';
import { fetchMetersDetailsIfNeeded } from '../../actions/meters';
import { fetchGroupsDetailsIfNeeded, changeDisplayedGroups } from '../../actions/groups';
import GroupMainComponent from '../../components/groups/GroupMainComponent';

function mapStateToProps(state) {
	const sortedGroups = _.sortBy(_.values(state.groups.byGroupID).map(group => ({ id: group.id, name: group.name.trim() })), 'name');
	const sortedMeters = _.sortBy(_.values(state.meters.byMeterID).map(meter => ({ id: meter.id, name: meter.name.trim() })), 'name');
	const selectGroups = undefined ? [] : state.groups.selectedGroups;
	return {
		groups: sortedGroups,
		meters: sortedMeters,
		selectedGroups: selectGroups,
		displayMode: state.groups.displayMode
	};
}

function mapDispatchToProps(dispatch) {
	return {
		selectGroups: newSelectedGroupIDs => dispatch(changeDisplayedGroups(newSelectedGroupIDs)),
		fetchGroupsDetailsIfNeeded: () => dispatch(fetchGroupsDetailsIfNeeded()),
		fetchMetersDetailsIfNeeded: () => dispatch(fetchMetersDetailsIfNeeded()),
	};
}

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

/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 */
import _ from 'lodash';
import { connect } from 'react-redux';
import { fetchMetersDetailsIfNeeded } from '../../actions/meters';
import { fetchGroupsDetailsIfNeeded, changeDisplayedGroups } from '../../actions/groups';
import GroupMainComponent from '../../components/groups/GroupMainComponent';

function mapStateToProps(state) {
	const sortedGroups = _.sortBy(_.values(state.groups.byGroupID).map(group => ({ id: group.id, name: group.name.trim() })), 'name');
	const sortedMeters = _.sortBy(_.values(state.meters.byMeterID).map(meter => ({ id: meter.id, name: meter.name.trim() })), 'name');
	const selectGroups = state.groups.selectedGroups;
	return {
		groups: sortedGroups,
		meters: sortedMeters,
		selectedGroups: selectGroups,
		displayMode: state.groups.displayMode
	};
}

function mapDispatchToProps(dispatch) {
	return {
		selectGroups: newSelectedGroupIDs => dispatch(changeDisplayedGroups(newSelectedGroupIDs)),
		fetchGroupsDetailsIfNeeded: () => dispatch(fetchGroupsDetailsIfNeeded()),
		fetchMetersDetailsIfNeeded: () => dispatch(fetchMetersDetailsIfNeeded()),
	};
}

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

Remove useless ternary operator over undefined


var assert = require('assert');

require('./support/start')(function(command) {
    exports['test abilities endpoint'] = function() {
        assert.response(command.servers['Core'],
            { url: '/assets/tilemill/js/abilities.js' },
            { status: 200 },
            function(res) {
                var body = res.body.replace(/^\s*var\s+abilities\s*=\s*(.+?);?$/, '$1');
                var abilities = JSON.parse(body);

                assert.ok(abilities.fonts.indexOf('Arial Regular') >= 0 ||
                          abilities.fonts.indexOf('DejaVu Sans Book') >= 0);
                assert.deepEqual([0,206,209], abilities.carto.colors.darkturquoise);
                assert.deepEqual([
                    "background-color",
                    "background-image",
                    "srs",
                    "buffer-size",
                    "base",
                    "paths-from-xml",
                    "minimum-version",
                    "font-directory"
                ], Object.keys(abilities.carto.symbolizers.map));

                assert.deepEqual({
                    mbtiles: true,
                    png: true,
                    pdf: true,
                    svg: true
                }, abilities.exports);
            }
        );
    };
});

var assert = require('assert');

require('./support/start')(function(command) {
    exports['test abilities endpoint'] = function() {
        assert.response(command.servers['Core'],
            { url: '/assets/tilemill/js/abilities.js' },
            { status: 200 },
            function(res) {
                var body = res.body.replace(/^\s*var\s+abilities\s*=\s*(.+?);?$/, '$1');
                var abilities = JSON.parse(body);

                assert.ok(/v\d+.\d+.\d+-\d+-[a-z0-9]+/.test(abilities.version[0]));
                assert.ok(/\d+.\d+.\d+.\d+/.test(abilities.version[1]));
                assert.ok(abilities.fonts.indexOf('Arial Regular') >= 0 ||
                          abilities.fonts.indexOf('DejaVu Sans Book') >= 0);
                assert.deepEqual([0,206,209], abilities.carto.colors.darkturquoise);
                assert.deepEqual([
                    "background-color",
                    "background-image",
                    "srs",
                    "buffer-size",
                    "base",
                    "paths-from-xml",
                    "minimum-version",
                    "font-directory"
                ], Object.keys(abilities.carto.symbolizers.map));

                assert.deepEqual({
                    mbtiles: true,
                    png: true,
                    pdf: true,
                    svg: true
                }, abilities.exports);
            }
        );
    };
});

Test for version strings in abilities.


import React from 'react'
import { connect } from 'react-redux'
import { openCommentThreads, selectCommentThread } from 'redux/actions.js'

function mapStateToProps(state, { contentState, children }) {
  let commentThreadId = getFirstThreadId(contentState, children[0])
  return {
    cardId: state.commentThreadsById[commentThreadId].cardId,
    commentThreadId,
  }
}

function mergeProps(
  {cardId, commentThreadId},
  {openCommentThreads, selectCommentThread},
  {children}
) {
  return {
    onClick: () => {
      openCommentThreads(cardId) &&
        selectCommentThread(parseInt(commentThreadId, 10))
    },
    children: children.length > 0
      && React.cloneElement(children[0], {forceSelection: true}),
  }
}

const CommentThreadEntity = ({ onClick, children }) => {
  return <span onClick={onClick}>
    {children}
  </span>
}

export default connect(
  mapStateToProps,
  { openCommentThreads, selectCommentThread },
  mergeProps,
)(CommentThreadEntity)

function getFirstThreadId(contentState, leaf) {
  const styles = contentState.getBlockForKey(leaf.props.blockKey)
    .getInlineStyleAt(leaf.props.start)
  const ids = styles.map(s => s.match(/thread--([0-9]+)/))
    .filter(s => s && s[1])
    .map(s => s[1])
  return ids.count() > 0 ? ids.toJS()[0] : null
}

import React from 'react'
import { connect } from 'react-redux'
import { openCommentThreads, selectCommentThread } from 'redux/actions.js'

function mapStateToProps(state, { contentState, children }) {
  let commentThreadId = getFirstThreadId(contentState, children[0])
  return {
    cardId: state.commentThreadsById[commentThreadId].cardId,
    disabled: state.ui.acceptingSelection,
    commentThreadId,
  }
}

function mergeProps(
  {cardId, commentThreadId, disabled},
  {openCommentThreads, selectCommentThread},
  {children}
) {
  return {
    onClick: () => {
      disabled ||
      ( openCommentThreads(cardId) &&
        selectCommentThread(parseInt(commentThreadId, 10)) )
    },
    children: children.length > 0
      && React.cloneElement(children[0], {forceSelection: true}),
  }
}

const CommentThreadEntity = ({ onClick, children }) => {
  return <span onClick={onClick}>
    {children}
  </span>
}

export default connect(
  mapStateToProps,
  { openCommentThreads, selectCommentThread },
  mergeProps,
)(CommentThreadEntity)

function getFirstThreadId(contentState, leaf) {
  const styles = contentState.getBlockForKey(leaf.props.blockKey)
    .getInlineStyleAt(leaf.props.start)
  const ids = styles.map(s => s.match(/thread--([0-9]+)/))
    .filter(s => s && s[1])
    .map(s => s[1])
  return ids.count() > 0 ? ids.toJS()[0] : null
}

Disable clicking on thread underline when accepting selection


var assert   = require("assert");
var schedule = require("../../js/debug/schedule");
var isNodeJS = typeof process !== "undefined" && typeof process.execPath === "string";

describe.only("schedule", function () {
    if (isNodeJS) {
        describe("for Node.js", function () {
            it("should preserve the active domain", function (done) {
                var domain       = require("domain");
                var activeDomain = domain.create();

                activeDomain.run(function () {
                    schedule(function () {
                        assert(domain.active);
                        assert.equal(domain.active, activeDomain);

                        done();
                    });
                });
            });
        });
    }
});

var assert   = require("assert");
var schedule = require("../../js/debug/schedule");
var isNodeJS = typeof process !== "undefined" && typeof process.execPath === "string";

describe("schedule", function () {
    if (isNodeJS) {
        describe("for Node.js", function () {
            it("should preserve the active domain", function (done) {
                var domain       = require("domain");
                var activeDomain = domain.create();

                activeDomain.run(function () {
                    schedule(function () {
                        assert(domain.active);
                        assert.equal(domain.active, activeDomain);

                        done();
                    });
                });
            });
        });
    }
});

Remove describe.only as it blocks other tests to run


/* eslint-disable react/forbid-prop-types */
/* eslint-disable import/prefer-default-export */
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

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

/**
 * TouchableWithoutFeedback doesn't have a style prop. View doesn't have an onPress
 * Prop. This hack ensures events don't propogate to the parent, styling stays consistent
 * and no feedback (i.e. gesture echo) is given to the user.
 */
const TouchableNoFeedback = ({ children, style }) => (
  <TouchableWithoutFeedback onPress={() => {}}>
    <View style={style}>{children}</View>
  </TouchableWithoutFeedback>
);

TouchableNoFeedback.defaultProps = {
  style: null,
};

TouchableNoFeedback.propTypes = {
  children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired,
  style: PropTypes.object,
};

export default TouchableNoFeedback;

/* eslint-disable react/forbid-prop-types */
/* eslint-disable import/prefer-default-export */
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

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

/**
 * TouchableWithoutFeedback doesn't have a style prop. View doesn't have an onPress
 * Prop. This hack ensures events don't propogate to the parent, styling stays consistent
 * and no feedback (i.e. gesture echo) is given to the user.
 */
const onPressNoOp = () => {};

const TouchableNoFeedback = ({ children, style, ...touchableProps }) => (
  <TouchableWithoutFeedback onPress={onPressNoOp} {...touchableProps}>
    <View style={style}>{children}</View>
  </TouchableWithoutFeedback>
);

TouchableNoFeedback.defaultProps = {
  style: null,
};

TouchableNoFeedback.propTypes = {
  children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired,
  style: PropTypes.object,
};

export default TouchableNoFeedback;

Add constant defined no op on press


const BaseChecker = require('./../base-checker')
const naming = require('./../../common/identifier-naming')

const ruleId = 'const-name-snakecase'
const meta = {
  type: 'naming',

  docs: {
    description: 'Constant name must be in capitalized SNAKE_CASE.',
    category: 'Style Guide Rules'
  },

  isDefault: false,
  recommended: true,
  defaultSetup: 'warn',

  schema: []
}

class ConstNameSnakecaseChecker extends BaseChecker {
  constructor(reporter) {
    super(reporter, ruleId, meta)
  }

  StateVariableDeclaration(node) {
    // as of time of this writing: despite being an array, `variables` is always returning one single element.
    if (node.variables[0].isDeclaredConst) {
      this.validateConstantName(node.variables[0])
    }
  }

  validateConstantName(variable) {
    if (naming.isNotUpperSnakeCase(variable.name)) {
      this.error(variable, 'Constant name must be in capitalized SNAKE_CASE')
    }
  }
}

module.exports = ConstNameSnakecaseChecker

const BaseChecker = require('./../base-checker')
const naming = require('./../../common/identifier-naming')

const ruleId = 'const-name-snakecase'
const meta = {
  type: 'naming',

  docs: {
    description: 'Constant name must be in capitalized SNAKE_CASE.',
    category: 'Style Guide Rules'
  },

  isDefault: false,
  recommended: true,
  defaultSetup: 'warn',

  schema: []
}

class ConstNameSnakecaseChecker extends BaseChecker {
  constructor(reporter) {
    super(reporter, ruleId, meta)
  }

  VariableDeclaration(node) {
    if (node.isDeclaredConst) {
      this.validateConstantName(node)
    }
  }

  validateConstantName(variable) {
    if (naming.isNotUpperSnakeCase(variable.name)) {
      this.error(variable, 'Constant name must be in capitalized SNAKE_CASE')
    }
  }
}

module.exports = ConstNameSnakecaseChecker

Use `VariableDeclaration` node type instead of `StateVariableDeclaration`


if (typeof wysihtml5ParserRules === 'undefined') {
  var wysihtml5ParserRules = {
    classes: {
      "wysiwyg-color-gold": 1
    },
    tags: {
      "b": 1,
      "strong": { "rename_tag": "b" },
      "i": 1,
      "h1": 1,
      "p": 1,
      "span": 1,
      "ul": 1,
      "li": 1
    }
  }
}

var Select2Trigger = Class.extend({
  init: function(jq_obj){
    jq_obj.select2({ tags: true });
  }
});

var RichTextEditor = Class.extend({
  init: function($textarea){
    self.$form = $textarea.parents("form");
    self.$toolbar = self.$form.find('[data-pulitzer-toolbar]');
    self.editor = new wysihtml5.Editor($textarea[0], {
      toolbar: self.$toolbar[0],
      parserRules:  wysihtml5ParserRules
    });
  }
})

$(document).ready(function(){
  window.any_time_manager.registerList([ 'select2-trigger', 'rich-text-editor' ]);
  window.any_time_manager.load();
});

if (typeof wysihtml5ParserRules === 'undefined') {
  var wysihtml5ParserRules = {
    classes: {
      "wysiwyg-color-gold": 1
    },
    tags: {
      "b": 1,
      "strong": { "rename_tag": "b" },
      "i": 1,
      "h1": 1,
      "p": 1,
      "span": 1,
      "ul": 1,
      "li": 1
    }
  }
}

if (typeof wysihtml5Stylesheets === 'undefined') {
  var wysihtml5Stylesheets = []
}

var Select2Trigger = Class.extend({
  init: function(jq_obj){
    jq_obj.select2({ tags: true });
  }
});

var RichTextEditor = Class.extend({
  init: function($textarea){
    self.$form = $textarea.parents("form");
    self.$toolbar = self.$form.find('[data-pulitzer-toolbar]');
    self.editor = new wysihtml5.Editor($textarea[0], {
      toolbar: self.$toolbar[0],
      stylesheets: wysihtml5Stylesheets,
      parserRules: wysihtml5ParserRules
    });
  }
})

$(document).ready(function(){
  window.any_time_manager.registerList([ 'select2-trigger', 'rich-text-editor' ]);
  window.any_time_manager.load();
});

Set default value for stylesheets


/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

(function () {
    'use strict';

    describe('Unit: NotesTabController', function () {
        //Load the module with NotesTabController
        beforeEach(module('trackerApp'));

        var ctrl, scope;
        var $httpBackend, createController, authRequestHandler;

        // inject the $controller and $rootScope services
        // in the beforeEach block\

        beforeEach(inject(function ($controller, $rootScope, $injector) {
            // Set up the mock http service responses
            $httpBackend = $injector.get('$httpBackend');

            // backend definition common for all tests
            authRequestHandler = $httpBackend.when('GET', 'exampleNotes')
                    .respond();

            //Create a new scope that's a child of the $rootScope
            scope = $rootScope.$new();

            //Create the controller
            ctrl = $controller('NotesTabController', {$scope: scope});

        }));

        it('Should define $scope.notesTabText',
                function () {
                    expect(scope.notesTabText).toBeDefined();
                    scope.clearNotesTab();
                    expect(scope.notesTabText).toEqual("");
                    $httpBackend.flush();

                }

        );

        it('should fetch notes.json', function () {
            $httpBackend.expectGET('exampleNotes');
            var controller = ctrl;
            $httpBackend.flush();
        });

        afterEach(function () {
            $httpBackend.verifyNoOutstandingExpectation();
            $httpBackend.verifyNoOutstandingRequest();
        })

    });

})();

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

(function () {
    'use strict';

    describe('Unit: NotesTabController', function () {
        //Load the module with NotesTabController
        beforeEach(module('trackerApp'));

        var ctrl, scope;
        var $httpBackend, createController, authRequestHandler;

        // inject the $controller and $rootScope services
        // in the beforeEach block\

        beforeEach(inject(function ($controller, $rootScope, $injector,$http) {
            // Set up the mock http service responses
            $httpBackend = $injector.get('$httpBackend');

            // backend definition common for all tests
            authRequestHandler = $httpBackend.when('GET', 'exampleNotes')
                    .respond([{}]);

            //Create a new scope that's a child of the $rootScope
            scope = $rootScope.$new();

            //Create the controller
            ctrl = $controller('NotesTabController', {$scope: scope,$http: $http});

        }));

        it('Should define $scope.notesTabText',
                function () {
                    expect(scope.notesTabText).toBeDefined();
                    scope.clearNotesTab();
                    expect(scope.notesTabText).toEqual("");
                    $httpBackend.flush();

                }

        );

        it('should fetch notes.json', function () {
            $httpBackend.expectGET('exampleNotes');
            var controller = ctrl;
            $httpBackend.flush();
        });

it("should have 1 object", function () {
            $httpBackend.flush();
            expect(scope.notesTabText.length).toBe(1);
        });

        afterEach(function () {
            $httpBackend.verifyNoOutstandingExpectation();
            $httpBackend.verifyNoOutstandingRequest();
        })

    });

})();

Add $httpbackend test to test notesTabController


define([

  'App.config',
  'App.models.Program',
  'backbone'

], function(config, ProgramModel, Backbone) {

  'use strict';

  return Backbone.Collection.extend({

    model : ProgramModel,
    url   : config.data.programs,

    parse: function(res) {
      return res.programs;
    },

    findByCandidate: function(id) {
      return this.find(function(model) {
        if (model.get('candidate')) return model.get('candidate').id === id;
      });
    },

    findByTheme: function(id) {
      var models = [];
      this.each(function(model) {
        var projects = _.filter(model.get('projects'), function(project) {
          return project.theme.id === id;
        });
        if (projects.length > 0) {
          model.attributes.projects = projects;
          models.push(model);
        }
      });
      return new this.constructor(models);
    },

    findByThemeAndGroupByCandidate: function(id) {
      var models = this.findByTheme(id);
      models = new this.constructor(models.shuffle());
      models = models.groupBy(function(m) { return m.get('candidate').name; });
      Object.keys(models).forEach(function(key) {
        models[key] = models[key][0].toJSON();
      });
      return models;
    },

    candidateProjects: function(id) {
      var model = this.findByCandidate(id);
      if (!model) return;
      var projects = model.get('projects');
      return _.groupBy(projects, function(obj) { return obj.theme.name; });
    }
  });
});

define([

  'backbone',
  'App.config',
  'App.models.Program'

], function(Backbone, config, ProgramModel) {

  'use strict';

  return Backbone.Collection.extend({

    model : ProgramModel,
    url   : config.data.programs,

    findByCandidate: function(id) {
      return this.find(function(model) {
        if (model.get('candidate')) return model.get('candidate').id === id;
      });
    },

    findByTheme: function(id) {
      var models = [];
      this.each(function(model) {
        var projects = _.filter(model.get('projects'), function(project) {
          return project.theme.id === id;
        });
        if (projects.length > 0) {
          model.attributes.projects = projects;
          models.push(model);
        }
      });
      return new this.constructor(models);
    },

    findByThemeAndGroupByCandidate: function(id) {
      var models = this.findByTheme(id);
      models = new this.constructor(models.shuffle());
      models = models.groupBy(function(m) { return m.get('candidate').name; });
      Object.keys(models).forEach(function(key) {
        models[key] = models[key][0].toJSON();
      });
      return models;
    },

    candidateProjects: function(id) {
      var model = this.findByCandidate(id);
      if (!model) return;
      var projects = model.get('projects');
      return _.groupBy(projects, function(obj) { return obj.theme.name; });
    }
  });
});

Remove parse in Progam collection.


Template.dash.total = function(){
  var total = 0;
  Expenses.find().map(function(doc){
    total += doc.ammount;
  });
  return total;
};
Template.dash.me = function(){
  var total = 0;
  Expenses.find().map(function(doc){
    if (doc.payer === Meteor.userId()) {
      total -= doc.ammount;
    }
    if (doc.owers.indexOf(Meteor.userId()) != -1){
      total += doc.ammount / doc.owers.length;
    }
  });
  return total;
};

Template.dash.getOwes = function(){
  var result = {};
  // For each expense where I owe money...
  Expenses.find({owers : Meteor.userId()}).map(function (doc) {
    // ...subtract my share of the expense
    result[doc.payer] = (result[doc.payer]|0) - (doc.ammount / doc.owers.length);
  });
  // For each expense where I paid...
  Expenses.find({payer: Meteor.userId()}).map(function (doc) {
    // ...and for each person that is splitting the cost...
    doc.owers.forEach(function(ower){
      // ...add their share of the expense
      result[ower] = (result[ower]|0) + (doc.ammount / doc.owers.length);
    });
  });

  var objs = [];
  for(var id in result){
    if (result[id]){
        objs.push({user: id, ammount: result[id]});
      }
  }
  return objs;
}

Template.dash.total = function(){
  var total = 0;
  Expenses.find().map(function(doc){
    total += doc.ammount;
  });
  return total;
};
Template.dash.me = function(){
  var total = 0;
  Expenses.find().map(function(doc){
    if (doc.payer === Meteor.userId()) {
      total -= doc.ammount;
    }
    if (doc.owers.indexOf(Meteor.userId()) != -1){
      total += doc.ammount / doc.owers.length;
    }
  });
  return total;
};

Template.dash.getOwes = function(){
  var result = {};
  // For each expense where I owe money...
  Expenses.find({owers : Meteor.userId()}).map(function (doc) {
    // ...subtract my share of the expense
    result[doc.payer] = (result[doc.payer]||0) - (doc.ammount / doc.owers.length);
  });
  // For each expense where I paid...
  Expenses.find({payer: Meteor.userId()}).map(function (doc) {
    // ...and for each person that is splitting the cost...
    doc.owers.forEach(function(ower){
      // ...add their share of the expense
      result[ower] = (result[ower]||0) + (doc.ammount / doc.owers.length);
    });
  });

  var objs = [];
  for(var id in result){
    if (result[id]){
        objs.push({user: id, ammount: result[id]});
      }
  }
  return objs;
}

Fix numbers cast to int


import React from 'react';
import { InitialSearchContainer } from '../containers';

const HomePage = () => (

Todas las respuestas en un solo lugar

    <InitialSearchContainer />

);

export default HomePage;

import React from 'react';
import { InitialSearchContainer } from '../containers';

const HomePage = () => (

Encuentra ejercicios resueltos de los libros de ingeniería que necesitas

    <InitialSearchContainer />

);

export default HomePage;

Change slogan y home page


import React from 'react';
import styled from '@emotion/styled';

const Content = styled.div`
  margin: 0 0 30px;
  text-align: center;
  color: #666;
`;

const Title = styled.h2`
  margin: 0 0 5px;
  text-align: center;
  font-family: 'noe', serif;
  font-size: 26px;
  font-weight: bold;
  color: #444;
`;

export default ({title, children}) => (
  <Content>
    <Title dangerouslySetInnerHTML={{__html: title}} />
    {children}
  </Content>
);

import React from 'react';
import styled from '@emotion/styled';

const Content = styled.div`
  margin: 0 0 30px;
  padding: 0 10px;
  text-align: center;
  color: #666;
`;

const Title = styled.h2`
  margin: 0 0 5px;
  text-align: center;
  font-family: 'noe', serif;
  font-size: 26px;
  font-weight: bold;
  color: #444;
`;

export default ({title, children}) => (
  <Content>
    <Title dangerouslySetInnerHTML={{__html: title}} />
    {children}
  </Content>
);

Add side padding to subtitles


import React, { useState, useEffect } from 'react';
import { c } from 'ttag';
import PropTypes from 'prop-types';

const SubSidebar = ({ list = [], children }) => {
    const clean = (h = '') => h.replace(/#/g, '');
    const [hash, setHash] = useState(clean(location.hash));
    const onHashChange = () => setHash(clean(location.hash));

    useEffect(() => {
        window.addEventListener('hashchange', onHashChange);
        return () => {
            window.removeEventListener('hashchange', onHashChange);
        };
    }, []);

    return (

{c('Title').t`Jump to`}

                    {list.map(({ id = '', text }) => {
                        const isCurrent = hash === id;
                        return (

                                <a
                                    href={`${location.pathname}#${id}`}
                                    className="subnav-link"
                                    disabled={isCurrent}
                                    aria-current={isCurrent}
                                >
                                    {text}

                        );
                    })}

{children}

    );
};

SubSidebar.propTypes = {
    list: PropTypes.array,
    children: PropTypes.node
};

export default SubSidebar;

import React, { useState, useEffect } from 'react';
import { c } from 'ttag';
import PropTypes from 'prop-types';

const SubSidebar = ({ list = [], children }) => {
    const clean = (h = '') => h.replace(/#/g, '');
    const [hash, setHash] = useState(clean(location.hash));
    const onHashChange = () => setHash(clean(location.hash));

    useEffect(() => {
        window.addEventListener('hashchange', onHashChange);
        return () => {
            window.removeEventListener('hashchange', onHashChange);
        };
    }, []);

    return (

{c('Title').t`Navigation`}

                    {list.map(({ id = '', text }) => {
                        const isCurrent = hash === id;
                        return (

                                <a
                                    href={`${location.pathname}#${id}`}
                                    className="subnav-link"
                                    disabled={isCurrent}
                                    aria-current={isCurrent}
                                >
                                    {text}

                        );
                    })}

{children}

    );
};

SubSidebar.propTypes = {
    list: PropTypes.array,
    children: PropTypes.node
};

export default SubSidebar;

Update title for the sub sidebar


import React from 'react';
import PropTypes from 'prop-types';
import styles from './boardCard.css';

const BoardCard = ({
  alt,
  name,
  role,
  src,
  description
}) => (

      {name}

      Role:  {role}
      {description !== null &&
{description}
}

);

BoardCard.propTypes = {
  name: PropTypes.string.isRequired,
  src: PropTypes.string.isRequired,
  alt: PropTypes.string.isRequired,
  role: PropTypes.string.isRequired,
  description: PropTypes.string
};

BoardCard.defaultProps = {
  description: null
};

export default BoardCard;

import React from 'react';
import PropTypes from 'prop-types';
import styles from './boardCard.css';

const BoardCard = ({
  alt,
  name,
  role,
  src,
  description
}) => (

      {name}

      Role:  {role}
      {description &&
{description}
}

);

BoardCard.propTypes = {
  name: PropTypes.string.isRequired,
  src: PropTypes.string.isRequired,
  alt: PropTypes.string.isRequired,
  role: PropTypes.string.isRequired,
  description: PropTypes.string
};

BoardCard.defaultProps = {
  description: null
};

export default BoardCard;

Make conditional rendering even shorter syntax


import defineClasses from '../util/defineClasses'

export default function() {
  return defineClasses({
    'wrap': { 'white-space': 'normal' },
    'no-wrap': { 'white-space': 'nowrap' },
    'pre': { 'white-space': 'pre' },
    'pre-line': { 'white-space': 'pre-line' },
    'pre-wrap': { 'white-space': 'pre-wrap' },
    'break-words': { 'word-wrap': 'break-word' },
    'break-normal': { 'word-wrap': 'normal' },
    'truncate': {
      'overflow': 'hidden',
      'text-overflow': 'ellipsis',
      'white-space': 'nowrap',
    },
  })
}

import defineClasses from '../util/defineClasses'

export default function() {
  return defineClasses({
    'white-space-normal': { 'white-space': 'normal' },
    'white-space-nowrap': { 'white-space': 'nowrap' },
    'white-space-pre': { 'white-space': 'pre' },
    'white-space-pre-line': { 'white-space': 'pre-line' },
    'white-space-pre-wrap': { 'white-space': 'pre-wrap' },

    'break-words': { 'word-wrap': 'break-word' },
    'break-normal': { 'word-wrap': 'normal' },

    'truncate': {
      'overflow': 'hidden',
      'text-overflow': 'ellipsis',
      'white-space': 'nowrap',
    },
  })
}

Use grim uncreative white-space utility names


import { expeditions } from 'constants/expeditions';

const RECENT_EXPEDITIONS_LENGTH = 4;

export function findExpedition(key) {
  return expeditions[key] ? expeditions[key] : expeditions.DEFAULT;
}

export const expeditionsInGroup = (group, allWorkflows) =>
  allWorkflows.filter(e => e.display_name.startsWith(group));

export function recentExpeditions(allWorkflows, classifications) {
  const recent = classifications.map(c => c.links.workflow)
    .filter((v, i, self) => self.indexOf(v) === i)
    .map(id => {
      const workflow = allWorkflows.find(w => w.id === id);
      const expedition = findExpedition(workflow.display_name);
      expedition.id = workflow.id;
      expedition.active = workflow.active;
      return expedition;
    });
  if (recent.length > RECENT_EXPEDITIONS_LENGTH) { recent.length = RECENT_EXPEDITIONS_LENGTH; }
  return recent;
}

import { expeditions } from 'constants/expeditions';

const RECENT_EXPEDITIONS_LENGTH = 4;

export function findExpedition(key) {
  return expeditions[key] ? expeditions[key] : expeditions.DEFAULT;
}

export const expeditionsInGroup = (group, allWorkflows) =>
  allWorkflows.filter(e => e.display_name.startsWith(group));

export function recentExpeditions(allWorkflows, classifications) {
  const recent = classifications.map(c => c.links.workflow)
    .filter((v, i, self) => self.indexOf(v) === i)
    .map(id => {
      const workflow = allWorkflows.find(w => w.id === id);
      if (workflow) {
        const expedition = findExpedition(workflow.display_name);
        expedition.id = workflow.id;
        expedition.active = workflow.active;
        return expedition;
      }
    });
  if (recent.length > RECENT_EXPEDITIONS_LENGTH) { recent.length = RECENT_EXPEDITIONS_LENGTH; }
  return recent;
}

Add another check for missing workflow


import Text from './text'
import Browser from '../browser';

describe('Text', () => {
  describe('clearSelection', () => {
    describe('when document.body.createTextRange is defined', () => {
      it('calls collapse and select on the text range', () => {
        const mockTextRange = {
          collapse() {
            console.log('range.collapse()');
          },
          select() {
            console.log('range.select()');
          }
        };

        const mockDocument = {
          body: {
            createTextRange() {
              console.log('createTextRange called');
              return mockTextRange;
            }
          }
        };

        spyOn(mockTextRange, 'collapse');
        spyOn(mockTextRange, 'select');

        spyOn(Browser, 'getDocument').and.returnValue(mockDocument);

        Text.clearSelection();

        expect(mockTextRange.collapse).toHaveBeenCalled();
        expect(mockTextRange.select).toHaveBeenCalled();
      });
    });

    describe('when window.getSelection is defined', () => {
      it('calls removeAllRanges on the selection', () => {
        const mockSelection = {
          removeAllRanges() {
            console.log('removeAllRanges');
          }
        };

        const mockWindow = {
          getSelection() {
            return mockSelection;
          }
        };

        spyOn(mockSelection, 'removeAllRanges');

        spyOn(Browser, 'getWindow').and.returnValue(mockWindow);

        Text.clearSelection();

        expect(mockSelection.removeAllRanges).toHaveBeenCalled();
      });
    });
  });
});

import Text from './text'
import Browser from '../browser';

describe('Text', () => {
  describe('clearSelection', () => {
    describe('when document.body.createTextRange is defined', () => {
      it('calls collapse and select on the text range', () => {
        const mockTextRange = {
          collapse() {},
          select() {}
        };

        const mockDocument = {
          body: {
            createTextRange() {
              return mockTextRange;
            }
          }
        };

        spyOn(mockTextRange, 'collapse');
        spyOn(mockTextRange, 'select');

        spyOn(Browser, 'getDocument').and.returnValue(mockDocument);

        Text.clearSelection();

        expect(mockTextRange.collapse).toHaveBeenCalled();
        expect(mockTextRange.select).toHaveBeenCalled();
      });
    });

    describe('when window.getSelection is defined', () => {
      it('calls removeAllRanges on the selection', () => {
        const mockSelection = {
          removeAllRanges() {}
        };

        const mockWindow = {
          getSelection() {
            return mockSelection;
          }
        };

        spyOn(mockSelection, 'removeAllRanges');

        spyOn(Browser, 'getWindow').and.returnValue(mockWindow);

        Text.clearSelection();

        expect(mockSelection.removeAllRanges).toHaveBeenCalled();
      });
    });
  });
});

Remove console statements from tests


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

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

import { GREY, DARK_GREY } from '../globalStyles';
import { CloudIcon, ArrowIcon, TemperatureIcon } from './icons';

export const TemperatureSyncIcon = ({ isActive }) => {
  const iconColor = isActive ? DARK_GREY : GREY;

  return (
    <>
      <CloudIcon color={iconColor} />
      <ArrowIcon color={iconColor} />
      <TemperatureIcon color={iconColor} />
    </>
  );
};

TemperatureSyncIcon.defaultProps = {
  isActive: true,
};

TemperatureSyncIcon.propTypes = {
  isActive: PropTypes.bool,
};

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

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

import { GREY, DARK_GREY } from '../globalStyles';
import { CloudIcon, ArrowIcon, TemperatureIcon } from './icons';

export const TemperatureSyncIcon = React.memo(({ isActive }) => {
  const iconColor = isActive ? DARK_GREY : GREY;

  return (
    <>
      <CloudIcon color={iconColor} />
      <ArrowIcon color={iconColor} />
      <TemperatureIcon color={iconColor} />
    </>
  );
});

TemperatureSyncIcon.defaultProps = {
  isActive: true,
};

TemperatureSyncIcon.propTypes = {
  isActive: PropTypes.bool,
};

Add memo temperature sync icon


import Ember from 'ember';
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';

export default Ember.Route.extend(AuthenticatedRouteMixin, {
  activate() {
    this.notifications.clearAll();
  },

  model(params) {
    // alert('finding model');
    return this.store.findRecord('network', params.id);
  },

  setupController(controller, model) {
    // alert('found model');
    controller.set('model', model);
    // alert('set model');
  },

  actions: {
    openSidebar() {
      this.transitionTo('network.show.details');
    }
  }
});

import Ember from 'ember';

export default Ember.Route.extend({
  activate() {
    this.notifications.clearAll();
  },

  model(params) {
    // alert('finding model');
    return this.store.findRecord('network', params.id);
  },

  setupController(controller, model) {
    // alert('found model');
    controller.set('model', model);
    // alert('set model');
  },

  actions: {
    openSidebar() {
      this.transitionTo('network.show.details');
    }
  }
});

Allow unauthenticated access to networks


const express = require('express');
const Post = require('../models/post');

const postsRouter = express.Router();

postsRouter.get('/', (req, res, next) => {
  Post.find((err, posts) => {
    if (err) next(err);
    res.json({posts});
  });
});

postsRouter.get('/:slug', (req, res, next) => {
  console.log(req.params);
});

module.exports = postsRouter;

const express = require('express');
const Post = require('../models/post');

const postsRouter = express.Router();

postsRouter.get('/', (req, res, next) => {
  Post.find((err, posts) => {
    if (err) next(err);
    return res.json({ posts });
  });
});

module.exports = postsRouter;

Change formatting in Posts router


import selftest, {Sandbox} from '../tool-testing/selftest.js';

selftest.define('standard-minifiers - CSS splitting', function (options) {
  const s = new Sandbox({
    clients: options.clients
  });

  s.createApp('myapp', 'minification-css-splitting');
  s.cd('myapp');

  s.testWithAllClients(function (run) {
    run.waitSecs(5);
    run.match('myapp');
    run.match('proxy');
    run.match('MongoDB');
    run.match('your app');
    run.match('running at');
    run.match('localhost');

    run.connectClient();
    run.waitSecs(20);
    run.match('client connected');

    run.match('the number of stylesheets: <2>');
    run.match('the color of the tested 4097th property: <rgb(0, 128, 0)>');

    s.append('client/lots-of-styles.main.styl', `
.class-4097
  color: blue
`);

    run.waitSecs(60);
    run.match('Client modified -- refreshing');
    run.match('the number of stylesheets: <2>');
    run.match('the color of the tested 4097th property: <rgb(0, 0, 255)>');

    run.stop();
  }, '--production');
});

import selftest, {Sandbox} from '../tool-testing/selftest.js';

selftest.define('standard-minifiers - CSS splitting', function (options) {
  const s = new Sandbox({
    clients: options.clients
  });

  s.createApp('myapp', 'minification-css-splitting');
  s.cd('myapp');

  s.testWithAllClients(function (run) {
    run.waitSecs(5);
    run.match('myapp');
    run.match('proxy');
    run.match('MongoDB');
    run.match('your app');
    run.match('running at');
    run.match('localhost');

    run.connectClient();
    run.waitSecs(20);
    run.match('client connected');

    run.match('the number of stylesheets: <2>');
    run.match('the color of the tested 4097th property: <rgb(0, 128, 0)>');

    s.append('client/lots-of-styles.main.styl', `
.class-4097
  color: blue
`);

    run.match('Client modified -- refreshing');
    run.waitSecs(90);
    run.match('the number of stylesheets: <2>');
    run.match('the color of the tested 4097th property: <rgb(0, 0, 255)>');

    run.stop();
  }, '--production');
});

Move standard-minifiers self-test timeout *after* slow run.match.


import React, { PropTypes, Component } from "react";
import { LOGO_BACKGROUND_PATH } from "../constants/image_paths";

const BetterSelfAddress = () => (

    BetterHealth, Inc.
99 St Marks, 3D
New York, NY, 10009

);

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

              We  analytics

            <BetterSelfAddress />

              Please take a few minutes to read our
              {" "}
              Terms & Conditions
              {" "}
              and
              {" "}
              Privacy Policy

    );
  }
}

import React, { PropTypes, Component } from "react";
import { LOGO_BACKGROUND_PATH } from "../constants/image_paths";

const BetterSelfAddress = () => (

    BetterHealth, Inc.

    99 St Marks, 3D

    New York, NY, 10009

    jeffshek@gmail.com

);

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

              We  analytics

            <BetterSelfAddress />

              Please take a few minutes to read our
              {" "}
              Terms & Conditions
              {" "}
              and
              {" "}
              Privacy Policy

    );
  }
}

Add Email Address for Contact


import 'aframe';
import 'babel-polyfill';
import {Animation, Entity, Scene} from 'aframe-react';
import React from 'react';
import ReactDOM from 'react-dom';

import Camera from './components/Camera';
import Cursor from './components/Cursor';
import Sky from './components/Sky';

class SpaceScene extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      color: 'red'
    }
  }

  changeColor = () => {
    const colors = ['red', 'orange', 'yellow', 'green', 'blue'];
    this.setState({
      color: colors[Math.floor(Math.random() * colors.length)],
    });
  };

  render () {
    return (
      <Scene>
        <Camera><Cursor/></Camera>

        <Sky/>

        <Entity light={{type: 'ambient', color: '#888'}}/>
        <Entity light={{type: 'directional', intensity: 0.2}} position={[-1, 1, 0]}/>
        <Entity light={{type: 'directional', intensity: 1}} position={[1, 1, 0]}/>

        <Entity geometry="primitive: box" material={{color: this.state.color}}
                onClick={this.changeColor}
                position="0 0 -5">
          <Animation attribute="rotation" dur="5000" repeat="indefinite" to="0 360 360"/>
        </Entity>
      </Scene>
    );
  }
}

ReactDOM.render(<SpaceScene/>, document.querySelector('.scene-container'));

import 'aframe';
import 'babel-polyfill';
import {Animation, Entity, Scene} from 'aframe-react';
import React from 'react';
import ReactDOM from 'react-dom';

import Camera from './components/Camera';
import Cursor from './components/Cursor';
import Sky from './components/Sky';

class SpaceScene extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      color: 'red'
    }
  }

  changeColor = () => {
    const colors = ['red', 'orange', 'yellow', 'green', 'blue'];
    this.setState({
      color: colors[Math.floor(Math.random() * colors.length)],
    });
  };

  render () {
    return (
      <Scene>
        <Camera><Cursor/></Camera>

        <Sky/>

        <Entity light={{type: 'ambient', color: '#888'}}/>
        <Entity light={{type: 'directional', intensity: 0.2}} position={[-1, 1, 0]}/>
        <Entity light={{type: 'directional', intensity: 1}} position={[1, 1, 0]}/>

        <a-box width="4" height="10" depth="2" color={'#FFF'}
                onClick={this.changeColor}
                position="0 0 -5">
          <Animation attribute="rotation" dur="5000" repeat="indefinite" to="0 360 360"/>
        </a-box>
      </Scene>
    );
  }
}

ReactDOM.render(<SpaceScene/>, document.querySelector('.scene-container'));

Test basic a-frame tag (not react component)


const url = require('url');
const WebSocket = require('ws');

var wss;

/* Initializes the WebSocket on the given server. */
function start(server) {
	wss = new WebSocket.Server({ server, perMessageDeflate: false });
	wss.on('connection', onConnection);
}

/* Called when a client connects to the WebSocket. */
function onConnection(ws) {
	const location = url.parse(ws.upgradeReq.url, true);

	// On message received from client
	ws.on('message', onIncomingMessage);

	// When each client first connects
	ws.send('Connection opened.');
}

/* Called when the server receives a message from a client. */
function onIncomingMessage(message) {
	// Broadcast the echoed message
	wss.clients.forEach(function each(client) {
		if (client.readyState === WebSocket.OPEN) {
			client.send(message);
		}
	});
}

module.exports = {start: start};

const url = require('url');
const WebSocket = require('ws');

var wss;

/* Initializes the WebSocket on the given server. */
function start(server) {
	wss = new WebSocket.Server({ server, perMessageDeflate: false });
	wss.on('connection', onConnection);
}

/* Called when a client connects to the WebSocket. */
function onConnection(ws) {
	const location = url.parse(ws.upgradeReq.url, true);

	// On message received from client
	ws.on('message', function(message) {onIncomingMessage(message, ws)});

	// When each client first connects
	ws.send('Connection opened.');
}

/* Called when the server receives a message from a client. */
function onIncomingMessage(message, ws) {
	// Broadcast the echoed message
	wss.clients.forEach(function each(client) {
		if (client.readyState === WebSocket.OPEN) {
			client.send(message);
		}
	});
}

module.exports = {start: start};

Include client's WebSocket in onMessage function.


const express = require('express'),
      router = express.Router({mergeParams: true}),
      db = require('../models');

router.get('/', function(req, res, next) {
  db.Emoticon.find({}).then(function(emoticons) {
      res.render('emoticons/index', {emoticons});
  }).catch(function(err) {
      console.log(err);
  });
});

router.get('/collection', function(req, res, next) {
  Emoticon.find({}, function(err, emoticons) {
    if (err) {
      console.log('Error');
    } else {
      res.render('index', {emoticons});
    }
  });
});

router.post('/collection/add', function(req, res, next) {
  Emoticon.create(req.body.emoticon, function(err, newEmoticon) {
    if (err) {
      res.render('error');
    } else {
      res.redirect('/collection');
    }
  });
});

router.delete('/collection/:id', function(req, res, next) {

});

module.exports = router;

const express = require('express'),
      router = express.Router({mergeParams: true}),
      db = require('../models');

router.get('/', function(req, res, next) {
  db.Emoticon.find({}).then(function(emoticons) {
      res.render('emoticons/index', {emoticons});
  }).catch(function(err) {
      console.log(err);
  });
});

router.get('/new', function(req, res, next) {
  res.render('emoticons/new', {user: req.params.username});
});

router.post('/collection/add', function(req, res, next) {
  Emoticon.create(req.body.emoticon, function(err, newEmoticon) {
    if (err) {
      res.render('error');
    } else {
      res.redirect('/collection');
    }
  });
});

router.delete('/collection/:id', function(req, res, next) {

});

module.exports = router;

Add GET request for page with form to add new emoticon


const fs = require('fs')
const path = require('path')
const promisify = require('util').promisify
const writeFile = promisify(fs.writeFile)
const globby = require('globby')

;(async () => {
  const posts = require('./dist/content/posts/index.json')
  const files = await globby('./dist/**/*.{css,mjs}')
  const headers = []

  for (const file of files) {
    const relative = path.relative('./dist', file)

    switch (path.extname(relative)) {
      case '.css':
        headers.push(`  Link: </${relative}>; rel=preload; as=style`)
        break

      case '.mjs':
        headers.push(`  Link: </${relative}>; rel=modulepreload; as=script`)
        break
    }
  }

  const lines = []

  for (const path of ['/', '/posts/*']) {
    lines.push(path)

    if (path === '/') {
      lines.push(`  Link: </content/posts/${posts[posts.length - 1].slug}.md>; rel=preload; as=fetch`)
    }

    lines.push(...headers)
  }

  for (const post of posts) {
    lines.push(`/posts/${post.slug}`, `  Link: </content/posts/${post.slug}.md>; rel=preload; as=fetch`)
  }

  await writeFile('./dist/_headers', lines.join('\n'))
})()

const fs = require('fs')
const path = require('path')
const promisify = require('util').promisify
const writeFile = promisify(fs.writeFile)
const globby = require('globby')

;(async () => {
  // const posts = require('./dist/content/posts/index.json')
  const files = await globby('./dist/**/*.{css,mjs}')
  const headers = []

  for (const file of files) {
    const relative = path.relative('./dist', file)

    switch (path.extname(relative)) {
      case '.css':
        headers.push(`  Link: </${relative}>; rel=preload; as=style`)
        break

      case '.mjs':
        headers.push(`  Link: </${relative}>; rel=modulepreload; as=script`)
        break
    }
  }

  const lines = []

  for (const path of ['/', '/posts/*']) {
    lines.push(path)

    // if (path === '/') {
    //   lines.push(`  Link: </content/posts/${posts[posts.length - 1].slug}.md>; rel=preload; as=fetch`)
    // }

    lines.push(...headers)
  }

  // for (const post of posts) {
  //   lines.push(`/posts/${post.slug}`, `  Link: </content/posts/${post.slug}.md>; rel=preload; as=fetch`)
  // }

  await writeFile('./dist/_headers', lines.join('\n'))
})()

Comment out preload as fetch headers


// @flow

import type { ActionArgs } from '../src/runners/action-args.js'

const util = require('util')
const exec = util.promisify(require('child_process').exec)

module.exports = async function (args: ActionArgs) {
  const makeExpression = args.nodes.textInNodeOfType('code')
  const expected = makeExpression.split(' ')[1]
  const { stdout, stderr } = await exec(
    "cat Makefile | grep '^[^ ]*:' | grep -v '.PHONY' | grep -v help | sed 's/:.*#//'"
  )
  if (stderr.length > 0) {
    throw new Error(`Error running 'make help': ${stderr}`)
  }
  const actuals = stdout.split('\n').map(actual => actual.split(' ')[0])
  if (!actuals.includes(expected)) {
    throw new Error(`binary '${expected}' does not exist`)
  }
}

// @flow

import type { ActionArgs } from '../src/runners/action-args.js'

const util = require('util')
const exec = util.promisify(require('child_process').exec)
const path = require('path')

module.exports = async function (args: ActionArgs) {
  const makeExpression = args.nodes.text()
  const expected = makeExpression.replace(/make\s+/, '')
  const makePath = path.join(args.configuration.sourceDir, 'Makefile')
  const { stdout, stderr } = await exec(
    `cat ${makePath} | grep '^[^ ]*:' | grep -v '.PHONY' | grep -v help | sed 's/:.*#//'`
  )
  if (stderr.length > 0) {
    throw new Error(`Error running 'make help': ${stderr}`)
  }
  const actuals = stdout.split('\n').map(actual => actual.split(' ')[0])
  if (!actuals.includes(expected)) {
    throw new Error(`make script '${expected}' does not exist`)
  }
}

Use code tags for Make script checks


Split sides into left and right


const WebSocket = require('ws');
const gpio = require('rpi-gpio')

const wss = new WebSocket.Server({ port: 8080 });

var power = false;
var mute = false;

const pinConfig = {
    power: 18,
    mute: 17
}

wss.on('connection', function connection(ws) {
    gpio.setMode(gpio.MODE_BCM);

    ws.on('message', function incoming(message) {
        if (message == 'powerToggle') {
            power = !power;

            writePin(pinConfig.mute, true);
            writePin(pinConfig.power, power);
        }

        if (message == 'muteToggle') {
            mute = !mute;

            writePin(pinConfig.mute, mute);
        }
    });

    ws.send('something');
});

function writePin(pinNumber, value) {
    gpio.setup(pinNumber, gpio.DIR_OUT, write);

    function write() {
        this.gpio.write(pinNumber, value, function (err) {
            if (err) throw err;
            console.log('Written to pin ' + pinNumber);
        });
    }
}

const WebSocket = require('ws');
const gpio = require('rpi-gpio')

const wss = new WebSocket.Server({ port: 8080 });

var power = false;
var mute = false;

const pinConfig = {
    power: 18,
    mute: 17
}

wss.on('connection', function connection(ws) {
    gpio.setMode(gpio.MODE_BCM);

    ws.on('message', function incoming(message) {
        if (message == 'powerToggle') {
            power = !power;

            writePin(pinConfig.mute, true);
            writePin(pinConfig.power, power);
        }

        if (message == 'muteToggle') {
            mute = !mute;

            writePin(pinConfig.mute, mute);
        }
    });

    ws.send('something');
});

function writePin(pinNumber, value) {
    gpio.setup(pinNumber, gpio.DIR_OUT, write);

    function write() {
        gpio.write(pinNumber, value, function (err) {
            if (err) throw err;
            console.log('Written to pin ' + pinNumber);
        });
    }
}

Remove this. on gpio ref


jQuery(function($){
  $('#annotations').hide()

  var allTooltips = $();

  $('<button class="btn btn-info"> Toggle annotations</button>')
    .css({
      position: 'fixed',
      right: '1em',
      top: '1em'
    })
    .prependTo('body')
    .click(function(){
      allTooltips.tooltip('toggle');
    });

  $('#annotations>[data-ref]').each(function(){
    var tooltip = $($(this).data('ref')).tooltip({
      trigger: 'manual',
      html: true,
      title: $(this).html(),
      placement: $(this).data('placement') || 'bottom'
    });
    allTooltips = allTooltips.add(tooltip);
  });
});

jQuery(function($){
  $('#annotations').hide()

  var allTooltips = $();

  $('<button class="btn btn-info"> Toggle annotations</button>')
    .css({
      position: 'fixed',
      right: '1em',
      top: '1em'
    })
    .appendTo('body')
    .click(function(){
      allTooltips.tooltip('toggle');
    });

  $('#annotations>[data-ref]').each(function(){
    var tooltip = $($(this).data('ref')).tooltip({
      trigger: 'manual',
      html: true,
      title: $(this).html(),
      placement: $(this).data('placement') || 'bottom'
    });
    allTooltips = allTooltips.add(tooltip);
  });
});

Append the button rather than pre-pend to give a natural z-index boost without having to specify overrides.


import { terser } from 'rollup-plugin-terser'
import filesize from 'rollup-plugin-filesize'
import license from 'rollup-plugin-license'

export default [
  {
    input: 'src/js.cookie.mjs',
    output: [
      // config for <script type="module">
      {
        dir: 'dist',
        entryFileNames: '[name].mjs',
        format: 'esm'
      },
      // config for <script nomodule>
      {
        dir: 'dist',
        name: 'Cookies',
        entryFileNames: '[name].js',
        format: 'umd',
        noConflict: true
      }
    ]
  },
  {
    input: 'src/js.cookie.mjs',
    output: [
      // config for <script type="module">
      {
        dir: 'dist',
        entryFileNames: '[name].min.mjs',
        format: 'esm'
      },
      // config for <script nomodule>
      {
        dir: 'dist',
        name: 'Cookies',
        entryFileNames: '[name].min.js',
        format: 'umd',
        noConflict: true
      }
    ],
    plugins: [
      terser(),
      license({
        banner: {
          content:
            '/*! <%= pkg.name %> v<%= pkg.version %> | <%= pkg.license %> */',
          commentStyle: 'none'
        }
      }),
      filesize()
    ]
  }
]

import { terser } from 'rollup-plugin-terser'
import filesize from 'rollup-plugin-filesize'
import license from 'rollup-plugin-license'

export default [
  {
    input: 'src/js.cookie.mjs',
    output: [
      // config for <script type="module">
      {
        dir: 'dist',
        entryFileNames: '[name].mjs',
        format: 'esm'
      },
      // config for <script nomodule>
      {
        dir: 'dist',
        name: 'Cookies',
        entryFileNames: '[name].js',
        format: 'umd',
        noConflict: true,
        banner: ';'
      }
    ]
  },
  {
    input: 'src/js.cookie.mjs',
    output: [
      // config for <script type="module">
      {
        dir: 'dist',
        entryFileNames: '[name].min.mjs',
        format: 'esm'
      },
      // config for <script nomodule>
      {
        dir: 'dist',
        name: 'Cookies',
        entryFileNames: '[name].min.js',
        format: 'umd',
        noConflict: true
      }
    ],
    plugins: [
      terser(),
      license({
        banner: {
          content:
            '/*! <%= pkg.name %> v<%= pkg.version %> | <%= pkg.license %> */',
          commentStyle: 'none'
        }
      }),
      filesize()
    ]
  }
]

Fix problem with missing semicolon


"use strict";

/*global nodeca*/

var NLib = require('nlib');

var Async = NLib.Vendor.Async;

var models = nodeca.models;

module.exports.up = function (cb) {
  // FIXME implement sets of real froup items
  Async.forEachSeries(['administrators','members','guests'],
    function(current_group, next_group) {
      var usergroup = new models.users.UserGroup({short_name: current_group});
      usergroup.save(next_group);
    }
  , cb);
};

"use strict";

/*global nodeca*/

var NLib = require('nlib');

var Async = NLib.Vendor.Async;

var models = nodeca.models;

module.exports.up = function (cb) {
  // FIXME implement sets of real froup items
  Async.forEachSeries(['administrators','members','guests'],
    function(current_group, next_group) {
      var usergroup = new models.users.UserGroup({
        short_name: current_group,
        is_protected: true
      });
      usergroup.save(next_group);
    }
  , cb);
};

Set default groups as protected in usergroup seed


describe('svgStar', function () {
  var $compile,
      $rootScope;

  beforeEach(module('md5.svg-star'));

  beforeEach(inject(function (_$compile_, _$rootScope_) {
    $compile = _$compile_;
    $rootScope = _$rootScope_;
  }));

  it('should work as an element', function () {
    var elt = angular.element('<svg-star></svg-star>');
    $compile(elt)($rootScope);
    $rootScope.$digest();

    var svg = elt.find('svg');
    expect(svg.length).toBe(1);
    expect(svg.eq(0).attr('viewBox')).toBe('0 0 20 20');

    var star = svg.find('polygon');
    expect(star.length).toBe(1);

    var points = star.eq(0).attr('points');
    expect(points).not.toBeNull();
    expect(points.split(/ /).length).toBe(10);
  });
});

describe('svgStar', function () {
  var $compile,
      $rootScope;

  beforeEach(module('md5.svg-star'));

  beforeEach(inject(function (_$compile_, _$rootScope_) {
    $compile = _$compile_;
    $rootScope = _$rootScope_;
  }));

  it('should work as an element', function () {
    var elt = angular.element('<svg-star></svg-star>');
    $compile(elt)($rootScope);
    $rootScope.$digest();

    var svg = elt.find('svg');
    expect(svg.length).toBe(1);
    expect(svg.eq(0).attr('viewBox')).toBe('0 0 20 20');

    var star = svg.find('polygon');
    expect(star.length).toBe(1);

    var points = star.eq(0).prop('points');
    expect(points).not.toBeNull();
    expect(points.numberOfItems).toBe(10);
  });
});

Use SVGPointList API in test


/*!
 * angular-validation-match
 * Checks if one input matches another
 * @version v1.7.0
 * @link https://github.com/TheSharpieOne/angular-validation-match
 * @license MIT License, http://www.opensource.org/licenses/MIT
 */
!function(e,a,t){"use strict";function i(e){return{require:"?ngModel",restrict:"A",link:function(t,i,n,r){function c(){var e=o(t);return a.isObject(e)&&e.hasOwnProperty("$viewValue")&&(e=e.$viewValue),e}if(r){var o=e(n.match),u=e(n.matchCaseless),l=e(n.notMatch);t.$watch(c,function(){r.$$parseAndValidate()}),r.$validators.match=function(){var e,i=c(),n=l(t);return e=u(t)?a.lowercase(r.$viewValue)===a.lowercase(i):r.$viewValue===i,e^=n,!!e}}}}}a.module("validation.match",[]),a.module("validation.match").directive("match",i),i.$inject=["$parse"]}(window,window.angular);

/*!
 * angular-validation-match
 * Checks if one input matches another
 * @version v1.7.1
 * @link https://github.com/TheSharpieOne/angular-validation-match
 * @license MIT License, http://www.opensource.org/licenses/MIT
 */
!function(e,a,t){"use strict";function i(e){return{require:"?ngModel",restrict:"A",link:function(t,i,n,r){function c(){var e=o(t);return a.isObject(e)&&e.hasOwnProperty("$viewValue")&&(e=e.$viewValue),e}if(r){var o=e(n.match),u=e(n.matchCaseless),l=e(n.notMatch),s=e(n.matchIgnoreEmpty);t.$watch(c,function(){r.$$parseAndValidate()}),r.$validators.match=function(){var e,i=c(),n=l(t);return s(t)&&!r.$viewValue?!0:(e=u(t)?a.lowercase(r.$viewValue)===a.lowercase(i):r.$viewValue===i,e^=n,!!e)}}}}}i.$inject=["$parse"],a.module("validation.match",[]),a.module("validation.match").directive("match",i)}(window,window.angular);

Upgrade Angular Validation Match to 1.7.1


(function ($) {

    $.fn.dragster = function (options) {
        var settings = $.extend({
            enter: function () { },
            leave: function () { }
        }, options);

        return this.each(function () {
            var first = false,
                second = false;

            $(this).on('dragenter', function () {
                if (this.first) {
                    return this.second = true;
                } else {
                    this.first = true;
                    $(this).trigger('dragster:enter');
                }
            });

            $(this).on('dragleave', function () {
                if (this.second) {
                    this.second = false;
                } else if (this.first) {
                    this.first = false;
                }
                if (!this.first && !this.second) {
                    $(this).trigger('dragster:leave');
                }
            });

            $(this).on('dragster:enter', settings.enter);
            $(this).on('dragster:leave', settings.leave);
        });
    };

}(jQuery));

(function ($) {

    $.fn.dragster = function (options) {
        var settings = $.extend({
            enter: function () { },
            leave: function () { }
        }, options);

        return this.each(function () {
            var first = false,
                second = false;

            $(this).on('dragenter', function () {
                if (first) {
                    return second = true;
                } else {
                    first = true;
                    $(this).trigger('dragster:enter');
                }
            });

            $(this).on('dragleave', function () {
                if (second) {
                    second = false;
                } else if (first) {
                    first = false;
                }
                if (!first && !second) {
                    $(this).trigger('dragster:leave');
                }
            });

            $(this).on('dragster:enter', settings.enter);
            $(this).on('dragster:leave', settings.leave);
        });
    };

}(jQuery));

Use the declared first and second variables


const Config                    = use('config');
const Errors                    = use('core/errors');

const DefaultProperty           = require('./default');
const PrimaryKeyPattern         = new RegExp('^'+Config.router.primaryKeyPattern + '$');

class ModelProperty extends DefaultProperty {

    constructor(modelName) {
        super();

        const Factories = use('core/factories');

        this._addValidator((value, propertyName) => {
            if (value !== null && (
                       (typeof value === "object" && (!PrimaryKeyPattern.test(value.id)))
                    || (typeof value !== "object" && (!PrimaryKeyPattern.test(value)))
                )
            ) {
                throw new Errors.Validation(`property ${propertyName} should be positive integer or model instance, given '${value}'`);
            }
        });

        this._addValidator((value, propertyName) => {
            try {
                Factories(modelName);
            }
            catch (error) {
                if (error instanceof Errors.Fatal) {
                    throw new Errors.Validation(`property ${propertyName} should be a model ${modelName}, but such model is not defined`);
                } else {
                    throw error;
                }
            }
        });

        this._addOutputModification((outputObject, propertyName) => {
            outputObject[propertyName] = {
                id: outputObject[propertyName],
                link: modelName.toLowerCase() + "/" + outputObject[propertyName]
            };
        });

        this._inputModification = (value) => {
            return (value !== null && typeof value === "object" && PrimaryKeyPattern.test(value.id)) ? value.id : value;
        };
    }

}

module.exports = ModelProperty;

const Config                    = use('config');
const Errors                    = use('core/errors');

const DefaultProperty           = require('./default');
const PrimaryKeyPattern         = new RegExp('^'+Config.router.primaryKeyPattern + '$');

class ModelProperty extends DefaultProperty {

    constructor(modelName) {
        super();

        const Factories = use('core/factories');

        this._addValidator((value, propertyName) => {
            if (value != null && (
                       (typeof value === "object" && (!PrimaryKeyPattern.test(value.id)))
                    || (typeof value !== "object" && (!PrimaryKeyPattern.test(value)))
                )
            ) {
                throw new Errors.Validation(`property ${propertyName} should be positive integer or model instance, given '${value}'`);
            }
        });

        this._addValidator((value, propertyName) => {
            try {
                Factories(modelName);
            }
            catch (error) {
                if (error instanceof Errors.Fatal) {
                    throw new Errors.Validation(`property ${propertyName} should be a model ${modelName}, but such model is not defined`);
                } else {
                    throw error;
                }
            }
        });

        this._addOutputModification((outputObject, propertyName) => {
            if(outputObject[propertyName] !== null) {
                outputObject[propertyName] = {
                    id: outputObject[propertyName],
                    link: modelName.toLowerCase() + "/" + outputObject[propertyName]
                };
            }
        });

        this._inputModification = (value) => {
            return (value !== null && typeof value === "object" && PrimaryKeyPattern.test(value.id)) ? value.id : value;
        };
    }

}

module.exports = ModelProperty;

Allow Property Models to be undefined


module.exports = function(ctx) {
  ctx.cookies.set('returning', 'true', { maxAge: 10 * 365 * 24 * 60 * 60 * 1000 })
}

module.exports = function(ctx) {
  ctx.cookies.set('returning', 'true', { maxAge: 10 * 365 * 24 * 60 * 60 * 1000, httpOnly: false })
}

Fix returning user cookie to not be HTTP-only.


({
    /* ... */
            content: [
                { block: 'logo' },
                {
                    block: 'social',
                    providers: ['vk', 'facebook', 'twitter']
                },
                { block: 'copyright' }
            ]
    /* ... */
});

({
            content: [
                {
                    block: 'logo',
                    text: 'HTML Academy'
                },

                {
                    block: 'social',
                    providers: ['vk', 'facebook', 'twitter']
                },

                { block: 'copyright'}
            ]
});

Refactor footer col left example


'use strict';
// External Modules -----------------------------------------------------
const mongoose = require('mongoose');

// My Modules -------------------------------------------------------
const projectSchema = require('./project.schema');

const Project = mongoose.model('Project', projectSchema);

Project.getProjects = (masterId, callback) => {
  const query = {$or: [{masterId: masterId}, {evaluators: {$elemMatch: {_id: masterId}}}]};
  Project.find(query, callback).sort({updated: -1}).select({
    _id: 1, name: 1, updated: 1
  });
};

Project.getProjectById = (id, callback) => Project.findById(id, callback);

Project.addProject = (body, callback) => {
  Project.create(body, callback);
};

Project.saveProject = (id, project, callback) => Project.update({_id:id}, project, callback);

Project.delete = (projectId, callback) => {
  Project.remove(projectId, callback);
};

module.exports = Project;

'use strict';
// External Modules -----------------------------------------------------
const mongoose = require('mongoose');

// My Modules -------------------------------------------------------
const projectSchema = require('./project.schema');

const Project = mongoose.model('Project', projectSchema);

Project.getProjects = (masterId, callback) => {
  const query = {$or: [{masterId: masterId}, {evaluators: {$elemMatch: {_id: masterId}}}]};
  Project.find(query, callback).sort({updated: -1}).select({
    _id: 1, masterId:1, name: 1, updated: 1
  });
};

Project.getProjectById = (id, callback) => Project.findById(id, callback);

Project.addProject = (body, callback) => {
  Project.create(body, callback);
};

Project.saveProject = (id, project, callback) => Project.update({_id:id}, project, callback);

Project.delete = (projectId, callback) => {
  Project.remove(projectId, callback);
};

module.exports = Project;

Add MAsterId to the dashboard projects query


'use strict';

angular.module('payMyMeetingApp')
.controller('showMeetingController', function ($scope, $http, $routeParams, $log) {
	$scope.meetingId = $routeParams.meetingId;

	$http.get('/api/attendees/' + $scope.meetingId).
	success(function (data) {
		$scope.attendees = data;
	}).
	error(function () {
	});
	var sock = new SockJS('http://127.0.0.1:3000/notifications');

	sock.onopen = function() {
		$log.log('socket connection open');
	};

	sock.onmessage = function(e) {
		$log.log('socket message ' + e.data);
	};

	sock.onclose = function() {
		$log.log('socket connection closed');
	};
});

'use strict';

angular.module('payMyMeetingApp')
.controller('showMeetingController', function ($scope, $http, $routeParams, $log) {
	$scope.meetingId = $routeParams.meetingId;

	var loadAttendees = function(){
		$http.get('/api/attendees/' + $scope.meetingId).
		success(function (data) {
			$scope.attendees = data;
		}).
		error(function () {
		});
	};

	loadAttendees();

	var sock = new SockJS('http://127.0.0.1:3000/notifications');

	sock.onopen = function() {
		$log.log('socket connection open');
	};

	sock.onmessage = function() {
		loadAttendees();
	};

	sock.onclose = function() {
		$log.log('socket connection closed');
	};
});

Refresh attendees list when a new attendee join the meeting


const { findOneByName } = require('../../common')

function validateOption(name, value, config) {
  if (config.required && typeof value === 'undefined') {
    throw new Error(`Option "${name}" is required`)
  }
}

function validateCommand(command, commandConfig, options, optionConfigs) {
  Object.keys(options).forEach((option) => {
    let optionConfig = findOneByName(optionConfigs, option)
    let optionValue = options[option]

    if (!optionConfig && commandConfig.strict) {
      throw new Error(`Unknown option ${option}`)
    }

    validateOption(option, optionValue, optionConfig)
  })
}

module.exports = validateCommand

const { findOneByName } = require('../../common')

function validateOption(name, value, config) {
  if (config.required && typeof value === 'undefined') {
    throw new Error(`Option "${name}" is required`)
  }
}

function validateCommand(command, commandConfig, options, optionConfigs) {
  Object.keys(options).forEach((option) => {
    let optionConfig = findOneByName(optionConfigs, option)
    let optionValue = options[option]

    if (!optionConfig) {
      if (commandConfig.strict) {
        throw new Error(`Unknown option ${option}`)
      }

      return
    }

    validateOption(option, optionValue, optionConfig)
  })
}

module.exports = validateCommand

Fix validation failing for unknown options


import Vue from 'vue'
import Router from 'vue-router'
import index from '@/components/index'
import four from '@/components/four'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'github',
      component: index
    },
    {
      path: '/streams/:stream_id',
      name: 'stream',
      component: () => import('@/components/stream')
    },
    {
      path: '/cves',
      name: 'cves',
      component: () => import('@/components/cves')
    },
    {
      path: '/cves/:cve_id',
      name: 'cve',
      component: () => import('@/components/cve')
    },
    {
      path: '/404',
      name: 'four',
      component: four
    },
    {
      path: '*',
      redirect: '/404'
    }
  ]
})

import Vue from 'vue'
import Router from 'vue-router'
import index from '@/components/index'
import four from '@/components/four'

Vue.use(Router)

export default new Router({
  mode: 'history',
  routes: [
    {
      path: '/',
      name: 'github',
      component: index
    },
    {
      path: '/streams/:stream_id',
      name: 'stream',
      component: () => import('@/components/stream')
    },
    {
      path: '/cves',
      name: 'cves',
      component: () => import('@/components/cves')
    },
    {
      path: '/cves/:cve_id',
      name: 'cve',
      component: () => import('@/components/cve')
    },
    {
      path: '/404',
      name: 'four',
      component: four
    },
    {
      path: '*',
      redirect: '/404'
    }
  ]
})

Change to history mode, v2


const aws = require('aws-sdk');
const config = require('../config');
const sendRegistrationEmail = require('../sendRegistrationEmail');

describe('sendRegistrationEmail', () => {
  let sandbox;
  beforeEach(() => {
    sandbox = sinon.sandbox.create();
  });

  afterEach(() => {
    sandbox.restore();
  });

  it('sends an email with default messaging using SES', () => {
    const sendEmail = sinon.spy();
    sandbox.stub(aws, 'SES').returns({ sendEmail });
    config.emailFromAddress = 'admin@rr.com';

    sendRegistrationEmail('new@member.com')

    expect(sendEmail).to.have.been.calledWith({
      Source: 'admin@rr.com',
      ReplyToAddresses: ['admin@rr.com'],
      Destination: { ToAddresses: ['new@member.com'] },
      Message: {
        Subject: { Data: 'Thanks for joining!' },
        Body: {
          Text: { Data: "We're excited to have you on board" },
        },
      },
    });
  });
});

const aws = require('aws-sdk');
const config = require('../config');
const sendRegistrationEmail = require('../sendRegistrationEmail');

describe('sendRegistrationEmail', () => {
  let sandbox;
  beforeEach(() => {
    sandbox = sinon.sandbox.create();
  });

  afterEach(() => {
    sandbox.restore();
  });

  it('sends an email with default messaging using SES', () => {
    const sendEmail = sinon.spy();
    sandbox.stub(aws, 'SES').returns({ sendEmail });
    config.emailFromAddress = 'admin@rr.com';

    sendRegistrationEmail('new@member.com')

    expect(sendEmail).to.have.been.calledWith({
      Source: 'admin@rr.com',
      ReplyToAddresses: ['admin@rr.com'],
      Destination: { ToAddresses: ['new@member.com'] },
      Message: {
        Subject: { Data: 'Thanks for joining!' },
        Body: {
          Text: { Data: "We're excited to have you on board." },
        },
      },
    });
  });
});

Update test with new default messaging


// Only create the Sharding Manager
const config = require('./config.js')
const DiscordRSS = require('./index.js')
const Discord = require('discord.js')
const shardingManager = new Discord.ShardingManager('./server-shard.js', { respawn: false })
const shardedDiscordRSSClient = new DiscordRSS.ClientSharded(shardingManager, { readFileSchedules: true, setPresence: true, forceSharded: true })

shardedDiscordRSSClient.run()

shardedDiscordRSSClient.once('finishInit', () => {
  // Do whatever once the sharding manager has finished spawning and waiting for all shards to finish initialization
  try {
    require('./web/index.js')()
  } catch (err) {
    if (config.web.enabled === true) console.log(err)
  }
})

// Only create the Sharding Manager
const config = require('./config.js')
const DiscordRSS = require('./index.js')
const Discord = require('discord.js')
const shardingManager = new Discord.ShardingManager('./server-shard.js', { respawn: false })
const clientManager = new DiscordRSS.ClientManager(shardingManager, { readFileSchedules: true, setPresence: true, forceSharded: true })

clientManager.run()

clientManager.once('finishInit', () => {
  // Do whatever once the sharding manager has finished spawning and waiting for all shards to finish initialization
  try {
    require('./web/index.js')()
  } catch (err) {
    if (config.web.enabled === true) console.log(err)
  }
})

Update from ClientSharded to ClientManager


$(function() {
  var mediaDropzone;
  mediaDropzone = new Dropzone("#media-dropzone");
  return mediaDropzone.on("success", function(file, responseText) {
    var imageUrl;
    imageUrl = responseText.file_name.url;
    $("#media-contents").append('

');
    $('#myModal').modal('hide');
  });
});

$(function() {
  var mediaDropzone;
  mediaDropzone = new Dropzone("#media-dropzone");
  return mediaDropzone.on("success", function(file, responseText) {
    var _this = this;
    appendContent(responseText.file_name.url, responseText.id);
    setTimeout(function(){
      $('#myModal').modal('hide');
      _this.removeAllFiles();
    },1000);
  });
});

var appendContent = function(imageUrl, mediaId) {
  $("#media-contents").append('
' +
    '
' +
    '
' +
    '<input id="media_contents_" name="media_contents[]" value="' + mediaId +'" type="checkbox">' +
    '
' +
    '

');
  $("#delete").removeAttr('disabled');
  $("#no-media").html("");
};

Handle after dropzone upload completes


require("referee-sinon");
require("buster-sinon");
var referee = require("referee");
var test = require("buster-test");
var formatio = require("formatio");
var stackFilter = require("stack-filter");
stackFilter.filters.push("buster-node/lib");

var formatter = formatio.configure({ quoteStrings: true });
referee.format = formatter.ascii.bind(formatter);

module.exports = {
    referee: referee,
    assert: referee.assert,
    refute: referee.refute,
    expect: referee.expect
};

// Expose buster test sub modules
for (var exp in test) {
    module.exports[exp] = test[exp];
}

// Configure assertion counting
test.testRunner.onCreate(function (runner) {
    referee.on("pass", function () {
        runner.assertionPass();
    });
});

// Configure automagic runner
var runner = test.autoRun({}, {
    end: function (results) {
        if (!results.ok) { process.exit(1); }
    }
});

test.testContext.on("create", runner);

require("referee-sinon");
require("buster-sinon");
var referee = require("referee");
var test = require("buster-test");
var formatio = require("formatio");
var stackFilter = require("stack-filter").configure({
    filters: ["buster-node/lib", "buster-node/node_modules"],
    cwd: process.cwd()
});

var formatter = formatio.configure({ quoteStrings: true });
referee.format = formatter.ascii.bind(formatter);

module.exports = {
    referee: referee,
    assert: referee.assert,
    refute: referee.refute,
    expect: referee.expect
};

// Expose buster test sub modules
for (var exp in test) {
    module.exports[exp] = test[exp];
}

// Configure assertion counting
test.testRunner.onCreate(function (runner) {
    referee.on("pass", function () {
        runner.assertionPass();
    });
});

// Configure automagic runner
var runner = test.autoRun({ stackFilter: stackFilter }, {
    end: function (results) {
        if (!results.ok) { process.exit(1); }
    }
});

test.testContext.on("create", runner);

Use the stack filter correctly


(function () {
  'use strict';

  // Participants controller
  angular
    .module('participants')
    .controller('TrackerController', TrackerController);

  TrackerController.$inject = ['$scope', '$state', 'Authentication', 'participantResolve', '$timeout'];

  function TrackerController ($scope, $state, Authentication, participant, $timeout) {
    var vm = this;

    vm.authentication = Authentication;
    vm.participant = participant;
    vm.location;

    var refreshLocation = function () {
      // Try HTML5 geolocation.
      if (navigator.geolocation) {
          navigator.geolocation.getCurrentPosition(function(position) {
              var pos = {
                  lat: position.coords.latitude,
                  lng: position.coords.longitude
              };

              vm.location = JSON.stringify(pos);
          }, function() {
              console.log('Can not get location!');
          });
      } else {
          console.log('Can not get location!');
      }

      $timeout(refreshLocation, 200);
    };

    refreshLocation();

  }
})();

(function () {
  'use strict';

  // Participants controller
  angular
    .module('participants')
    .controller('TrackerController', TrackerController);

  TrackerController.$inject = ['$scope', '$state', 'Authentication', 'participantResolve', 'TrackinginfosService', '$timeout'];

  function TrackerController ($scope, $state, Authentication, participant, TrackinginfosService, $timeout) {
    var vm = this;

    vm.authentication = Authentication;
    vm.participant = participant;
    vm.location;

    var refreshLocation = function () {
      // Try HTML5 geolocation.
      if (navigator.geolocation) {
          navigator.geolocation.getCurrentPosition(function(position) {
              var pos = {
                      lat: position.coords.latitude,
                      lng: position.coords.longitude
                  },
                  trackingInfosService = new TrackinginfosService();

              trackingInfosService.participant = vm.participant._id;
              trackingInfosService.pulse = 156;
              trackingInfosService.race = '56e318bbea64528fdbc1ffda';
              trackingInfosService.lat = pos.lat;
              trackingInfosService.lng = pos.lng;

              trackingInfosService.$save()
              vm.location = JSON.stringify(pos);

          }, function() {
              console.log('Can not get location!');
          });
      } else {
          console.log('Can not get location!');
      }

      $timeout(refreshLocation, 200);
    };

    refreshLocation();

  }
})();

Update tracker example to save values


/* jshint node: true */
/* jshint esnext: true */
'use strict';

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

router.get('/', (req, res) => {
  res.render('index', {})
  console.log(res);
}
);

module.exports = router;

/* jshint node: true */
/* jshint esnext: true */
'use strict';

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

router.get('/', (req, res) =>
  res.render('index', {})
);

module.exports = router;

Revert "debugging why we don't get cookies in aws"


import AccountSelectors from '~client/account/redux/selectors'
import * as CommunitySelectors from '~client/community/selectors'
import * as Paths from '~client/paths'

export const showMobilizationPublicView = ({ host, domain }) => {
  return (isSubdomain(host, domain) || !isDomain(host, domain))
}

// eslint-disable-next-line
export const isSubdomain = (host, domain) => host.match(`(.+)\.${domain}`)
export const isDomain = (host, domain) => host.match(domain)

export const getDomain = (store, serverConfig) => {
  const { sourceRequest: { host } } = store.getState()
  const domain = serverConfig.appDomain
  return { domain, host }
}

export const isIndexRedirected = (store) => (nextState, replace) => {
  const credentials = AccountSelectors(store.getState()).getCredentials()

  if (!credentials) {
    UserIsLogged(store)(nextState, replace)
  } else {
    IsCommunitySelected(store)(nextState, replace)
  }
}

export const IsCommunitySelected = (store) => (nextState, replace) => {
  const community = CommunitySelectors.getCurrent(store.getState())
  if (!community) {
    replace(Paths.list())
  }
}

export const UserIsLogged = (store) => (nextState, replace) => {
  const credentials = AccountSelectors(store.getState()).getCredentials()

  if (!credentials) {
    replace(Paths.login())
  }
}

import AccountSelectors from '~client/account/redux/selectors'
import * as CommunitySelectors from '~client/community/selectors'
import * as Paths from '~client/paths'

export const showMobilizationPublicView = ({ host, domain }) => {
  return (isSubdomain(host, domain) || !isDomain(host, domain))
}

// eslint-disable-next-line
export const isSubdomain = (host, domain) => host.match(`(.+)\.${domain}`)
export const isDomain = (host, domain) => host.match(domain)

export const getDomain = (store, serverConfig) => {
  const { sourceRequest: { host } } = store.getState()
  const domain = serverConfig.appDomain
  return { domain, host }
}

export const isIndexRedirected = (store) => (nextState, replace) => {
  const credentials = AccountSelectors(store.getState()).getCredentials()
  const community = CommunitySelectors.getCurrent(store.getState())

  if (!credentials) {
    replace(Paths.login())
  } else if (!community) {
    replace(Paths.list())
  } else {
    replace(Paths.mobilizations())
  }
}

export const IsCommunitySelected = (store) => (nextState, replace) => {
  const community = CommunitySelectors.getCurrent(store.getState())
  if (!community) {
    replace(Paths.list())
  }
}

export const UserIsLogged = (store) => (nextState, replace) => {
  const credentials = AccountSelectors(store.getState()).getCredentials()

  if (!credentials) {
    replace(Paths.login())
  }
}

Fix redirect for index when community in cookie