const express = require('express');
const compression = require('compression');
const path = require('path');

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

const Metascraper = require('metascraper');

// gzip the static resources before seding to browser, if the browser supports gzip compression
// Verification : Observe the response header Content-Encoding: gzip
// app.use(compression());

// Priority serve any static files.
app.use(express.static(path.resolve(__dirname, '../teletobit/build')));

// Answer API requests.
// app.get('/api', function (req, res) {
//   res.send('{"message":"Hello from the custom server!"}');
// });

// Answer API requests.
app.get('/b/:url', function (req, res) {
    // Get meta data from URL
    Metascraper
        .scrapeUrl(req.params.url)
        .then((metadata) => {
            res.set('Content-Type', 'application/json');
            res.send(metadata);
        })
        .catch((error) => {
            console.log("fail");
            res.status(500).send({ error: error })
        })
});

// All remaining requests return the React app, so it can handle routing.
app.get('*', function(request, response) {
  response.sendFile(path.resolve(__dirname, '../teletobit/build', 'index.html'));
});

app.listen(PORT, function () {
  console.log(`Listening on port ${PORT}`);
});

const express = require('express');
const compression = require('compression');
const path = require('path');

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

const Metascraper = require('metascraper');

// gzip the static resources before seding to browser, if the browser supports gzip compression
// Verification : Observe the response header Content-Encoding: gzip
app.use(compression());

// Priority serve any static files.
app.use(express.static(path.resolve(__dirname, '../teletobit/build')));

// Answer API requests.
// app.get('/api', function (req, res) {
//   res.send('{"message":"Hello from the custom server!"}');
// });

// Answer API requests.
app.get('/b/:url', function (req, res) {
    // Get meta data from URL
    Metascraper
        .scrapeUrl(req.params.url)
        .then((metadata) => {
            res.set('Content-Type', 'application/json');
            res.send(metadata);
        })
        .catch((error) => {
            console.log("fail");
            res.status(500).send({ error: error })
        })
});

// All remaining requests return the React app, so it can handle routing.
app.get('*', function(request, response) {
  response.sendFile(path.resolve(__dirname, '../teletobit/build', 'index.html'));
});

app.listen(PORT, function () {
  console.log(`Listening on port ${PORT}`);
});

Use compression on server side


var path = require('path');

exports.config = {
  //directConnect: true,

  seleniumAddress: 'http://localhost:4444/wd/hub',

  // Spec patterns are relative to the location of this config.
  //specs: [
  //  'example_spec.js'
  //],

  capabilities: {
    'browserName': 'chrome',
    'chromeOptions': {
      'args': ['--load-extension=' + path.join(__dirname, '../extension')]
    }
  },

  onPrepare: function() {
    $PREPARE
  }
};

var path = require('path');

exports.config = {
  //directConnect: true,

  seleniumAddress: 'http://localhost:4444/wd/hub',

  // Spec patterns are relative to the location of this config.
  //specs: [
  //  'example_spec.js'
  //],

  capabilities: {
    browserName: 'chrome',
    chromeOptions: {
      args: [
        $CHROME_OPTIONS
      ]
    }
  },

  onPrepare: function() {
    $PREPARE
  }
};

Add variable for chrome options


import React, { PropTypes } from 'react';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
import styles from './Incident.css';
import Map from './Map';
import GridTile from 'material-ui/lib/grid-list/grid-tile';
import Paper from 'material-ui/lib/paper';

const Incident = ({ incident }) => (
  <ReactCSSTransitionGroup
    transitionName={styles}
    transitionEnterTimeout={500}
    transitionLeaveTimeout={300}
    transitionAppear
    transitionAppearTimeout={500}
  >

      <Paper zDepth={2}>
        <GridTile
          key={incident.id}
          title={incident.description}
          subtitle={incident.address}
        >
          <Map address={incident.address} />
        </GridTile>

{"id: " + incident.id}

{"date: " + incident.date}

{"address: " + incident.address}

{"description: " + incident.narrative}

      </Paper>

  </ReactCSSTransitionGroup>
);

Incident.propTypes = {
  incident: PropTypes.object.isRequired
};

export default Incident;

import React, { PropTypes } from 'react';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
import styles from './Incident.css';
import Map from './Map';
import GridTile from 'material-ui/lib/grid-list/grid-tile';
import Paper from 'material-ui/lib/paper';
const moment = require('moment');

const Incident = ({ incident }) => (
  <ReactCSSTransitionGroup
    transitionName={styles}
    transitionEnterTimeout={500}
    transitionLeaveTimeout={300}
    transitionAppear
    transitionAppearTimeout={500}
  >

      <Paper zDepth={2}>
        <GridTile
          key={incident.id}
          title={incident.description}
          subtitle={incident.address}
        >
          <Map address={incident.address} />
        </GridTile>

{"id: " + incident.id}

{"date: " + moment(incident.date).format('llll')}

{"address: " + incident.address}

{"description: " + incident.narrative}

      </Paper>

  </ReactCSSTransitionGroup>
);

Incident.propTypes = {
  incident: PropTypes.object.isRequired
};

export default Incident;

Add human friendly date in component


(function () {
    'use strict';

    angular.module('ngDeferredShow', [])
        .directive('ngDeferredShow', function ($parse, $timeout, $animate) {
            var timeoutBatches = {};

            return {
                restrict: 'A',
                transclude: 'element',
                multiElement: true,
                priority: 600,
                terminal: true,
                link: function (scope, element, attrs, $ctrl, $transclude) {
                    var visible, compiled, timeout;

                    visible = $parse(attrs.ngDeferredShow)(scope);
                    compiled = false;

                    if (visible) {
                        compile();
                    }
                    else {
                        timeout = $parse(attrs.compileTimeout)(scope);

                        batchTimeout(compile, timeout);
                    }

                    scope.$watch(attrs.ngDeferredShow, function (value) {
                        visible = value;

                        if (compiled) {
                            updateVisibility();
                        }
                    });

                    function compile() {
                        $transclude(function (clone) {
                            $animate.enter(clone, element.parent(), element);
                            element = clone;

                            compiled = true;

                            updateVisibility();
                        });
                    }

                    function updateVisibility() {
                        $animate[visible ? 'removeClass' : 'addClass'](element, 'ng-hide');
                    }
                }
            };

            function batchTimeout(callback, timeout) {
                var batch = timeoutBatches[timeout];

                if (!batch) {
                    batch = timeoutBatches[timeout] = [];

                    $timeout(function () {
                        delete timeoutBatches[timeout];

                        angular.forEach(batch, function (callback) {
                            callback();
                        });
                    }, timeout);
                }

                batch.push(callback);
            }
        });
}());

(function () {
    'use strict';

    angular.module('ngDeferredShow', [])
        .directive('ngDeferredShow', function ($parse, $timeout, $animate) {
            var timeoutBatches = {};

            return {
                restrict: 'A',
                transclude: 'element',
                multiElement: true,
                priority: 600,
                terminal: true,
                link: function (scope, element, attrs, $ctrl, $transclude) {
                    var visible, deferVisibleCompile, compiled, timeout;

                    visible = $parse(attrs.ngDeferredShow)(scope);
                    deferVisibleCompile = $parse(attrs.deferVisibleCompile)(scope);

                    compiled = false;

                    if (visible && !deferVisibleCompile) {
                        compile();
                    }
                    else {
                        timeout = $parse(attrs.compileTimeout)(scope);

                        batchTimeout(compile, timeout);
                    }

                    scope.$watch(attrs.ngDeferredShow, function (value) {
                        visible = value;

                        if (compiled) {
                            updateVisibility();
                        }
                    });

                    function compile() {
                        $transclude(function (clone) {
                            $animate.enter(clone, element.parent(), element);
                            element = clone;

                            compiled = true;

                            updateVisibility();
                        });
                    }

                    function updateVisibility() {
                        $animate[visible ? 'removeClass' : 'addClass'](element, 'ng-hide');
                    }
                }
            };

            function batchTimeout(callback, timeout) {
                var batch = timeoutBatches[timeout];

                if (!batch) {
                    batch = timeoutBatches[timeout] = [];

                    $timeout(function () {
                        delete timeoutBatches[timeout];

                        angular.forEach(batch, function (callback) {
                            callback();
                        });
                    }, timeout);
                }

                batch.push(callback);
            }
        });
}());

Add deferVisibleCompile option (default: false)


module.exports = {
  scenarios: [
    {
      name: 'default',
      dependencies: { }
    },
    {
      name: 'ember-release',
      dependencies: {
        'ember': 'components/ember#release'
      },
      resolutions: {
        'ember': 'release'
      }
    },
    {
      name: 'ember-beta',
      dependencies: {
        'ember': 'components/ember#beta'
      },
      resolutions: {
        'ember': 'beta'
      }
    },
    {
      name: 'ember-canary',
      dependencies: {
        'ember': 'components/ember#canary'
      },
      resolutions: {
        'ember': 'canary'
      }
    }
  ]
};

module.exports = {
  scenarios: [
    {
      name: 'default',
      dependencies: { }
    },
    {
      name: 'ember-release',
      dependencies: {
        'ember': 'components/ember#release',
        'ember-data': 'components/ember-data#canary'
      },
      resolutions: {
        'ember': 'release',
        'ember-data': 'canary'
      }
    },
    {
      name: 'ember-beta',
      dependencies: {
        'ember': 'components/ember#beta',
        'ember-data': 'components/ember-data#canary'
      },
      resolutions: {
        'ember': 'beta',
        'ember-data': 'canary'
      }
    },
    {
      name: 'ember-canary',
      dependencies: {
        'ember': 'components/ember#canary',
        'ember-data': 'components/ember-data#canary'

      },
      resolutions: {
        'ember': 'canary',
        'ember-data': 'canary'
      }
    }
  ]
};

Adjust ember try config to make travis pass


'use strict';

var grunt = require('grunt');

exports.bump = {
  default_options: function(test) {
    test.expect(1);

    var actual = grunt.file.readJSON( this.defOpts.fixtures );
    var expected = grunt.file.readJSON( this.defOpts.expected );
    test.equal( actual.version, expected.version, 'should set version to 0.1.0-1' );

    test.done();
  },
  custom_options: function(test) {
    test.expect(1);

    var actual = grunt.file.readJSON( this.custOpts.fixtures );
    var expected = grunt.file.readJSON( this.custOpts.expected );
    test.equal( actual.version, expected.version, 'should set version to 0.2.0' );

    test.done();
  },
};

'use strict';

var grunt = require('grunt');

exports.bump = {
  setUp: function(cb){
    this.defOpts = {
      fixtures: 'test/fixtures/default_options.json',
      expected: 'test/expected/default_options.json'
    },
    this.custOpts = {
      fixtures: 'test/fixtures/custom_options.json',
      expected: 'test/expected/custom_options.json'
    };
    cb();
  },
  default_options: function(test) {
    test.expect(1);

    var actual = grunt.file.readJSON( this.defOpts.fixtures );
    var expected = grunt.file.readJSON( this.defOpts.expected );
    test.equal( actual.version, expected.version, 'should set version to 0.1.0-1' );

    test.done();
  },
  custom_options: function(test) {
    test.expect(1);

    var actual = grunt.file.readJSON( this.custOpts.fixtures );
    var expected = grunt.file.readJSON( this.custOpts.expected );
    test.equal( actual.version, expected.version, 'should set version to 0.2.0' );

    test.done();
  },
};

Fix a missing part of code in unit test


'use strict';

var fs = require('fs');

/**
 * Check the existence of the file
 */
function checkFileExistsSync(filepath){
  let flag = true;

  try {
    fs.accessSync(filepath, fs.F_OK);
  } catch(e){
    flag = false;
  }

  return flag;
};

module.exports = {
 checkFileExistsSync: checkFileExistsSync
};

'use strict';

var fs = require('fs');

/**
 * Check the existence of the file
 */
function checkFileExistsSync(filepath){
  var flag = true;

  try {
    fs.accessSync(filepath, fs.F_OK);
  } catch(e){
    flag = false;
  }

  return flag;
};

module.exports = {
 checkFileExistsSync: checkFileExistsSync
};

Use var rather than let for pre-ES6 cross-compatibility


import React, { PureComponent } from 'react';
import { Label, DownArrow } from 'binary-components';
import DropDown from '../containers/DropDown';
import { actions } from '../_store';
import AssetPickerContainer from './AssetPickerContainer';

type Props = {
    index: number,
    selectedSymbol: string,
    selectedSymbolName: string,
};

export default class AssetPickerDropDown extends PureComponent {

    props: Props;

    static contextTypes = {
        router: () => undefined,
    };

    constructor(props: Props) {
        super(props);
        this.state = {
            dropdownShown: false,
        };
    }

    onClose = () =>
        this.setState({ dropdownShown: false });

    onSelect = newAsset => {
		const { index } = this.props;
		actions.reqSymbolChange(index, newAsset);
	}

    openPicker = () => {
        actions.resetAssetPickerFilter();
        this.setState({ dropdownShown: true });
    }

    render() {
        const { index, selectedSymbol, selectedSymbolName } = this.props;
        const { dropdownShown } = this.state;

        return (

                <DropDown
                    shown={dropdownShown}
                    title="Assets"
                    onClose={this.onClose}
                >
                    <AssetPickerContainer
                        index={index}
                        selectedAsset={selectedSymbol}
                        onSelect={this.onSelect}
                    />
                </DropDown>

{selectedSymbolName}

                    <DownArrow />

        );
    }
}

Fix trade-type-picker click event leak to asset-picker


(function (window, $) {
    $.fn.progressiveDatePicker = function (value, options) {
        return this.each(function () {
            var el = $(this);
            if (!Modernizr.touch || !Modernizr.inputtypes || !Modernizr.inputtypes.date) {
                el.attr('type', 'text');
                el.val(value);
                el.datepicker(options);
            }
        });
    };
})(window, jQuery);

(function (window, $) {
    $.fn.progressiveDatePicker = function (value, options) {
        return this.each(function () {
            var el = $(this);
            if (!Modernizr.touch || !Modernizr.inputtypes || !Modernizr.inputtypes.date) {
                try {
                    el.attr('type', 'text');
                } catch (e) {}
                el.val(value);
                el.datepicker(options);
            }
        });
    };
})(window, jQuery);

Fix IE8 complaining when trying to set type attribute


"use strict";

exports.arrayApply = function (fs) {
  return function (xs) {
    var l = fs.length;
    var k = xs.length;
    var result = Array(l*k);
    var n = 0;
    for (var i = 0; i < l; i++) {
      var f = fs[i];
      for (var j = 0; j < k; j++) {
        result[n++] = f(xs[j]);
      }
    }
    return result;
  };
};

"use strict";

exports.arrayApply = function (fs) {
  return function (xs) {
    var l = fs.length;
    var k = xs.length;
    var result = new Array(l*k);
    var n = 0;
    for (var i = 0; i < l; i++) {
      var f = fs[i];
      for (var j = 0; j < k; j++) {
        result[n++] = f(xs[j]);
      }
    }
    return result;
  };
};

Use new for constructing the array


const esbuild = require("esbuild");

esbuild.buildSync({
  entryPoints: ['./src/platform/current/server/serverStartup.ts'],
  bundle: true,
  outfile: './build/server/js/bundle2.js',
  platform: "node",
  sourcemap: true,
  define: {
    "process.env.NODE_ENV": "\"development\"",
    "webpackIsServer": true,
  },
  external: [
    "akismet-api", "mongodb", "canvas", "express", "mz", "pg", "pg-promise",
    "mathjax-node", "jsdom", "@sentry/node", "node-fetch", "later", "turndown",
    "apollo-server", "apollo-server-express",
  ],
})

esbuild.buildSync({
  entryPoints: ['./src/client.js'],
  bundle: true,
  target: "es6",
  sourcemap: true,
  outfile: "./build/client/js/bundle.js",
  define: {
    "process.env.NODE_ENV": "\"development\"",
    "webpackIsServer": false,
    "global": "window",
  },
});

const esbuild = require("esbuild");

esbuild.buildSync({
  entryPoints: ['./src/platform/current/server/serverStartup.ts'],
  bundle: true,
  outfile: './build/server/js/bundle2.js',
  platform: "node",
  sourcemap: true,
  define: {
    "process.env.NODE_ENV": "\"development\"",
    "webpackIsServer": true,
  },
  external: [
    "akismet-api", "mongodb", "canvas", "express", "mz", "pg", "pg-promise",
    "mathjax-node", "jsdom", "@sentry/node", "node-fetch", "later", "turndown",
    "apollo-server", "apollo-server-express", "graphql",
  ],
})

esbuild.buildSync({
  entryPoints: ['./src/client.js'],
  bundle: true,
  target: "es6",
  sourcemap: true,
  outfile: "./build/client/js/bundle.js",
  define: {
    "process.env.NODE_ENV": "\"development\"",
    "webpackIsServer": false,
    "global": "window",
  },
});

Add graphql to extrenal-libs list for webpack, fixing duplication causing Apollo internal errors


// Load modules

var Crypto = require('crypto');

// Declare internals

var internals = {};

exports = module.exports = function (options) {

    return {
        protocol: 'oauth2',
        auth: 'https://www.linkedin.com/uas/oauth2/authorization',
        token: 'https://www.linkedin.com/uas/oauth2/accessToken',
        scope: ['r_fullprofile', 'r_emailaddress', 'r_contactinfo'],
        scopeSeparator: ',',
        profile: function (credentials, params, get, callback) {

            var query = {
                format: 'json',
                appsecret_proof: Crypto.createHmac('sha256', this.clientSecret).update(credentials.token).digest('hex')
            };

            var fields = '';
            if (this.providerParams.fields) {
                fields = this.providerParams.fields;
            }

            get('https://api.linkedin.com/v1/people/~' + fields, query, function (profile) {

                credentials.profile = {
                    id: profile.id,
                    name: {
                        first: profile.firstName,
                        last: profile.lastName
                    },
                    email: profile.email,
                    headline: profile.headline,
                    raw: profile
                };
                return callback();
            });
        }
    };
};

// Load modules

var Crypto = require('crypto');

// Declare internals

var internals = {};

exports = module.exports = function (options) {

    return {
        protocol: 'oauth2',
        auth: 'https://www.linkedin.com/uas/oauth2/authorization',
        token: 'https://www.linkedin.com/uas/oauth2/accessToken',
        scope: ['r_fullprofile', 'r_emailaddress', 'r_contactinfo'],
        scopeSeparator: ',',
        profile: function (credentials, params, get, callback) {

            var query = {
                format: 'json',
                appsecret_proof: Crypto.createHmac('sha256', this.clientSecret).update(credentials.token).digest('hex')
            };

            var fields = '';
            if (this.providerParams && this.providerParams.fields) {
                fields = this.providerParams.fields;
            }

            get('https://api.linkedin.com/v1/people/~' + fields, query, function (profile) {

                credentials.profile = {
                    id: profile.id,
                    name: {
                        first: profile.firstName,
                        last: profile.lastName
                    },
                    email: profile.email,
                    headline: profile.headline,
                    raw: profile
                };
                return callback();
            });
        }
    };
};

Check if `this.providerParams` is defined


function $init$native$Exception$before(exc) {
  var _caller=arguments.callee.caller.caller;
  exc.stack_trace=[];
  while(_caller) {
    exc.stack_trace.push(_caller);
    _caller = _caller.caller;
  }
}
Exception.$$.prototype.printStackTrace = function() {
  var _c = className(this);
  if (this.message.size > 0) {
    _c += ' "' + this.message + '"';
  }
  print(_c);
  for (var i=0; i<this.stack_trace.length; i++) {
    var f = this.stack_trace[i];
    var mm = f.$$metamodel$$;
    if (typeof(mm)==='function') {
      mm = mm();
      f.$$metamodel$$=mm;
    }
    if (mm) {
      var _src = '';
      if (i==0) {
        if (this.$loc && this.$file) _src = ' (' + this.$file + " " + this.$loc + ')';
      }
      print("    at " + mm.pkg + "::" + mm.d.$nm + _src);
    }
  }
}

function $init$native$Exception$before(exc) {
  var _caller=arguments.callee.caller.caller;
  exc.stack_trace=[];
  while(_caller) {
    exc.stack_trace.push(_caller);
    _caller = _caller.caller;
  }
}
Exception.$$.prototype.printStackTrace = function() {
  var _c = className(this);
  if (this.message.size > 0) {
    _c += ' "' + this.message + '"';
  }
  print(_c);
  for (var i=0; i<this.stack_trace.length; i++) {
    var f = this.stack_trace[i];
    var mm = f.$$metamodel$$;
    if (typeof(mm)==='function') {
      mm = mm();
      f.$$metamodel$$=mm;
    }
    if (mm) {
      var _src = '';
      if (i==0) {
        if (this.$loc && this.$file) _src = ' (' + this.$file + " " + this.$loc + ')';
      }
      print("    at " + mm[0] + "::" + mm.d[mm.d.length-1] + _src);
    }
  }
}

Update stacktrace info with new model path structure


const router = require('express').Router()
const fs = require('fs')
const logger = require('../../config/logger')

const appsRouters = []
const subApps = fs.readdirSync(__dirname)

subApps.forEach(subAppDir => {
  try {
    const subApp = require(`./${subAppDir}`)

    if (subApp.mountpath) {
      appsRouters.push(router.use(subApp.mountpath, subApp.router))
    } else if (subApp.router) {
      appsRouters.push(router.use(subApp.router))
    } else {
      appsRouters.push(() => {}) // add dummy router as fallback
    }
  } catch (err) {
    logger.warn(err.message)
  }
})

module.exports = appsRouters

const router = require('express').Router()
const fs = require('fs')
const logger = require('../../config/logger')

const appsRouters = []
const subApps = fs.readdirSync(__dirname)

subApps.forEach(subAppDir => {
  try {
    const subApp = require(`./${subAppDir}`)

    if (subApp.mountpath) {
      appsRouters.push(router.use(subApp.mountpath, subApp.router))
    } else if (subApp.router) {
      appsRouters.push(router.use(subApp.router))
    } else {
      appsRouters.push((req, res, next) => next())
    }
  } catch (err) {
    logger.warn(err.message)
  }
})

module.exports = appsRouters

Allow empty router to pass to next middleware


!function() {
  'use strict';

  angular.module('crumble', ['ngRoute'])
    .factory('crumble', function ($location, $route, $interpolate) {
      var context;

      var crumble = function (ctx) {
        context = ctx;
        return build($location.path());
      };

      var build = function (path) {
        return !path ? [] : build(crumble.getParent(path)).concat(crumble.getCrumb(path));
      };

      crumble.getParent = function (path) {
        return path.replace(/[^\/]*\/?$/, '');
      };

      crumble.getCrumb = function (path) {
        var route = find($route.routes, function (route) {
          return route.regexp.test(path);
        });
        return {
          path: path,
          label: $interpolate(route.label)(context),
        };
      };

      var find = function (obj, fn, thisArg) {
        for (var key in obj) {
          if (obj.hasOwnProperty(key) && fn.call(thisArg, obj[key], key, obj)) {
            return obj[key];
          }
        }
      };

      return crumble;
    });

}();

!function() {
  'use strict';

  angular.module('crumble', ['ngRoute'])
    .factory('crumble', function ($location, $route, $interpolate) {
      var context;
      var trail = [];
      var crumble = {
        getTrail: function () {
          return trail;
        },
      };

      crumble.setContext = function (ctx) {
        context = ctx;
        angular.copy(build($location.path(), trail));
      };

      var build = function (path) {
        return !path ? [] : build(crumble.getParent(path)).concat(crumble.getCrumb(path));
      };

      crumble.getParent = function (path) {
        return path.replace(/[^\/]*\/?$/, '');
      };

      crumble.getCrumb = function (path) {
        var route = find($route.routes, function (route) {
          return route.regexp.test(path);
        });
        return {
          path: path,
          label: $interpolate(route.label)(context),
        };
      };

      var find = function (obj, fn, thisArg) {
        for (var key in obj) {
          if (obj.hasOwnProperty(key) && fn.call(thisArg, obj[key], key, obj)) {
            return obj[key];
          }
        }
      };

      return crumble;
    });

}();

Drop function only style for being more angular


import {editor} from 'pageflow-scrolled/editor';

import {SidebarRouter} from './SidebarRouter';
import {SidebarController} from './SidebarController';
import {SidebarListView} from './SidebarListView';
import {ExternalLinkCollection} from './models/ExternalLinkCollection';

//register sidebar router to handle multiple sidebar views of this content element
//router defines the URL hash path mapping and controller provides functions for the paths
editor.registerSideBarRouting({
  router: SidebarRouter,
  controller: SidebarController
});

// register external link list content element configuration editor for sidebar
editor.contentElementTypes.register('externalLinkList', {
  configurationEditor({entry}) {
    this.tab('general', function() {
      this.view(SidebarListView, {
        contentElement: this.model.parent,
        collection: ExternalLinkCollection.forContentElement(this.model.parent, entry)
      });
    });
  }
});

// register file handler for thumbnail of external link
editor.registerFileSelectionHandler('contentElement.externalLinks.link', function (options) {
  const contentElement = options.entry.contentElements.get(options.contentElementId);
  const links = ExternalLinkCollection.forContentElement(contentElement, options.entry)

  this.call = function(file) {
    const link = links.get(options.id);
    link.setReference('thumbnail', file);
  };

  this.getReferer = function() {
    return '/scrolled/' + contentElement.id + '/' + options.id;
  };
});

import {editor} from 'pageflow-scrolled/editor';

import {SidebarRouter} from './SidebarRouter';
import {SidebarController} from './SidebarController';
import {SidebarListView} from './SidebarListView';
import {ExternalLinkCollection} from './models/ExternalLinkCollection';

//register sidebar router to handle multiple sidebar views of this content element
//router defines the URL hash path mapping and controller provides functions for the paths
editor.registerSideBarRouting({
  router: SidebarRouter,
  controller: SidebarController
});

// register external link list content element configuration editor for sidebar
editor.contentElementTypes.register('externalLinkList', {
  configurationEditor({entry}) {
    this.tab('general', function() {
      this.view(SidebarListView, {
        contentElement: this.model.parent,
        collection: ExternalLinkCollection.forContentElement(this.model.parent, entry)
      });
    });
  }
});

// register file handler for thumbnail of external link
editor.registerFileSelectionHandler('contentElement.externalLinks.link', function (options) {
  const contentElement = options.entry.contentElements.get(options.contentElementId);
  const links = ExternalLinkCollection.forContentElement(contentElement, options.entry)

  this.call = function(file) {
    const link = links.get(options.id);
    link.setReference('thumbnail', file);
  };

  this.getReferer = function() {
    return '/scrolled/external_links/' + contentElement.id + '/' + options.id;
  };
});

Fix external link file selection handler referer


const path = require('path')
const webpack = require('webpack')

module.exports = {
  debug: true,
  cache: false,
  process: true,
  stats: {
    colors: true
  },
  entry: {
    'scrollto-with-animation': 'src'
  },
  output: {
    path: 'dist',
    filename: '[name].min.js'
  },
  plugins: [
    new webpack.SourceMapDevToolPlugin({
      filename: '[name].js.map'
    }),
    new webpack.optimize.UglifyJsPlugin({
      minimize: true,
      compressor: {
        screw_ie8: true,
        warnings: false
      },
      output: {
        comments: false
      }
    })
  ],
  module: {
    loaders: [{
      test: /\.js?$/,
      exclude: /node_modules/,
      loader: 'babel',
      query: {
        cacheDirectory: true,
        presets: ['es2015', 'stage-2']
      }
    }]
  }
}

const path = require('path')
const webpack = require('webpack')

module.exports = {
  context: __dirname,
  debug: true,
  cache: false,
  process: true,
  stats: {
    colors: true
  },
  entry: {
    'scrollto-with-animation': path.join(__dirname, 'src')
  },
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].min.js'
  },
  plugins: [
    new webpack.SourceMapDevToolPlugin({
      filename: '[name].js.map'
    }),
    new webpack.optimize.UglifyJsPlugin({
      minimize: true,
      compressor: {
        screw_ie8: true,
        warnings: false
      },
      output: {
        comments: false
      }
    })
  ],
  module: {
    loaders: [{
      test: /\.js?$/,
      exclude: /node_modules/,
      loader: 'babel',
      query: {
        cacheDirectory: true,
        presets: ['es2015', 'stage-2']
      }
    }]
  }
}

Use path for fix webpack build


#!/usr/bin/env node
var runCommand = require('run-command'),
    dotenv     = require('dotenv');

runCommand("gulp");

#!/usr/bin/env node
var runCommand = require('run-command');

runCommand("gulp");

Remove outdated dependency in postintall


const userModel = require('../../models/users')
const validator = require('./registerValidation')
const { buildResponse } = require('../../utils/responseService')

module.exports = (req, res) => {
  const {error, value} = validator.validate(req.body)
  if (error) {
    buildResponse(res, 400, { message: error.details[0].message })
  }

  userModel.findOne(value.username)
    .then(user => {
      if (user) {
        buildResponse(res, 403, { message: 'user already exists' })
      } else {
         userModel.createUser(value)
          .then(user => {
            buildResponse(res, 200, { message: 'successfully created user.', user })
          })

          .catch(error => {
            buildResponse(res, 500, { message: 'something happened', error })
          })
      }
    })

}

const userModel = require('../../models/users')
const validator = require('./registerValidation')
const { buildResponse } = require('../../utils/responseService')

module.exports = (req, res) => {
  const {error, value} = validator.validate(req.body)
  if (error) {
    return buildResponse(res, 400, { message: error.details[0].message })
  }

  userModel.findOne(value.username)
    .then(user => {
      if (user) {
        buildResponse(res, 403, { message: 'user already exists' })
      } else {
         userModel.createUser(value)
          .then(user => {
            buildResponse(res, 200, { message: 'successfully created user.', user })
          })

          .catch(error => {
            buildResponse(res, 500, { message: 'something happened', error })
          })
      }
    })

}

Make the registration process not to proceed after validation fails


/* eslint-env node */

const webpack = require('webpack');
const packageJson = require('./package.json');

const isProduction = process.env.NODE_ENV === 'production';

function getLicenseComment(version) {
    return [
        'Likely $version by Ilya Birman (ilyabirman.net)',
        'Rewritten sans jQuery by Evgeny Steblinsky (volter9.github.io)',
        'Supported by Ivan Akulov (iamakulov.com), Viktor Karpov (vitkarpov.com), and contributors',
        'Inspired by Social Likes by Artem Sapegin (sapegin.me)',
    ].join('\n').replace(/\$version/g, version);
}

module.exports = {
    entry: {
        likely: './source/likely.js',
        // [] is a workaround, see https://github.com/webpack/webpack/issues/300
        'likely-commonjs': ['./source/index.js'],
    },
    output: {
        filename: '[name].js',
        library: 'likely',
        libraryTarget: 'umd',
    },
    module: {
        rules: [{
            test: /\.js$/,
            exclude: /node_modules/,
            loader: 'babel-loader',
        }],
    },
    devtool: isProduction ? 'eval' : 'source-map',
    watch: !isProduction,
    plugins: isProduction ? [
        new webpack.BannerPlugin(getLicenseComment(packageJson.version)),
    ] : [],
};

/* eslint-env node */

const webpack = require('webpack');
const packageJson = require('./package.json');

const isProduction = process.env.NODE_ENV === 'production';

function getLicenseComment(version) {
    return [
        'Likely $version by Ilya Birman (ilyabirman.net)',
        'Rewritten sans jQuery by Evgeny Steblinsky (volter9.github.io)',
        'Supported by Ivan Akulov (iamakulov.com), Viktor Karpov (vitkarpov.com), and contributors',
        'Inspired by Social Likes by Artem Sapegin (sapegin.me)',
    ].join('\n').replace(/\$version/g, version);
}

module.exports = {
    entry: {
        likely: './source/likely.js',
        // [] is a workaround, see https://github.com/webpack/webpack/issues/300
        'likely-commonjs': ['./source/index.js'],
    },
    output: {
        filename: '[name].js',
        library: 'likely',
        libraryTarget: 'umd',
    },
    module: {
        rules: [{
            test: /\.js$/,
            exclude: /node_modules/,
            loader: 'babel-loader',
        }],
    },
    devtool: 'source-map',
    watch: !isProduction,
    plugins: isProduction ? [
        new webpack.BannerPlugin(getLicenseComment(packageJson.version)),
    ] : [],
};

Use `devtool: 'source-map'` because 'eval' isn't suited for production


var webpack = require('webpack');
var BundleTracker = require('webpack-bundle-tracker');
var path = require("path");

module.exports = {
  context: __dirname,

  entry: [
    'webpack-dev-server/client?http://0.0.0.0:3000', // WebpackDevServer host and port
    'webpack/hot/only-dev-server',
    './ditto/static/chat/js/base.js',
  ],

  output: {
    path: path.resolve('./ditto/static/bundles/'),
    filename: "[name]-[hash].js",
    publicPath: 'http://localhost:3000/assets/bundles/', // Tell django to use this URL to load packages and not use STATIC_URL + bundle_name
  },

  module: {
    loaders: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        loaders: ["react-hot", "babel-loader?optional[]=es7.comprehensions&optional[]=es7.classProperties&optional[]=es7.objectRestSpread&optional[]=es7.decorators"],
        include: [path.join(__dirname, 'ditto/static')]
      },
      // installing a dev package with npm link breaks hot loader, not sure why. So split it out here to main code above can still be hot loaded
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        loaders: ["babel-loader?optional[]=es7.comprehensions&optional[]=es7.classProperties&optional[]=es7.objectRestSpread&optional[]=es7.decorators"],
        include: [path.join(__dirname, '../lib/redux-rest')]
      }
    ]
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new BundleTracker({filename: './webpack-stats.json'}),
  ],
  resolve: {
    extensions: ['', '.js', '.jsx']
  }
};

var webpack = require('webpack');
var BundleTracker = require('webpack-bundle-tracker');
var path = require("path");

module.exports = {
    context: __dirname,

    entry: [
        'webpack-dev-server/client?http://0.0.0.0:3000', // WebpackDevServer host and port
        'webpack/hot/only-dev-server',
        './ditto/static/chat/js/base.js',
    ],

    output: {
        path: path.resolve('./ditto/static/bundles/'),
        filename: "[name]-[hash].js",
        publicPath: 'http://localhost:3000/assets/bundles/', // Tell django to use this URL to load packages and not use STATIC_URL + bundle_name
    },

    module: {
        loaders: [
            {
                test: /\.jsx?$/,
                exclude: /node_modules/,
                loaders: ['react-hot', "babel-loader?optional[]=es7.comprehensions&optional[]=es7.classProperties&optional[]=es7.objectRestSpread&optional[]=es7.decorators"],
            }
        ]
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin(),
        new BundleTracker({filename: './webpack-stats.json'}),
    ]
};

Revert "Fix webpack to load jsx files and fix hot loading by separating out redux-rest"


Add new colors to the palette


define(
    [
        'ko',
        'Omise_Payment/js/view/payment/omise-offsite-method-renderer',
        'Magento_Checkout/js/view/payment/default',
        'Magento_Checkout/js/model/quote',
    ],
    function (
        ko,
        Base,
        Component,
        quote
    ) {
        'use strict';

        return Component.extend(Base).extend({
            defaults: {
                template: 'Omise_Payment/payment/offsite-grabpay-form'
            },

            isPlaceOrderActionAllowed: ko.observable(quote.billingAddress() != null),

            code: 'omise_offsite_grabpay',
            restrictedToCurrencies: ['sgd', 'myr']
        });
    }
);

define(
    [
        'ko',
        'Omise_Payment/js/view/payment/omise-offsite-method-renderer',
        'Magento_Checkout/js/view/payment/default',
        'Magento_Checkout/js/model/quote',
    ],
    function (
        ko,
        Base,
        Component,
        quote
    ) {
        'use strict';

        return Component.extend(Base).extend({
            defaults: {
                template: 'Omise_Payment/payment/offsite-grabpay-form'
            },

            isPlaceOrderActionAllowed: ko.observable(quote.billingAddress() != null),

            code: 'omise_offsite_grabpay',
            restrictedToCurrencies: ['thb', 'sgd', 'myr']
        });
    }
);

Allow GrabPay to have payment in THB


function IO(){
	var secure = window.location.protocol.match(/https/);
	var socket = this.socket = new io.Socket(window.location.hostname, {
			secure: secure,
			port: secure ? '' : window.location.port
			,reconnectionDelay: 2000 + Math.random() * 2000
		});

	var messageHandlers = [];

	this.processMessage = function(data){
		var stop;
		messageHandlers.forEach(function(handler){
			if(!stop) stop = handler.newMessage(data);
		});
	}

	this.addMessageHandler = function(handler){
		messageHandlers.push(handler);
	}

	this.connect = function(){
		socket.connect();
		socket.send({channel: channel, action: 'join', connector_id: connector_id});
	}

	socket.on('message', this.processMessage);
}

function IO(){
	var secure = window.location.protocol.match(/https/);
	var socket = this.socket = new io.Socket(window.location.hostname, {
			secure: secure,
			reconnectionDelay: 2000 + Math.random() * 2000
		});

	var messageHandlers = [];

	this.processMessage = function(data){
		var stop;
		messageHandlers.forEach(function(handler){
			if(!stop) stop = handler.newMessage(data);
		});
	}

	this.addMessageHandler = function(handler){
		messageHandlers.push(handler);
	}

	this.connect = function(){
		socket.connect();
		socket.send({channel: channel, action: 'join', connector_id: connector_id});
	}

	socket.on('message', this.processMessage);
}

Remove "port" setting for Socket.IO config It was messing up the websocket url in Firefox


Template.registerHelper("layoutHidden", function (type) {
  return (!!Session.get('fullApi')) ^ (type === 'full') ? 'hidden' : '';
});

Template.basicOrFullSelect.events({
  "change .basic-or-full": function (event) {
    // XXX might not work in IE9?
    navigate("#/" + event.target.value + "/");
  }
});

Template.basicOrFullSelect.helpers({
  isBasic: function () {
    return ! Session.get("fullApi");
  },
  isFull: function () {
    return Session.get("fullApi");
  }
});

Template.sidebar.helpers({
  topLink: function () {
    var docsType = Session.get("fullApi") ? "full" : "basic";
    return "#/" + docsType + "/";
  }
});

Template.registerHelper("layoutHidden", function (type) {
  return (!!Session.get('fullApi')) ^ (type === 'full') ? 'hidden' : '';
});

Template.basicOrFullSelect.events({
  "change .basic-or-full": function (event) {
    // XXX might not work in IE9?
    window.location.hash = "#/" + event.target.value + "/";
  }
});

Template.basicOrFullSelect.helpers({
  isBasic: function () {
    return ! Session.get("fullApi");
  },
  isFull: function () {
    return Session.get("fullApi");
  }
});

Template.sidebar.helpers({
  topLink: function () {
    var docsType = Session.get("fullApi") ? "full" : "basic";
    return "#/" + docsType + "/";
  }
});

Make back button work when switching from basic to full via dropdown


const path = require('path');

const BUILD_DIR = 'dist';

module.exports = {
  globDirectory: BUILD_DIR,
  globPatterns: ['/', '**/*.{js,css,woff2,webmanifest}'],
  swSrc: path.join(BUILD_DIR, 'service-worker.js'),
  swDest: path.join(BUILD_DIR, 'service-worker.js'),
  mode: 'production',
};

const path = require('path');

const BUILD_DIR = 'dist';

module.exports = {
  globDirectory: BUILD_DIR,
  globPatterns: ['*', '**/*'],
  swSrc: path.join(BUILD_DIR, 'service-worker.js'),
  swDest: path.join(BUILD_DIR, 'service-worker.js'),
  mode: 'production',
};

Put everything in the precache?


import { createAction } from 'redux-actions';
import { createThunkAction } from 'utils/redux';

const fetchCustomCompareAccordionInit = createAction(
  'fetchCustomCompareAccordionInit'
);
const fetchCustomCompareAccordionReady = createAction(
  'fetchCustomCompareAccordionReady'
);
const fetchCustomCompareAccordionFailed = createAction(
  'fetchCustomCompareAccordionFailed'
);
const fetchCustomCompareAccordion = createThunkAction(
  'fetchCustomCompareAccordion',
  params => dispatch => {
    const { locationsDocuments, category } = params;
    if (locationsDocuments) {
      dispatch(fetchCustomCompareAccordionInit());
      fetch(
        `/api/v1/ndcs?locations_documents=${locationsDocuments}&category=${category}&filter=overview&source[]=CAIT&source[]=WB&source[]=NDC%20Explorer`
      )
        .then(response => {
          if (response.ok) return response.json();
          throw Error(response.statusText);
        })
        .then(data => dispatch(fetchCustomCompareAccordionReady(data)))
        .catch(error => {
          dispatch(fetchCustomCompareAccordionFailed());
          console.info(error);
        });
    }
  }
);

export default {
  fetchCustomCompareAccordion,
  fetchCustomCompareAccordionInit,
  fetchCustomCompareAccordionReady,
  fetchCustomCompareAccordionFailed
};

import { createAction } from 'redux-actions';
import { createThunkAction } from 'utils/redux';

const fetchCustomCompareAccordionInit = createAction(
  'fetchCustomCompareAccordionInit'
);
const fetchCustomCompareAccordionReady = createAction(
  'fetchCustomCompareAccordionReady'
);
const fetchCustomCompareAccordionFailed = createAction(
  'fetchCustomCompareAccordionFailed'
);
const fetchCustomCompareAccordion = createThunkAction(
  'fetchCustomCompareAccordion',
  params => dispatch => {
    const { locationsDocuments, category } = params;
    if (locationsDocuments) {
      dispatch(fetchCustomCompareAccordionInit());
      fetch(
        `/api/v1/ndcs?locations_documents=${locationsDocuments}&category=${category}&filter=overview`
      )
        .then(response => {
          if (response.ok) return response.json();
          throw Error(response.statusText);
        })
        .then(data => dispatch(fetchCustomCompareAccordionReady(data)))
        .catch(error => {
          dispatch(fetchCustomCompareAccordionFailed());
          console.info(error);
        });
    }
  }
);

export default {
  fetchCustomCompareAccordion,
  fetchCustomCompareAccordionInit,
  fetchCustomCompareAccordionReady,
  fetchCustomCompareAccordionFailed
};

Remove source from custom compare provider


I.grade_list_table = (function () {

  function showMore(grade) {
    var table = $(".grade-table-"+grade);
    table.find("tr.show_more").hide();
    table.find("tr.hidden_object").show();
  }

  return {
    showMore: showMore
  };
}());

jQuery(function($) {
  $("a[data-show-more]").click(function(evt) {
    evt.preventDefault();
    var grade = $(this).data('show-more');
    I.grade_list_table.showMore(grade);
    return false;
  });

  $("[data-toggle=tooltip]").tooltip({delay: { show: 300, hide: 100 }});
});

I.grade_list_table = (function () {

  function showMore(grade) {
    var table = $(".grade-table-"+grade);
    table.find("tr.show_more").hide();
    table.find("tr.hidden_object").show();
  }

  return {
    showMore: showMore
  };
}());

jQuery(function($) {
  $("body").on('click', 'a[data-show-more]', function(evt) {
    evt.preventDefault();
    var grade = $(this).data('show-more');
    I.grade_list_table.showMore(grade);
    return false;
  });

  $("[data-toggle=tooltip]").tooltip({delay: { show: 300, hide: 100 }});
});

Fix show-more links not working after rebuild


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

/**
 * External dependencies
 */
import { percySnapshot } from '@percy/puppeteer';

/**
 * WordPress dependencies
 */
import { visitAdminPage } from '@wordpress/e2e-test-utils';

describe('Get Started Story', () => {
  it('should prefill post title and post content', async () => {
    await visitAdminPage(
      'post-new.php',
      'post_type=web-story&web-stories-demo=1'
    );
    await expect(page).toMatch('Tips to make the most of Web Stories');

    await percySnapshot(page, 'Get Started Story');
  });
});

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

/**
 * External dependencies
 */
import { percySnapshot } from '@percy/puppeteer';

/**
 * WordPress dependencies
 */
import {
  loginUser,
  switchUserToAdmin,
  visitAdminPage,
} from '@wordpress/e2e-test-utils';

describe('Get Started Story', () => {
  describe('Admin User', () => {
    it('should prefill post title and post content', async () => {
      await visitAdminPage(
        'post-new.php',
        'post_type=web-story&web-stories-demo=1'
      );
      await expect(page).toMatch('Tips to make the most of Web Stories');

      await percySnapshot(page, 'Get Started Story (Admin)');
    });
  });

  describe('Author User', () => {
    beforeAll(async () => {
      await loginUser('author', 'password');
    });

    afterAll(async () => {
      await switchUserToAdmin();
    });

    it('should prefill post title and post content', async () => {
      await visitAdminPage(
        'post-new.php',
        'post_type=web-story&web-stories-demo=1'
      );
      await expect(page).toMatch('Tips to make the most of Web Stories');

      await percySnapshot(page, 'Get Started Story (Author)');
    });
  });
});

Add e2e test for author user role


module.exports = {
  plugins: {
    'autoprefixer': {},
  }
}

module.exports = {
  plugins: {
    autoprefixer: {
      browsers: [
        '>1%',
        'last 4 versions',
        'Firefox ESR',
        'not ie < 9', // React doesn't support IE8 anyway
      ]
    },
  }
}

Add diff browser support for autoprefixer


class Randomcase {
	/**
	 * A function that transforms a normal string into Randomcase
	 *
	 * @static
	 * @param {String} input
	 * @memberof Randomcase
	 */
	static transform(input) {
		return input
			.split("")
			.map(char =>
				Math.random() > 0.5 ? char.toUpperCase() : char.toLowerCase()
			)
			.reduce((acc, curr) => acc += curr, "");
	}
}

/**
 * Program definition - main
 */

const input = process.argv[2];

if(!input) {
	console.error('Input has to be provided! Use "--help" for help');
} else {
	processInput(input);
}

function processInput(input) {
	switch(input) {
		case "--help":
		case "-h":
			console.log("Usage: node index.js \"<string to randomcase>\"");
			break;
		default:
			console.log(Randomcase.transform(input));
			break;
	}
}

class Randomcase {
	/**
	 * A function that transforms a normal string into Randomcase
	 *
	 * @static
	 * @param {String} input
	 * @memberof Randomcase
	 */
	static transform(input) {
		return input
			.split("")
			.map(char =>
				Math.random() > 0.5 ? char.toUpperCase() : char.toLowerCase()
			)
			.join("");
	}
}

/**
 * Program definition - main
 */

const input = process.argv[2];

if (!input) {
	console.error('Input has to be provided! Use "--help" for help');
} else {
	processInput(input);
}

function processInput(input) {
	switch (input) {
		case "--help":
		case "-h":
			console.log("Usage: node index.js \"<string to randomcase>\"");
			break;
		default:
			console.log(Randomcase.transform(input));
			break;
	}
}

Use join instead of reduce


// Initialize an OpenTok Session object
var session = OT.initSession(apiKey, sessionId);

// Initialize a Publisher, and place it into the element with id="publisher"
var publisher = OT.initPublisher('publisher');

// Attach an event handler for when the session dispatches the 'streamCreated' event.
session.on('streamCreated', function(event) {
    // This function runs when another client publishes a stream (eg. session.publish())

    // Subscribe to the stream that caused this event, put it inside the DOM element with id='subscribers'
    session.subscribe(event.stream, 'subscribers', {
        insertMode: 'append'
    }, function(error) {
        if (error) {
            console.error('Failed to subscribe', error);
        }
    });
});

// Connect to the Session using the 'apiKey' of the application and a 'token' for permission
session.connect(token, function(error) {
    // This function runs when session.connect() asynchronously completes

    // Handle connection errors
    if (error) {
        console.error('Failed to connect', error);
    } else {
        // Publish the publisher we initialzed earlier (this will trigger 'streamCreated' on other
        // clients)
        session.publish(publisher, function(error) {
            if (error) {
                console.error('Failed to publish', error);
            }
        });
    }
});

Update JS style: use 2 spaces, not 4


module.exports = {
  'notifications' : {
    enabled: false, //if disabled, no notifications will be sent
    to: ['ivan@iloire.com'], //default notification list if no alert_to is specified for host or url
    postmark : {
      from: 'ivan@iloire.com',
      api_key : 'your-postmark-key-here'
    }
  }
};

module.exports = {
  'notifications' : {
    enabled: false, //if disabled, no notifications will be sent
    to: ['your-email@domain.com'], //default notification list if no alert_to is specified for host or url
    postmark : {
      from: 'your-email@domain.com',
      api_key : 'your-postmark-key-here'
    }
  }
};

Use dummy email for config sample file


'use strict';

var config = {
	name: '<%= name %>',
	slug: '<%= slug %>',
	type: '<%= type %>',
	kind: '<%= kind %>',
	deprecated: false,
	scripts:{ <% if(scripts.indexOf('input')!==-1){ %>
		input: require('./input'), <% } %><% if(scripts.indexOf('output')!==-1){%>
		output: require('./output'), <% } %>
		run: require('./run')
	},
	fields:{
		input: [],
		output: []
	}
};

module.exports = function(service){
	service.registerMethod(config);
};

'use strict';

var config = {
	name: '<%= name %>',
	slug: '<%= slug %>',
	type: '<%= type %>',
	kind: '<%= kind %>',
	scripts:{ <% if(scripts.indexOf('input')!==-1){ %>
		input: require('./input'), <% } %><% if(scripts.indexOf('output')!==-1){%>
		output: require('./output'), <% } %>
		run: require('./run')
	},
	fields:{
		input: [],
		output: []
	}
};

module.exports = function(service){
	service.registerMethod(config);
};

Remove deprecated from method default config


'use strict';

export default class AnalyzerConfigFormController {
  constructor($log) {
    'ngInject';

    this.$log = $log;

    this.rateUnits = ['Day', 'Month'];
    this.formData = {};
  }

  $onInit() {
    this.$log.log(
      'onInit of AnalyzerConfigFormController',
      this.definition,
      this.analyzer
    );

    const { name, configuration } = this.analyzer;

    this.formData = {
      name,
      configuration
    };
  }

  typeOf(config) {
    return `${config.multi ? 'multi-' : ''}${config.tpe}`;
  }

  addOption(config) {
    let defaultValues = {
      string: null,
      number: 0,
      boolean: true
    };
    this.analyzer.configuration[config.name].push(defaultValues[config.tpe]);
  }
}

'use strict';

export default class AnalyzerConfigFormController {
  constructor($log) {
    'ngInject';

    this.$log = $log;

    this.rateUnits = ['Day', 'Month'];
    this.formData = {};
  }

  $onInit() {
    this.$log.log(
      'onInit of AnalyzerConfigFormController',
      this.definition,
      this.analyzer
    );

    const { name, configuration } = this.analyzer;

    this.formData = {
      name,
      configuration
    };
  }

  typeOf(config) {
    return `${config.multi ? 'multi-' : ''}${config.type}`;
  }

  addOption(config) {
    let defaultValues = {
      string: null,
      number: 0,
      boolean: true
    };
    this.analyzer.configuration[config.name].push(defaultValues[config.type]);
  }
}

Update analyzer definition access to type field


import Vue from "vue";
import VueResource from "vue-resource";
import {store} from "./store/store";

import Photos from "./components/Photos.vue";
import Theater from "./components/Theater.vue";

Vue.use(VueResource);
Vue.config.debug = true;

const carousel = new Vue({
    el: "#your-application-id",

    store,

    components: {
        Photos,
        Theater
    },

    mounted() {
        // Debug purpose
        if (Vue.config.debug) {
            console.log("Debug is on");
        }
    }
});

import Vue from "vue";
import {store} from "./store/store";
import Photos from "./components/Photos.vue";
import Theater from "./components/Theater.vue";

const carousel = new Vue({
    el: "#your-application-id",

    store,

    components: {
        Photos,
        Theater
    },
});

Remove debug code from the main entry file


module.exports = function(grunt) {
    return {
        dist: {
            options: {
                style: 'nested',
                sourcemap: 'none'
            },
            files: [{
                expand: true,
                cwd: 'src/style',
                src: ['*.scss'],
                dest: 'dist',
                ext: '.css'
            }]
        }
    };
};

module.exports = function(grunt) {
    return {
        dist: {
            options: {
                style: 'nested'
            },
            files: [{
                expand: true,
                cwd: 'src/style',
                src: ['*.scss'],
                dest: 'dist',
                ext: '.css'
            }]
        }
    };
};

Revert "Don't generate Sass sourcemaps"


var restify = require('express-restify-mongoose');
var Artist = require('../api/models/artist');
var Info = require('../api/models/info');
var News = require('../api/models/news');
var Event = require('../api/models/event');
var Location = require('../api/models/location');
var Festival = require('../api/models/festival');
var Localise = require('../lib/localise');

module.exports = function(app, apiVersion) {
  restify.defaults({
    outputFn: Localise.localiseApiCallResult,
    version: apiVersion,
    private: ['__v']
  });

  restify.serve(app, Artist);
  restify.serve(app, Info, {
    plural: false,
    contextFilter: function(model, req, cb) {
      // Sort by creation timestamp (included in Mongo's _id field)
      cb(model.find().sort('_id'));
    }
  });
  restify.serve(app, News);
  restify.serve(app, Event);
  restify.serve(app, Location);
  restify.serve(app, Festival, { plural: false });
};

var restify = require('express-restify-mongoose');
var Artist = require('../api/models/artist');
var Info = require('../api/models/info');
var News = require('../api/models/news');
var Event = require('../api/models/event');
var Location = require('../api/models/location');
var Festival = require('../api/models/festival');
var Localise = require('../lib/localise');

function shiftTimes(item) {
  onlyAttributes = ['start_time', 'end_time'];

  Object.keys(item)
    .filter(function(key) {
      return onlyAttributes.indexOf(key) > -1;
    })
    .forEach(function(key) {
      // add one hour for time attributes
      var shiftedTimestamp =
        new Date(item[key]).getTime() +
        60 * 60 * 1000/*ms*/;
      item[key] = new Date(shiftedTimestamp).toString();
    });
}

function transformItem(item) {
  shiftTimes(item);
}

function transformResult(result) {
  if (result instanceof Array) {
    result.forEach(function(item) {
      transformItem(item);
    });
  } else {
    transformItem(result);
  }
  return result;
}

module.exports = function(app, apiVersion) {
  restify.defaults({
    outputFn: Localise.localiseApiCallResult,
    version: apiVersion,
    private: ['__v']
  });

  restify.serve(app, Artist);
  restify.serve(app, Info, {
    plural: false,
    contextFilter: function(model, req, cb) {
      // Sort by creation timestamp (included in Mongo's _id field)
      cb(model.find().sort('_id'));
    }
  });
  restify.serve(app, News);
  restify.serve(app, Event, {
    outputFn: function(req, res) {
      res
        .status(req.erm.statusCode)
        .json(
          transformResult(req.erm.result)
        );
    }
  });
  restify.serve(app, Location);
  restify.serve(app, Festival, { plural: false });
};

Add 1 hour to datetimes for start_time and end_time in the events API result


import { combineReducers } from 'redux';
import assets from './assets';
import packages from './packages';

export default () => combineReducers({
  packages,
  assets,
});

/* eslint-disable no-console */
import { isDev } from 'worona-deps';
import { combineReducers } from 'redux';
import assets from './assets';
import packages from './packages';

const log = (state, action) => {
  if (isDev && !action.type.startsWith('@@redux')) console.log(action); return false;
};

export default () => combineReducers({
  packages,
  assets,
  log,
});

Add log in console (devtools don't work well)


var should = require("should")
var request = require('supertest')

var url = "http://localhost:3000"
var request = request(url);

describe('Stories', function(){

  describe('get /stories', function(){

  	it('gets success and returns json', function(done){
	  	request.get('/stories')
	  		.set('Accept', 'application/json')
	  		.expect('Content-Type', /json/)
	  		.expect(200, done);
  	})
  })

})

var should = require("should")
var request = require('supertest')

var url = "http://localhost:3000"
var request = request(url);

describe('Stories', function(){

  describe('POST /stories', function(){

  	it('gets success and returns json', function(done){
  		var story = {story: {title: "Supernatural Winnipeg", contribution_limit: 12, lat:-41.270833, lng: 173.283889}}
	  	request.post('/stories')
	  		.set('Accept', 'application/json')
	  		.send(story)
	  		.expect('Content-Type', /json/)
	  		.end(function(err, res){
	  			console.log(res.body.story.title);
				(res.body.story.title).should.be.eql(story.story.title)
	  			done()
	  		});
  	})
  })

  describe('GET /stories', function(){

  	it('gets success and returns json', function(done){
	  	request.get('/stories')
	  		.set('Accept', 'application/json')
	  		.expect('Content-Type', /json/)
	  		.expect(200, done);
  	})
  })

})

Add test for creating story


// helper functions

var http = require("http");

exports.urlForLibrary = function (library, file, version) {
  return "//cdnjs.cloudflare.com/ajax/libs/" + library + "/" + version + "/" + file;
};

exports.libraryUrls = function (callback) {
  http.get("http://cdnjs.com/packages.json", function (res) {
    var data = "";
    var chunkNumber = 1;

    res.on("data", function (chunk) {
      data += chunk;
      console.log("chunk " + chunkNumber++);
    });

    res.on("end", function () {
      console.log("done");
      var libraries = JSON.parse(data).packages;
      var result = libraries.map(function (data) {
        return exports.urlForLibrary(data.name, data.filename, data.version);
      });
      callback(result);
    });
  });
};

// helper functions

var http = require("http");

exports.urlForLibrary = function (library, file, version) {
  return "//cdnjs.cloudflare.com/ajax/libs/" + library + "/" + version + "/" + file;
};

exports.libraryUrls = function (callback) {
  http.get("http://cdnjs.com/packages.json", function (res) {
    console.log("Downloading the cdnjs package list... (this might take a minute)");
    var data = "";

    res.on("data", function (chunk) {
      data += chunk;
    });

    res.on("end", function () {
      console.log("Done!");
      var libraries = JSON.parse(data).packages;
      var result = libraries.map(function (data) {
        return exports.urlForLibrary(data.name, data.filename, data.version);
      });
      callback(result);
    });
  });
};

Make the console output for downloading the package list more concise


PinPoint.NotePresenter = function(note) {
  this.note = note;
  this.rootNodeType = "tr";
  this.childNodeType = "td";
}

PinPoint.NotePresenter.prototype = {
  present: function() {
    var noteNode = document.createElement(this.rootNodeType),
    timeNode = document.createElement(this.childNodeType),
    contentNode = document.createElement(this.childNodeType);

    noteNode.className = "note";

    timeNode.className = 'note_time';
    timeNode.textContent = this.note.noteTime;

    contentNode.className = 'note_content';
    contentNode.textContent = this.note.noteContent;

    noteNode.appendChild(timeNode);
    noteNode.appendChild(contentNode);

    return noteNode;
  }
}

Create notes presenter to format new notes into nodes for table


import React from 'react';
import Route from 'react-router-dom/Route';
import { connectFor } from '@folio/stripes-connect';
import { modules } from 'stripes-loader'; // eslint-disable-line
import AddContext from './AddContext';

if (!Array.isArray(modules.app) && modules.length < 0) {
  throw new Error('At least one module of type "app" must be enabled.');
}

function getModuleRoutes(stripes) {
  return modules.app.map((module) => {
    const name = module.module.replace(/^@folio\//, '');
    const perm = `module.${name}.enabled`;
    if (!stripes.hasPerm(perm)) return null;

    const connect = connectFor(module.module, stripes.epics, stripes.logger);
    const Current = connect(module.getModule());
    const moduleStripes = Object.assign({}, stripes, { connect });

    return (
      <Route
        path={module.route}
        key={module.route}
        render={props =>
          <AddContext context={{ stripes: moduleStripes }}>
            <Current {...props} connect={connect} stripes={moduleStripes} />
          </AddContext>
        }
      />
    );
  }).filter(x => x);
}

export default getModuleRoutes;

import React from 'react';
import Route from 'react-router-dom/Route';
import { connectFor } from '@folio/stripes-connect';
import { modules } from 'stripes-loader'; // eslint-disable-line
import AddContext from './AddContext';

if (!Array.isArray(modules.app) && modules.length < 0) {
  throw new Error('At least one module of type "app" must be enabled.');
}

function getModuleRoutes(stripes) {
  return modules.app.map((module) => {
    const name = module.module.replace(/^@folio\//, '');
    const perm = `module.${name}.enabled`;
    if (!stripes.hasPerm(perm)) return null;

    const connect = connectFor(module.module, stripes.epics, stripes.logger);
    const Current = connect(module.getModule());
    const moduleStripes = stripes.clone({ connect });

    return (
      <Route
        path={module.route}
        key={module.route}
        render={props =>
          <AddContext context={{ stripes: moduleStripes }}>
            <Current {...props} connect={connect} stripes={moduleStripes} />
          </AddContext>
        }
      />
    );
  }).filter(x => x);
}

export default getModuleRoutes;

Use clone method for Stripes objects


module.exports = (function () {
	'use strict';

	function SGSServerErrorLogger (config, saveCallback, sendCallback) {
		if (sendCallback === undefined) {
			saveCallback = config;
			sendCallback = saveCallback;
			config = {};
		}

		config = config || {};

		this.save = saveCallback || function () {};

		this.send = sendCallback;

		return this.middleware.bind(this);
	}

	SGSServerErrorLogger.prototype.middleware = function (error, req, res, next) {
		if (!(error instanceof Error)) {
			error = new Error(error);
		}

		this.save({
			pid: process.pid,
			reqId: req.data.id,
			name: error.name,
			stack: error.stack,
			message: error.message
		});

		if (this.send === undefined) {
			return res.status(500).json({
				error: {
					id: req.data.id,
					code: error.code,
					status: 500
				}
			});
		}

		this.send(error, req, res, next);
	};

	return SGSServerErrorLogger;

})();

module.exports = (function () {
	'use strict';

	function SGSServerErrorLogger (config, saveCallback, sendCallback) {
		if (sendCallback === undefined) {
			sendCallback = saveCallback;
			saveCallback = config;
			config = {};
		}

		config = config || {};

		this.save = saveCallback || function () {};

		this.send = sendCallback;

		return this.middleware.bind(this);
	}

	SGSServerErrorLogger.prototype.middleware = function (error, req, res, next) {
		if (!(error instanceof Error)) {
			error = new Error(error);
		}

		this.save({
			pid: process.pid,
			reqId: req.data.id,
			name: error.name,
			stack: error.stack,
			message: error.message
		});

		if (this.send === undefined) {
			return res
				.status(500)
				.json({
					error: {
						id: req.data.id,
						code: error.code,
						status: 500
					}
				});
		}

		this.send(error, req, res, next);
	};

	return SGSServerErrorLogger;

})();

Send calback is now set correctly when underloading arguments.g


define(["dist/local_object"],  function(LocalObject){
    function Pipeline(id, finalNode, resetPage, overwrite){
        this.id = id;
        this.finalNode = finalNode;
        this.resetPage = resetPage;
        this.indexObj = new LocalObject(this.id, overwrite);
    }
    Pipeline.prototype.nodes = [];
    Pipeline.prototype.add = function(runFn){
        var that = this;
        this.nodes.push(function(){
            runFn(function(){that.runNext()});
        });
    };

    Pipeline.prototype.start = function(){
        var that = this;
        if(this.indexObj.get("index") === undefined){
            this.run(0);
        } else {
            this.resetPage(function(reset){
                if(reset){
                    that.run(0);
                } else {
                    that.run(that.indexObj.get("index"));
                }
            });
        }
    };

    Pipeline.prototype.runNext = function(){
        var index = this.indexObj.get("index") + 1;
        this.run(index);
    };

    Pipeline.prototype.run = function(index){
        this.indexObj.set("index", index);
        var node = this.nodes[index];
        if(node){
            node();
        } else {
            this.finalNode();
            this.indexObj = new LocalObject(this.id, true);
        }
    }
    console.log("pipeline", Pipeline);
    return Pipeline;
});

define(["dist/local_object"],  function(LocalObject){
    function Pipeline(id, finalNode, resetPage, overwrite){
        this.id = id;
        this.finalNode = finalNode;
        this.resetPage = resetPage;
        this.indexObj = new LocalObject(this.id, overwrite);
    }
    Pipeline.prototype.nodes = [];
    Pipeline.prototype.add = function(runFn){
        var that = this;
        this.nodes.push(function(){
            runFn(function(){that.runNext()});
        });
    };

    Pipeline.prototype.start = function(){
        var that = this;
        if(this.indexObj.get("index") === undefined){
            this.run(0);
        } else {
            var index = that.indexObj.get("index");
            if(index === 0){
                this.run(0);
            } else {
                this.resetPage(function(reset){
                    if(reset){
                        that.run(0);
                    } else {
                        that.run(index);
                    }
                });
            }
        }
    };

    Pipeline.prototype.runNext = function(){
        var index = this.indexObj.get("index") + 1;
        this.run(index);
    };

    Pipeline.prototype.run = function(index){
        this.indexObj.set("index", index);
        var node = this.nodes[index];
        if(node){
            node();
        } else {
            this.finalNode();
            this.indexObj = new LocalObject(this.id, true);
        }
    }
    console.log("pipeline", Pipeline);
    return Pipeline;
});

Allow quit on first page without reset prompt


function ngGridLayoutPlugin () {
    var self = this;
    this.grid = null;
    this.scope = null;
    this.init = function(scope, grid, services) {
        self.domUtilityService = services.DomUtilityService;
        self.grid = grid;
        self.scope = scope;
    };

    this.updateGridLayout = function () {
        if (!self.scope.$$phase) {
            self.scope.$apply(function(){
                self.domUtilityService.RebuildGrid(self.scope, self.grid);
            });
        }
        else {
            // $digest or $apply already in progress
            self.domUtilityService.RebuildGrid(self.scope, self.grid);
        }
    };
}

function ngGridLayoutPlugin () {
    var self = this;
    this.grid = null;
    this.scope = null;
    this.init = function(scope, grid, services) {
        self.domUtilityService = services.DomUtilityService;
        self.grid = grid;
        self.scope = scope;
    };

    this.updateGridLayout = function () {
        self.scope.$evalAsync(function(){
			self.domUtilityService.RebuildGrid(self.scope, self.grid);
		});
    };
}

Update to use preferred $scope.apply check


import { expect } from 'chai'
import types from '../../src/constants/actionTypes'
import { onClickMenuButton } from '../../src/actions/nav'

describe('actions/nav.js', () => {
  it('should toggle sidebar menu when click on menu button', () => {
    const isExpanded = false

    expect(onClickMenuButton(isExpanded)).to.deep.equal(Object.assign({}, {
      type: types.ON_CLICK_MENU_BUTTON,
      isExpanded: ! isExpanded
    }))
  })
})

import { expect } from 'chai'
import types from '../../src/constants/actionTypes'
import { onClickMenuButton } from '../../src/actions/nav'

describe('actions/nav.js', () => {
  it('should toggle sidebar menu when click on menu button', () => {
    const isExpanded = false

    expect(onClickMenuButton(isExpanded)).to.deep.equal({
      type: types.ON_CLICK_MENU_BUTTON,
      isExpanded: ! isExpanded
    })
  })
})

Change from immutable > mutable object


/**
 * Created by Omnius on 6/27/16.
 */
$(document).ready(function() {
    const hawkSessionTokenCookie = MyUtils.getCookie('Hawk-Session-Token');

    console.log(hawkSessionTokenCookie);

    if (hawkSessionTokenCookie) {
        window.location = 'http://' + location.host + '/user/welcomeh';
    } else {
        $('body').css('visibility', 'visible');
    }
});

/**
 * Created by Omnius on 6/27/16.
 */
$(document).ready(function() {
    const hawkSessionTokenCookie = MyUtils.getCookie('Hawk-Session-Token');

    $('#closeUnauthorized').click(function () {
        $('#formUnauthorized').addClass('hidden');
    });

    const query = document.URL.split('?');
    if (query.length === 2) {
        if (query[1].indexOf('registered') > -1) {
            $('#myModal').modal();
        }
    }

    if (hawkSessionTokenCookie) {
        window.location = 'http://' + location.host + '/user/welcome';
    } else {
        $('body').show();
    }
});

Support modal call using jQuery; fix authorised user URL; show error for unauthorised


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

import { areWeFunYet, translate as $t } from '../../helpers';
import ExternalLink from './external-link.js';

let showLicense = areWeFunYet();

const LoadingMessage = props => {
    let message = props.message || $t('client.spinner.generic');

    let license = showLicense ? (

            {$t('client.spinner.license')}
            <ExternalLink href="https://liberapay.com/Kresus">Kresus</ExternalLink>

    ) : null;

    return (

{$t('client.spinner.title')}

{message}

                {license}

    );
};

LoadingMessage.propTypes = {
    // Message indicating why we're doing background loading (and the UI is
    // frozen).
    message: PropTypes.string
};

export default LoadingMessage;

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

import { areWeFunYet, translate as $t } from '../../helpers';
import ExternalLink from './external-link.js';
import DisplayIf from './display-if';

let showLicense = areWeFunYet();

const LoadingMessage = props => {
    let message = props.message || $t('client.spinner.generic');

    return (

{$t('client.spinner.title')}

{message}

                <DisplayIf condition={showLicense}>

                        {$t('client.spinner.license')}
                        <ExternalLink href="https://liberapay.com/Kresus">Kresus</ExternalLink>

                </DisplayIf>

    );
};

LoadingMessage.propTypes = {
    // Message indicating why we're doing background loading (and the UI is
    // frozen).
    message: PropTypes.string
};

export default LoadingMessage;

Use DisplayIf in the licence.


import debugModule from "debug";
const debug = debugModule("debugger:trace:sagas");

import { take, takeLatest, put, select } from "redux-saga/effects";

import * as actions from "./actions";
import trace from "./selectors";

export function *waitForTrace() {
  let {steps} = yield take(actions.SAVE_STEPS);

  let addresses = [
    ...new Set(
      steps
        .filter( ({op}) => op == "CALL" || op == "DELEGATECALL" )
        .map( ({stack}) => "0x" + stack[stack.length - 2].substring(24) )
    )
  ];

  yield put(actions.receiveAddresses(addresses));
}

export function* next() {
  let remaining = yield select(trace.stepsRemaining);
  debug("remaining: %o", remaining);
  let steps = yield select(trace.steps);
  debug("total steps: %o", steps.length);

  if (remaining > 0) {
    debug("putting TICK");
    // updates state for current step
    yield put(actions.tick());
    debug("put TICK");

    remaining--; // local update, just for convenience
  }

  if (remaining) {
    debug("putting TOCK");
    // updates step to next step in trace
    yield put(actions.tock());
    debug("put TOCK");

  } else {

    yield put(actions.endTrace());
  }
}

export default function* saga() {
  // wait for trace to be defined
  yield *waitForTrace();

  yield takeLatest(actions.NEXT, next);
}

import debugModule from "debug";
const debug = debugModule("debugger:trace:sagas");

import { take, takeEvery, put, select } from "redux-saga/effects";

import * as actions from "./actions";
import trace from "./selectors";

export function *waitForTrace() {
  let {steps} = yield take(actions.SAVE_STEPS);

  let addresses = [
    ...new Set(
      steps
        .filter( ({op}) => op == "CALL" || op == "DELEGATECALL" )
        .map( ({stack}) => "0x" + stack[stack.length - 2].substring(24) )
    )
  ];

  yield put(actions.receiveAddresses(addresses));
}

export function* next() {
  let remaining = yield select(trace.stepsRemaining);
  debug("remaining: %o", remaining);
  let steps = yield select(trace.steps);
  debug("total steps: %o", steps.length);

  if (remaining > 0) {
    debug("putting TICK");
    // updates state for current step
    yield put(actions.tick());
    debug("put TICK");

    remaining--; // local update, just for convenience
  }

  if (remaining) {
    debug("putting TOCK");
    // updates step to next step in trace
    yield put(actions.tock());
    debug("put TOCK");

  } else {

    yield put(actions.endTrace());
  }
}

export default function* saga() {
  // wait for trace to be defined
  yield *waitForTrace();

  yield takeEvery(actions.NEXT, next);
}

Switch trace saga to use takeEvery


import Item from '../../../../../src/js/structs/Item';

module.exports = class ServiceSpec extends Item {
  getId() {
    return this.get('id') || '';
  }

  toJSON() {
    return this.get();
  }
};

import Item from '../../../../../src/js/structs/Item';

module.exports = class ServiceSpec extends Item {
  getId() {
    return this.get('id') || '';
  }

  getResources() {
    return {
      cpus: 0,
      mem: 0,
      gpus: 0,
      disk: 0
    };
  }

  toJSON() {
    return this.get();
  }
};

Add a resource getter to the abstract service spec


import { computed } from '@ember/object';
import Component from '@ember/component';
import layout from '../templates/components/star-rating';

export default Component.extend({
  tagName: 'div',
  classNames: ['rating-panel'],

  layout: layout,

  rating:     0,
  maxRating:  5,
  item:       null,
  "on-click": null,

  fullClassNames: 'glyphicon glyphicon-star',
  emptyClassNames: 'glyphicon glyphicon-star-empty',

  stars: computed('rating', 'maxRating', function() {
    var rating = Math.round(this.get('rating'));
    var fullStars = this.starRange(1, rating, 'full');
    var emptyStars = this.starRange(rating + 1, this.get('maxRating'), 'empty');
    return fullStars.concat(emptyStars);
  }),

  starRange: function(start, end, type) {
    var starsData = [];
    for (var i = start; i <= end; i++) {
      starsData.push({ rating: i, full: type === 'full' });
    }
    return starsData;
  },

  actions: {
    setRating: function(newRating) {
      var actionType = typeof this.attrs['on-click'];
      if (actionType === 'function') {
        this.attrs['on-click']({
          item: this.get('item'),
          rating: newRating
        });
      } else {
        this.sendAction('on-click', {
          item: this.get('item'),
          rating: newRating
        });
      }
    }
  }
});

import { computed } from '@ember/object';
import Component from '@ember/component';
import layout from '../templates/components/star-rating';

export default Component.extend({
  tagName: 'div',
  classNames: ['rating-panel'],

  layout: layout,

  rating:     0,
  maxRating:  5,
  item:       null,
  "on-click": null,

  fullClassNames: 'glyphicon glyphicon-star',
  emptyClassNames: 'glyphicon glyphicon-star-empty',

  stars: computed('rating', 'maxRating', function() {
      var rating = this.get('rating');
      var end = this.get('maxRating')
      var starsArray = [];
      for (var i = 0; i < end ; i++) {
        starsArray.push({ rating: i+1, full: i < rating });
      }
      return starsArray;
    }),

  actions: {
    setRating: function(newRating) {
      var actionType = typeof this.attrs['on-click'];
      if (actionType === 'function') {
        this.attrs['on-click']({
          item: this.get('item'),
          rating: newRating
        });
      } else {
        this.sendAction('on-click', {
          item: this.get('item'),
          rating: newRating
        });
      }
    }
  }
});

Simplify the stars computed property


try {

    // fail fast if no jack libs on the module path
    require('jack/narwhal');

    var http = require('helma/httpserver');
    http.start({
        port: 8080,
        moduleName: 'jack/narwhal',
        functionName: 'Narwhal'
    });

    // Alternatively, you can pass the jack app to start() as second argument.
    // this bypasses module loading in the servlet, so changes in the Jack app won't be picked up.
    // http.start({ port: 8080 }, require('jack/narwhal').Narwhal);

} catch (error) {
    print();
    print("Can't start Jack demo. Make sure you have Jack installed and configured as described in README.txt.");
    print();
    print(error);
}

Print message and exit if jack libs can't be loaded.


// In a real use case, the endpoint could point to another origin.
var LOG_ENDPOINT = 'report/logs';

// The code in `oninstall` and `onactive` force the service worker to
// control the clients ASAP.
self.oninstall = function(event) {
  event.waitUntil(self.skipWaiting());
};

self.onactivate = function(event) {
  event.waitUntil(self.clients.claim());
};

self.onfetch = function(event) {
  event.respondWith(
    // Log the request ...
    log(event.request)
    // .. and then actually perform it.
    .then(fetch)
  );
};

// Post basic information of the request to a backend for historical purposes.
function log(request) {
  var returnRequest = function() {
    return request;
  };

  var data = {
    method: request.method,
    url: request.url
  };

  return fetch(LOG_ENDPOINT, {
    method: 'POST',
    body: JSON.stringify(data),
    headers: { 'content-type': 'application/json' }
  })
  .then(returnRequest, returnRequest);
}

// In a real use case, the endpoint could point to another origin.
var LOG_ENDPOINT = 'report/logs';

// The code in `oninstall` and `onactive` force the service worker to
// control the clients ASAP.
self.oninstall = function(event) {
  event.waitUntil(self.skipWaiting());
};

self.onactivate = function(event) {
  event.waitUntil(self.clients.claim());
};

self.onfetch = function(event) {
  event.respondWith(
    // Log the request …
    log(event.request)
    // … and then actually perform it.
    .then(fetch)
  );
};

// Post basic information of the request to a backend for historical purposes.
function log(request) {
  var returnRequest = function() {
    return request;
  };

  var data = {
    method: request.method,
    url: request.url
  };

  return fetch(LOG_ENDPOINT, {
    method: 'POST',
    body: JSON.stringify(data),
    headers: { 'content-type': 'application/json' }
  })
  .then(returnRequest, returnRequest);
}

Use ellipsis character instead of three points


/** @jsx React.DOM */

// For testing
var React;
if (typeof module !== 'undefined') {
    React = require('react');
    Authenticate = require('./gebo/Authenticate');
}

/**
 * Interface
 */
var Interface = React.createClass({

    /**
     * componentDidMount
     */
    getInitialState: function() {
        return {
            email: 'You haven\'t signed in',
        };
    },

    /**
     * componentDidMount
     */
//    componentDidMount: function() {
//        if (this.props.friendo) {
//          this.setState({
//              friendo: this.props.friendo,
//            });
//        }
//    },

    /**
     * Called whenever the user's authentication status changes
     *
     * @param object
     */
    authenticate: function(user) {
                    console.log('authenticated', user);
        if (user.verified) {
          this.setState(user);
        }
        else {
          this.setState(this.getInitialState());
        }
    },

    /**
     * Render the component
     */
    render: function() {
        return(

                <Authenticate ref='authenticated' update={this.authenticate} />

Client ID: {this.state.email}

        );
    }
});

if (typeof module !== 'undefined') {
  module.exports = Interface;
}
else {
  React.renderComponent(
      <Interface />, document.getElementById('interface')
  );
}

/** @jsx React.DOM */

// For testing
var React;
if (typeof module !== 'undefined') {
    React = require('react');
    Authenticate = require('./gebo/Authenticate');
}

/**
 * Interface
 */
var Interface = React.createClass({

    /**
     * componentDidMount
     */
    getInitialState: function() {
        return {
            email: 'You haven\'t signed in',
        };
    },

    /**
     * Called whenever the user's authentication status changes
     *
     * @param object
     */
    authenticate: function(user) {
        if (user.verified) {
          this.setState(user);
        }
        else {
          this.setState(this.getInitialState());
        }
    },

    /**
     * Render the component
     */
    render: function() {
        return(

                <Authenticate ref='authenticated' update={this.authenticate} />

Client ID: {this.state.email}

        );
    }
});

if (typeof module !== 'undefined') {
  module.exports = Interface;
}
else {
  React.renderComponent(
      <Interface />, document.getElementById('interface')
  );
}

Put feedback in smaller header


var TvApi = require('node-lgtv-api');
var imageName = 'screen.jpg';

module.exports = {
    handle: function (ctx, message, sendMessage) {
        var lgTV = ctx.network.lgTV;
        var tvApi = new TvApi(lgTV.host, lgTV.port, lgTV.pairingKey);
        tvApi.authenticate(function (err, sessionKey) {
                if (err) {
                    console.error(err);
                } else {
                    tvApi.takeScreenShot(imageName, function (err, data) {
                        if (err) {
                            console.error(err);
                        } else {
                            ctx.log.debug(message.from.username + ' <- ' + imageName);
                            ctx.bot.sendPhoto(message.from.id, imageName, {caption: 'TV Screen'});
                        }
                    });
                }
            }
        );
    }
};

var TvApi = require('node-lgtv-api');
var imageName = 'screen.jpg';

module.exports = {
    handle: function (ctx, message, sendMessage) {
        var lgTV = ctx.config.network.lgTV;
        var tvApi = new TvApi(lgTV.host, lgTV.port, lgTV.pairingKey);
        tvApi.authenticate(function (err, sessionKey) {
                if (err) {
                    console.error(err);
                } else {
                    tvApi.takeScreenShot(imageName, function (err, data) {
                        if (err) {
                            console.error(err);
                        } else {
                            ctx.log.debug(message.from.username + ' <- ' + imageName);
                            ctx.bot.sendPhoto(message.from.id, imageName, {caption: 'TV Screen'});
                        }
                    });
                }
            }
        );
    }
};

Fix error with TV screen


App.Search.SearchPanel = Ext.extend(App.Search.SearchPanelUi, {
  nSearchPanels: 0,
	resultPagerSize: 15,

	// keep references to the criteria and results handler so that
	// the search function can access them
	searchCriteriaPanel: null,
	searchResultsPanel: null,

  initComponent: function() {
    App.Search.SearchPanel.superclass.initComponent.call(this);

    this.constructor.prototype.nSearchPanels++;
    this.searchPanelNo = this.constructor.prototype.nSearchPanels;
    this.title = 'Search ' + this.searchPanelNo;
  },

  // returns the query spec generated by its embedded SearchCriteriaPanel
  getQuerySpec: function() {
    return this.items.getAt(0).getQuerySpec();
  },

	// perform a search
	// used by search button handlers
	search: function(corpus, spec) {
		// reset the queryId before getting a new result
		this.searchResultsPanel.queryId = '0';

		var store = this.searchResultsPanel.getStore();

		store.setBaseParam('query', Ext.encode(spec));
		store.setBaseParam('corpus', corpus);
		store.setBaseParam('caseInsensitive', false);

		store.load({ params: {
			start: 0,
			limit: this.resultPagerSize
		}});
	}
});
Ext.reg("searchpanel", App.Search.SearchPanel);

App.Search.SearchPanel = Ext.extend(App.Search.SearchPanelUi, {
  nSearchPanels: 0,
	resultPagerSize: 15,

	// keep references to the criteria and results handler so that
	// the search function can access them
	searchCriteriaPanel: null,
	searchResultsPanel: null,

  initComponent: function() {
    if(this.constructor.prototype.nSearchPanels > 0) {
      Ext.apply(this, Ext.apply(this.initialConfig, {closable: true}));
    }
    App.Search.SearchPanel.superclass.initComponent.call(this);

    this.constructor.prototype.nSearchPanels++;
    this.searchPanelNo = this.constructor.prototype.nSearchPanels;
    this.title = 'Search ' + this.searchPanelNo;
  },

  // returns the query spec generated by its embedded SearchCriteriaPanel
  getQuerySpec: function() {
    return this.items.getAt(0).getQuerySpec();
  },

	// perform a search
	// used by search button handlers
	search: function(corpus, spec) {
		// reset the queryId before getting a new result
		this.searchResultsPanel.queryId = '0';

		var store = this.searchResultsPanel.getStore();

		store.setBaseParam('query', Ext.encode(spec));
		store.setBaseParam('corpus', corpus);
		store.setBaseParam('caseInsensitive', false);

		store.load({ params: {
			start: 0,
			limit: this.resultPagerSize
		}});
	}
});
Ext.reg("searchpanel", App.Search.SearchPanel);

Make Search Panels other than the first one closable


'use strict';
dataDashboard.controller('MainCtrl', ['$scope', 'Traffic', function ($scope, Traffic) {

    var getAllTraffic = function() {
      Traffic.getTraffic()
        .then(function(data) {
          if (data) {
            $scope.trafficList = data;
            // console.log($scope.trafficList);
            $scope.countryCount = _.countBy($scope.trafficList, 'country');
            // console.log($scope.countryCount);
            $scope.countries = _.map($scope.countryCount, function(a, b) {return b ? b : "Unknown";});
            $scope.visits = _.map($scope.countryCount, function(a, b) {return a;});
          }
        }, function(error) {
            console.log(error);
        });
    }();
  }]);

'use strict';
dataDashboard.controller('MainCtrl', ['$scope', 'Traffic', function ($scope, Traffic) {

    var getAllTraffic = function() {
      Traffic.getTraffic()
        .then(function(data) {
          if (data) {
            $scope.trafficList = data;
            // console.log($scope.trafficList);
            $scope.countryCount = _.countBy($scope.trafficList, 'country');
            // console.log($scope.countryCount);
            $scope.countries = _.keys($scope.countryCount);
            $scope.visits = _.values($scope.countryCount);
          }
        }, function(error) {
            console.log(error);
        });
    }();
  }]);

Change to keys and values functions from map for labels and visits and countries


var exec = require('child_process').exec,
    Q = require('q');

module.exports = function runCommand(command) {
  var child,
      dfd = Q.defer();

  child = exec(command, function callback(error, stdout, stderr) {
    if (error) {
      error.stdout = stdout;
      error.stderr = stderr;
      error.message = 'Error running command: `' + command + '`.';

      dfd.reject(error);
    } else {
      dfd.resolve();
    }
  });

  child.stdout.pipe(process.stdout);
  child.stderr.pipe(process.stderr);

  return dfd.promise;
};

var exec = require('child_process').exec,
    Q = require('q');

module.exports = function runCommand(command, options) {
  var child,
      dfd = Q.defer();

  child = exec(command, options, function callback(error, stdout, stderr) {
    if (error) {
      error.stdout = stdout;
      error.stderr = stderr;
      error.message = 'Error running command: `' + command + '`.';

      dfd.reject(error);
    } else {
      dfd.resolve();
    }
  });

  child.stdout.pipe(process.stdout);
  child.stderr.pipe(process.stderr);

  return dfd.promise;
};

Allow passing options into `runCommand()`


// # Imports
var express         = require('express');
var app             = express();

// # Configuration
var environment = process.env.NODE_ENV || 'development';
var serverPort = process.env.PORT || 6001;

// # Routes
// Static file serving point

app.get('*', function(req, res) {
	res.redirect('https://www.facebook.com/events/1756954314531757/');
});

console.log('### Konehuone Server started!');
console.log('on port', serverPort);
app.listen(serverPort);

// # Imports
var express         = require('express');
var app             = express();

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

// # Configuration
var environment = process.env.NODE_ENV || 'development';
var serverPort = process.env.PORT || 6001;

// # Fetch
facebook.initialize('1756954314531757'); // KH10 FB Event ID

// # Routes
// Static file serving point

app.get('/api/fb-event/kpi', function(req, res) {
    res.send(facebook.getEventInfo());
});

console.log('### Konehuone Server started!');
console.log('on port', serverPort);
app.listen(serverPort);

Add "Facebook Event KPI API function"


const PDFDocument = require('../../lib/document').default;
const PDFSecurity = require('../../lib/security').default;

// manual mock for PDFSecurity to ensure stored id will be the same accross different systems
PDFSecurity.generateFileID = () => {
  return new Buffer('mocked-pdf-id');
};

describe('Annotations', () => {
  let document;

  beforeEach(() => {
    document = new PDFDocument({
      info: { CreationDate: new Date(Date.UTC(2018, 1, 1)) }
    });
  });

  describe('link', () => {
    test('using page index', () => {
      document.addPage();

      const dataLog = [];
      document._write = function(data) {
        dataLog.push(data);
      };

      document.text('Go To First Page', { link: 0 });

      const expected = [
        [
          `10 0 obj`,
          `<<
/S /GoTo
/D [6 0 R /XYZ null null null]
>>`
        ]
      ];

      for (let i = 0; i < expected.length; ++i) {
        let idx = dataLog.indexOf(expected[i][0]);
        for (let j = 1; j < expected[i].length; ++j) {
          expect(dataLog[idx + j]).toEqual(expected[i][j]);
        }
      }
    });
  });
});

const PDFDocument = require('../../lib/document').default;
const PDFSecurity = require('../../lib/security').default;

// manual mock for PDFSecurity to ensure stored id will be the same accross different systems
PDFSecurity.generateFileID = () => {
  return new Buffer('mocked-pdf-id');
};

describe('Annotations', () => {
  let document;

  beforeEach(() => {
    document = new PDFDocument({
      info: { CreationDate: new Date(Date.UTC(2018, 1, 1)) }
    });
  });

  describe('link', () => {
    test('using page index', () => {
      document.addPage();

      const dataLog = [];
      document._write = function(data) {
        dataLog.push(data);
      };

      document.text('Go To First Page', { link: 0 });

      const expected = [
        [
          `11 0 obj`,
          `<<
/S /GoTo
/D [7 0 R /XYZ null null null]
>>`
        ]
      ];

      for (let i = 0; i < expected.length; ++i) {
        let idx = dataLog.indexOf(expected[i][0]);
        for (let j = 1; j < expected[i].length; ++j) {
          expect(dataLog[idx + j]).toEqual(expected[i][j]);
        }
      }
    });
  });
});

Fix object index in annotation test


/**
 * @class Denkmal_Component_EventAdd
 * @extends Denkmal_Component_Abstract
 */
var Denkmal_Component_EventAdd = Denkmal_Component_Abstract.extend({

	/** @type String */
	_class: 'Denkmal_Component_EventAdd'
});

/**
 * @class Denkmal_Component_EventAdd
 * @extends Denkmal_Component_Abstract
 */
var Denkmal_Component_EventAdd = Denkmal_Component_Abstract.extend({

	/** @type String */
	_class: 'Denkmal_Component_EventAdd',

	childrenEvents: {
		'Denkmal_Form_EventAdd change': function() {
			console.log('change-' + new Date().getTime());
		}
	},

	ready: function() {
		var eventAddForm = this.findChild('Denkmal_Form_EventAdd');
		_.each(eventAddForm.getChildren('CM_FormField_Text'), function(field) {
			field.enableTriggerChange();
		});
	}
});

Introduce change event bidning for compontent reload


Fix global object with babel 6.0