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

export async function trackAPIError( type, identifier, datapoint, error ) {
	await trackEvent(
		'api_error',
		`${ type }/${ identifier }/data/${ datapoint }`,
		`${ error.message } (code: ${ error.code }${ error.data?.reason ? ', reason: ' + error.data.reason : '' } ])`,
		error.data?.status || error.code
	);
}

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

export async function trackAPIError( method, type, identifier, datapoint, error ) {
	await trackEvent(
		'api_error',
		`${ method }:${ type }/${ identifier }/data/${ datapoint }`,
		`${ error.message } (code: ${ error.code }${ error.data?.reason ? ', reason: ' + error.data.reason : '' } ])`,
		error.data?.status || error.code
	);
}

Include the request method in `trackAPIError`.


/*

Kent Hack Enough

Let's try to organize organizing.
@author Paul Dilyard

To get the app instance, just call getAppInstance() from anywhere
To get a db connection, require('mongoose')

*/
var express = require('express');
var mongoose = require('mongoose');
var bodyParser = require('body-parser');
var multer = require('multer');
var error = require('./app/error');

// Start up server
var app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(multer({dest: './uploads/'}));
app.use(error);
var port = process.env.PORT || 3000;
var server = app.listen(port, function () {
  var host = server.address().address;
  var port = server.address().port;
  console.log('\n  🎉  Kent Hack Enough listening at %s%s\n', host, port);
});

// Connect to database
var mongo = process.env.MONGO_URI || 'mongodb://localhost:27017/khe';
mongoose.connect(mongo);

module.exports.app = app;
module.exports.mongoose = mongoose;
GLOBAL.getAppInstance = function () {
  return app;
};

// Include modules
[
  'users',
  'applications'
].forEach(function (module) {
  require('./app/' + module + '/controller');
});

/*

Kent Hack Enough

Let's try to organize organizing.
@author Paul Dilyard

To get the app instance, just call getAppInstance() from anywhere
To get a db connection, require('mongoose')

*/
var express = require('express');
var mongoose = require('mongoose');
var bodyParser = require('body-parser');
var multer = require('multer');
var error = require('./app/error');

// Start up server
var app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(multer({dest: './uploads/'}));
app.use(error);
var port = process.env.PORT || 3000;
var server = app.listen(port);

// Connect to database
var mongo = process.env.MONGO_URI || 'mongodb://localhost:27017/khe';
mongoose.connect(mongo);

module.exports.app = app;
module.exports.mongoose = mongoose;
GLOBAL.getAppInstance = function () {
  return app;
};

// Include modules
[
  'users',
  'applications'
].forEach(function (module) {
  require('./app/' + module + '/controller');
});

Remove console.log from server start


var connect = require('connect');
var PORT = process.env.PORT || 5000;
var diffy = require('./diffy');
var http = require('http');
var postMessage = require('./lib/post_message');

var app = connect()
.use(require('body-parser').urlencoded({ extended: true }))
.use(function(req, res){
  if (req.body.token && req.body.token === process.env.AUTH_TOKEN) {
    diffy(decodeURIComponent(req.body.url), function (err, s3Url) {
      if (err) {
        res.statusCode = 500;
        res.end('Something went horribly wrong: ' + err.message);
        return;
      }

      // Nothing has changed
      if (!s3Url) {
        res.statusCode = 204;
        res.end();
        return;
      }

      // Post the message to slack
      postMessage(s3Url, function (err) {
        if (err) {
          res.statusCode = 500;
          res.end('Failed to post message to Slack ' + err.message);
          return;
        }

        res.statusCode = 200;
        res.end(s3Url);
      });
    });
  } else {
    res.statusCode = 404;
    res.end('Not Found');
  }
});

http.createServer(app).listen(PORT);

var connect = require('connect');
var PORT = process.env.PORT || 5000;
var diffy = require('./diffy');
var http = require('http');
var postMessage = require('./lib/post_message');

var app = connect()
.use(require('body-parser').urlencoded({ extended: true }))
.use(function(req, res){
  if (req.body.token && req.body.token === process.env.AUTH_TOKEN) {
    diffy(decodeURIComponent(req.body.url), function (err, s3Url) {
      if (err) {
        res.statusCode = 500;
        res.end('Something went horribly wrong: ' + err.message);
        return;
      }

      // Nothing has changed
      if (!s3Url) {
        res.statusCode = 204;
        res.end();
        return;
      }

      // Post the message to slack
      postMessage(s3Url + '?' + Math.round(Math.random() * 1e4), function (err) {
        if (err) {
          res.statusCode = 500;
          res.end('Failed to post message to Slack ' + err.message);
          return;
        }

        res.statusCode = 200;
        res.end(s3Url);
      });
    });
  } else {
    res.statusCode = 404;
    res.end('Not Found');
  }
});

http.createServer(app).listen(PORT);

Add a random param to the S3 URL so that Slack will always expand the image


// ==UserScript==
// @name        Todays PPC script
// @namespace   http://www.github.com/gh4ck3r
// @description Customize and remove Ad. from Todays PPC site
// @include     https://www.todaysppc.com/*
// @version     9
// @grant       none
// @run-at      document-end
// ==/UserScript==

document.styleSheets[0].insertRule('body>table:first-of-type>tbody>tr:first-of-type, ins.adsbygoogle, zeroboard>zeroboard>p:first-of-type, #right_ad {display: none;}');

//var adSelectors = [];
//document.querySelectorAll(adSelectors.join()).forEach(elem => elem.style.display='none');

const workDocument = document.querySelector('#work').contentDocument;
workDocument.querySelectorAll('#layer50,body>font:last-of-type')
	.forEach(elem => elem.style.display='none');

// ==UserScript==
// @name        Todays PPC script
// @namespace   http://www.github.com/gh4ck3r
// @description Customize and remove Ad. from Todays PPC site
// @include     https://www.todaysppc.com/*
// @version     10
// @grant       none
// @run-at      document-end
// ==/UserScript==

(function(){
  const css = document.styleSheets[0];
  if (!css) return;

  [
    'body>table:first-of-type>tbody>tr:first-of-type, ins.adsbygoogle, zeroboard>zeroboard>p:first-of-type, #right_ad {display: none;}',
    '#layer1, #layer1 ~ div { top: 40px !important;}',
  ].forEach(css.insertRule.bind(css));
})()

const workDocument = document.querySelector('#work').contentDocument;
workDocument.querySelectorAll('#layer50,body>font:last-of-type')
  .forEach(elem => elem.style.display='none');

Move hidden layered menu to proper position


/**
 * User
 *
 * @module      :: Model
 * @description :: A short summary of how this model works and what it represents.
 * @docs		:: http://sailsjs.org/#!documentation/models
 */

module.exports = {

  attributes: {
    email: {
        type: 'email',
        required: true
    },
    name: 'STRING'
  	/* e.g.
  	nickname: 'string'
  	*/

  }

};

/**
 * User
 *
 * @module      :: Model
 * @description :: A short summary of how this model works and what it represents.
 * @docs		:: http://sailsjs.org/#!documentation/models
 */

module.exports = {
  tableName : 'sails_user',
  attributes: {
    email: {
        type: 'email',
        required: true
    },
    name: 'STRING'
  	/* e.g.
  	nickname: 'string'
  	*/

  }

};

Change db table name to not be reserved sql keyword


import React from 'react';
import style from 'HPCCloudStyle/ItemEditor.mcss';

export default React.createClass({

    displayName: 'Simulation/New/Visualizer',

    propTypes: {
        owner: React.PropTypes.func,
    },

    getInitialState() {
        return {
            mesh: null,
        };
    },

    componentDidMount() {

    },

    componentWillUnmount(){

    },

    processFile(event) {
        var file;
        if (event.target.files.length) {
            file = event.target.files[0];
        } else if (event.dataTransfer.files.length) {
            file = event.dataTransfer.files[0];
        }
        event.preventDefault();
        event.stopPropagation();

        if (!file) {
            return;
        }

        const name = event.target.dataset.name;
        this.setState({[name]: file.name});

        // Let's record attachement
        if(this.props.owner) {
            this.props.owner().addAttachement(name, file);
        }
    },

    render() {
        return (

                Result file
                <input
                    className={style.input}
                    data-name='mesh'
                    type="file"
                    value={this.state.mesh}
                    onChange={this.processFile}/>

);
    },
});

import React                from 'react';
import { FileUploadEntry }  from '../../../../panels/ItemEditor';

export default React.createClass({

    displayName: 'Simulation/New/Visualizer',

    propTypes: {
        owner: React.PropTypes.func,
    },

    render() {
        return <FileUploadEntry name='dataset' label='Result file' owner={ this.props.owner }/>;
    },
});

Fix file upload component for Visualizer


#!/usr/bin/env node
'use strict';

var meow = require('meow');
var getBrowsers = require('./');

meow({
	help: [
		'Usage',
		'  $ get-browsers'
	].join('\n')
});

getBrowsers(function (err, res) {
	if (err) {
		console.error(err.message);
		process.exit(1);
	}

	res.forEach(function (r, i) {
		i = i + 1;
		console.log(i + '. ' + r.item);
	});
});

#!/usr/bin/env node
'use strict';

var meow = require('meow');
var getBrowsers = require('./');

meow({
	help: [
		'Usage',
		'  $ get-browsers'
	].join('\n')
});

getBrowsers(function (err, res) {
	if (err) {
		console.error(err.message);
		process.exit(1);
	}

	res.forEach(function (r, i) {
		i = i + 1;
		console.log(i + '. ' + r.item + ' (' + r.percent + ')');
	});
});

Add percentage to CLI output


'use strict';

var express = require('express'),
    favicon = require('serve-favicon'),
    routes = require('./app/routes/index.js'),
    mongo = require('mongodb').MongoClient;

var app = express();

mongo.connect('mongodb://localhost:27017/timolawlurlshortener', function(err, db) {
    if (err) {
        throw new Error('Database failed to connect!');
    }
    else {
        console.log('MongoDB successfully connected on port 27017.');
    }

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

    app.use('/public', express.static(process.cwd() + '/public'));
    app.use(favicon(process.cwd() + '/public/images/favicon.ico'));

    routes(app, db);

    app.listen(app.get('port'), function() {
        console.log('Node app is running on port', app.get('port'));
    });
});

'use strict';

var express = require('express'),
    favicon = require('serve-favicon'),
    routes = require('./app/routes/index.js'),
    mongo = require('mongodb').MongoClient;

var app = express();

var mongoURL = process.env.MONGOLAB_URI || 'mongodb://localhost:27017/timolawlurlshortener';

mongo.connect(mongoURL, function(err, db) {
    if (err) {
        throw new Error('Database failed to connect!');
    }
    else {
        console.log('MongoDB successfully connected on port 27017.');
    }

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

    app.use('/public', express.static(process.cwd() + '/public'));
    app.use(favicon(process.cwd() + '/public/images/favicon.ico'));

    routes(app, db);

    app.listen(app.get('port'), function() {
        console.log('Node app is running on port', app.get('port'));
    });
});

Add mongolab mongodb hosting (free...)


console.log("Deploying Application...");
var fs = require("fs");
var path = require("path");
var ShopifyAPI = require("shopify-node-api");

var pageId = process.env.SHOPIFY_PAGE_ID;
var Shopify = new ShopifyAPI({
    shop: process.env.SHOPIFY_SHOP,
    shopify_api_key: process.env.SHOPIFY_API_KEY,
    access_token: process.env.SHOPIFY_API_PASSWORD
});

var indexPagePath = path.join(__dirname, "..", "dist", "index.html");
fs.readFile(indexPagePath, "utf-8", function (err, data) {
    if (!err) {
        var put_data = {
            page: {
                body_html: data
            }
        };

        Shopify.put("/admin/pages/" + pageId + ".json", put_data, function (err, data, headers) {
            if (err) {
                console.error("Failed to Deploy during API call");
                return 0;
            } else {
                console.log("Deploy Success");
                return 1;
            }
        });
    }
});

var fs = require("fs");
var path = require("path");
var ShopifyAPI = require("shopify-node-api");

var pageId = process.env.SHOPIFY_PAGE_ID;
var Shopify = new ShopifyAPI({
    shop: process.env.SHOPIFY_SHOP,
    shopify_api_key: process.env.SHOPIFY_API_KEY,
    access_token: process.env.SHOPIFY_API_PASSWORD
});

var indexPagePath = path.join(__dirname, "..", "dist", "index.html");
fs.readFile(indexPagePath, "utf-8", function (err, data) {
    if (!err) {
        var put_data = {
            page: {
                body_html: data
            }
        };
        console.log("Deploying Page Body: ", data);

        Shopify.put("/admin/pages/" + pageId + ".json", put_data, function (err, data, headers) {
            if (err) {
                console.error("Failed to Deploy during API call");
                return 0;
            } else {
                console.log("Deploy Success");
                return 1;
            }
        });
    } else {
        console.error("Error reading file: ", err);
    }
});

Add console logs for deploy status


import { app, BrowserWindow } from 'electron'

export var devMenuTemplate = {
  label: 'Development',
  submenu: [{
    label: 'Reload',
    accelerator: 'CmdOrCtrl+R',
    click: function () {
      BrowserWindow.getFocusedWindow().webContents.reloadIgnoringCache()
    },
  }, {
    label: 'Toggle DevTools',
    accelerator: 'Alt+CmdOrCtrl+I',
    click: function () {
      BrowserWindow.getFocusedWindow().toggleDevTools()
    },
  }, {
    label: 'Quit',
    accelerator: 'CmdOrCtrl+Q',
    click: function () {
      app.quit()
    },
  }],
}

import { app, BrowserWindow } from 'electron'

export var devMenuTemplate = {
  label: 'Development',
  submenu: [{
    label: 'Reload',
    accelerator: 'CmdOrCtrl+R',
    click: function () {
      BrowserWindow.getFocusedWindow().webContents.reloadIgnoringCache()
    },
  }, {
    label: 'Toggle DevTools',
    accelerator: 'Alt+CmdOrCtrl+I',
    click: function () {
      BrowserWindow.getFocusedWindow().toggleDevTools()
    },
  }, {
    label: 'Quit',
    accelerator: 'CmdOrCtrl+Q',
    click: function () {
      app.exit(0)
    },
  }],
}

Allow cmd+q to quit the application fully.


// Load libraries
import angular from 'angular';

import 'angular-animate';
import 'angular-aria';
import 'angular-material';

import 'tinycolor'
import 'md-color-picker'

import AppController from 'src/AppController';
import MaterialHue from 'src/materialhue/MaterialHue';

export default angular.module( 'material-hue', [ 'ngMaterial', MaterialHue.name ] )
  .config(($mdIconProvider, $mdThemingProvider) => {
    // Register the user `avatar` icons
    $mdIconProvider
      .defaultIconSet("./assets/svg/avatars.svg", 128)
      .icon("menu", "./assets/svg/menu.svg", 24)
      .icon("share", "./assets/svg/share.svg", 24)
      .icon("phone", "./assets/svg/phone.svg", 24);

    $mdThemingProvider.theme('default')
      .primaryPalette('blue')
      .accentPalette('pink');
  })
  .controller('AppController', AppController);

// Load libraries
import angular from 'angular';

import 'angular-animate';
import 'angular-aria';
import 'angular-material';

//import 'tinycolor'
//import 'md-color-picker'

import AppController from 'src/AppController';
import MaterialHue from 'src/materialhue/MaterialHue';

export default angular.module( 'material-hue', [ 'ngMaterial', MaterialHue.name ] )
  .config(($mdIconProvider, $mdThemingProvider) => {
    // Register the user `avatar` icons
    $mdIconProvider
      .defaultIconSet("./assets/svg/avatars.svg", 128)
      .icon("menu", "./assets/svg/menu.svg", 24)
      .icon("share", "./assets/svg/share.svg", 24)
      .icon("phone", "./assets/svg/phone.svg", 24);

    $mdThemingProvider.theme('default')
      .primaryPalette('blue')
      .accentPalette('pink');
  })
  .controller('AppController', AppController);

Remove these dependencies for now


/* @flow strict-local */
import { createSelector } from 'reselect';

import type { Account, Identity, Selector } from '../types';
import { NULL_ACCOUNT } from '../nullObjects';
import { getAccounts } from '../directSelectors';

export const getActiveAccount: Selector<Account> = createSelector(
  getAccounts,
  accounts => (accounts && accounts.length > 0 ? accounts[0] : NULL_ACCOUNT),
);

export const getOwnEmail: Selector<string> = createSelector(
  getActiveAccount,
  activeAccount => activeAccount.email,
);

export const getCurrentRealm: Selector<string> = createSelector(
  getActiveAccount,
  activeAccount => activeAccount.realm,
);

export const getAuth = getActiveAccount;

export const getIdentity: Selector<Identity> = createSelector(getAuth, ({ email, realm }) => ({
  email,
  realm,
}));

/* @flow strict-local */

import type { Account, GlobalState, Identity } from '../types';
import { NULL_ACCOUNT } from '../nullObjects';
import { getAccounts } from '../directSelectors';

export const getActiveAccount = (state: GlobalState): Account => {
  const accounts = getAccounts(state);
  return accounts && accounts.length > 0 ? accounts[0] : NULL_ACCOUNT;
};

export const getOwnEmail = (state: GlobalState) => getActiveAccount(state).email;

export const getCurrentRealm = (state: GlobalState) => getActiveAccount(state).realm;

export const getAuth = getActiveAccount;

export const getIdentity = (state: GlobalState): Identity => {
  const { email, realm } = getAuth(state);
  return {
    email,
    realm,
  };
};

Cut Reselect for trivial selectors.


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

module.exports = function (req, res) {
	if (!keystone.security.csrf.validate(req)) {
		return res.apiError(403, 'invalid csrf');
	}
	var item = new req.list.model();
	item.getUpdateHandler(req).process(req.body, { flashErrors: false, logErrors: true }, function (err) {
		if (err) {
			if (err.name === 'ValidationErrors') {
				return res.apiError(400, 'validation errors', err.errors);
			} else {
				return res.apiError(500, 'error', err);
			}
		}
		res.json(req.list.getData(item));
	});
};

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

module.exports = function (req, res) {
	if (!keystone.security.csrf.validate(req)) {
		return res.apiError(403, 'invalid csrf');
	}
	var item = new req.list.model();
	item.getUpdateHandler(req).process(req.body, { flashErrors: false, logErrors: true, ignoreNoedit: true }, function (err) {
		if (err) {
			if (err.name === 'ValidationErrors') {
				return res.apiError(400, 'validation errors', err.errors);
			} else {
				return res.apiError(500, 'error', err);
			}
		}
		res.json(req.list.getData(item));
	});
};

Fix validation on noedit initial fields


const fs = require("fs");
const path = require("path");
const pify = require("pify");
const os = require("os");

const { Archive } = require("buttercup");
const csvparse = require("csv-parse/lib/sync");

/**
 * Import csv archive from Chrome/Chromium/Google/Opera and from other popular browsers
 * @param {string} archiveCSVPath
 */
const importFromCSV = archiveCSVPath => {
    const groupName = path.basename(archiveCSVPath).split(".")[0];
    return pify(fs.readFile)(archiveCSVPath, "utf8").then(contents => {
        const archive = new Archive();
        const group = archive.createGroup(groupName);
        csvparse(contents, { columns: true }).forEach((item, index) => {
            const entry = group.createEntry(
                item.name || item.url || `Item ${index}`
            );
            if (item.username) {
                entry.setProperty("username", item.username);
            }
            if (item.password) {
                entry.setProperty("password", item.password);
            }
            if (item.url) {
                entry.setMeta("url", item.url);
            }
        });

        return archive;
    });
};

module.exports = importFromCSV;

const fs = require("fs");
const path = require("path");
const pify = require("pify");
const os = require("os");

const { Archive } = require("buttercup");
const csvparse = require("csv-parse/lib/sync");

/**
 * Import csv archive from Chrome/Chromium/Google/Opera and from other popular browsers
 * @param {string} archiveCSVPath
 */
const importFromCSV = archiveCSVPath => {
    const groupName = path.basename(archiveCSVPath).split(".")[0];
    return pify(fs.readFile)(archiveCSVPath, "utf8").then(contents => {
        const archive = new Archive();
        const group = archive.createGroup(groupName);
        csvparse(contents, { columns: true }).forEach((item, index) => {
            const entry = group.createEntry(
                item.name || item.title || item.url || `Entry ${index}`
            );
            if (item.username) {
                entry.setProperty("username", item.username);
            }
            if (item.password) {
                entry.setProperty("password", item.password);
            }
            if (item.url) {
                entry.setMeta("url", item.url);
            }
        });

        return archive;
    });
};

module.exports = importFromCSV;

Add title property just in case


var webpack = require('webpack');

module.exports = {
  entry: "./src/app.js",
  output: {
    path: "./build",
    filename: "bundle.js",
    publicPath: 'http://localhost:8090/assets'
  },
  module: {
    loaders: [
      { test: /\.scss$/, loader: "style!css!sass" },
      { test: /\.css$/, loader: "style!css" },
      { test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192' },
      { test: /\.js$/, loader: "jsx-loader?insertPragma=React.DOM&harmony" },
      { test: /\.json$/, loader: "json" }
    ]
  },
  externals: {
    'react': 'React'
  },
  resolve: {
    extensions: ['', '.js', '.jsx']
  },
  plugins: [
    // Ignore extra languages for moment.js
    new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
  ]
};

var webpack = require('webpack');

module.exports = {
  entry: "./src/app.js",
  output: {
    path: "./build",
    filename: "bundle.js",
    publicPath: 'http://localhost:8090/assets'
  },
  module: {
    loaders: [
      { test: /\.scss$/, loader: "style!css!sass" },
      { test: /\.css$/, loader: "style!css" },
      { test: /\.(png|jpg|svg)$/, loader: 'url-loader?limit=8192' },
      { test: /\.js$/, loader: "jsx-loader?insertPragma=React.DOM&harmony" },
      { test: /\.json$/, loader: "json" }
    ]
  },
  externals: {
    'react': 'React'
  },
  resolve: {
    extensions: ['', '.js', '.jsx']
  },
  plugins: [
    // Ignore extra languages for moment.js
    new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
  ]
};

Add svg to be loaded as image.


var webpack = require('webpack');
var path = require('path');
var node_modules_dir = path.join(__dirname, 'node_modules');

module.exports = {
    devtool: 'inline-cheap-source-map',
    entry: path.resolve(__dirname, 'annotator/static/js/index.js'),
    module: {
        rules: [
            {
                test: /\.js$/,
                use: {
                  loader: 'babel-loader',
                  options: {
                    presets: ['react-es2015']
                  }
                },
                exclude: node_modules_dir,
                include: path.join(__dirname, 'annotator/static/js/')
            },
        ]
    },
    plugins: [
        // build optimization plugins
        new webpack.optimize.UglifyJsPlugin({
          compress: false,
          mangle: false,
        }),
        new webpack.ProvidePlugin({
          jQuery: 'jquery',
          $: 'jquery',
          jquery: 'jquery'
        })
    ],
    // resolve: {
    //     alias: {
    //         jquery: "jquery/src/jquery"
    //     }
    // }
}

var webpack = require('webpack');
var path = require('path');
var node_modules_dir = path.join(__dirname, 'node_modules');

module.exports = {
    devtool: 'inline-cheap-source-map',
    entry: path.resolve(__dirname, 'annotator/static/js/index.js'),
    module: {
        rules: [
            {
                test: /\.js$/,
                use: {
                  loader: 'babel-loader',
                  options: {
                    presets: ['react-es2015']
                  }
                },
                exclude: node_modules_dir,
                include: path.join(__dirname, 'annotator/static/js/')
            },
        ]
    },
    plugins: [
        // build optimization plugins
        new webpack.optimize.UglifyJsPlugin({
          compress: false,
          mangle: false,
        }),
    ],
}

Remove old jQuery webpack stuff


const webpack = require('webpack');

module.exports = {
  entry: {
    'pusher-platform': './src/index.ts'
  },
  output: {
    filename: 'target/[name].js',
    libraryTarget: 'umd',
    library: 'PusherPlatform',
  },
  resolve: {
    extensions: ['.ts', '.js'],
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        loader: `ts-loader?${ JSON.stringify({ logInfoToStdOut: true }) }`,
        exclude: [/node_modules/, /target/ ]
      }
    ],
  },
  plugins: [
    // new DtsBundlePlugin()
  ],
};

function DtsBundlePlugin(){}
DtsBundlePlugin.prototype.apply = function (compiler) {
  compiler.plugin('done', function(){
    var dts = require('dts-bundle');

    dts.bundle({
      name: 'pusher-platform',
      main: 'src/index.d.ts',
      out: '../target/index.d.ts',
      removeSource: true,
      outputAsModuleFolder: true // to use npm in-package typings
    });
  });
};

const webpack = require('webpack');

module.exports = {
  entry: {
    'pusher-platform': './src/index.ts'
  },
  output: {
    filename: 'target/[name].js',
    libraryTarget: 'umd',
    library: 'PusherPlatform',
  },
  resolve: {
    extensions: ['.ts', '.js'],
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        loader: `ts-loader?${ JSON.stringify({ logInfoToStdOut: true }) }`,
        exclude: [/node_modules/, /target/ ]
      }
    ],
  },
};

Remove the typings merging plugin


var ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
    context: __dirname + '/app',
    entry: ['./app.jsx'],
    output: {
        filename: 'molten.js',
        path: './dist',
        publicPath: 'assets'
    },
    module: {
        loaders: [
            {
                test: /\.jsx?$/,
                exclude: /(node_modules|bower_components)/,
                loader: 'babel'
            },
            {
                test: /\.css$/,
                loader: 'style-loader!css-loader'
            },
            {
                test: /\.less$/,
                loader: ExtractTextPlugin.extract(
                    // activate source maps via loader query
                    'css?sourceMap!' +
                    'less?sourceMap'
                )
            },
            {
                test: /\.png$/,
                loader: 'url-loader?limit=100000&mimetype=image/png'
            }
        ]
    },
    resolve: {
        alias: {
            react: __dirname + '/node_modules/react',
            'react/addons': __dirname + '/node_modules/react/addons'
        },
        root: __dirname + '/app',
        extensions: ['', '.js', '.jsx'],
        modulesDirectories: ['node_modules']
    },
    devtool: 'source-map',
    devServer: {
        headers: {
            'Access-Control-Allow-Origin': '*'
        }
    },
    plugins: [
        // extract inline css into separate 'styles.css'
        new ExtractTextPlugin('styles.css')
    ]
};

var ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
    context: __dirname + '/app',
    entry: ['./app.jsx'],
    output: {
        filename: 'molten.js',
        path: './dist',
        publicPath: 'assets'
    },
    module: {
        loaders: [
            {
                test: /\.jsx?$/,
                exclude: /(node_modules|bower_components)/,
                loader: 'babel'
            },
            {
                test: /\.less$/,
                loader: ExtractTextPlugin.extract(
                    // activate source maps via loader query
                    'css?sourceMap!' +
                    'less?sourceMap'
                )
            },
            {
                test: /\.png$/,
                loader: 'url-loader?limit=100000&mimetype=image/png'
            }
        ]
    },
    resolve: {
        alias: {
            react: __dirname + '/node_modules/react',
            'react/addons': __dirname + '/node_modules/react/addons'
        },
        root: __dirname + '/app',
        extensions: ['', '.js', '.jsx'],
        modulesDirectories: ['node_modules']
    },
    devtool: 'source-map',
    devServer: {
        headers: {
            'Access-Control-Allow-Origin': '*'
        }
    },
    plugins: [
        // extract inline css into separate 'styles.css'
        new ExtractTextPlugin('styles.css')
    ]
};

Remove loading of css files


errors = new ReactiveArray([]);

Template.register.helpers({
  errorList: function() {
    return errors.list();
  },

  formValid: function(user) {
    return !($.isEmptyObject(user.userName) &&
          $.isEmptyObject(user.firstName) &&
          $.isEmptyObject(user.lastName) &&
          $.isEmptyObject(user.email) &&
          $.isEmptyObject(user.password));
  }
});

Template.register.events({
  'submit .form-signup': function(e) {
    Session.set('errors', null);
    e.preventDefault();
    var user = {
        email: $('#inputEmail').val(),
        password: $('#inputPassword').val(),
        profile: {
          userName: $('#inputUsername').val(),
          firstName: $('#inputFirstName').val(),
          lastName: $('#inputLastName').val()
        }
    };

    Accounts.createUser(user, function(err) {
      if (err) {
        console.log(err);
        errors.push({message: err.reason});
      } else {
        console.log('User with id: ' + Meteor.userId() + ' added successfully');
        Router.go('projects', {
          _id: Meteor.userId()
        });

      }
    });
  },
});

Template.register.onCreated(function() {
  this.errors = new ReactiveVar();
  this.errors.set([])
});

Template.register.helpers({
  errorList: function() {
    return Template.instance().errors.get();
  },

  formValid: function(user) {
    return !($.isEmptyObject(user.userName) &&
          $.isEmptyObject(user.firstName) &&
          $.isEmptyObject(user.lastName) &&
          $.isEmptyObject(user.email) &&
          $.isEmptyObject(user.password));
  }
});

Template.register.events({
  'submit .form-signup': function(e, template) {

    e.preventDefault();
    var user = {
        email: $('#inputEmail').val(),
        password: $('#inputPassword').val(),
        profile: {
          userName: $('#inputUsername').val(),
          firstName: $('#inputFirstName').val(),
          lastName: $('#inputLastName').val()
        }
    };

    Accounts.createUser(user, function(err) {
      if (err) {
        console.log(err);
        var errors = [];
        errors.push({message: err.reason});
        template.errors.set(errors);
      } else {
        console.log('User with id: ' + Meteor.userId() + ' added successfully');
        Router.go('projects', {
          _id: Meteor.userId()
        });
      }
    });
  },
});

Switch to ReactiveVar and fix issue with error message displaying more than once


/*
* @Author: KevinMidboe
* @Date:   2017-06-01 19:09:16
* @Last Modified by:   KevinMidboe
* @Last Modified time: 2017-10-24 21:55:41
*/

const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
	entry: {
		app: './app/index.js',
	},
	plugins: [
		new CleanWebpackPlugin(['dist']),
		new HtmlWebpackPlugin({
			template: './app/index.html',
		})
	],
	 module: {
	 	loaders: [
	 		{ test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ },
	 		{ test: /\.jsx$/, loader: 'babel-loader', exclude: /node_modules/ },
	 	]
	 },

	output: {
		filename: '[name].bundle.js',
		path: path.resolve(__dirname, 'dist')
	}
};

/*
* @Author: KevinMidboe
* @Date:   2017-06-01 19:09:16
* @Last Modified by:   KevinMidboe
* @Last Modified time: 2017-10-24 21:55:41
*/

const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
	entry: {
		app: './app/index.js',
	},
	plugins: [
		new CleanWebpackPlugin(['dist']),
		new HtmlWebpackPlugin({
			template: './app/index.html',
		})
	],
	 module: {
	 	loaders: [
	 		{ test: /\.(js|jsx)$/, loader: 'babel-loader', exclude: /node_modules/ },
	 	]
	 },

	output: {
		filename: '[name].bundle.js',
		path: path.resolve(__dirname, 'dist')
	}
};

Put both jsx and js varaibles on same line in config file.


// Map centered in NowSecure HQ by default
var map = L.map('map').setView([40.2001925,-89.0876265], 3);

L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
    attribution: '© OpenStreetMap contributors'
}).addTo(map);

console.log('Adding markers for ' + rows.length + ' rows');
rows.forEach(function(row) {
    var coordinates = [row.latitude, row.longitude];
    L.marker(coordinates).addTo(map)
        .bindPopup(
            'Filename: ' + row.filename + '
' +
            'GPS datetime: ' + row.datetime
        )
    ;
});

// Map centered in NowSecure HQ by default
var map = L.map('map').setView([40.2001925,-89.0876265], 3);

L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
    attribution: '© OpenStreetMap contributors'
}).addTo(map);

console.log('Adding markers for ' + rows.length + ' rows');
rows.forEach(function(row) {
    var coordinates = [row.latitude, row.longitude];
    var text = 'Filename: ' + row.filename;
    if (row.datetime) {
      text += '
GPS datetime: ' + row.datetime;
    }

    L.marker(coordinates).addTo(map).bindPopup(text);
});

Add GPS datetime to popup if such information exists


import {Component} from 'react'

export default class MockTestComponentUniqueName extends Component {
  static defaultProps = {
    test: 'hi'
  }

  render () {
    <div />
  }
}

import uuid from 'uuid'

export default class MockTestComponentUniqueName {
  static defaultProps = {
    test: 'hi'
  }

  render () {
    <div />
  }
}

uuid.v4()

Fix test that required React in a mock file.


import React, { Component, PropTypes } from 'react';
import Slide from '../slide';
import BulletPoint from '../bullet-point';

require('./app.scss');

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

        this.state = {
            current: 1,
            total: 2
        }
    }

    render() {
        return (
            <div className="presentation">
                <Slide title="What is ReactJS?" order="1" total={this.state.total} current={this.state.current}>
                    <BulletPoint>Hello this will be a simple slide presentation app when done.</BulletPoint>
                    <BulletPoint>A tag is just a tag</BulletPoint>
                </Slide>
                <Slide title="V is for View" order="2" total={this.state.total} current={this.state.current}>
                    <BulletPoint>Hello this will be a simple slide presentation app when done.</BulletPoint>
                    <BulletPoint>A tag is just a tag</BulletPoint>
                </Slide>
            </div>
        );
    }
}

export default App;

import React, { Component, PropTypes } from 'react';
import Slide from '../slide';
import BulletPoint from '../bullet-point';
import Presentation from '../presentation';

require('./app.scss');

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

        this.state = {
            current: 3
        }
    }

    render() {
        return (
            <Presentation current={this.state.current}>
                <Slide title="What is ReactJS?">
                    <BulletPoint>Hello this will be a simple slide presentation app when done.</BulletPoint>
                    <BulletPoint>A tag is just a tag</BulletPoint>
                </Slide>
                <Slide title="V is for View">
                    <BulletPoint>Hello this will be a simple slide presentation app when done.</BulletPoint>
                    <BulletPoint>A tag is just a tag</BulletPoint>
                </Slide>
                <Slide title="Why Use ReactJS?">
                    <BulletPoint>Thinking in modularity</BulletPoint>
                    <BulletPoint>Flexiblity</BulletPoint>
                </Slide>
            </Presentation>
        );
    }
}

export default App;

Add presentation to the app component


$(document).ready(function() {
	// select input content when focused
	$('input[type=text],input[type=password],input[type=search]')
		.focus(function(e) {
			this.select();
		});
	$('a.partial').click(function(){
		$.get(this.href+'?format=partial', function(data, textStatus){
			if (textStatus == 'success') {
				$('#maincontent').html(data);
				// @todo push the url onto the history stack
			}
		});
		return false;
	});
});

function changeContents(url) {
	//first, set the height of maincontent to fix jumping
	$('#maincontent').css({'height' : $('#maincontent').height()});
    //$('#maincontent').html('

');
    $.get(
        url+"?format=partial",
        function(data) {
            $('#maincontent').html(data);
            $('#maincontent').css({'height' : 'auto'});
            $('pre code').each(function(i, e) {hljs.highlightBlock(e, '    ')});
        }
    );
}

$(document).ready(function() {
	// select input content when focused
	$('input[type=text],input[type=password],input[type=search]')
		.focus(function(e) {
			this.select();
		});
	$('a.partial').live('click', function(event){
		var url = this.href;
	    var direction = {'right': "+=1000px"};
	    if (this.id == 'next') {
	    	direction = {'right': "+=1000px"};
	    } else {
	    	// figure out how to determine which way to go
	    }

	    $("#maincontent").animate(direction, "fast", function(){
	        if (supports_history()) {
	            var title = url.split(window.url);
	            window.History.pushState(null, title, url);
	        } else {
	        	changeContents(url);
	        }
	    });
		return false;
	});
});

function supports_history() {
	return !!(window.history && history.pushState)
}

//Smart browser history for HTML 5 browsers.
(function(window,undefined){
	if (supports_history()) {
		var History = window.History;
		$(window).bind('statechange',function(){
			var State = History.getState();
			changeContents(State.url);
		});
	}
})(window);

Add HTML5 history pushState support when navigating the release files.


import React, {Component, Image, Text, TouchableHighlight, View, StyleSheet} from 'react-native';

export default class FriendListItem extends Component {

  constructor(props) {
    super(props);
  }

  render() {
    const { friend } = this.props;
    const pic = friend.picture;
    return (
      <TouchableHighlight
        onPress={() => {
          this.props.onPress( friend );
          // TODO: And then make the friend list go away.
        }}
      >
        <View style={style.container}>
          <Image
            style={styles.icon}
            source={require(pic)}
          />
          <Text style={style.text}>
            {friend.name}
          </Text>
          <Text style={style.text}>
            {friend.email}
          </Text>
        </View>
      </TouchableHighlight>
    );
  }
}

const style = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: 'white',
    borderRadius: 4,
    margin: 4,
    padding: 6,
  },
  text: {
    alignSelf: 'center',
    fontSize: 24,
  },
  icon: {
    alignSelf: 'flex-start',
    margin: 4,
    borderWidth: 1,
    borderColor: '#000000',
  }
});

import React, {Component, Image, Text, TouchableHighlight, View, StyleSheet} from 'react-native';

export default class FriendListItem extends Component {

  constructor(props) {
    super(props);
  }

  render() {
    const { friend } = this.props;
    // const pic = friend.picture; TODO: Add picture and email
    return (
      <TouchableHighlight
        onPress={() => {
          this.props.onPress( friend );
          // TODO: And then make the friend list go away.
        }}
      >
        <View style={style.container}>
          <Text style={style.text}>
            {friend.name}
          </Text>
        </View>
      </TouchableHighlight>
    );
  }
}

const style = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: 'white',
    borderRadius: 4,
    margin: 4,
    padding: 6,
  },
  text: {
    alignSelf: 'center',
    fontSize: 24,
  },
  icon: {
    alignSelf: 'flex-start',
    margin: 4,
    borderWidth: 1,
    borderColor: '#000000',
  }
});

Remove picture and email for now


import React from 'react';
import { Link } from 'react-router';
import classNames from 'classnames';
import Title from 'react-title-component';

import main from '../../assets/css/main.scss';

const primaryBtnClassnames = classNames(main['primary-btn'], main.btn, main.a);

import styles from '../../assets/css/pages/notfound.scss';

const NotFound = () =>

        <Title render={parentTitle => `Page Not Found | ${parentTitle}`}/>

                404

                        Whoops! This page has either relocated or no longer exists (maybe it's just on vacation).

                <Link to="/" className={primaryBtnClassnames}> Back to Safety</Link>

;

export default NotFound;

Fix 404 page font issue


import { fromJS } from 'immutable';
import {
  CHANGE_USER,
} from './constants';

const initialState = fromJS({
  currentUser: 1,
  users: [
    { userId: 1, name: 'Nico Greenarry' },
    { userId: 2, name: 'Elyse Greenarry' },
  ],
});

function signInReducer(state = initialState, action) {
  switch (action.type) {
    case CHANGE_USER:
      return state.set('currentUser', action.userId);
    default:
      return state;
  }
}

export default signInReducer;

import { fromJS } from 'immutable';
import {
  CHANGE_USER,
} from './constants';

const initialState = fromJS({
  currentUser: 1,
  users: [
    { userId: 1, name: 'Nico Greenarry' },
    { userId: 3, name: 'George Weiler' },
    { userId: 2, name: 'Elyse Greenarry' },
  ],
});

function signInReducer(state = initialState, action) {
  switch (action.type) {
    case CHANGE_USER:
      return state.set('currentUser', action.userId);
    default:
      return state;
  }
}

export default signInReducer;

Add George as another "user"


define([
  'ember',
],

function (Em) {
  'use strict';

  var Router = Em.Router.extend({
    location: 'history'
  });

  Router.map(function () {
    this.resource('groups', {path: '/groups'}, function () {
      this.resource('group', {path: ':group_id'}, function () {
        this.route('edit');
      });
      this.resource('items', {path: ':group_id/items'}, function () {
        this.resource('item', {path: ':group_id/item/:item_id'}, function () {
          this.route('edit');
        });
      });
    });
  });

  return Router;
});

define([
  'ember',
],

function (Em) {
  'use strict';

  var Router = Em.Router.extend({
    location: 'history'
  });

  Router.map(function () {
    this.resource('groups', {path: '/groups'}, function () {
      this.resource('group', {path: ':group_id'}, function () {
        this.route('edit');
      });
      this.resource('items', {path: ':group_id/items'}, function () {
        this.resource('item', {path: ':group_id/items/:item_id'}, function () {
          this.route('edit');
        });
      });
    });
  });

  return Router;
});

Change item route URL to be items/:id instead of item/:id


$(document).ready( function () {
  $('#signup').click(function(event){
    event.preventDefault();
    $('#screen_block').show();
    $('#signup_modal').show();
  });

  $('#screen_block').click(function(event){
    event.preventDefault();
    clear_modals();
  });

  $('#login').click(function(event){
    event.preventDefault();
    $('#screen_block').show();
    $('#login_modal').show();
  });

  $('.new_question_button').click(function(event){
    event.preventDefault();
    $('#screen_block').show();
    $('#new_question_modal').show();
  });
  $('form#new_question').submit(function(event){
    event.preventDefault();
    $.ajax({
      url: '/questions',
      type: 'POST',
      data: $('form#new_question').serialize(),
      dataType: 'JSON',
    }).done(function(data){
      $('ol.thumb-grid').load('/ .thumb-grid')
      clear_modals();
    }).fail(function(data){
      console.log(data);
    });
  });
});

function clear_modals(){
  $('#screen_block').hide();
    $('#login_modal').hide();
    $('#signup_modal').hide();
    $('#new_question_modal').hide();
}

$(document).ready( function () {
  $('#signup').click(function(event){
    event.preventDefault();
    $('#screen_block').show();
    $('#signup_modal').show();
  });

  $('#screen_block').click(function(event){
    event.preventDefault();
    clear_modals();
  });

  $('#login').click(function(event){
    event.preventDefault();
    $('#screen_block').show();
    $('#login_modal').show();
  });

  $('.new_question_button').click(function(event){
    event.preventDefault();
    $('#screen_block').show();
    $('#new_question_modal').show();
  });
  $('form#new_question').submit(function(event){
    event.preventDefault();
    $('form#new_question input[type="submit"').prop('disable', true);
    $.ajax({
      url: '/questions',
      type: 'POST',
      data: $('form#new_question').serialize(),
      dataType: 'JSON',
    }).done(function(data){
        $('ol.thumb-grid').load('/ .thumb-grid');
        // clear_modals();
        reset_question_form()
    }).fail(function(data){
        reset_question_form()
    });
  });
});

function reset_question_form(){
  $('form#new_question').trigger('reset');
  $('form#new_question input[type="submit"').prop('disable', false);
}

function clear_modals(){
  $('#screen_block').hide();
    $('#login_modal').hide();
    $('#signup_modal').hide();
    $('#new_question_modal').hide();
}

Disable button on New Question Form After Click


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

import Realm from 'realm';

/**
 * An abstract object which contains metadata describing an indicator row or column.
 *
 * @property  {string}              id
 * @property  {string}              storeId
 * @property  {Period}              period
 * @property  {IndicatorAttribute}  column
 * @property  {IndicatorAttribute}  row
 * @property  {string}              value
 */
export class IndicatorValue extends Realm.Object {
  get indicator() {
    const { indicator: rowIndicator } = this.row;
    const { indicator: columnIndicator } = this.column;
    return rowIndicator || columnIndicator;
  }
}

export default IndicatorValue;

IndicatorValue.schema = {
  name: 'IndicatorValue',
  primaryKey: 'id',
  properties: {
    id: 'string',
    storeId: 'string',
    period: 'Period',
    column: 'IndicatorAttribute',
    row: 'IndicatorAttribute',
    value: 'string',
  },
};

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

import Realm from 'realm';

/**
 * An abstract object which contains metadata describing an indicator row or column.
 *
 * @property  {string}              id
 * @property  {string}              storeId
 * @property  {Period}              period
 * @property  {IndicatorAttribute}  column
 * @property  {IndicatorAttribute}  row
 * @property  {string}              value
 */
export class IndicatorValue extends Realm.Object {
  get indicator() {
    return this.row.indicator ?? this.column.indicator;
  }
}

export default IndicatorValue;

IndicatorValue.schema = {
  name: 'IndicatorValue',
  primaryKey: 'id',
  properties: {
    id: 'string',
    storeId: 'string',
    period: 'Period',
    column: 'IndicatorAttribute',
    row: 'IndicatorAttribute',
    value: 'string',
  },
};

Use null coalescing for indicator value indicator getter


Krang.deepExtend = function(destination, source) {
  for (var property in source) {
    var type = typeof source[property];
    if (type === 'object' && !Object.isElement(source[property])) {
      destination[property] = destination[property] || {};
      arguments.callee(destination[property], source[property]);
    } else {
      destination[property] = source[property];
    }
  }
  return destination;
};

Krang.Mixin = {};

Krang.Mixin.Configurable = {
  setOptions: function(options) {
    this.options = {};
    var constructor = this.constructor;

    if (constructor.superclass) {
      // Build the inheritance chain.
      var chain = [], klass = constructor;
      while (klass = klass.superclass)
        chain.push(klass);
      chain = chain.reverse();

      for (var i = 0, l = chain.length; i < l; i++)
        Krang.deepExtend(this.options, chain[i].DEFAULT_OPTIONS || {});
    }

    Krang.deepExtend(this.options, constructor.DEFAULT_OPTIONS);
    return Krang.deepExtend(this.options, options || {});
  }
};

Krang.deepExtend = function(destination, source) {
  for (var property in source) {
    var type = typeof source[property], deep = true;

    if (type !== 'object' || Object.isElement(source[property]) ||
     (source[property].constructor && source[property].constructor !== Object)) {
       deep = false;
    }

    if (deep) {
      destination[property] = destination[property] || {};
      arguments.callee(destination[property], source[property]);
    } else {
      destination[property] = source[property];
    }
  }
  return destination;
};

Krang.Mixin = {};

Krang.Mixin.Configurable = {
  setOptions: function(options) {
    this.options = {};
    var constructor = this.constructor;

    if (constructor.superclass) {
      // Build the inheritance chain.
      var chain = [], klass = constructor;
      while (klass = klass.superclass)
        chain.push(klass);
      chain = chain.reverse();

      for (var i = 0, l = chain.length; i < l; i++)
        Krang.deepExtend(this.options, chain[i].DEFAULT_OPTIONS || {});
    }

    Krang.deepExtend(this.options, constructor.DEFAULT_OPTIONS);
    return Krang.deepExtend(this.options, options || {});
  }
};

Fix the deepExtend logic to copy class instances by reference.


'use strict';

var Logging = require('./core/logging');

var configs = require('./configs');
var logging = new Logging();

logging.conf(configs);

logging.getLogger().setup(logging);

module.exports = logging;

'use strict';

var Logging = require('./core/logging');

var configs = require('./configs');
var logging = new Logging();

logging.conf(configs);

logging.getLogger('default').setup(logging);

module.exports = logging;

Add name to default logger


'use strict';

module.exports = function(grunt) {
  grunt.config.set('requirejs', {
    prod: {
      options: {
        baseUrl: '.',
        mainConfigFile: 'src/requirejs-config.js',
        insertRequire: ['main'],
        name: 'bower_components/almond/almond',
        out: 'prod/app.js',
        // Because we override the application `baseUrl` to be the root of the
        // projuct during the build (see above), the paths to the `main` script
        // and the `modules` directory has to be modified to include `src/`.
        paths: {
          main: 'src/main',
          modules: 'src/modules',
        },
      },
    },
  });

  grunt.loadNpmTasks('grunt-contrib-requirejs');
};

'use strict';

module.exports = function(grunt) {
  grunt.config.set('requirejs', {
    prod: {
      options: {
        baseUrl: '.',
        mainConfigFile: 'src/requirejs-config.js',
        insertRequire: ['main'],
        name: 'bower_components/almond/almond',
        out: 'prod/app.js',
        optimize: 'uglify2',
        generateSourceMaps: true,
        preserveLicenseComments: false,
        // Because we override the application `baseUrl` to be the root of the
        // projuct during the build (see above), the paths to the `main` script
        // and the `modules` directory has to be modified to include `src/`.
        paths: {
          main: 'src/main',
          modules: 'src/modules',
        },
      },
    },
  });

  grunt.loadNpmTasks('grunt-contrib-requirejs');
};

Compress built JS and generate sourcemaps


/**
 * @class HTMLCollectionHelper
 * Class provides static methods to perform checks and operations on arrays
 */

class HTMLCollectionHelper {
    // Class Methods

    /**
     * Converts a given HTMLCollection to an Array.
     * @param {HTMLCollection} htmlCollection - Collection to be converted.
     * @return {array} - Return an array.
     */
    static convertCollectionToArray(htmlCollection) {
        let array = [...htmlCollection];

        return array;
    }
}

/**
 * @class HTMLCollectionHelper
 * Class provides static methods to perform checks and operations on HTMLCollections
 */

export class HTMLCollectionHelper {
    // Class Methods

    /**
     * Converts a given HTMLCollection to an Array.
     * @param {HTMLCollection} htmlCollection - Collection to be converted.
     * @return {array} - Return an array.
     */
    static convertCollectionToArray(htmlCollection) {
        let array = [...htmlCollection];

        return array;
    }
}

Update Class description, Add export keyword


/* global toastr:false, moment:false, _:false, $:false, session:false */
(function() {
  'use strict';

  angular.module('app.core')
    .constant('lodash', _)
    .constant('jQuery', $)
    .constant('toastr', toastr)
    .constant('moment', moment)
    .constant('userSession', session);
})();

/* global toastr:false, moment:false, _:false, $:false, session:true */
(function() {
  'use strict';

  // Session is set during the bootstrap process (prior to angular running)
  // The variable exists in index.html and will be undefined in tests
  window.session = window.session || {};

  angular.module('app.core')
    .constant('lodash', _)
    .constant('jQuery', $)
    .constant('toastr', toastr)
    .constant('moment', moment)
    .constant('userSession', window.session);
})();

Use or set session to empty object on window


function StretchCell(inner, width, height) {
    this.inner = inner;
    this.width = width;
    this.height = height;
}
StretchCell.prototype.minWidth = function() {
    return Math.max(this.inner.minWidth(), this.width);
}
StretchCell.prototype.minHeight = function() {
    return Math.max(this.inner.minHeight(), this.height);
}
StretchCell.prototype.draw = function() {
    return this.inner.draw();
}

class Group {
    constructor() {
        this.members = [];
    }

    add(elem) {
        if (!this.members.includes(elem)) {
            this.members.push(elem);
        }
    }

    delete(elem) {
        if (this.members.includes(elem)) {
            this.members = this.members.filter(e => e !== elem);
        }
    }

    has(elem) {
        return this.members.includes(elem);
    }

    static from(iterable) {
        let group = new Group();
        for (let elem of iterable) {
            group.add(elem);
        }
        return group;
    }
}

Add Chapter 06, exercise 2


/* eslint-env node */

const { JEST_NOTIFY, ENABLE_JEST_NOTIFICATIONS } = process.env

module.exports = {
  // Provides nice test output of what's being run
  verbose: true,

  // OS notifications of test results in watch mode only
  // 😢 https://github.com/facebook/jest/issues/8036
  notify: Boolean(JEST_NOTIFY) && Boolean(ENABLE_JEST_NOTIFICATIONS),

  // Test coverage can be enforced with a coverageThreshold
  collectCoverage: true,
  coverageReporters: ['text-summary'],
  collectCoverageFrom: ['src/**/*.js'],

  // Pre/Post test framework setup configs
  setupFilesAfterEnv: ['<rootDir>/test/jest-extend.js'],
}

/* eslint-env node */

const { ENABLE_JEST_NOTIFICATIONS } = process.env

module.exports = {
  // Provides nice test output of what's being run
  verbose: true,

  // OS notifications of test results is an opt in for funesies
  notify: Boolean(ENABLE_JEST_NOTIFICATIONS),

  // Test coverage can be enforced with a coverageThreshold
  collectCoverage: true,
  coverageReporters: ['text-summary'],
  collectCoverageFrom: ['src/**/*.js', '!**/*.stories.js'],

  // Pre/Post test framework setup configs
  setupFilesAfterEnv: ['<rootDir>/test/jest-extend.js'],
}

Fix test coverage metrics and enable notifications again 🎉


$(document).ready(() => {
  // alert('Hello World');
  console.log('Check Console');
});

$(document).ready(() => {
	let preview = $('.preview-content')[0];
  // Update text into preview window in realtime
	$('textarea.form-control').keyup(function() {
		preview.innerHTML = this.value;
	});
});

Add keyup() listening to preview input


import {MyClass} from "./modules/demomodule";
import {unique} from 'uniq';
import {Cookie} from "ievv_jsbase/Cookie";

new MyClass();
console.log('unique test:', unique([1, 1, 2]));

import {MyClass} from "./modules/demomodule";
import {unique} from 'uniq';
import Cookies from "ievv_jsbase/Cookies";

new MyClass();
console.log('unique test:', unique([1, 1, 2]));

Update for change is ievv_jsbase.


// @ngInject
export default function invoicesService(baseServiceClass, $http, ENV, $state) {
  let ServiceClass = baseServiceClass.extend({
    init:function() {
      this._super();
      this.endpoint = '/invoices/';
    },
    sendNotification: function(invoice_uuid) {
      let url = ENV.apiEndpoint + 'api' + this.endpoint + invoice_uuid + '/send_notification/';
      return $http.post(url, {link_template: this.getTemplateUrl()});
    },
    getTemplateUrl: function() {
      let path = $state.href('billingDetails', {uuid: 'TEMPLATE'});
      return location.origin + '/' + path.replace('TEMPLATE', '{uuid}');
    }
  });
  return new ServiceClass();
}

// @ngInject
export default function invoicesService(baseServiceClass, $http, ENV) {
  let ServiceClass = baseServiceClass.extend({
    init:function() {
      this._super();
      this.endpoint = '/invoices/';
    },
    sendNotification: function(invoice_uuid) {
      let url = ENV.apiEndpoint + 'api' + this.endpoint + invoice_uuid + '/send_notification/';
      return $http.post(url);
    },
  });
  return new ServiceClass();
}

Move invoice link template from UI to API config


import Ember from 'ember';

export default Ember.Controller.extend({
    notifications: Ember.inject.service(),

    userPostCount: Ember.computed('model.id', function () {
        var promise,
            query = {
                author: this.get('model.slug'),
                status: 'all'
            };

        promise = this.store.query('post', query).then(function (results) {
            return results.meta.pagination.total;
        });

        return Ember.Object.extend(Ember.PromiseProxyMixin, {
            count: Ember.computed.alias('content'),

            inflection: Ember.computed('count', function () {
                return this.get('count') > 1 ? 'posts' : 'post';
            })
        }).create({promise: promise});
    }),

    actions: {
        confirmAccept: function () {
            var self = this,
                user = this.get('model');

            user.destroyRecord().then(function () {
                self.get('notifications').closeAlerts('user.delete');
                self.store.unloadAll('post');
                self.transitionToRoute('team');
            }, function () {
                self.get('notifications').showAlert('The user could not be deleted. Please try again.', {type: 'error', key: 'user.delete.failed'});
            });
        },

        confirmReject: function () {
            return false;
        }
    },

    confirm: {
        accept: {
            text: 'Delete User',
            buttonClass: 'btn btn-red'
        },
        reject: {
            text: 'Cancel',
            buttonClass: 'btn btn-default btn-minor'
        }
    }
});

import Ember from 'ember';

export default Ember.Controller.extend({
    notifications: Ember.inject.service(),

    userPostCount: Ember.computed('model.id', function () {
        var promise,
            query = {
                filter: `author:${this.get('model.slug')}`,
                status: 'all'
            };

        promise = this.store.query('post', query).then(function (results) {
            return results.meta.pagination.total;
        });

        return Ember.Object.extend(Ember.PromiseProxyMixin, {
            count: Ember.computed.alias('content'),

            inflection: Ember.computed('count', function () {
                return this.get('count') > 1 ? 'posts' : 'post';
            })
        }).create({promise: promise});
    }),

    actions: {
        confirmAccept: function () {
            var self = this,
                user = this.get('model');

            user.destroyRecord().then(function () {
                self.get('notifications').closeAlerts('user.delete');
                self.store.unloadAll('post');
                self.transitionToRoute('team');
            }, function () {
                self.get('notifications').showAlert('The user could not be deleted. Please try again.', {type: 'error', key: 'user.delete.failed'});
            });
        },

        confirmReject: function () {
            return false;
        }
    },

    confirm: {
        accept: {
            text: 'Delete User',
            buttonClass: 'btn btn-red'
        },
        reject: {
            text: 'Cancel',
            buttonClass: 'btn btn-default btn-minor'
        }
    }
});

Update admin area queries to use new `filter` parameter


import React from 'react';
import {
  Image,
  StyleSheet,
  Text,
  View,
} from 'react-native';

export default class Welcome extends React.Component {

  render() {
    return (
      <View style={styles.welcomeContainer}>
        <Image
          source={ require('../assets/images/silhouette.jpg') }
          style={styles.welcomeImage}
        />
        <View style={styles.textContainer}>
          <Text style={styles.welcomeText}>Welcome to your</Text>
          <Text style={styles.welcomeText}>place of rest.</Text>

        </View>
      </View>
    );
  }
}

const styles = StyleSheet.create ({
  textContainer: {
    paddingBottom: 50
  },
  welcomeContainer: {
    flex: 1,
    justifyContent: "space-between"
  },
  welcomeImage: {
    height: 300,
    width: 600
  },
  welcomeText: {
    fontFamily: "Futura",
    textAlign: "center",
    fontSize: 24,
    paddingTop: 10
  }
})

import React from 'react';
import {
  Image,
  StyleSheet,
  Text,
  View,
} from 'react-native';

export default class Welcome extends React.Component {

  render() {
    return (
      <View style={styles.welcomeContainer}>
        <Image
          source={ require('../assets/images/silhouette.jpg') }
          style={styles.welcomeImage}
        />
        <View style={styles.textContainer}>
          <Text style={styles.welcomeText}>Welcome to your</Text>
          <Text style={styles.welcomeText}>place of rest.</Text>

        </View>
      </View>
    );
  }
}

const styles = StyleSheet.create ({
  textContainer: {
    paddingBottom: 50
  },
  welcomeContainer: {
    flex: 1,
    justifyContent: "space-between"
  },
  welcomeImage: {
    height: 300,
    width: 600
  },
  welcomeText: {
    // fontFamily: "Futura",
    textAlign: "center",
    fontSize: 24,
    paddingTop: 10
  }
})

Remove Futura from welcome component


"use strict";

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

if (env.lcd.enable) {
	let Lcd;
	if (env.inDevMode) {
		Lcd = require('gpio-peripherals-test').lcd;
	} else {
		Lcd = require('lcd');
	}

	let lcd = new Lcd({
		cols: env.lcd.cols,
		rows: env.lcd.rows,
		rs: env.lcd.rs,
		e: env.lcd.e,
		data: env.lcd.data
	});

	let printLcd = function (data) {
		lcd.clear();
		console.log(typeof data.inside.temperature);
		lcd.print('I: '+
					("     " + data.inside.temperature.toFixed(1)).slice(-5) + '°C  ' +
					("   " + data.inside.humidity).slice(-3) + '%',
			() => {
				lcd.setCursor(0, 1);
				console.log(typeof data.outside.temperature);
				lcd.print('O: '+
					("     " + data.outside.temperature.toFixed(1)).slice(-5) + '°C  ' +
					("   " + data.outside.humidity).slice(-3) + '%');
			}
		);
	};

	const ambientalConditions = require('./ambientalConditions');
	let data = ambientalConditions.get();
	printLcd(data);

	ambientalConditions.evts.on('change', function (data) {
		printLcd(data);
	});
}

"use strict";

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

if (env.lcd.enable) {
	let Lcd;
	if (env.inDevMode) {
		Lcd = require('gpio-peripherals-test').lcd;
	} else {
		Lcd = require('lcd');
	}

	let lcd = new Lcd({
		cols: env.lcd.cols,
		rows: env.lcd.rows,
		rs: env.lcd.rs,
		e: env.lcd.e,
		data: env.lcd.data
	});

	let printLcd = function (data) {
		lcd.clear();
		lcd.setCursor(0, 0);
		lcd.print('I: '+
					("     " + data.inside.temperature.toFixed(1)).slice(-5) + '°C  ' +
					("   " + data.inside.humidity).slice(-3) + '%',
			() => {
				lcd.setCursor(0, 1);
				lcd.print('O: '+
					("     " + data.outside.temperature.toFixed(1)).slice(-5) + '°C  ' +
					("   " + data.outside.humidity).slice(-3) + '%');
			}
		);
	};

	const ambientalConditions = require('./ambientalConditions');
	let data = ambientalConditions.get();
	printLcd(data);

	ambientalConditions.evts.on('change', function (data) {
		printLcd(data);
	});
}

Fix LCD display of the first line.


(function() {
  angular.module('notely.notes', [
    'ui.router'
  ])
  .controller('NotesController', NotesController)
  .config(notesConfig);

  notesConfig['$inject'] = ['$stateProvider'];
  function notesConfig($stateProvider) {
    $stateProvider

      .state('notes', {
        url: '/notes',
        abstract: true,
        templateUrl: '/notes/notes.html',
        controller: NotesController
      })

      .state('notes.form', {
        url: '/{noteId}',
        templateUrl: '/notes/notes-form.html'
      });
  }

  NotesController['$inject'] = ['$scope', '$state', 'notes'];
  function NotesController($scope, $state, notesService) {
    notesService.fetchNotes(function(notes) {
      $scope.notes = notes;
    });
    $state.go('notes.form');
  }
})();

(function() {
  angular.module('notely.notes', [
    'ui.router'
  ])
  .controller('NotesController', NotesController)
  .config(notesConfig);

  notesConfig['$inject'] = ['$stateProvider'];
  function notesConfig($stateProvider) {
    $stateProvider

      .state('notes', {
        url: '/notes',
        abstract: true,
        templateUrl: '/notes/notes.html',
        controller: NotesController
      })

      .state('notes.form', {
        url: '/{noteId}',
        templateUrl: '/notes/notes-form.html'
      });
  }

  NotesController['$inject'] = ['$scope', '$state', 'notes'];
  function NotesController($scope, $state, notesService) {
    notesService.fetchNotes(function(notes) {
      $scope.notes = notes;
    });
  }
})();

Remove unnecessary redirect in NotesController.


module.exports = {
  run(editor, sender, opts = {}) {
    const modal = editor.Modal;
    const am = editor.AssetManager;
    const config = am.getConfig();
    const amContainer = am.getContainer();
    const title = opts.modalTitle || config.modalTitle || '';
    const types = opts.types;
    const accept = opts.accept;

    am.setTarget(opts.target);
    am.onClick(opts.onClick);
    am.onDblClick(opts.onDblClick);
    am.onSelect(opts.onSelect);

    if (!this.rendered || types) {
      let assets = am.getAll();

      if (types) {
        assets = assets.filter(a => types.indexOf(a.get('type')) !== -1);
      }

      am.render(assets);
      this.rendered = 1;
    }

    if (accept) {
      const uploadEl = amContainer.querySelector(
        `input#${config.stylePrefix}uploadFile`
      );
      uploadEl && uploadEl.setAttribute('accept', accept);
    }

    modal.setTitle(title);
    modal.setContent(amContainer);
    modal.open();
  }
};

module.exports = {
  run(editor, sender, opts = {}) {
    const modal = editor.Modal;
    const am = editor.AssetManager;
    const config = am.getConfig();
    const amContainer = am.getContainer();
    const title = opts.modalTitle || config.modalTitle || '';
    const types = opts.types;
    const accept = opts.accept;

    am.setTarget(opts.target);
    am.onClick(opts.onClick);
    am.onDblClick(opts.onDblClick);
    am.onSelect(opts.onSelect);

    if (!this.rendered || types) {
      let assets = am.getAll();

      if (types) {
        assets = assets.filter(a => types.indexOf(a.get('type')) !== -1);
      }

      am.render(assets);
      this.rendered = 1;
    }

    if (accept) {
      const uploadEl = amContainer.querySelector(
        `input#${config.stylePrefix}uploadFile`
      );
      uploadEl && uploadEl.setAttribute('accept', accept);
    }

    modal
      .open({
        title,
        content: amContainer
      })
      .getModel()
      .once('change:open', () => editor.stopCommand(this.id));
    return this;
  },

  stop(editor) {
    editor.Modal.close();
    return this;
  }
};

Make open assets command trackable


(function () {
    'use strict';

    var React = require('react'),

        clearingItem = function () {
            return React.DOM.div(
                {className: 'star-rating-clear-container', key: 's0'},

                React.DOM.a({
                    className: 'star-rating-clear',
                    title: 'Reset value to no stars',
                    href: ''
                }, 'Clear')
            );
        },

        anchorState = function (value, props) {
            if (props.prospectiveValue > 0) {
                return (value <= props.prospectiveValue ? 'suggested' : 'off');
            }

            return (value <= props.currentValue ? 'on' : 'off');
        },

        starItem = function (value, state) {
            return React.DOM.div(
                {className: 'star-rating-star-container'},

                React.DOM.a({
                    className: 'star-rating-star ' + state,
                    title: value,
                    href: '',
                    key: 's' + value
                }, value)
            );
        },

        starItems = function (props) {
            return [1, 2, 3, 4, 5].map(function (value) {
                return starItem(value, anchorState(value, props));
            });
        };

    module.exports = React.createClass({
        render: function () {
            return React.DOM.div(
                {className: 'star-rating-input'},
                [clearingItem()].concat(starItems(this.props))
            );
        }
    });
}());

(function () {
    'use strict';

    var React = require('react'),

        clearingItem = function () {
            return React.DOM.div(
                {className: 'star-rating-clear-container', key: 0},

                React.DOM.a({
                    className: 'star-rating-clear',
                    title: 'Reset value to no stars',
                    href: ''
                }, 'Clear')
            );
        },

        anchorState = function (value, props) {
            if (props.prospectiveValue > 0) {
                return (value <= props.prospectiveValue ? 'suggested' : 'off');
            }

            return (value <= props.currentValue ? 'on' : 'off');
        },

        starItem = function (value, state) {
            return React.DOM.div(
                {className: 'star-rating-star-container', key: value},

                React.DOM.a({
                    className: 'star-rating-star ' + state,
                    title: value,
                    href: ''
                }, value)
            );
        },

        starItems = function (props) {
            return [1, 2, 3, 4, 5].map(function (value) {
                return starItem(value, anchorState(value, props));
            });
        };

    module.exports = React.createClass({
        render: function () {
            return React.DOM.div(
                {className: 'star-rating-input'},
                [clearingItem()].concat(starItems(this.props))
            );
        }
    });
}());

Fix the missing key-s warning


(function () {
  'use strict';

  var $app = angular.module('store', ['ui.router']);
  $app.config(function($stateProvider) {
    $stateProvider
      .state('store', {
        url: '/store',
        templateUrl: 'store/store.html'
      });
  });

}());

(function () {
  'use strict';

  var app = angular.module('store', ['ui.router']);

  app.config(['$stateProvider', function($stateProvider) {
    $stateProvider
      .state('store', {
        url: '/store',
        templateUrl: 'store/store.html'
      });
  }]);

}());

Change app config form to be minification safe.


module.exports = function ExcludePodcastAnnouncements(item) {
    if (item.title.indexOf('#') >= 0) {
        if (item.content.indexOf('This week on') >= 0 && item.content.indexOf('relay.fm') >= 0) {
            return true;
        }
    }

    if (/^Appearance: /.test(item.title)) {
        if (item.content.indexOf('I joined') >= 0) {
            return true;
        }
    }

    if (item.title.indexOf('(Podcast)') === 0) return true;
    if (item.title.indexOf('Podcast: ') === 0) return true;

    return false;
}

module.exports = function ExcludePodcastAnnouncements(item) {
    if (item.title.indexOf('#') >= 0) {
        if (item.content.indexOf('relay.fm') >= 0) {
            return true;
        }
    }

    if (/^Appearance: /.test(item.title)) {
        if (item.content.indexOf('I joined') >= 0) {
            return true;
        }
    }

    if (item.title.indexOf('(Podcast)') === 0) return true;
    if (item.title.indexOf('Podcast: ') === 0) return true;

    return false;
}

Make the relay.fm announcement rule more general


/*global require: false, module: false */
'use strict';
var ga4gh = require('ga4gh-rxjs');
var clinvarMeta = require('./metadataStub');
var Rx = require('rx');
require('rx.experimental');
var _ = require('./underscore_ext');

// {url, dataset, chrom, start, end, fields}
function variants({url, dataset, chrom, start, end}) {
	if (chrom.slice(0,3)==="chr"){
		chrom=chrom.slice(3);
	}
	return ga4gh.all.variants(url, {
		variantSetIds: [dataset],
		start: start,
		end: end,
		referenceName: chrom
	});
}

//
function variantSetsQuery (url) {
	return ga4gh.all.variantSets(url);
}

function metadata(host, dataset) {
	// stub, until the server is working
	var {variantSets} = clinvarMeta;
	//var md = _.find(variantSets, ds => ds.id === dataset).metadata;
	return Rx.Observable.return(variantSets);
}

module.exports = {
	variants: variants,
	variantSetsQuery: variantSetsQuery,
	metadata: metadata
};

/*global require: false, module: false */
'use strict';
var ga4gh = require('ga4gh-rxjs');
var clinvarMeta = require('./metadataStub');
var Rx = require('rx');
require('rx.experimental');
var _ = require('./underscore_ext');

// {url, dataset, chrom, start, end, fields}
function variants({url, dataset, chrom, start, end}) {
	if (chrom.slice(0,3)==="chr"){
		chrom=chrom.slice(3);
	}
	return ga4gh.all.variants(url, {
		variantSetIds: [dataset],
		pageSize: 1000,
		start: start,
		end: end,
		referenceName: chrom
	});
}

//
function variantSetsQuery (url) {
	return ga4gh.all.variantSets(url);
}

function metadata(host, dataset) {
	// stub, until the server is working
	var {variantSets} = clinvarMeta;
	//var md = _.find(variantSets, ds => ds.id === dataset).metadata;
	return Rx.Observable.return(variantSets);
}

module.exports = {
	variants: variants,
	variantSetsQuery: variantSetsQuery,
	metadata: metadata
};

Use larger page size for ga4gh.


import React from 'react';

const Item = ({
  onDeleteClick,
  name,
  year,
  rated,
  runtime,
  genre,
  director,
  actors,
  description,
  country,
  awards,
  poster,
  scoreMetacritic,
  scoreImdb,
  scoreTomato,
  scoreTomatoUser,
  tomatoConsensus }) =>

{name}

{genre}

{year + ' ' + rated + ' ' + runtime}

{scoreTomato}%

{scoreTomatoUser}%

{scoreImdb}

{scoreMetacritic}

{description}

          Starring: {actors}

          Director: {director}

        <button className="item__btn-delete" onClick={onDeleteClick}>X</button>

export default Item;

import React from 'react';

const Item = ({
  onDeleteClick,
  name,
  year,
  rated,
  runtime,
  genre,
  director,
  actors,
  description,
  country,
  awards,
  poster,
  scoreMetacritic,
  scoreImdb,
  scoreTomato,
  scoreTomatoUser,
  tomatoConsensus }) =>

      <img className="item__poster" src={poster} alt={`${name} movie poster`} />

{name}

{genre}

{`${year} ${rated} ${runtime}`}

{scoreTomato}%

{scoreTomatoUser}%

{scoreImdb}

{scoreMetacritic}

{description}

          Starring: {actors}

          Director: {director}

        <button className="item__btn-delete" onClick={onDeleteClick}>X</button>

export default Item;

Add ES6 string interpolation in template


const prefix = '@@redux-simple-auth'

export const AUTHENTICATE = `${prefix}/AUTHENTICATE`
export const AUTHENTICATE_FAILED = `${prefix}/AUTHENTICATE_FAILED`
export const AUTHENTICATE_SUCCEEDED = `${prefix}/AUTHENTICATE_SUCCEEDED`
export const FETCH = `${prefix}/FETCH`
export const INITIALIZE = `${prefix}/INITIALIZE`
export const INVALIDATE_SESSION = `${prefix}/INVALIDATE_SESSION`
export const INVALIDATE_SESSION_FAILED = `${prefix}/INVALIDATE_SESSION_FAILED`
export const UPDATE_SESSION = `${prefix}/UPDATE_SESSION`
export const RESTORE = `${prefix}/RESTORE`
export const RESTORE_FAILED = `${prefix}/RESTORE_FAILED`

const prefix = '@@redux-simple-auth'

export const AUTHENTICATE = `${prefix}/AUTHENTICATE`
export const AUTHENTICATE_FAILED = `${prefix}/AUTHENTICATE_FAILED`
export const AUTHENTICATE_SUCCEEDED = `${prefix}/AUTHENTICATE_SUCCEEDED`
export const FETCH = `${prefix}/FETCH`
export const INITIALIZE = `${prefix}/INITIALIZE`
export const INVALIDATE_SESSION = `${prefix}/INVALIDATE_SESSION`
export const INVALIDATE_SESSION_FAILED = `${prefix}/INVALIDATE_SESSION_FAILED`
export const UPDATE_SESSION = `${prefix}/UPDATE_SESSION`
export const RESET = `${prefix}/RESET`
export const RESTORE = `${prefix}/RESTORE`
export const RESTORE_FAILED = `${prefix}/RESTORE_FAILED`

Add a reset action type


var browserify = require('browserify');
var watchify = require('watchify');
var gulp = require('gulp');
var gutil = require('gulp-util');
var source = require('vinyl-source-stream');
var reactify = require('reactify');

function bundle(b) {
    gutil.log('Retranspiling source code');
    return b.bundle()
           .pipe(source('main.js'))
           .pipe(gulp.dest('./dist'));
}

function act(watch) {
    var b = browserify({
        debug: true,
        cache: {},
        packageCache: {},
        fullPaths: true
    });
    if (watch) {
        b = watchify(b);
    }
    b.transform(function(file) {
        return reactify(file, {
            harmony: true
        });
    });
    if (watch) {
        b.on('update', function(ids) {
            gutil.log('Updated files:');
            gutil.log(ids.join(','));
            bundle(b);
        });
        b.on('log', function(msg) {
            gutil.log(msg);
        });
        b.on('time', function(time) {
            gutil.log('Finished in ' + time + 'ms');
        });
    }
    b.add('./main.js');
    bundle(b);
}

gulp.task('watchify', function() {
    act(true);
});

gulp.task('browserify', function() {
    act(false);
});

var browserify = require('browserify');
var watchify = require('watchify');
var gulp = require('gulp');
var gutil = require('gulp-util');
var source = require('vinyl-source-stream');
var reactify = require('reactify');

function bundle(b) {
    gutil.log('Retranspiling source code');
    return b.bundle()
           .pipe(source('main.js'))
           .pipe(gulp.dest('./dist'));
}

function act(watch) {
    var b = browserify({
        debug: true,
        cache: {},
        packageCache: {},
        fullPaths: true
    });
    if (watch) {
        b = watchify(b, {
            poll: true
        });
    }
    b.transform(function(file) {
        return reactify(file, {
            harmony: true
        });
    });
    if (watch) {
        b.on('update', function(ids) {
            gutil.log('Updated files:');
            gutil.log(ids.join(','));
            bundle(b);
        });
        b.on('log', function(msg) {
            gutil.log(msg);
        });
        b.on('time', function(time) {
            gutil.log('Finished in ' + time + 'ms');
        });
    }
    b.add('./main.js');
    bundle(b);
}

gulp.task('watchify', function() {
    act(true);
});

gulp.task('browserify', function() {
    act(false);
});

Fix watchify by enabling poll option


const path = require('path');
const port = process.env.PORT || 3000;
const screenshotPath = path.resolve(__dirname, '..', 'test', 'acceptance', 'report', 'screenshots');
const reportPath = path.resolve(__dirname, '..', 'test', 'acceptance', 'report');

module.exports = {
  root: path.normalize(`${__dirname}/..`),
  env: process.env.NODE_ENV || 'development',
  ci: process.env.CI,
  port,
  googleAnalyticsId: process.env.GOOGLE_ANALYTICS_TRACKING_ID,
  staticCdn: process.env.STATIC_CDN || '/',
  trustProtoHeader: typeof process.env.DYNO !== undefined,
  trustAzureHeader: typeof process.env.WEBSITE_SITE_NAME !== undefined,
  logLevel: process.env.LOG_LEVEL || 'warn',
  webdriver: {
    baseUrl: process.env.WDIO_BASEURL || `http://localhost:${port}`,
    screenshotPath: process.env.WDIO_SCREENSHOTPATH || screenshotPath,
    reportOutput: process.env.WDIO_REPORTPATH || reportPath,
  },
  browserstack: {
    user: process.env.BROWSERSTACK_USERNAME,
    key: process.env.BROWSERSTACK_ACCESS_KEY,
  },
  travis: {
    buildNum: process.env.TRAVIS_BUILD_NUMBER,
    jobNum: process.env.TRAVIS_JOB_NUMBER,
  },
  feedbackApi: {
    baseUrl: process.env.FEEDBACK_API_BASEURL,
    apiKey: process.env.FEEDBACK_API_KEY,
  },
};

const path = require('path');
const port = process.env.PORT || 3000;
const screenshotPath = path.resolve(__dirname, '..', 'test', 'acceptance', 'report', 'screenshots');
const reportPath = path.resolve(__dirname, '..', 'test', 'acceptance', 'report');

module.exports = {
  root: path.normalize(`${__dirname}/..`),
  env: process.env.NODE_ENV || 'development',
  ci: process.env.CI,
  port,
  googleAnalyticsId: process.env.GOOGLE_ANALYTICS_TRACKING_ID,
  staticCdn: process.env.STATIC_CDN || '/',
  trustProtoHeader: typeof process.env.DYNO !== 'undefined',
  trustAzureHeader: typeof process.env.WEBSITE_SITE_NAME !== 'undefined',
  logLevel: process.env.LOG_LEVEL || 'warn',
  webdriver: {
    baseUrl: process.env.WDIO_BASEURL || `http://localhost:${port}`,
    screenshotPath: process.env.WDIO_SCREENSHOTPATH || screenshotPath,
    reportOutput: process.env.WDIO_REPORTPATH || reportPath,
  },
  browserstack: {
    user: process.env.BROWSERSTACK_USERNAME,
    key: process.env.BROWSERSTACK_ACCESS_KEY,
  },
  travis: {
    buildNum: process.env.TRAVIS_BUILD_NUMBER,
    jobNum: process.env.TRAVIS_JOB_NUMBER,
  },
  feedbackApi: {
    baseUrl: process.env.FEEDBACK_API_BASEURL,
    apiKey: process.env.FEEDBACK_API_KEY,
  },
};

Use correct check for not undefined


import React from 'react'
import Document, { Head, Main, NextScript } from 'next/document'
import { JssProvider, SheetsRegistry } from 'react-jss'

export default class SSRDocument extends Document {
  static getInitialProps ({ renderPage }) {
    const sheets = new SheetsRegistry()
    const page = renderPage((Page) => (props) => (
      <JssProvider registry={sheets}>
        <Page {...props} />
      </JssProvider>
    ))

    return {
      ...page,
      jss: sheets.toString()
    }
  }

  render () {
    return (
      <html>
        <Head>
          <title>üWave</title>
          <style id='ssr' dangerouslySetInnerHTML={{ __html: this.props.jss || '' }} />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </html>
    )
  }
}

import React from 'react'
import Document, { Head, Main, NextScript } from 'next/document'
import { MuiThemeProvider } from 'material-ui/styles'
import { JssProvider, SheetsRegistry } from 'react-jss'

export default class SSRDocument extends Document {
  static getInitialProps ({ renderPage }) {
    const sheets = new SheetsRegistry()
    const page = renderPage((Page) => (props) => (
      <JssProvider registry={sheets}>
        <MuiThemeProvider sheetsManager={new Map()}>
          <Page {...props} />
        </MuiThemeProvider>
      </JssProvider>
    ))

    return {
      ...page,
      jss: sheets.toString()
    }
  }

  render () {
    return (
      <html>
        <Head>
          <title>üWave</title>
          <style id='ssr' dangerouslySetInnerHTML={{ __html: this.props.jss || '' }} />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </html>
    )
  }
}

Fix mui internal sheetsManager being shared between requests.


/*
 * Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
 * http://paperjs.org/
 *
 * Copyright (c) 2011 - 2014, Juerg Lehni & Jonathan Puckey
 * http://scratchdisk.com/ & http://jonathanpuckey.com/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * All rights reserved.
 */

module('TextItem');

test('PointText', function() {
    var text = new PointText({
        fontFamily: 'Arial',
        fontSize: 14,
        point: [100, 100],
        content: 'Hello World!'
    });
    equals(text.fillColor, new Color(0, 0, 0), 'text.fillColor should be black by default');
    equals(text.point, new Point(100, 100), 'text.point');
    equals(text.bounds.point, new Point(100, 87.4), 'text.bounds.point');
    equals(text.bounds.size, new Size(77, 16.8), 'text.bounds.size', { tolerance: 1.0 });
    equals(function() {
        return text.hitTest(text.bounds.center) != null;
    }, true);
});

/*
 * Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
 * http://paperjs.org/
 *
 * Copyright (c) 2011 - 2014, Juerg Lehni & Jonathan Puckey
 * http://scratchdisk.com/ & http://jonathanpuckey.com/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * All rights reserved.
 */

module('TextItem');

test('PointText', function() {
    var text = new PointText({
        fontFamily: 'Helvetica, Arial',
        fontSize: 14,
        point: [100, 100],
        content: 'Hello World!'
    });
    equals(text.fillColor, new Color(0, 0, 0), 'text.fillColor should be black by default');
    equals(text.point, new Point(100, 100), 'text.point');
    equals(text.bounds.point, new Point(100, 87.4), 'text.bounds.point');
    equals(text.bounds.size, new Size(76, 16.8), 'text.bounds.size', { tolerance: 1.0 });
    equals(function() {
        return text.hitTest(text.bounds.center) != null;
    }, true);
});

Fix PointText size test on new Safari...


jQuery(document).ready(function($) {
    var container = $('#splashing_images');
    $.LoadingOverlaySetup({
        color           : "rgba(241,241,241,0.8)",
        maxSize         : "80px",
        minSize         : "20px",
        resizeInterval  : 0,
        size            : "30%"
    });

    $('a.upload').click(function(e){
        var element = $(this);
        var image = element.find('img');
        // If not saving, then proceed
        if(!element.hasClass('saving')){
            element.addClass('saving');
            e.preventDefault();
            var payload = { source : $(this).data('source'), author: $(this).data('author'), credit: $(this).data('credit')};
            payload[window.csrfTokenName] = window.csrfTokenValue;
            $.ajax({
                type: 'POST',
                url: Craft.getActionUrl('unsplash/download/save'),
                dataType: 'JSON',
                data: payload,
                beforeSend: function() {
                    image.LoadingOverlay("show");
                },
                success: function(response) {
                    image.LoadingOverlay("hide");
                    Craft.cp.displayNotice(Craft.t('Image saved!'));

                },
                error: function(xhr, status, error) {
                    Craft.cp.displayError(Craft.t('Oops, something went wrong!'));
                }
            });
        };
    });
});

jQuery(document).ready(function($) {
    var container = $('#splashing_images');
    $.LoadingOverlaySetup({
        color           : "rgba(241,241,241,0.8)",
        maxSize         : "80px",
        minSize         : "20px",
        resizeInterval  : 0,
        size            : "30%"
    });

    $('a.upload').click(function(e){
        var element = $(this);
        var image = element.find('img');
        // If not saving, then proceed
        if(!element.hasClass('saving')){
            element.addClass('saving');
            e.preventDefault();
            var payload = { source : $(this).data('source'), author: $(this).data('author'), credit: $(this).data('credit')};
            payload[window.csrfTokenName] = window.csrfTokenValue;
            $.ajax({
                type: 'POST',
                url: Craft.getActionUrl('unsplash/download/save'),
                dataType: 'JSON',
                data: payload,
                beforeSend: function() {
                    image.LoadingOverlay("show");
                },
                success: function(response) {
                    image.LoadingOverlay("hide");
                    Craft.cp.displayNotice(Craft.t('Image saved!'));

                },
                error: function(xhr, status, error) {
                    image.LoadingOverlay("hide");
                    Craft.cp.displayError(Craft.t('Oops, something went wrong!'));
                }
            });
        };
    });
});

Hide overlay on ajax error


var moment = require('moment');

var _floor = function(val) {
    return Math.floor(val);
};

var Formatters = {
    speed: function(speed) {
        var str;
        speed *= 8;
        if (speed > 1024 * 1024) str = _floor(speed * 10 / (1024 * 1024)) / 10 + ' Mbps';
        else if (speed > 1024) str = _floor(speed * 10 / 1024) / 10 + ' Kbps';
        else str = _floor(speed) + ' bps';
        return str + ' ';
    },

    elapsedTime: function(seconds) {
        return _floor(seconds) + 's';
    },

    remainingTime: function(seconds) {
        return moment.duration(seconds, 'seconds').humanize();
    }
};

module.exports = Formatters;

var moment = require('moment');

var _floor = function(val) {
    return Math.floor(val);
};

var Formatters = {
    speed: function(speed) {
        var str;
        speed *= 8;
        if (speed > 1024 * 1024) str = _floor(speed * 10 / (1024 * 1024)) / 10 + ' Mbps';
        else if (speed > 1024) str = _floor(speed * 10 / 1024) / 10 + ' Kbps';
        else str = _floor(speed) + ' bps';
        return str + '';
    },

    elapsedTime: function(seconds) {
        return _floor(seconds) + 's';
    },

    remainingTime: function(seconds) {
        return moment.duration(seconds, 'seconds').humanize();
    }
};

module.exports = Formatters;

Remove whitespace in speed formatter


Settings = {
  chart: {
    padding: { right: 40, bottom: 100 },
    margins: { top: 30, bottom: 0, right: 35 },
    dots: {
      size: 5,
      color: '#378E00',
      opacity: 0.7
    }
  },
  defaultData: {
    title: 'Scatter Plot',
    byYear: false,
    chosenYear: '',
    x: {
      indicator: 'ipr', // Percentage of individuals using the Internet
      log: false,
      jitter: 0.0
    },
    y: {
      indicator: 'downloadkbps', // Average download speed (kbps)
      log: false,
      jitter: 0.0
    }
  }
};

IMonScatterWidget = function(doc) {
  Widget.call(this, doc);
  _.defaults(this.data, Settings.defaultData);
};

IMonScatterWidget.prototype = Object.create(Widget.prototype);
IMonScatterWidget.prototype.constructor = IMonScatterWidget;

IMonScatter = {
  widget: {
    name: 'Scatterplot',
    description: 'Shows a scatterplot of two chosen metrics from the Internet Monitor dataset across all countries',
    url: 'https://thenetmonitor.org/sources',
    dimensions: { width: 3, height: 2 },
    resize: { mode: 'cover' },
    constructor: IMonScatterWidget,
    typeIcon: 'line-chart'
  },
  org: {
    name: 'Internet Monitor',
    shortName: 'IM',
    url: 'http://thenetmonitor.org'
  }
};

Settings = {
  chart: {
    padding: { right: 40, bottom: 100 },
    margins: { top: 30, bottom: 0, right: 35 },
    dots: {
      size: 5,
      color: '#378E00',
      opacity: 0.7
    }
  },
  defaultData: {
    title: 'Scatterplot',
    byYear: false,
    chosenYear: '',
    x: {
      indicator: 'ipr', // Percentage of individuals using the Internet
      log: false,
      jitter: 0.0
    },
    y: {
      indicator: 'downloadkbps', // Average download speed (kbps)
      log: false,
      jitter: 0.0
    }
  }
};

IMonScatterWidget = function(doc) {
  Widget.call(this, doc);
  _.defaults(this.data, Settings.defaultData);
};

IMonScatterWidget.prototype = Object.create(Widget.prototype);
IMonScatterWidget.prototype.constructor = IMonScatterWidget;

IMonScatter = {
  widget: {
    name: 'Scatterplot',
    description: 'Shows a scatterplot of two chosen metrics from the Internet Monitor dataset across all countries',
    url: 'https://thenetmonitor.org/sources',
    dimensions: { width: 3, height: 2 },
    resize: { mode: 'cover' },
    constructor: IMonScatterWidget,
    typeIcon: 'line-chart'
  },
  org: {
    name: 'Internet Monitor',
    shortName: 'IM',
    url: 'http://thenetmonitor.org'
  }
};

Change Scatter Plot to Scatterplot in default title


import React, { PureComponent } from 'react';

// Styles
import './footer.css';

class Footer extends PureComponent {
  render() {
    return

        v0.0.1

        A project by @angel
        { '' } and @carlosrdrz

  }
}

export default Footer;

import React, { PureComponent } from 'react';

// Styles
import './footer.css';

class Footer extends PureComponent {
  render() {
    return

        v0.0.1

        Created by @angel

  }
}

export default Footer;

Remove Carlos from footer ;(


'use strict';

var path = require('path');
var less = require('../thirdparty/less');

module.exports = fixUrls;
function fixUrls(getURL) {
  less.tree.URL.prototype.genCSS = function (env, output) {
    output.add("url(");

    var basedir = this.currentFileInfo.currentDirectory;
    var add = output.add;
    var buf = [];
    output.add = function (chunk) {
      buf.push(chunk);
    };
    this.value.genCSS(env, output);
    output.add = add;
    if (buf.length === 3 && buf[0] === buf[2] && buf[0] === '\'' || buf[0] === '"') {
      var fragment = buf[1].replace(/^[^#]*/, '');
      buf[1] = getURL(path.resolve(basedir, buf[1].split('#')[0]));
    }
    for (var i = 0; i < buf.length; i++) {
      output.add(buf[i]);
    }

    output.add(")");
  }
}

'use strict';

var path = require('path');
var less = require('../thirdparty/less');

module.exports = fixUrls;
function fixUrls(getURL) {
  less.tree.URL.prototype.genCSS = function (env, output) {
    output.add("url(");

    var basedir = this.currentFileInfo.currentDirectory;
    var add = output.add;
    var buf = [];
    output.add = function (chunk) {
      buf.push(chunk);
    };
    this.value.genCSS(env, output);
    output.add = add;
    if (buf.length === 3 && buf[0] === buf[2] && (buf[0] === '\'' || buf[0] === '"')
        && buf[1].substr(0, 2) !== '//'
        && buf[1].substr(0, 5) !== 'http:'
        && buf[1].substr(0, 6) !== 'https:') {
      var fragment = buf[1].replace(/^[^#]*/, '');
      buf[1] = getURL(path.resolve(basedir, buf[1].split('#')[0]));
    }
    for (var i = 0; i < buf.length; i++) {
      output.add(buf[i]);
    }

    output.add(")");
  }
}

Support urls that are not relative by leaving them un-changed


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

var module = require('tachyons-borders/package.json')
var moduleCss = fs.readFileSync('node_modules/tachyons-borders/tachyons-borders.min.css', 'utf8')
var moduleObj = cssstats(moduleCss)
var moduleSize = filesize(moduleObj.gzipSize)

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

var template = fs.readFileSync('./templates/docs/borders/index.html', 'utf8')
var tpl = _.template(template)
var html = tpl({
  moduleVersion: module.version,
  moduleSize: moduleSize,
  moduleObj: moduleObj,
  srcCSS: srcCSS,
  navDocs: navDocs
})

fs.writeFileSync('./docs/themes/borders/index.html', html)

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

var module = require('tachyons-borders/package.json')
var moduleCss = fs.readFileSync('node_modules/tachyons-borders/tachyons-borders.min.css', 'utf8')
var moduleObj = cssstats(moduleCss)
var moduleSize = filesize(moduleObj.gzipSize)

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

var template = fs.readFileSync('./templates/docs/borders/index.html', 'utf8')
var tpl = _.template(template)
var html = tpl({
  moduleVersion: module.version,
  moduleSize: moduleSize,
  moduleObj: moduleObj,
  srcCSS: srcCSS,
  navDocs: navDocs,
  siteFooter: siteFooter
})

fs.writeFileSync('./docs/themes/borders/index.html', html)

Add site footer to each documentation generator