import http from '../http';

export default class collections {

    static get(collectionID) {
        return http.get(`/zebedee/collectionDetails/${collectionID}`)
            .then(response => {
                return response;
            })
    }

    static getAll() {
        return http.get(`/zebedee/collections`)
            .then(response => {
                return response;
            })
    }

    static create(body) {
        return http.post(`/zebedee/collection`, body)
            .then(response => {
                return response;
            })
    }

    static approve(collectionID) {
        return http.post(`/zebedee/approve/${collectionID}`);
    }

    static delete(collectionID) {
        return http.delete(`/zebedee/collection/${collectionID}`);
    }

    static update(collectionID, body) {
        body.id = collectionID;
        return http.put(`/zebedee/collection/${collectionID}`, body);
    }

    static deletePage(collectionID, pageURI) {
        return http.delete(`/zebedee/content/${collectionID}?uri=${pageURI}`);
    }

    static cancelDelete(collectionID, pageURI) {
        return http.delete(`/zebedee/DeleteContent/${collectionID}?uri=${pageURI}`);
    }

    static async checkContentIsInCollection(pageURI) {
        return http.get(`/zebedee/checkcollectionsforuri?uri=${pageURI}`)
    }

}

import http from '../http';

export default class collections {

    static get(collectionID) {
        return http.get(`/zebedee/collectionDetails/${collectionID}`)
            .then(response => {
                return response;
            })
    }

    static getAll() {
        return http.get(`/zebedee/collections`)
            .then(response => {
                return response;
            })
    }

    static create(body) {
        return http.post(`/zebedee/collection`, body)
            .then(response => {
                return response;
            })
    }

    static approve(collectionID) {
        return http.post(`/zebedee/approve/${collectionID}`);
    }

    static delete(collectionID) {
        return http.delete(`/zebedee/collection/${collectionID}`);
    }

    static update(collectionID, body) {
        body.id = collectionID;
        return http.put(`/zebedee/collection/${collectionID}`, body);
    }

    static deletePage(collectionID, pageURI) {
        return http.delete(`/zebedee/content/${collectionID}?uri=${pageURI}`);
    }

    static cancelDelete(collectionID, pageURI) {
        return http.delete(`/zebedee/DeleteContent/${collectionID}?uri=${pageURI}`);
    }

    static async checkContentIsInCollection(pageURI) {
        return http.get(`/zebedee/checkcollectionsforuri?uri=${pageURI}`)
    }

    static getInProgressContent(collectionID) {
        return http.get(`/zebedee/collectionDetails/${collectionID}`)
            .then(response => {
                return response.inProgress;
            })
    }

}

Add get in progress content mehtod


import React from 'react'
import ReactDOM from 'react-dom'
import CircularProgressButton from './CircularProgressButton'
import Button from 'material-ui/Button'

import { mount, shallow, render } from 'enzyme'

it('renders without crashing', () => {
    const div = document.createElement('div');
    ReactDOM.render(<CircularProgressButton />, div);
});

it('calls onClick method when loading is off and onClick is passed to the component', () => {
    const onClick = jest.fn()
    const wrapper = mount(<CircularProgressButton onClick={onClick}/>)
    const button = wrapper.find(Button)
    expect(button.length).toBe(1)
    button.simulate('click')
    expect(onClick.mock.calls.length).toBe(1);
});

it('does not call onClick method when loading is on', () => {
    const onClick = jest.fn()
    const wrapper = mount(<CircularProgressButton status='loading' onClick={onClick}/>)
    const button = wrapper.find(Button)
    expect(button.length).toBe(1)
    button.simulate('click')
    expect(onClick.mock.calls.length).toBe(0);
});

it('does not call onClick method when onClick is undefined', () => {
    const onClick = jest.fn()
    const wrapper = mount(<CircularProgressButton status='loading'/>)
    const button = wrapper.find(Button)
    expect(button.length).toBe(1)
    button.simulate('click')
    expect(onClick.mock.calls.length).toBe(0);
});

import React from 'react'
import ReactDOM from 'react-dom'
import CircularProgressButton from './CircularProgressButton'
import Button from 'material-ui/Button'
import { createShallow } from 'material-ui/test-utils'

let shallow;

beforeAll(() => {
    shallow = createShallow({ dive: true });
});

it('renders without crashing', () => {
    const div = document.createElement('div');
    ReactDOM.render(<CircularProgressButton />, div);
});

it('calls onClick method when loading is off and onClick is passed to the component', () => {
    const onClick = jest.fn()
    const wrapper = shallow(<CircularProgressButton onClick={onClick}/>)
    const button = wrapper.find(Button)
    expect(button.length).toBe(1)
    button.simulate('click')
    expect(onClick.mock.calls.length).toBe(1);
});

it('does not call onClick method when loading is on', () => {
    const onClick = jest.fn()
    const wrapper = shallow(<CircularProgressButton status='loading' onClick={onClick}/>)
    const button = wrapper.find(Button)
    expect(button.length).toBe(1)
    button.simulate('click')
    expect(onClick.mock.calls.length).toBe(0);
});

it('does not call onClick method when onClick is undefined', () => {
    const onClick = jest.fn()
    const wrapper = shallow(<CircularProgressButton status='loading'/>)
    const button = wrapper.find(Button)
    expect(button.length).toBe(1)
    button.simulate('click')
    expect(onClick.mock.calls.length).toBe(0);
});

Use extended shallow instead of mount from enzyme


const express = require('express');
const exphbs = require('express-handlebars');
const bodyParser = require('body-parser');
const enforce = require('express-sslify');
const controller = require('./controller');

// ignore this, only for monitoring over heroku

const app = express();

// enforce to redirect to HTTPS
app.use(enforce.HTTPS({ trustProtoHeader: true }));

// app setup
app.engine('handlebars', exphbs({ defaultLayout: 'main' }));
app.set('view engine', 'handlebars');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use('/public', express.static(`${__dirname}/public`));

// app controller
app.get('/', controller.index);
// 404
app.use('*', controller.notFound);

// also ignore this if you run on your local machine

app.set('port', process.env.PORT || 5000);

module.exports = app;

const express = require('express');
const exphbs = require('express-handlebars');
const bodyParser = require('body-parser');
const enforce = require('express-sslify');
const controller = require('./controller');

// ignore this, only for monitoring over heroku

const app = express();

// enforce to redirect to HTTPS | disable if your localhost does not support https
app.use(enforce.HTTPS({ trustProtoHeader: true }));

// app setup
app.engine('handlebars', exphbs({ defaultLayout: 'main' }));
app.set('view engine', 'handlebars');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use('/public', express.static(`${__dirname}/public`));

// app controller
app.get('/', controller.index);
// 404
app.use('*', controller.notFound);

// also ignore this if you run on your local machine

app.set('port', process.env.PORT || 5000);

module.exports = app;

Add comment to disable https if running locally (without ssl support)


import axios from 'axios';

export const FETCH_POSTS = 'FETCH_POSTS';

export function fetchPosts() {
  return {
    type: FETCH_POSTS
  };
}

import axios from 'axios';

export const FETCH_POSTS = 'fetch_posts';

const ROOT_URL ='https://reduxblog.herokuapp.com/api';
const API_KEY = '?key=unhommequidort';

export function fetchPosts() {
  const request = axios.get(`${ROOT_URL}/posts${API_KEY}`);

  return {
    type: FETCH_POSTS,
    payload: request
  };
}

Add api request to action creator


/* 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/. */

const config = require('./config');

const HEADERS = ['X-Content-Security-Policy', 'Content-Security-Policy'];
const VALUE = [
  "default-src 'self' " + config.get('personaUrl'),
  "img-src 'self' data:"
].join(';');

module.exports = function cspFactory(routes) {
  return function csp(req, res, next) {
    if (routes.indexOf(req.path) > -1) {
      HEADERS.forEach(function(header) {
        res.setHeader(header, VALUE);
      });
    }
    next();
  };
};

/* 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/. */

const config = require('./config');

const HEADERS = ['X-Content-Security-Policy', 'Content-Security-Policy'];
const VALUE = [
  "default-src 'self' " + config.get('personaUrl'),
  "img-src 'self' data:",
  "frame-src https://accounts.google.com"
].join(';');

module.exports = function cspFactory(routes) {
  return function csp(req, res, next) {
    if (routes.indexOf(req.path) > -1) {
      HEADERS.forEach(function(header) {
        res.setHeader(header, VALUE);
      });
    }
    next();
  };
};

Allow the logout iframe to work


class ThrusterClient {
    constructor() {
        // TODO: initialize our websocket connection
    }

    sendMessage(controller, type, index, value) {
        // build message binary payload per services/controllers/message.py
        let fa = new Float32Array([value]);
        let ba = new Int8Array(fa.buffer);
        console.log(ba);

        // send websocket packet
    }
}

class ThrusterClient {
    constructor(cb) {
        this.cb = cb;
        this.socket = new WebSocket("ws://127.0.0.1:9997/");
        this.socket.type = "arraybuffer";
        this.socket.addEventListener("message", (e) => this.message(e));

        if (this.cb !== null && this.cb !== undefined) {
            this.socket.addEventListener("open", cb);
        }
    }

    sendMessage(controller, type, index, value) {
        console.log(`C: ${controller}, T: ${type}, I: ${index}, V: ${value}`);

        // build message binary payload per services/controllers/message.py
        let b1 =
            (controller & 0x03) << 6 |
            (type & 0x03) << 4 |
            (index & 0x0F);

        let floatArray = new Float32Array([value]);
        let byteArray = new Int8Array(floatArray.buffer);

        let result = new Int8Array([
            b1,
            byteArray[0],
            byteArray[1],
            byteArray[2],
            byteArray[3],
        ]);

        // send websocket packet
        this.socket.send(result);
    }

    message(e) {

    }
}

Add socket and message packing


"use strict";
let fetch = require('node-fetch');
let Converter = require("csvtojson");
let Promise = require('bluebird');
let _ = require("lodash");
let writeFile = Promise.promisify(require('fs').writeFile);

const updateTimestampFile = require("./helpers/update-timestamp-file");

const input = require("./input");
const output = require("./output");

/**
 *
 * @param {Array} judges
 * @returns {PromiseLike<*[]>|Promise<*[]>|JQueryPromise<*[]>|JQueryPromise<void>|Promise.<*[]>}
 */
module.exports = function zipJudges (judges, dictionary) {
    judges = _.map(judges, (judge) => {
        return {
            d: _.get(dictionary, judge.d), // department
            p: _.get(dictionary, judge.p), // position
            r: _.get(dictionary, judge.r), // region
            n: judge.n, // Surname Name Patronymic
            k: judge.k // key of JSON file under http://prosud.info/declarations/AbdukadirovaKarineEskenderivna.json
        };
    });

    var content = JSON.stringify(judges);
    return updateTimestampFile(output.judges, content)
        .then(() => writeFile(output.judges, content))
        .then(() => judges);
};

"use strict";
let fetch = require('node-fetch');
let Converter = require("csvtojson");
let Promise = require('bluebird');
let _ = require("lodash");
let writeFile = Promise.promisify(require('fs').writeFile);

const updateTimestampFile = require("./helpers/update-timestamp-file");

const input = require("./input");
const output = require("./output");

/**
 *
 * @param {Array} judges
 * @param {Array} dictionary
 * @returns {PromiseLike<*[]>|Promise<*[]>|JQueryPromise<*[]>|JQueryPromise<void>|Promise.<*[]>}
 */
module.exports = function zipJudges (judges, dictionary) {
    judges = _.map(judges, (judge) => {
        return {
            d: _.get(dictionary, judge.d), // department
            p: _.get(dictionary, judge.p), // position
            r: _.get(dictionary, judge.r), // region
            n: judge.n, // Surname Name Patronymic
            k: judge.k // key of JSON file under http://prosud.info/declarations/AbdukadirovaKarineEskenderivna.json
        };
    });

    var content = JSON.stringify(judges);
    return updateTimestampFile(output.judges, content)
        .then(() => writeFile(output.judges, content))
        .then(() => judges);
};

Divide and conquer update JSDoc


/**
 * Controller Object to dispatch actions to view/Contact and model/Contacts.
 * @constructor
 */
var ContactsController = function() {
};

ContactsController.remove = function(id) {
  ContactModel.remove(id);
  ContactView.remove(id);
};

ContactsController.render = function(id, contact) {
  var contactView = new ContactView(id, contact);
};

ContactsController.prototype = {
  setup: function() {
    var addContact = new AddContactForm();
  },

  /**
   * @description Fetches all existing contacts from LocalStorage.
   */
  fetchAll: function() {
    var contacts = [];
    var total = localStorage.length;

    for (i = 0; i < total; i++) {
      var contact = {};
      var key = localStorage.key(i);
      if (key !== 'debug') {
        contact.key = key;
        contact.value = JSON.parse(localStorage.getItem((i + 1).toString()));
        contacts.push(contact);
      }
    }

    return contacts;
  },

  /**
   * @description Adds all existing contacts to table. Intended for use
   * on startup.
   */
  renderAll: function() {
    var contacts = this.fetchAll();
    contacts.forEach(function(currentValue) {
      var contact = new ContactView(currentValue.key, currentValue.value);
    });
  }
};

module.exports = ContactsController;

// Keep requires after the exports to prevent cirular dependency issues
var ContactModel = require('../model/Contacts');
var ContactView = require('../view/Contact');
var AddContactForm = require('../view/AddContactForm');

/**
 * Controller Object to dispatch actions to view/Contact and model/Contacts.
 * @constructor
 */
var ContactsController = function() {
};

ContactsController.prototype = {
  setup: function() {
    var addContact = new AddContactForm();
  },

  /**
   * @description Fetches all existing contacts from LocalStorage.
   */
  fetchAll: function() {
    var contacts = [];
    var total = localStorage.length;

    for (i = 0; i < total; i++) {
      var contact = {};
      var key = localStorage.key(i);
      if (key !== 'debug') {
        contact.key = key;
        contact.value = JSON.parse(localStorage.getItem((i + 1).toString()));
        contacts.push(contact);
      }
    }

    return contacts;
  },

  /**
   * @description Adds all existing contacts to table. Intended for use
   * on startup.
   */
  renderAll: function() {
    var contacts = this.fetchAll();
    contacts.forEach(function(currentValue) {
      var contact = new ContactView(currentValue.key, currentValue.value);
    });
  }
};

ContactsController.remove = function(id) {
  ContactModel.remove(id);
  ContactView.remove(id);
};

ContactsController.render = function(id, contact) {
  var contactView = new ContactView(id, contact);
};

module.exports = ContactsController;

// Keep requires after the exports to prevent cirular dependency issues
var ContactModel = require('../model/Contacts');
var ContactView = require('../view/Contact');
var AddContactForm = require('../view/AddContactForm');

Move static methods to be after instance methods


/**
 * @class Wasp.Portlet
 * @extends Ext.panel.Panel A {@link Ext.panel.Panel Panel} class that is
 *          managed by {@link Wasp.PortalPanel}.
 */
Ext.define('Wasp.Portlet', {
			extend : 'Ext.panel.Panel',
			alias : 'widget.portlet',
			layout : 'fit',
			anchor : '100%',
			frame : true,
			closable : true,
			collapsible : true,
			animCollapse : true,
			draggable : {
				moveOnDrag : false
			},
			cls : 'x-portlet',

			// Override Panel's default doClose to provide a custom fade out
			// effect
			// when a portlet is removed from the portal
			doClose : function() {
				if (!this.closing) {
					this.closing = true;
					if (this.el !== undefined) {
						this.el.animate({
									opacity : 0,
									callback : function() {
										var closeAction = this.closeAction;
										this.closing = false;
										this.fireEvent('close', this);
										this[closeAction]();
										if (closeAction == 'hide') {
											this.el.setOpacity(1);
										}
									},
									scope : this
								});
					}
				}
			}
		});

/**
 * @class Wasp.Portlet
 * @extends Ext.panel.Panel A {@link Ext.panel.Panel Panel} class that is
 *          managed by {@link Wasp.PortalPanel}.
 */
Ext.define('Wasp.Portlet', {
			extend : 'Ext.panel.Panel',
			alias : 'widget.portlet',
			layout : 'fit',
			anchor : '100%',
			frame : true,
			closable : true,
			collapsible : true,
			animCollapse : true,
			autoScroll: true,
			draggable : {
				moveOnDrag : false
			},
			cls : 'x-portlet',

			// Override Panel's default doClose to provide a custom fade out
			// effect
			// when a portlet is removed from the portal
			doClose : function() {
				if (!this.closing) {
					this.closing = true;
					if (this.el !== undefined) {
						this.el.animate({
									opacity : 0,
									callback : function() {
										var closeAction = this.closeAction;
										this.closing = false;
										this.fireEvent('close', this);
										this[closeAction]();
										if (closeAction == 'hide') {
											this.el.setOpacity(1);
										}
									},
									scope : this
								});
					}
				}
			}
		});

Add scroll bar to the file view window


import { SlidehubPlugin } from '../core/SlidehubPlugin';
import { config } from '../config';
import { listener } from '../util/passive-event-listener';

export { Highlighter };

/**
 * Highlighter.
 *
 * Highlights documents/items on hover
 */
class Highlighter extends SlidehubPlugin {
  constructor(slidehub) {
    const description = 'Highlights documents/items on hover';
    super(slidehub, 'Highlighter', description);

    this.boundHandleHighlight = this.handleHighlight.bind(this);
  }

  enable() {
    document.addEventListener('mousemove', this.boundHandleHighlight, listener.passive);
    super.enable();
  }

  disable() {
    this.slidehub.unhighlightDocument();
    document.removeEventListener('mousemove', this.boundHandleHighlight);
    super.disable();
  }

  /**
   * @param {MouseEvent} event
   */
  handleHighlight(event) {
    if (event.target instanceof Element) {
      const docNode = event.target.closest(config.selector.doc);
      if (docNode) {
        const doc = this.slidehub.documents.get(docNode.id);
        this.slidehub.highlightDocument(doc);

        if (config.keepSelectedPageInFirstColumn) {
          return;
        }

        const itemNode = event.target.closest(config.selector.item);
        if (itemNode) {
          doc.highlightItem(itemNode);
        }
      }
    }
  }
};

import { SlidehubPlugin } from '../core/SlidehubPlugin';
import { config } from '../config';
import { listener } from '../util/passive-event-listener';

export { Highlighter };

/**
 * Highlighter.
 *
 * Highlights documents/items on hover
 */
class Highlighter extends SlidehubPlugin {
  constructor(slidehub) {
    const description = 'Highlights documents/items on hover';
    super(slidehub, 'Highlighter', description);

    this.boundHandleHighlight = this.handleHighlight.bind(this);
  }

  enable() {
    document.addEventListener('mousemove', this.boundHandleHighlight, listener.passive);
    super.enable();
  }

  disable() {
    this.slidehub.unhighlightDocument();
    document.removeEventListener('mousemove', this.boundHandleHighlight);
    super.disable();
  }

  /**
   * @param {MouseEvent} event
   */
  handleHighlight(event) {
    if (event.target instanceof Element) {
      const docNode = event.target.closest(config.selector.doc);
      const doc = this.slidehub.documents.get(docNode.id);
      if (doc.loaded) {
        this.slidehub.highlightDocument(doc);

        if (config.keepSelectedPageInFirstColumn) {
          return;
        }

        const itemNode = event.target.closest(config.selector.item);
        if (itemNode) {
          doc.highlightItem(itemNode);
        }
      }
    }
  }
};

Fix attempting to highlight a unloaded document


"use strict";

const express = require("express");
const bodyParser = require("body-parser");
const barelyBot = require("./barelyBot").bot;
const defineBot = require("./defineBot");

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

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

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

app.post("/doesheknower", barelyBot);
app.post("/define", defineBot);

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

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

"use strict";

const express = require("express");
const bodyParser = require("body-parser");
const barelyBot = require("./barelyBot").bot;
const defineBot = require("./defineBot");

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

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

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

app.post("/doesheknower", barelyBot);
app.post("/define", defineBot);

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

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

Update name on index page


(function (window) {
	'use strict';

	var hearingTest = HearingTest;
	var hearing = hearingTest.init();

	var app = new Vue({
		el: '#hearing-test-app',
		data: {
			isPlaySound: false,
			frequency: 1000
		},
		methods: {
			changeFrequency: function() {
				hearing.sound.frequency.value = this.frequency;
			},

			playSound: function() {
				hearingTest.playSound();
				this.isPlaySound = true;
			},

			stopSound: function() {
				hearingTest.stopSound();
				this.isPlaySound = false;
			}
		}
	});
}(window));

(function (exports) {
	'use strict';

	var hearingTest = HearingTest;
	var hearing = hearingTest.init();

	var app = new Vue({
		el: '#hearing-test-app',
		data: {
			isPlaySound: false,
			frequency: 1000
		},
		methods: {
			changeFrequency: function() {
				hearing.sound.frequency.value = this.frequency;
			},

			playSound: function() {
				hearingTest.playSound();
				this.isPlaySound = true;
			},

			stopSound: function() {
				hearingTest.stopSound();
				this.isPlaySound = false;
			}
		}
	});
}(window));

Rename argument window to exports


'use strict';

var livereload = require('livereload');
var connectLr = require('connect-livereload-safe');

module.exports = function(lingon, config) {

  // add script tag injection middleware to the express server
  lingon.one('serverConfigure', function() {

    config = config || {};
    config.port = config.port || 35729;

    // inject browser script tag
    lingon.server.use(connectLr({
      port: config.port
    }));

    process.on('uncaughtException', function(error) {
      if(error.code == 'EADDRINUSE') {
        lingon.log.error('Port ' + config.port + ' is already in use, lingon-livereload could not start!');
        lingon.log.info('[Info] Try changing the port in your lingon-livereload config');
        process.exit();
      }
    });
  });

  // start the livereload server
  lingon.one('serverStarted', function() {

    var server;

    // set livereload to watch the source dir
    config.watchDir = config.watchDir || process.cwd() + '/' + lingon.config.sourcePath;

    // add extensions for livereload to watch
    config.exts = config.exts || lingon.config.directiveFileTypes.concat(Object.keys(lingon.config.extensionRewrites));

    server = livereload.createServer(config);

    server.watch(config.watchDir);

    lingon.log('Livereload is listening on port ' + config.port);
  });
};

'use strict';

var livereload = require('livereload'),
    connectLr = require('connect-livereload-safe');

module.exports = function(lingon, config) {

  // add script tag injection middleware to the express server
  lingon.one('serverConfigure', function() {

    config = config || {};
    config.port = config.port || 35729;

    // inject browser script tag
    lingon.server.use(connectLr({
      port: config.port
    }));
  });

  // start the livereload server
  lingon.one('serverStarted', function() {

    var server;

    // set livereload to watch the source dir
    config.watchDir = config.watchDir || process.cwd() + '/' + lingon.config.sourcePath;

    // add extensions for livereload to watch
    config.exts = config.exts || lingon.config.directiveFileTypes.concat(Object.keys(lingon.config.extensionRewrites));

    server = livereload.createServer(config);

    server.watch(config.watchDir);

    lingon.log('Livereload is listening on port ' + config.port);
  });
};

Revert "catch port in use error"


var Promise = require('../utils/promise');
var IGNORE_FILES = require('../constants/ignoreFiles');

/**
    Parse ignore files

    @param {Book}
    @return {Book}
*/
function parseIgnore(book) {
    if (book.isLanguageBook()) {
        return Promise.reject(new Error('Ignore files could be parsed for language books'));
    }

    var fs = book.getFS();
    var ignore = book.getIgnore();

    ignore.addPattern([
        // Skip Git stuff
        '.git/',

        // Skip OS X meta data
        '.DS_Store',

        // Skip stuff installed by plugins
        'node_modules',

        // Skip book outputs
        '_book',
        '*.pdf',
        '*.epub',
        '*.mobi',

        // Ignore files in the templates folder
        '_layouts'
    ]);

    return Promise.serie(IGNORE_FILES, function(filename) {
        return fs.readAsString(filename)
        .then(function(content) {
            ignore.addPattern(content.toString().split(/\r?\n/));
        }, function(err) {
            return Promise();
        });
    })

    .thenResolve(book);
}

module.exports = parseIgnore;

var Promise = require('../utils/promise');
var IGNORE_FILES = require('../constants/ignoreFiles');

/**
    Parse ignore files

    @param {Book}
    @return {Book}
*/
function parseIgnore(book) {
    if (book.isLanguageBook()) {
        return Promise.reject(new Error('Ignore files could be parsed for language books'));
    }

    var fs = book.getFS();
    var ignore = book.getIgnore();

    ignore.addPattern([
        // Skip Git stuff
        '.git/',

        // Skip OS X meta data
        '.DS_Store',

        // Skip stuff installed by plugins
        'node_modules',

        // Skip book outputs
        '_book',

        // Ignore files in the templates folder
        '_layouts'
    ]);

    return Promise.serie(IGNORE_FILES, function(filename) {
        return fs.readAsString(filename)
        .then(function(content) {
            ignore.addPattern(content.toString().split(/\r?\n/));
        }, function(err) {
            return Promise();
        });
    })

    .thenResolve(book);
}

module.exports = parseIgnore;

Remove filter for pdf/epub/mobi files when outputting


// created to start cleaning up "window" interaction
//
window.show = function(id) {
        window.hideall();
        if (typeof android !== 'undefined' && android && android.switchToPane) {
          android.switchToPane(id);
        }
        switch(id) {
                case 'full':
                        window.chat.show('full');
                        break;
                case 'compact':
                        window.chat.show('compact');
                        break;
                case 'public':
                        window.chat.show('public');
                        break;
                case 'faction':
                        window.chat.show('faction');
                        break;
                case 'debug':
                        window.debug.console.show();
                        break;
                case 'map':
                        window.smartphone.mapButton.click();
                        $('#portal_highlight_select').show();
                        $('#farm_level_select').show();
                        break;
                case 'info':
                        window.smartphone.sideButton.click();
                        break;
                default:
                        window.smartphone.mapButton.click();
                        break;
        }
}

window.hideall = function() {
    $('#chatcontrols, #chat, #chatinput, #sidebartoggle, #scrollwrapper, #updatestatus, #portal_highlight_select').hide();
    $('#farm_level_select').hide();
    $('#map').css('visibility', 'hidden');
}

// created to start cleaning up "window" interaction
//
window.show = function(id) {
        window.hideall();
        if (typeof android !== 'undefined' && android && android.switchToPane) {
          android.switchToPane(id);
        }
        switch(id) {
                case 'full':
                        window.chat.show('full');
                        break;
                case 'compact':
                        window.chat.show('compact');
                        break;
                case 'public':
                        window.chat.show('public');
                        break;
                case 'faction':
                        window.chat.show('faction');
                        break;
                case 'debug':
                        window.debug.console.show();
                        break;
                case 'map':
                        window.smartphone.mapButton.click();
                        $('#portal_highlight_select').show();
                        $('#farm_level_select').show();
                        break;
                case 'info':
                        window.smartphone.sideButton.click();
                        break;
                default:
                        window.smartphone.mapButton.click();
                        break;
        }
}

window.hideall = function() {
    $('#chatcontrols, #chat, #chatinput, #sidebartoggle, #scrollwrapper, #updatestatus, #portal_highlight_select').hide();
    $('#farm_level_select').hide();
    $('#map').css('visibility', 'hidden');
    $('.ui-tooltip').remove();
}

Remove any tooltip on pane change


import { Meteor } from 'meteor/meteor';

const maybeGetUser = (userOrId) => {
  if (!userOrId)
    return null;
  if (userOrId._id)
    return userOrId;
  else
    return Meteor.users.findOne(userOrId);
};

export const getFirstInitial = (user) => {
  user = maybeGetUser(user);
  const firstName = user.profile.name.first;

  return firstName ? firstName.charAt(0) : null;
}

import { Meteor } from 'meteor/meteor';
import * as Helper from './helper';

const maybeGetUser = (userOrId) => {
  if (!userOrId)
    return null;
  if (userOrId._id)
    return userOrId;
  else
    return Meteor.users.findOne(userOrId);
};

export const getFirstInitial = (user) => {
  user = maybeGetUser(user);
  return propExistsDeep(user, ['profile', 'name', 'first']) ? user.profile.name.first.charAt(0) : null;
}

Fix thrown error if no firstName


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

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

Add tiny mce script to build


Package.describe({
  name: 'practicalmeteor:mocha-console-runner',
  version: '0.2.0',
  summary: 'A mocha console reporter for running your package tests from the command line with spacejam.',
  git: 'https://github.com/practicalmeteor/meteor-mocha-console-runner.git',
  // By default, Meteor will default to using README.md for documentation.
  // To avoid submitting documentation, set this field to null.
  documentation: 'README.md',
  testOnly: true
});

Package.onUse(function(api) {
  api.versionsFrom("1.3");
  api.use(['coffeescript', "practicalmeteor:mocha@2.4.5-rc.1", 'practicalmeteor:loglevel@1.2.0_2', 'ecmascript']);
  api.mainModule('ConsoleReporter.coffee', 'client');
  api.imply("practicalmeteor:mocha@2.4.5-rc.1");
});

Package.onTest(function(api) {

});

Package.describe({
  name: 'practicalmeteor:mocha-console-runner',
  version: '0.2.0-rc.4',
  summary: 'A mocha console reporter for running your package tests from the command line with spacejam.',
  git: 'https://github.com/practicalmeteor/meteor-mocha-console-runner.git',
  // By default, Meteor will default to using README.md for documentation.
  // To avoid submitting documentation, set this field to null.
  documentation: 'README.md',
  testOnly: true
});

Package.onUse(function(api) {
  api.versionsFrom("1.3");
  api.use(['coffeescript', "practicalmeteor:mocha@2.4.5_1", 'practicalmeteor:loglevel@1.2.0_2', 'ecmascript']);
  api.mainModule('ConsoleReporter.coffee', 'client');
  api.imply("practicalmeteor:mocha@2.4.5_1");
});

Package.onTest(function(api) {

});

Add dependecy to latest mocha release


Package.describe({
  name: 'qulture:qr-ui-base',
  version: '0.0.2',
  summary: 'QR base react components and styles',
  git: 'https://github.com/qulture/qr-ui-base',
  documentation: 'README.md'
});

Package.onUse(function(api) {
  api.versionsFrom('1.3.2.4');
  api.export('QRComponents');

  api.use('ecmascript');
  api.use('fourseven:scss@3.4.3');
  api.use('wolves:bourbon@3.1.0');
  api.use('cinn:react-form-helpers@0.0.1');

  api.addAssets([
    'public/fonts/font-icons.eot',
    'public/fonts/font-icons.ttf',
    'public/fonts/font-icons.woff',
    'public/fonts/font-icons.svg'
  ], 'client');

  api.addFiles('styles/_qr-base.scss', 'client', { isImport: true });
});

Package.describe({
  name: 'qulture:qr-ui-base',
  version: '0.0.3',
  summary: 'QR base react components and styles',
  git: 'https://github.com/qulture/qr-ui-base',
  documentation: 'README.md'
});

Package.onUse(function(api) {
  api.versionsFrom('1.3.2.4');
  api.export('QRComponents');

  api.use('ecmascript');
  api.use('fourseven:scss@3.4.3');
  api.use('wolves:bourbon@3.1.0');
  api.use('cinn:react-form-helpers@0.0.1');

  api.addAssets([
    'public/fonts/font-icons.eot',
    'public/fonts/font-icons.ttf',
    'public/fonts/font-icons.woff',
    'public/fonts/font-icons.svg'
  ], 'client');

  api.addFiles('styles/_qr-base.scss', 'client', { isImport: true });
});

Upgrade version number to 0.0.3


Package.describe({
  name: "mizzao:autocomplete",
  summary: "Client/server autocompletion designed for Meteor's collections and reactivity",
  version: "0.4.10",
  git: "https://github.com/mizzao/meteor-autocomplete.git"
});

Package.onUse(function (api) {
  api.versionsFrom("0.9.4");

  api.use(['blaze', 'templating', 'jquery'], 'client');
  api.use(['coffeescript', 'underscore']); // both
  api.use(['mongo', 'ddp']);

  api.use("dandv:caret-position@2.1.0-3", 'client');

  // Our files
  api.addFiles([
    'autocomplete.css',
    'inputs.html',
    'autocomplete-client.coffee',
    'templates.coffee'
  ], 'client');

  api.addFiles([
    'autocomplete-server.coffee'
  ], 'server');

  api.export('Autocomplete', 'server');
  api.export('AutocompleteTest', {testOnly: true});
});

Package.onTest(function(api) {
  api.use("mizzao:autocomplete");

  api.use('coffeescript');
  api.use('tinytest');

  api.addFiles('tests/rule_tests.coffee', 'client');
  api.addFiles('tests/regex_tests.coffee', 'client');
  api.addFiles('tests/param_tests.coffee', 'client');
  api.addFiles('tests/security_tests.coffee');
});

Package.describe({
  name: "mizzao:autocomplete",
  summary: "Client/server autocompletion designed for Meteor's collections and reactivity",
  version: "0.4.10",
  git: "https://github.com/mizzao/meteor-autocomplete.git"
});

Package.onUse(function (api) {
  api.versionsFrom("0.9.4");

  api.use(['blaze', 'templating', 'jquery'], 'client');
  api.use(['coffeescript', 'underscore']); // both
  api.use(['mongo', 'ddp']);

  api.use("dandv:caret-position@2.1.0-3", 'client');

  // Our files
  api.addFiles([
    'autocomplete.css',
    'inputs.html',
    'autocomplete-client.coffee',
    'templates.coffee'
  ], 'client');

  api.addFiles([
    'autocomplete-server.coffee'
  ], 'server');

  api.export('Autocomplete', 'server');
  api.export('AutocompleteTest', {testOnly: true});
});

Package.onTest(function(api) {
  api.use("mizzao:autocomplete");

  api.use('coffeescript');
  api.use('mongo');
  api.use('tinytest');

  api.addFiles('tests/rule_tests.coffee', 'client');
  api.addFiles('tests/regex_tests.coffee', 'client');
  api.addFiles('tests/param_tests.coffee', 'client');
  api.addFiles('tests/security_tests.coffee');
});

Add explicit dependency on mongo for tests


/*

My Custom JS
============

Author: Jürgen Scholz
Updated: November 2014
Notes:

*/

$(function() {

    //<!-- smooth scrolling -->
    $('a[href^="#"]').on('click',function (e) {
        e.preventDefault();

        var target = this.hash;
        $target = $(target);

        $('html, body').stop().animate({
            'scrollTop': ($target.offset().top-60)
        }, 900, 'swing', function () {
            window.location.hash = target;
        });
    });

    function load() {
        $('#getblocknumber').load('http://blockchain.machinecoin.org:8080/chain/Machinecoin/q/getblockcount',
          function (responseText, textStatus, XMLHttpRequest) {
            if (textStatus == 'success') {
                 // all good!
            }
            if (textStatus == 'error') {
                 // oh noes!
            }
          });

        $('#getdifficulty').load('http://blockchain.machinecoin.org:8080/chain/Machinecoin/q/getdifficulty',
          function (responseText, textStatus, XMLHttpRequest) {
            if (textStatus == 'success') {
                 // all good!
            }
            if (textStatus == 'error') {
                 // oh noes!
            }
          });

        $('#getcoins').load('http://blockchain.machinecoin.org:8080/chain/Machinecoin/q/totalbc',
          function (responseText, textStatus, XMLHttpRequest) {
            if (textStatus == 'success') {
                 // all good!
                $('#getcoins').append(' Machinecoin');
            }
            if (textStatus == 'error') {
                 // oh noes!
            }
          });

        $.getJSON("https://www.cryptonator.com/api/ticker/mac-btc", function(data){
            $('#mac-btc-exchangerate').text(data.ticker.price + ' BTC');
        });

        $.getJSON("https://www.cryptonator.com/api/ticker/mac-eur", function(data){
            $('#mac-eur-exchangerate').text(data.ticker.price + ' EUR');
        });

        $.getJSON("https://www.cryptonator.com/api/ticker/mac-usd", function(data){
            $('#mac-usd-exchangerate').text(data.ticker.price + ' USD');
        });

    }

    load();

    setInterval(load, 60000);

});

/*

My Custom JS
============

Author: Jürgen Scholz
Updated: October 2015
Notes:

*/

$(function() {

    //<!-- smooth scrolling -->
    $('a[href^="#"]').on('click',function (e) {
        e.preventDefault();

        var target = this.hash;
        $target = $(target);

        $('html, body').stop().animate({
            'scrollTop': ($target.offset().top-60)
        }, 900, 'swing', function () {
            window.location.hash = target;
        });
    });

});

Remove not needed code fragments


/**
 * Copyright (c) 2017, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @flow
 */

export { subscribe, createSourceEventStream } from './subscribe';

Add a missing license header


/**
 * Copyright (c) 2002-2018 "Neo4j,"
 * Neo4j Sweden AB [http://neo4j.com]
 *
 * This file is part of Neo4j.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
module.exports = function(config) {
  config.set({
    frameworks: ['jasmine'],
    basePath: '../../',
    files: ['lib/browser/neo4j-web.test.js'],
    reporters: ['spec'],
    port: 9876,  // karma web server port
    colors: true,
    logLevel: config.LOG_DEBUG,
    browsers: ['FirefoxHeadless'],
    autoWatch: false,
    singleRun: true,
    concurrency: 1,
    browserNoActivityTimeout: 30 * 60 * 1000,
    customLaunchers: {
      FirefoxHeadless: {
        base: 'Firefox',
        flags: [ '-headless' ],
      },
    },
  })
}

/**
 * Copyright (c) 2002-2018 "Neo4j,"
 * Neo4j Sweden AB [http://neo4j.com]
 *
 * This file is part of Neo4j.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
module.exports = function(config) {
  config.set({
    frameworks: ['jasmine'],
    basePath: '../../',
    files: ['lib/browser/neo4j-web.test.js'],
    reporters: ['spec'],
    port: 9876,  // karma web server port
    colors: true,
    logLevel: config.LOG_DEBUG,
    browsers: ['FirefoxHeadless'],
    autoWatch: false,
    singleRun: true,
    concurrency: 1,
    browserNoActivityTimeout: 30 * 60 * 1000,
    customLaunchers: {
      FirefoxHeadless: {
        base: 'Firefox',
        flags: [ '-headless' ],
        prefs: {
          'network.websocket.max-connections': 256 // as in Chrome
        }
      },
    },
  })
}

Increase WebSocket limit in FF to be as in Chrome


#!/usr/bin/env node

var BodyParser = require('body-parser');
var DataDog = require('connect-datadog');
var Express = require('express');
var HTTP = require('http');

require('../lib/config');

var app = Express();
var server = HTTP.createServer(app);

if (Config.get('datadog:enable'))
  app.use(DataDog(Config.get('datadog')));

app.use(BodyParser.urlencoded({ extended: false }))
app.use(BodyParser.json())

require('../lib/control').attach(app);

server.listen(Config.get('service:listen'), function() {
  Log.info('Listening on port TCP/' + Config.get('service:listen'));
});

Update executable script to be executable


module.exports = function(argv) {
  var SocketCluster = require('socketcluster').SocketCluster;

  return new SocketCluster({
    host: argv.hostname || null,
    port: Number(argv.port) || 8000,
    workerController: __dirname + '/worker.js',
    allowClientPublish: false,
    protocol: argv.protocol || 'http',
    protocolOptions: !(argv.protocol === 'https') ? null : {
      key: argv.key || null,
      cert: argv.cert || null,
      passphrase: argv.passphrase || null
    }
  });
};

module.exports = function(argv) {
  var SocketCluster = require('socketcluster').SocketCluster;

  return new SocketCluster({
    host: argv.hostname || process.env.npm_package_remotedev_hostname || null,
    port: Number(argv.port || process.env.npm_package_remotedev_port) || 8000,
    workerController: __dirname + '/worker.js',
    allowClientPublish: false,
    protocol: argv.protocol || process.env.npm_package_remotedev_protocol || 'http',
    protocolOptions: !(argv.protocol === 'https') ? null : {
      key: argv.key || process.env.npm_package_remotedev_key || null,
      cert: argv.cert || process.env.npm_package_remotedev_cert || null,
      passphrase: argv.passphrase || process.env.npm_package_remotedev_passphrase || null
    }
  });
};

Support use `remotedev` npm package field


var util = require('util');
var mu = require('mu2');
var Convert = require('ansi-to-html');

function htmlFromText(text) {
    var tagsToReplace = {
        '&': '&',
        '<': '<'
    };
    return text.replace(/[&<]/g, function(tag) {
        return tagsToReplace[tag];
    });
}

module.exports = function (subprocess, res) {
    var convert = new Convert();

    res.writeHead(200, {
        "Content-Type": "text/html;charset=utf8"
    });

    var stream = mu.compileAndRender(
        'subprocess.mu.html', {
            title: subprocess.cmd[0],
            commandLine: subprocess.cmd.join(' '),
            exitCode: (subprocess.exitCode == null ? "still running" : subprocess.exitCode),
            lines: subprocess.output.map(function (line) {
                return {
                    class: line.stream,
                    html: convert.toHtml(htmlFromText(line.line))
                };
            })
        });
    stream.pipe(res);
};

var util = require('util');
var mu = require('mu2');
var Convert = require('ansi-to-html');

function htmlFromText(text) {
    var tagsToReplace = {
        '&': '&',
        '<': '<'
    };
    return text.replace(/[&<]/g, function(tag) {
        return tagsToReplace[tag];
    });
}

module.exports = function (subprocess, res) {
    var convert = new Convert();

    res.writeHead(200, {
        "Content-Type": "text/html;charset=utf8"
    });

    var stream = mu.compileAndRender(
        'subprocess.mu.html', {
            title: subprocess.cmd[0],
            commandLine: subprocess.cmd.join(' '),
            exitCode: (subprocess.exitCode == null ? "still running" : subprocess.exitCode),
            lines: subprocess.output.map(function (line) {
                return {
                    class: line.stream,
                    html: convert.toHtml(htmlFromText(line.line))
                };
            })
        });

    // We apparently trigger a bug in the core stream library
    // if we simply use stream.pipe(res);
    stream.on('data', function (d) { res.write(d); });
    stream.on('end', function (d) { res.end(d); });
};

Work around bug in node stream library


// wrapper around the non-sync fs functions to gracefully handle
// having too many file descriptors open.  Note that this is
// *only* possible because async patterns let one interject timeouts
// and other cleverness anywhere in the process without disrupting
// anything else.
var fs = require("fs")
  , timeout = 0

Object.keys(fs)
  .forEach(function (i) {
    exports[i] = (typeof fs[i] !== "function") ? fs[i]
               : (i.match(/^[A-Z]|^create|Sync$/)) ? function () {
                   return fs[i].apply(fs, arguments)
                 }
               : graceful(fs[i])
  })

function graceful (fn) { return function GRACEFUL () {
  var args = Array.prototype.slice.call(arguments)
    , cb_ = args.pop()
  args.push(cb)
  function cb (er) {
    if (er && er.message.match(/^EMFILE, Too many open files/)) {
      return setTimeout(function () {
        GRACEFUL.apply(fs, args)
      }, timeout ++)
    }
    cb_.apply(null, arguments)
  }
  fn.apply(fs, args)
}}

// wrapper around the non-sync fs functions to gracefully handle
// having too many file descriptors open.  Note that this is
// *only* possible because async patterns let one interject timeouts
// and other cleverness anywhere in the process without disrupting
// anything else.
var fs = require("fs")
  , timeout = 0
  , resetTimer

Object.keys(fs)
  .forEach(function (i) {
    exports[i] = (typeof fs[i] !== "function") ? fs[i]
               : (i.match(/^[A-Z]|^create|Sync$/)) ? function () {
                   return fs[i].apply(fs, arguments)
                 }
               : graceful(fs[i])
  })

function graceful (fn) { return function GRACEFUL () {
  var args = Array.prototype.slice.call(arguments)
    , cb_ = args.pop()
  args.push(cb)
  function cb (er) {
    if (er && er.message.match(/^EMFILE, Too many open files/)) {
      return setTimeout(function () {
        GRACEFUL.apply(fs, args)
      }, timeout ++)
      if (resetTimer) clearTimeout(resetTimer)
      resetTimer = setTimeout(function () { timeout = 0 }, 1000)
    }
    cb_.apply(null, arguments)
  }
  fn.apply(fs, args)
}}

Reset the EMFILE timeout value if no writes in 1s


const ONE_DAY = 86400 * 1000;

export const getLastWorkableDayTime = () => {
  const today = new Date();
  today.setHours(9, 0, 0, 0);
  const todayWeekNumber = today.getDay();

  let offsetTime = ONE_DAY; // one day
  if (todayWeekNumber <= 1) {
    // sunday or monday
    offsetTime += ONE_DAY;
  }
  if (todayWeekNumber === 1) {
    // monday
    offsetTime += ONE_DAY;
  }

  return today.getTime() - offsetTime;
};

const ONE_DAY = 86400 * 1000;

export const getLastWorkableDayTime = () => {
  const today = new Date();
  today.setHours(10, 30, 0, 0); // TODO customize this hour in settings
  const todayWeekNumber = today.getDay();

  let offsetTime = ONE_DAY; // one day
  if (todayWeekNumber <= 1) {
    // sunday or monday
    offsetTime += ONE_DAY;
  }
  if (todayWeekNumber === 1) {
    // monday
    offsetTime += ONE_DAY;
  }

  return today.getTime() - offsetTime;
};

Set last workable day boundary to later 10:30 am


import Marked from 'marked'

export const orderedNotes = state => {
  return state.notes.slice(0).sort((a, b) => {
    const aOrder = state.notesOrder[a['.key']]
    const bOrder = state.notesOrder[b['.key']]
    if (aOrder === undefined || aOrder === null) return -1
    if (aOrder === bOrder) return 0
    if (aOrder < bOrder) return -1
    return 1
  })
}

export const markdown = state => text => {
  return Marked(text)
}

let mdRenderer = new Marked.Renderer()
mdRenderer.image = function (href, title, text) {
  return '

'
}
mdRenderer.link = function (href, title, text) {
  return '' + text + ''
}
Marked.setOptions({
  renderer: mdRenderer
})

import Marked from 'marked'

export const orderedNotes = state => {
  return state.notes.slice(0).sort((a, b) => {
    const aOrder = state.notesOrder[a['.key']]
    const bOrder = state.notesOrder[b['.key']]
    if (aOrder === undefined || aOrder === null) return -1
    if (aOrder === bOrder) return 0
    if (aOrder < bOrder) return -1
    return 1
  })
}

export const markdown = state => text => {
  return Marked(text)
}

let mdRenderer = new Marked.Renderer()
mdRenderer.image = function (href, title, altText) {
  return `<img src="${href}" alt="${altText}" title="${title}" class="ui image">`
}
mdRenderer.link = function (href, title, text) {
  return `<a href="${href}" title="${title}" target="_blank">${text}</a>`
}
Marked.setOptions({
  renderer: mdRenderer
})

Fix markdown image and link rendering


/* @flow strict-local */
import { connect } from 'react-redux';

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

import type { User } from '../types';
import { UserAvatarWithPresence } from '../common';
import { getRecipientsInGroupNarrow } from '../selectors';
import styles from '../styles';

type Props = {
  recipients: User[],
};

class TitleGroup extends PureComponent<Props> {
  render() {
    const { recipients } = this.props;

    return (
      <View style={styles.navWrapper}>
        {recipients.map((user, index) => (
          <View key={user.email} style={styles.titleAvatar}>
            <UserAvatarWithPresence size={32} avatarUrl={user.avatar_url} email={user.email} />
          </View>
        ))}
      </View>
    );
  }
}

export default connect((state, props) => ({
  recipients: getRecipientsInGroupNarrow(state, props.narrow),
}))(TitleGroup);

/* @flow strict-local */
import { connect } from 'react-redux';

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

import type { Dispatch, User } from '../types';
import { Touchable, UserAvatarWithPresence } from '../common';
import { getRecipientsInGroupNarrow } from '../selectors';
import styles from '../styles';
import { navigateToAccountDetails } from '../nav/navActions';

type Props = {
  dispatch: Dispatch,
  recipients: User[],
};

class TitleGroup extends PureComponent<Props> {
  handlePress = user => {
    const { dispatch } = this.props;
    dispatch(navigateToAccountDetails(user.email));
  };

  render() {
    const { recipients } = this.props;

    return (
      <View style={styles.navWrapper}>
        {recipients.map((user, index) => (
          <Touchable
            key={user.email}
            onPress={() => this.handlePress(user)}
            style={styles.titleAvatar}
          >
            <UserAvatarWithPresence size={32} avatarUrl={user.avatar_url} email={user.email} />
          </Touchable>
        ))}
      </View>
    );
  }
}

export default connect((state, props) => ({
  recipients: getRecipientsInGroupNarrow(state, props.narrow),
}))(TitleGroup);

Make each user's avatar in group-PM app bar lead to profile.


export default function mdlUpgrade(Constructor) {

  class NewConstructor extends Constructor {

    connectedCallback() {
      window.componentHandler.upgradeElement(this);
      return super.connectedCallback();
    };

    disconnectedCallback() {
      window.componentHandler.downgradeElements(this);
      return super.disconnectedCallback();
    };

  }

  return NewConstructor;
}

export default function mdlUpgrade(Constructor) {

  class NewConstructor extends Constructor {

    connectedCallback() {
      // super.connectedCallback();
      window.componentHandler.upgradeElement(this);
    };

    disconnectedCallback() {
      super.disconnectedCallback();
      window.componentHandler.downgradeElements(this);
    };

  }

  return NewConstructor;
}

Call connectedCallback before upGrade component.


module.exports = function (grunt) {
  grunt.config.merge({
    connect: {
      server: {
        options: {
          base: "build/", //TODO: once grunt-contrib-connect 0.9 is released, set index file
          livereload: true
        }
      }
    },
    watch: {
      sources: {
        options: {
          livereload: true
        },
        files: ["*.css", "app.js", "lib/**/*.js", "*.html"],
        tasks: ["default"]
      },
      config: {
        options: {
          reload: true
        },
        files: ["Gruntfile.js", "tasks/*.js"],
        tasks: []
      }
    }
  });

  grunt.loadNpmTasks("grunt-contrib-connect");
  grunt.loadNpmTasks("grunt-contrib-watch");
};

module.exports = function (grunt) {
  grunt.config.merge({
    connect: {
      server: {
        options: {
          base: {
            path: 'build',
            options: {
              index: 'index.html'
            }
          },
          livereload: true
        }
      }
    },
    watch: {
      sources: {
        options: {
          livereload: true
        },
        files: ["*.css", "app.js", "lib/**/*.js", "*.html"],
        tasks: ["default"]
      },
      config: {
        options: {
          reload: true
        },
        files: ["Gruntfile.js", "tasks/*.js"],
        tasks: []
      }
    }
  });

  grunt.loadNpmTasks("grunt-contrib-connect");
  grunt.loadNpmTasks("grunt-contrib-watch");
};

Add index option to grunt-conncet server


angular.module('zoomableApp').factory('servicesAPI', function($http, $q, Upload, $timeout) {
  return {
    createVideo : function(video) {
      return $http.post('/api/video', video);
    },
    createAccount : function(accountData) {
      return $http.post('/api/user/signup', accountData);
    },
    get : function() {
      return $http.get('api/video');
    },
    getOne : function(id) {
      return $http.get('/api/video/' + id);
    },
    getUploadProgress : function(id) {
      return $http.post('/api/video/isComplete', id);
    },
    delete : function(id) {
      return $http.delete('/api/video' + id);
    },
    deleteAll : function(ids) {
      return $http.delete('/api/video', {data : ids});
    },
    update : function(id, videoData) {
      return $http.put('/api/video/' + id, videoData);
    },
    login : function(accountData) {
      return $http.post('/api/user/login', accountData);
    },
    upload : function (file) {
      return $q(function(resolve, reject) {
        setTimeout(function() {
          Upload.upload({
            url : '/api/video/upload',
            data : {
                id : file.id,
                video : file
            }
          }).then(function (res) {
            resolve(res);
          });
        });
      });
    }
  }
});

angular.module('zoomableApp').factory('servicesAPI', function($http, $q, Upload, $timeout) {
  return {
    createVideo : function(video) {
      return $http.post('/api/video', video);
    },
    createAccount : function(accountData) {
      return $http.post('/api/user/signup', accountData);
    },
    get : function() {
      return $http.get('api/video');
    },
    getOne : function(id) {
      return $http.get('/api/video/' + id);
    },
    getUploadProgress : function(id) {
      return $http.post('/api/video/isComplete', id);
    },
    delete : function(id) {
      return $http.delete('/api/video/' + id);
    },
    deleteAll : function(ids) {
      return $http.delete('/api/video', {data : ids});
    },
    update : function(id, videoData) {
      return $http.put('/api/video/' + id, videoData);
    },
    login : function(accountData) {
      return $http.post('/api/user/login', accountData);
    },
    upload : function (file) {
      return $q(function(resolve, reject) {
        setTimeout(function() {
          Upload.upload({
            url : '/api/video/upload',
            data : {
                id : file.id,
                video : file
            }
          }).then(function (res) {
            resolve(res);
          });
        });
      });
    }
  }
});

Fix error in delete button in dashboard page


const Botkit = require('botkit')
const config = require('./config.json')
const version = require('./package.json').version
const logger = require('./src/logger')
const skillsLoader = require('./src/skills-loader')
const admins = require('./src/admins')
const rabbit = require('./src/rabbit')

logger.setLogLevel(config.logLevel)

logger.info(`Initializing J.A.R.V.I.S. version ${version}`)

const controller = Botkit.slackbot({
  send_via_rtm: true,
  json_file_store: './store'
})

admins.load(controller)
rabbit.load(controller)
skillsLoader.load(controller)

let bot = controller.spawn({
  token: config.slackbotToken
});

bot.startRTM((error, bot, payload) => {
  if (error) {
    logger.error('Unexpected error:', err)
  }
  logger.debug('startRTM payload:', payload)
});

const Botkit = require('botkit')
const config = require('./config.json')
const version = require('./package.json').version
const logger = require('./src/logger')
const skillsLoader = require('./src/skills-loader')
const admins = require('./src/admins')
const rabbit = require('./src/rabbit')

logger.setLogLevel(config.logLevel)

logger.info(`Initializing J.A.R.V.I.S. version ${version}`)

const controller = Botkit.slackbot({
  send_via_rtm: true,
  json_file_store: './store'
})

admins.load(controller)
rabbit.load(controller)
skillsLoader.load(controller)

let bot = controller.spawn({
  token: config.slackbotToken
});

const startRTM = () => {
  bot.startRTM((error, bot, payload) => {
    if (error) {
      logger.error('Unexpected error starting RTM:', err)
      setTimeout(startRTM, 60000)
    }
    logger.debug('startRTM payload:', payload)
  });
}

controller.on('rtm_close', (bot, error) => {
  logger.debug('RTM connection closed; restarting...')
  setTimeout(startRTM, 5000)
})

startRTM()

Add some code to handle reconnect after stale RTM


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

import block from '../../../helpers/bem-cn'

import './Row.css'

const row = block('j-row')
const column = block('j-col')

const propTypes = {
  children: PropTypes.node,
  col: PropTypes.bool,
  className: PropTypes.string
}

const emptyRow = (className, cn) =>

const wrapChild = (child, cn, className) => (

    {child}

)

const wrapChildren = (children, cn) => children.map((item, index) => (

    {item}

))

const Row = ({ children, className, col, alignCenter, fullwidth }) => {
  const cn = col ? column : row

  if (children === null) return emptyRow(className, cn)
  if (children.length > 1) {
    return (

        { wrapChildren(children, cn) }

    )
  }

  return wrapChild(children, cn, className)
}

Row.propTypes = propTypes

export default Row

Fix className warning (toString bem-cn)


import controlMixin from './controlMixin'

export default {
  name: 'GeolocateControl',
  mixins: [controlMixin],

  props: {
    position: {
      type: String,
      default: 'top-right'
    },
    positionOptions: {
      type: Object,
      default () {
        return {
          enableHighAccuracy: false,
          timeout: 6000
        }
      }
    },
    trackUserLocation: {
      type: Boolean,
      default: false
    },
    showUserLocation: {
      type: Boolean,
      default: true
    },
    fitBoundsOptions: {
      type: Object,
      default: () => ({ maxZoom: 15 })
    }
  },

  data () {
    return {
      control: undefined
    }
  },

  created () {
    this.control = new this.mapbox.GeolocateControl(this._props)

    this.control.on('error', err => {
      this.$emit('geolocate-error', err)
    })
    this.control.on('geolocate', position => {
      this.$emit('geolocate-error', position)
    })
  },

  methods: {
    $_deferredMount (payload) {
      this.map = payload.map
      this.map.addControl(this.control)
      this.$emit('added', this.control)
      payload.component.$off('load', this.$_deferredMount)
    }
  }
}

import controlMixin from './controlMixin'

export default {
  name: 'GeolocateControl',
  mixins: [controlMixin],

  props: {
    position: {
      type: String,
      default: 'top-right'
    },
    positionOptions: {
      type: Object,
      default () {
        return {
          enableHighAccuracy: false,
          timeout: 6000
        }
      }
    },
    trackUserLocation: {
      type: Boolean,
      default: false
    },
    showUserLocation: {
      type: Boolean,
      default: true
    },
    fitBoundsOptions: {
      type: Object,
      default: () => ({ maxZoom: 15 })
    }
  },

  data () {
    return {
      control: undefined
    }
  },

  created () {
    this.control = new this.mapbox.GeolocateControl(this._props)

    this.control.on('error', err => {
      this.$emit('geolocate-error', err)
    })
    this.control.on('geolocate', position => {
      this.$emit('geolocate-error', position)
    })
  },

  methods: {
    $_deferredMount (payload) {
      this.map = payload.map
      this.map.addControl(this.control, this.position)
      this.$emit('added', this.control)
      payload.component.$off('load', this.$_deferredMount)
    }
  }
}

Fix position in geolocate control


import Ember from "ember";
import Wormhole from 'ember-wormhole/components/ember-wormhole';

var titles = Ember.A([]);
export default Wormhole.extend({
  init: function () {
    this._super();

    if (titles.length === 0) {
      document.title = '';
    }
    titles.push(this);
  },

  destinationElement: Ember.computed(function () {
    return document.getElementsByTagName('title')[0];
  }),

  willClearRender: function () {
    titles.removeObject(this);
    this._super.apply(this, arguments);
  }
});

import Ember from "ember";
import Wormhole from 'ember-wormhole/components/ember-wormhole';

var titles = Ember.A([]);
export default Wormhole.extend({
  init: function () {
    this._super();

    if (titles.length === 0) {
      document.title = '';
    }
    titles.push(this);
  },

  destinationElement: Ember.computed(function () {
    return document.getElementsByTagName('title')[0];
  }),

  willDestroyElement: function () {
    titles.removeObject(this);
    this._super.apply(this, arguments);
  }
});

Replace test usage of `willClearRender` for `willDestroyElement`.


Zepto(document).ready(function() {
  $('.toggle-input').change(function() {
    $('body').toggleClass('night');
  });
});

jQuery(document).ready(function() {
  // Set initial state of checkbox
  $('.toggle-input').attr('checked', false);
  // Toggle day / night state on click
  $('.toggle-input').change(function() {
    $('html').toggleClass('night');
  });
});

Set initial state of checkbox to false


var sinon = require('sinon');
var chai = require('chai');
var expect = chai.expect;

describe('Util', function() {
  var jsonParser = require("../src/util.js").jsonParser();

  it('returns parsed JSON', function() {
    var json = jsonParser.parse('{"a": [1, "2"]}')
    expect(json).to.deep.equal({a: [1, '2']});
  });

  it('returns null when there is an exception', function() {
    expect(jsonParser.parse('{')).to.equal(undefined);
  });

  it('yields the parsed data', function() {
    var wasCalled = false;
    var result = jsonParser.parse('{"test": 123}', function(data) {
      wasCalled = true;
      expect(data).to.deep.equal({test: 123});
      return 'blah';
    });
    expect(result).to.equal('blah');
    expect(wasCalled).to.equal(true);
  });
});

var sinon = require('sinon');
var chai = require('chai');
var expect = chai.expect;

describe('Util', function() {
  var jsonParser = require("../src/util.js").jsonParser();

  it('returns parsed JSON', function() {
    var json = jsonParser.parse('{"a": [1, "2"]}')
    expect(json).to.deep.equal({a: [1, '2']});
  });

  it('returns null when there is an exception', function() {
    expect(jsonParser.parse('{')).to.equal(undefined);
  });

  it('yields the parsed data', function() {
    var callback = sinon.stub().withArgs(sinon.match({test: 123})).returns('blah');
    var result = jsonParser.parse('{"test": 123}', callback);
    expect(result).to.equal('blah');
  });
});

Clean up tests with sinon ftw


import React from 'react';
import { withRouter } from 'react-router';
import _ from 'lodash';
import { LinkWithIcon } from '../../common';

const HostingListItemLinks = ({ items, isDefault, params }) => {
  const styles = {
    item: {
      padding: '4px 0'
    }
  };
  let isCnameFounded = false;

  const getLinkUrl = (domain) => {
    if (domain === 'default' && isDefault) {
      return `https://${params.instanceName}.syncano.site`;
    }

    if (!isCnameFounded) {
      isCnameFounded = true;
      return domain;
    }

    return `https://${domain}--${params.instanceName}.syncano.site`;
  };

  return (
    <div>
      {_.map(items, (item, index) => (
        <div
          style={styles.item}
          key={`domain-${item}-${index}`}
        >
          <LinkWithIcon url={getLinkUrl(item)} />
        </div>
      ))}
    </div>
  );
};

export default withRouter(HostingListItemLinks);

import React from 'react';
import { withRouter } from 'react-router';
import _ from 'lodash';
import { LinkWithIcon } from '../../common';

const HostingListItemLinks = ({ items, isDefault, params }) => {
  const styles = {
    item: {
      padding: '4px 0'
    }
  };
  let isCnameFounded = false;

  const getLinkUrl = (domain) => {
    const isValidCname = /\.[a-zA-Z]+$/.test(domain);

    if (domain === 'default' && isDefault) {
      return `https://${params.instanceName}.syncano.site`;
    }

    if (!isCnameFounded && isValidCname) {
      isCnameFounded = true;
      return domain;
    }

    return `https://${domain}--${params.instanceName}.syncano.site`;
  };

  return (
    <div>
      {_.map(items, (item, index) => (
        <div
          style={styles.item}
          key={`domain-${item}-${index}`}
        >
          <LinkWithIcon url={getLinkUrl(item)} />
        </div>
      ))}
    </div>
  );
};

export default withRouter(HostingListItemLinks);

Fix display only valid domains in hosting URLs


let currentTabId = undefined;

function updateCurrentTab() {
    let query = {
        active: true,
        currentWindow: true,
    };
    browser.tabs.query(query).then(
        (tabs) => {
            if (tabs.length > 0) {
                currentTabId = tabs[0].id;
            }
        }
    );
}

updateCurrentTab();

browser.tabs.onActivated.addListener(updateCurrentTab);
browser.windows.onFocusChanged.addListener(updateCurrentTab);
browser.windows.onRemoved.addListener(updateCurrentTab);

browser.tabs.onCreated.addListener(moveTab);

function moveTab(newTab) {
    if (!currentTabId) {
        return;
    }

    Promise.all([
        browser.windows.getCurrent({ populate: true }),
        browser.tabs.get(currentTabId),
    ]).then(
        (result) => {
            let [currentWindow, currentTab] = result;
            let isUndoCloseOrRelatedTab = newTab.index < currentWindow.tabs.length - 1;
            if (!isUndoCloseOrRelatedTab) {
                browser.tabs.move(newTab.id, {index: currentTab.index + 1});
            }
        }
    );
}

let currentTabId = undefined;

function updateCurrentTab() {
    let query = {
        active: true,
        currentWindow: true,
    };
    browser.tabs.query(query).then(
        (tabs) => {
            if (tabs.length > 0) {
                currentTabId = tabs[0].id;
            }
        }
    );
}

updateCurrentTab();

browser.tabs.onActivated.addListener(updateCurrentTab);
browser.windows.onFocusChanged.addListener(updateCurrentTab);
browser.windows.onRemoved.addListener(updateCurrentTab);

browser.tabs.onCreated.addListener(moveTab);

browser.commands.onCommand.addListener(function(command) {
    if (command == "open-new-tab-at-default-location") {
        browser.tabs.onCreated.removeListener(moveTab);
        browser.tabs.onCreated.addListener(fixListeners);
        browser.tabs.create({});
    }
});

function fixListeners() {
    browser.tabs.onCreated.removeListener(fixListeners);
    browser.tabs.onCreated.addListener(moveTab);
}

function moveTab(newTab) {
    if (!currentTabId) {
        return;
    }

    Promise.all([
        browser.windows.getCurrent({ populate: true }),
        browser.tabs.get(currentTabId),
    ]).then(
        (result) => {
            let [currentWindow, currentTab] = result;
            let isUndoCloseOrRelatedTab = newTab.index < currentWindow.tabs.length - 1;
            if (!isUndoCloseOrRelatedTab) {
                browser.tabs.move(newTab.id, {index: currentTab.index + 1});
            }
        }
    );
}

Implement keyboard shortcut to open tab at default location


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

export default class HTML extends React.Component {
  render() {
    return (
      <html {...this.props.htmlAttributes}>
        <head>
          <meta charSet="utf-8" />
          <meta httpEquiv="x-ua-compatible" content="ie=edge" />
          <meta
            name="viewport"
            content="width=device-width, initial-scale=1, shrink-to-fit=no, minimum-scale=1, maximum-scale=2"
          />
          {this.props.headComponents}
        </head>
        <body {...this.props.bodyAttributes}>
          {this.props.preBodyComponents}
          <noscript>You need to enable JavaScript to run this app.</noscript>
          <div
            key={`body`}
            id="___gatsby"
            dangerouslySetInnerHTML={{ __html: this.props.body }}
          />
          {this.props.postBodyComponents}
        </body>
      </html>
    )
  }
}

HTML.propTypes = {
  htmlAttributes: PropTypes.object,
  headComponents: PropTypes.array,
  bodyAttributes: PropTypes.object,
  preBodyComponents: PropTypes.array,
  body: PropTypes.string,
  postBodyComponents: PropTypes.array,
}

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

export default class HTML extends React.Component {
  render() {
    return (
      <html {...this.props.htmlAttributes}>
        <head>
          <meta charSet="utf-8" />
          <meta httpEquiv="x-ua-compatible" content="ie=edge" />
          <meta
            name="viewport"
            content="width=device-width, initial-scale=1, shrink-to-fit=no, minimum-scale=1, maximum-scale=1, user-scalable=0, viewport-fit=cover"
          />
          {this.props.headComponents}
        </head>
        <body {...this.props.bodyAttributes}>
          {this.props.preBodyComponents}
          <noscript>You need to enable JavaScript to run this app.</noscript>
          <div
            key={`body`}
            id="___gatsby"
            dangerouslySetInnerHTML={{ __html: this.props.body }}
          />
          {this.props.postBodyComponents}
        </body>
      </html>
    )
  }
}

HTML.propTypes = {
  htmlAttributes: PropTypes.object,
  headComponents: PropTypes.array,
  bodyAttributes: PropTypes.object,
  preBodyComponents: PropTypes.array,
  body: PropTypes.string,
  postBodyComponents: PropTypes.array,
}

Add meta content for preventing double tab


// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

var buildExtension = require('jupyterlab-extension-builder').buildExtension;
var path = require('path');

buildExtension({
  name: 'mockextension',
  entryPath: './mockextension/index.js',
  config: {
    output: {
      path: path.join(process.cwd(), 'mockextension', 'build'),
    }
  }
});

// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

var buildExtension = require('jupyterlab-extension-builder').buildExtension;
var path = require('path');

buildExtension({
  name: 'mockextension',
  entry: './mockextension/index.js',
  outputDir: './mockextension/build'
});

Update the labextension javascript test


/**
 * Returns a random integer between min and max
 * Using Math.round() will give you a non-uniform distribution!
 */
function getRandomInt (min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

function GameViewModel() {
    this.exerciseNumber = ko.observable(1);
    this.exerciseCount = ko.observable(10);
    this.factor1 = ko.observable(getRandomInt(1,10));
    this.factor2 = ko.observable(getRandomInt(1,10));
    this.result = ko.observable("");
    this.resultIsCorrect = ko.computed(function() {
        return parseInt(this.result()) == parseInt(this.factor1()) * parseInt(this.factor2());
    }, this);
}

/**
 * Returns a random integer between min and max
 * Using Math.round() will give you a non-uniform distribution!
 */
function getRandomInt (min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

function GameViewModel() {
    this.exerciseNumber = ko.observable(1);
    this.exerciseCount = ko.observable(10);
    this.factor1 = ko.observable(getRandomInt(1,10));
    this.factor2 = ko.observable(getRandomInt(1,10));
    this.result = ko.observable("");
    this.resultIsCorrect = ko.computed(function() {
        return parseInt(this.result(), 10) == parseInt(this.factor1(), 10) * parseInt(this.factor2(), 10);
    }, this);
}

Add missing radix parameter (Woohoo! First lint failure!)


SetupPrototypes();

SetupRooms();

SpawnCreeps();

HandleCreeps();

EndTick();

function SetupPrototypes() {
    Room.prototype.getLOIs = function() {
        var lois = [];
        this.memory.sources.forEach(function(sourceId) {
            lois.push(sourceId);
        });

        if(this.controller) {
            lois.push(this.controller.id);
        }
        return lois;
    };
}

function SetupRooms() {
    Object.keys(Game.rooms).forEach(function (roomName) {
        var room = Game.rooms[roomName];

        if (!room.memory.calcComplete || Memory.recalcRooms) {
            // Do one time room calculations here

            if(!room.memory.sources) {
                room.memory.sources = room.find(FIND_SOURCES).map(function (sourceObject) {
                    return sourceObject.id;
                });
            }

            room.memory.calcComplete = true;
        }
    });
}

function SpawnCreeps() {
    if(Object.keys(Game.creeps).length < 4) {
        // Spawn a new creep
    }
}

function HandleCreeps() {
    Object.keys(Game.creeps).forEach(function (creepName) {
        var creep = Game.creeps[creepName];

        if(creep.carry.energy < creep.carryCapacity) {
            // Move to source or harvest
        } else {
            // Move to spawn or transfer
        }
    });
}

function EndTick() {
    Memory.recalcRooms = false;
    console.log(Game.getUsedCpu());
}

SetupPrototypes();

SetupRooms();

SpawnCreeps();

HandleCreeps();

EndTick();

function SetupPrototypes() {
    Room.prototype.getLOIs = function() {
        var lois = [];
        this.memory.sources.forEach(function(sourceId) {
            lois.push(sourceId);
        });

        if(this.controller) {
            lois.push(this.controller.id);
        }
        return lois;
    };
}

function SetupRooms() {
    Object.keys(Game.rooms).forEach(function (roomName) {
        var room = Game.rooms[roomName];

        if (!room.memory.calcComplete || Memory.recalcRooms) {
            // Do one time room calculations here

            if(!room.memory.sources || Memory.recalcRooms) {
                room.memory.sources = room.find(FIND_SOURCES).map(function (sourceObject) {
                    return sourceObject.id;
                });
            }

            room.memory.calcComplete = true;
        }
    });
}

function SpawnCreeps() {
    if(Object.keys(Game.creeps).length < 4) {
        // Spawn a new creep
    }
}

function HandleCreeps() {
    Object.keys(Game.creeps).forEach(function (creepName) {
        var creep = Game.creeps[creepName];

        if(creep.carry.energy < creep.carryCapacity) {
            // Move to source or harvest
        } else {
            // Move to spawn or transfer
        }
    });
}

function EndTick() {
    Memory.recalcRooms = false;
    console.log(Game.getUsedCpu());
}

Fix room not recalculating sources


import InsertInlineNodeCommand from './InsertInlineNodeCommand'
import { hasAvailableXrefTargets } from '../util/xrefHelpers'

export default class InsertXrefCommand extends InsertInlineNodeCommand {

  getType() {
    return 'xref'
  }

  createNode(tx) {
    const refType = this.config.refType
    let xref = tx.createElement('xref')
    xref.attr('ref-type', refType)
    xref.attr('rid', '')
    return xref
  }

  isDisabled(params) {
    const sel = params.selection
    const selectionState = params.editorSession.getSelectionState()
    const editor = params.editorSession.getEditor()
    const context = editor.getChildContext()
    const refType = this.config.refType
    const hasTargets = hasAvailableXrefTargets(refType, context)

    // We don't allow xref insertion if there are no available targets
    if (!hasTargets) {
      return true
    }

    if (!sel.isPropertySelection()) {
      return true
    }

    // We don't allow inserting an inline node on top of an existing inline
    // node.
    if (selectionState.isInlineNodeSelection()) {
      return true
    }
    return false
  }
}

import InsertInlineNodeCommand from './InsertInlineNodeCommand'
import { hasAvailableXrefTargets } from '../util/xrefHelpers'

export default class InsertXrefCommand extends InsertInlineNodeCommand {

  getType() {
    return 'xref'
  }

  createNode(tx) {
    const refType = this.config.refType
    let xref = tx.createElement('xref')
    xref.attr('ref-type', refType)
    xref.attr('rid', '')
    return xref
  }

  isDisabled(params) {
    const sel = params.selection
    const selectionState = params.editorSession.getSelectionState()
    const editor = params.editorSession.getEditor()
    if (!editor) return true
    const context = editor.getChildContext()
    const refType = this.config.refType
    const hasTargets = hasAvailableXrefTargets(refType, context)

    // We don't allow xref insertion if there are no available targets
    if (!hasTargets) {
      return true
    }

    if (!sel.isPropertySelection()) {
      return true
    }

    // We don't allow inserting an inline node on top of an existing inline
    // node.
    if (selectionState.isInlineNodeSelection()) {
      return true
    }
    return false
  }
}

Add a guard when EditorSession.getEditor() returns undefined.


import * as constants from '../../constants'

export function documentCapture(payload) {
  return {
    type: constants.DOCUMENT_CAPTURE,
    payload
  }
}

export function faceCapture(payload) {
  return {
    type: constants.FACE_CAPTURE,
    payload
  }
}

import * as constants from '../../constants'

export function documentCapture(payload) {
  return {
    type: constants.DOCUMENT_CAPTURE,
    payload
  }
}

export function captureIsValid(payload) {
  return {
    type: constants.CAPTURE_IS_VALID,
    payload
  }
}

export function faceCapture(payload) {
  return {
    type: constants.FACE_CAPTURE,
    payload
  }
}

Add action for setting valid capture


'use strict';

/* global Connector */

Connector.playerSelector = '.player';

Connector.artistTrackSelector = '.player__progress__title';

Connector.isPlaying = function () {
	return $('.icon-pause').length;
};

'use strict';

/* global Connector */

Connector.playerSelector = '.player';

Connector.artistTrackSelector = '.player__progress__title';

Connector.isPlaying = function () {
	return $('.icon-pause').length > 0;
};

Return boolean value in `isPlaying` function


'use strict';
const { isAndroidConfigured } = require('../../android');
const AndroidRecorder = require('./android/recorder');
const X11Recorder = require('./desktop/x11recorder');
const FirefoxWindowRecorder = require('./firefox/firefoxWindowRecorder');
const os = require('os');

module.exports = function getRecorder(options, browser, baseDir) {
  if (isAndroidConfigured(options)) {
    return new AndroidRecorder(options);
  } else {
    if (options.firefox.windowRecorder) {
      if (options.browser === 'firefox' &&
          (os.platform() === 'win32' || os.platform() === 'linux')) {
        return new FirefoxWindowRecorder(options, browser, baseDir);
      }
      throw new Error('firefox.windowRecorder is only supported in Firefox on Windows and Linux');
    } else {
      return new X11Recorder(options);
    }
  }
};

'use strict';
const { isAndroidConfigured } = require('../../android');
const AndroidRecorder = require('./android/recorder');
const X11Recorder = require('./desktop/x11recorder');
const FirefoxWindowRecorder = require('./firefox/firefoxWindowRecorder');
const os = require('os');

module.exports = function getRecorder(options, browser, baseDir) {
  if (isAndroidConfigured(options)) {
    return new AndroidRecorder(options);
  } else {
    if (options.browser === 'firefox' && options.firefox.windowRecorder) {
      return new FirefoxWindowRecorder(options, browser, baseDir);
    } else {
      return new X11Recorder(options);
    }
  }
};

Allow using Firefox Window Recorder on macOS.


/* globals describe, it */
import ColorPicker from '../src/app';

import assert from 'assert';
import { Observable } from 'rx';
import { mockDOMSource } from '@cycle/dom';
import rxAdapter from '@cycle/rx-adapter';

describe('ColorPicker', () => {
  it('takes a props$ of color and returns a color$', (done) => {
    const props$ = Observable.of({color: '#fff'});

    const Mouse = {
      up: () => Observable.empty()
    };

    const DOM = mockDOMSource(rxAdapter, {});

    const colorPicker = ColorPicker({Mouse, props$, DOM});

    colorPicker.color$.take(1).subscribe((color) => {
      assert.equal(color, 'rgb(255, 255, 255)');
      done();
    });
  });
});

/* globals describe, it */
import ColorPicker from '../src/app';

import assert from 'assert';
import { Observable } from 'rx';
import { mockDOMSource } from '@cycle/dom';
import rxAdapter from '@cycle/rx-adapter';

describe('ColorPicker', () => {
  const Mouse = {
    up: () => Observable.empty()
  };
  const DOM = mockDOMSource(rxAdapter, {});

  it('takes a props$ of color and returns a color$', (done) => {
    const props$ = Observable.of({color: 'red'});
    const colorPicker = ColorPicker({Mouse, props$, DOM});

    colorPicker.color$.skip(1).take(1).subscribe((color) => {
      assert.equal(color, 'rgb(255, 0, 0)');
      done();
    });
  });

  it('returns an rgba when alpha is required', (done) => {
    const expectedColor = 'rgba(255, 255, 255, 0.5)';
    const props$ = Observable.of({color: expectedColor});

    const colorPicker = ColorPicker({DOM, Mouse, props$});

    colorPicker.color$.skip(1).take(1).subscribe((color) => {
      assert.equal(color, expectedColor);
      done();
    });
  });
});

Test that ColorPicker returns an rgba string when required


Template.addProject.created = function () {
  // Get reference to template instance
  let instance = this;

  // Subscribe to all tags, for tag auto-complete field
  instance.subscribe("allTags");
};

Template.addProject.rendered = function() {
  // Get reference to template instance
  let instance = this;

  instance.autorun(function () {
    if (instance.subscriptionsReady()) {
      // Get all existing tags
      let tagOptions = Tags.find().fetch();

      $('#tags').selectize({
          delimiter: ',',
          persist: false,
          valueField: '_id',
          labelField: 'name',
          searchField: 'name',
          highlight: true,
          maxOptions: 5,
          options: tagOptions,
          create:true,
          onItemAdd(value){
            if(!Tags.findOne({_id:value})){
              Tags.insert({"name":value});
            }
          }
      });
    }

});
}

Template.addProject.helpers({
  existingTags: function () {
    // Get all existing tags
    let tags = Tags.find().fetch();

    return tags;
  }
});

Template.addProject.created = function () {
  // Get reference to template instance
  let instance = this;

  // Subscribe to all tags, for tag auto-complete field
  instance.subscribe("allTags");
};

Template.addProject.rendered = function() {
  // Get reference to template instance
  let instance = this;

  instance.autorun(function () {
    if (instance.subscriptionsReady()) {
      // Get all existing tags
      let tagOptions = Tags.find().fetch();

      $('#tags').selectize({
          delimiter: ',',
          persist: false,
          valueField: 'name',
          labelField: 'name',
          searchField: 'name',
          highlight: true,
          maxOptions: 5,
          options: tagOptions,
          create:true,
          onItemAdd(value){
            // Insert tag into Tags collection if it doesn't exist
            if(!Tags.findOne({"name":value})){
              Tags.insert({"name":value});
            }
          }
      });
    }

});
}

Template.addProject.helpers({
  existingTags: function () {
    // Get all existing tags
    let tags = Tags.find().fetch();

    return tags;
  }
});

Switch to name field for unique check and value


var createTimer = require('../create-timer')

it('starts events from zero', function () {
  let timer = createTimer('test')
  let time = timer()
  expect(time[2]).toBe(0)
})

it('saves host name', function () {
  let timer = createTimer('host')
  let time = timer()
  expect(time[2]).toBe(0)
})

it('generates unique time marks', function () {
  let timer = createTimer('test')
  var time1 = timer()
  var time2 = timer()
  expect(time1).not.toEqual(time2)
})

var createTimer = require('../create-timer')

it('starts events from zero', function () {
  var timer = createTimer('test')
  var time = timer()
  expect(time[2]).toBe(0)
})

it('saves host name', function () {
  var timer = createTimer('host')
  var time = timer()
  expect(time[2]).toBe(0)
})

it('generates unique time marks', function () {
  var timer = createTimer('test')
  var time1 = timer()
  var time2 = timer()
  expect(time1).not.toEqual(time2)
})

Use ES5 var instead of ES6 let


module.exports = function (grunt) {

  'use strict';

  grunt.loadNpmTasks('grunt-browserify');
  grunt.loadNpmTasks('grunt-contrib-watch');

  // Project configuration.
  grunt.initConfig({

    browserify: {
      options: {
        browserifyOptions: {
          debug: true // Ask for source maps
        },
        // Don't ignore transpilation in node_modules
        ignore: false,
        transform: ['babelify']
      },
      dist: {
        files: {
          'dist/basic-web-components.js': 'components/**/*.js'
        }
      }
    },

    watch: {
      scripts: {
        files: [
          'components/**/*.js',
          'mixins/*.js'
        ],
        tasks: ['build']
      }
    }

  });

  grunt.registerTask('default', ['build']);
  grunt.registerTask('build', ['browserify']);

};

module.exports = function (grunt) {

  'use strict';

  grunt.loadNpmTasks('grunt-browserify');

  // Project configuration.
  grunt.initConfig({

    browserify: {

      options: {
        browserifyOptions: {
          debug: true // Ask for source maps
        },
        // Don't ignore transpilation in node_modules
        ignore: false,
        transform: ['babelify']
      },

      package: {
        files: {
          'dist/basic-web-components.js': 'components/**/*.js'
        }
      },

      watch: {
        files: {
          'dist/basic-web-components.js': 'components/**/*.js'
        },
        options: {
          keepAlive: true,
          watch: true
        }
      }
    }

  });

  grunt.registerTask('default', ['build']);
  grunt.registerTask('build', ['browserify:package']);
  grunt.registerTask('watch', ['browserify:watch']);

};

Use watchify instead of grunt-contrib-watch for faster watched builds.


'use strict';
module.exports = function (grunt) {
	grunt.initConfig({
		svgmin: {
			compile: {
				files: {
					'test/tmp/test.svg': 'test/fixtures/test.svg'
				}
			},
			withconfig: {
				options: {
					plugins: [{
						removeViewBox: false
					}]
				},
				files: {
					'test/tmp/withconfig.svg': 'test/fixtures/test.svg'
				}
			},

			multiple: {
				files: [{
					expand:true,
					cwd: 'test/fixtures/',
					src: ['**/*.svg'],  // Actual pattern(s) to match.
					dest: 'test/tmp/'
				}]
			}
		},
		simplemocha: {
			test: {
				src: 'test/*.js'
			}
		},
		clean: {
			test: ['test/tmp']
		}
	});

	grunt.loadTasks('tasks');
	grunt.loadNpmTasks('grunt-contrib-clean');
	grunt.loadNpmTasks('grunt-simple-mocha');

	grunt.registerTask('default', ['clean', 'svgmin', 'simplemocha', 'clean']);
};

'use strict';
module.exports = function (grunt) {
	grunt.initConfig({
		svgmin: {
			compile: {
				files: {
					'test/tmp/test.svg': 'test/fixtures/test.svg'
				}
			},
			withconfig: {
				options: {
					plugins: [
						{removeViewBox: false},
						{convertPathData: { straightCurves:false }}  // Advanced svgo plugin option
					]
				},
				files: {
					'test/tmp/withconfig.svg': 'test/fixtures/test.svg'
				}
			},

			multiple: {
				files: [{
					expand:true,
					cwd: 'test/fixtures/',
					src: ['**/*.svg'],  // Actual pattern(s) to match.
					dest: 'test/tmp/'
				}]
			}
		},
		simplemocha: {
			test: {
				src: 'test/*.js'
			}
		},
		clean: {
			test: ['test/tmp']
		}
	});

	grunt.loadTasks('tasks');
	grunt.loadNpmTasks('grunt-contrib-clean');
	grunt.loadNpmTasks('grunt-simple-mocha');

	grunt.registerTask('default', ['clean', 'svgmin', 'simplemocha', 'clean']);
};

Add advanced svgo plugins option example


/**
 * External dependencies.
 */

var chai = require('chai');

/**
 * Register `should`.
 */

global.should = chai.should();

/**
 * Include stack traces.
 */

chai.Assertion.includeStack = true;

Make chai include stack traces


var assert = require("assert");
var test = require("./testing").test;

var HtmlGenerator = require("../lib/html-generator").HtmlGenerator;

describe('HtmlGenerator', function() {
    test('generates empty string when newly created', function() {
        var generator = new HtmlGenerator();
        return assert.equal("", generator.asString());
    })
})

var assert = require("assert");
var test = require("./testing").test;

var HtmlGenerator = require("../lib/html-generator").HtmlGenerator;

describe('HtmlGenerator', function() {
    test('generates empty string when newly created', function() {
        var generator = new HtmlGenerator();
        return assert.equal("", generator.asString());
    });

    test('HTML-escapes text', function() {
        var generator = new HtmlGenerator();
        generator.text("<");
        return assert.equal("<", generator.asString());
    })
})

Add test for HtmlGenerator.text and HTML-escaping


//

// Bucket where R5 JARs are to be found
export const R5_BUCKET = 'https://r5-builds.s3.amazonaws.com'

// Recommended r5 version
export const RECOMMENDED_R5_VERSION = 'v4.6.1'

// R5 v4 has numerous breaking changes in worker/broker communication
export const MINIMUM_R5_VERSION = 'v4.0.0'

// Determine if a particular version is a release version
export const RELEASE_VERSION_REGEX = /^v[0-9]+\.[0-9]+\.[0-9]+$/

// Parse a version into its constituent pieces (major, minor, patch, distance from last release, commit)
export const VERSION_PARSE_REGEX = /^v([0-9]+).([0-9]+).([0-9]+)-?([0-9]+)?-?(.*)?$/

//

// Bucket where R5 JARs are to be found
export const R5_BUCKET = 'https://r5-builds.s3.amazonaws.com'

// Recommended r5 version
export const RECOMMENDED_R5_VERSION = 'v4.7.0'

// R5 v4 has numerous breaking changes in worker/broker communication
export const MINIMUM_R5_VERSION = 'v4.0.0'

// Determine if a particular version is a release version
export const RELEASE_VERSION_REGEX = /^v[0-9]+\.[0-9]+\.[0-9]+$/

// Parse a version into its constituent pieces (major, minor, patch, distance from last release, commit)
export const VERSION_PARSE_REGEX = /^v([0-9]+).([0-9]+).([0-9]+)-?([0-9]+)?-?(.*)?$/

Update recommended r5 version to v4.7.0


const expect = require('expect');
const { run, createProject } = require('../cli');

describe('Child Entries', () => {
    beforeEach(createProject);

    it('should be able to delete a child entry with a new configuration', function* () {
        const firstConfig = {
            parent: { entry: 'entryToDelete' },
        };

        yield run(`echo '${JSON.stringify(firstConfig)}' > firstConfig.json`);
        yield run('comfy setall development firstConfig.json');

        const { stdout: firstConfigOutput } = yield run('comfy get development');
        expect(JSON.parse(firstConfigOutput)).toEqual(firstConfig);

        const secondConfig = {
            parent: { child: { entry: 'entry' } },
            child: { entry: 'entry' },
        };

        yield run(`echo '${JSON.stringify(secondConfig)}' > secondConfig.json`);
        yield run('comfy setall development secondConfig.json');

        const { stdout: secondConfigOutput } = yield run('comfy get development');
        expect(JSON.parse(secondConfigOutput)).toEqual(secondConfig);
    });
});

const expect = require('expect');
const { run, createProject } = require('../cli');

describe('Scenarios', () => {
    beforeEach(createProject);

    describe('Child Entries', () => {
        it('should be able to delete a child entry with a new configuration', function* () {
            const firstConfig = {
                parent: { entry: 'entryToDelete' },
            };

            yield run(`echo '${JSON.stringify(firstConfig)}' > firstConfig.json`);
            yield run('comfy setall development firstConfig.json');

            const { stdout: firstConfigOutput } = yield run('comfy get development');
            expect(JSON.parse(firstConfigOutput)).toEqual(firstConfig);

            const secondConfig = {
                parent: { child: { entry: 'entry' } },
                child: { entry: 'entry' },
            };

            yield run(`echo '${JSON.stringify(secondConfig)}' > secondConfig.json`);
            yield run('comfy setall development secondConfig.json');

            const { stdout: secondConfigOutput } = yield run('comfy get development');
            expect(JSON.parse(secondConfigOutput)).toEqual(secondConfig);
        });
    });

    describe('Boolean serialization', () => {
        it('should not transform a `false` bool to a `"false"` string', function* () {
            const config = { myEntry: false };

            yield run(`echo '${JSON.stringify(config)}' > config.json`);
            yield run('comfy setall development config.json');

            const { stdout } = yield run('comfy get development');
            expect(JSON.parse(stdout)).toEqual(config);
        });
    });
});

Make the issue appear in the tests