'use strict';
var path = require('path'),
    opener = require('opener'),
    chalk = require('chalk'),
    pkg = require('../package.json'),
    server = require('./server'),
    program = require('commander');

function getConfigPath(config) {
    return path.resolve(config);
}

exports.run = function() {
    program
        .version(pkg.version)
        .allowUnknownOption(true)
        .option('-p, --port <port>', 'Port to launch server on', 8000)
        .option('-h, --hostname <hostname>', 'Hostname to launch server on', 'localhost')
        .option('-c, --config <file>', 'Gemini config file', getConfigPath)
        .parse(process.argv);

    program.testFiles = [].concat(program.args);
    server.start(program).then(function(result) {
        console.log('GUI is running at %s', chalk.cyan(result.url));
        opener(result.url);
    }).done();
};

'use strict';
var path = require('path'),
    opener = require('opener'),
    chalk = require('chalk'),
    pkg = require('../package.json'),
    server = require('./server'),
    program = require('commander');

exports.run = function() {
    program
        .version(pkg.version)
        .allowUnknownOption(true)
        .option('-p, --port <port>', 'Port to launch server on', 8000)
        .option('-h, --hostname <hostname>', 'Hostname to launch server on', 'localhost')
        .option('-c, --config <file>', 'Gemini config file', path.resolve)
        .parse(process.argv);

    program.on('--help', function() {
        console.log('Also you can override gemini config options.');
        console.log('See all possible options in gemini documentation.');
    });

    program.testFiles = [].concat(program.args);
    server.start(program).then(function(result) {
        console.log('GUI is running at %s', chalk.cyan(result.url));
        opener(result.url);
    }).done();
};

Add notification about gemini config overriding


import React from 'react';
import SVGInline from 'react-svg-inline';
import svg from '../../static/logo.svg';

const Logo = (props) => {
    return (
        <SVGInline
            svg={svg}
            desc={props.desc}
            width={props.width}
            height={props.height}
        />
    );
};

Logo.propTypes = {
    desc:   React.PropTypes.string,
    width:  React.PropTypes.string,
    height: React.PropTypes.string,
};

Logo.defaultProps = {
    desc: 'devnews logo',
    width: 'auto',
    height: '20',
};

export default Logo;

import React from 'react';
import SVGInline from 'react-svg-inline';
import svg from '../../static/logo.svg';

const Logo = (props) => {
    return (
        <SVGInline
            svg={svg}
            desc={props.desc}
            width={props.width}
            height={props.height}
            cleanup={['width', 'height']}
        />
    );
};

Logo.propTypes = {
    desc:   React.PropTypes.string,
    width:  React.PropTypes.string,
    height: React.PropTypes.string,
};

Logo.defaultProps = {
    desc: 'devnews logo',
    width: 'auto',
    height: '20px',
};

export default Logo;

Fix weird logo size on Firefox


import React from 'react'

//class-based component (ES6)
class SearchBar extends React.Component {
  render() { //method definition in ES6
    return <input />;
  }
}

export default SearchBar;

import React, { Component } from 'react'

//class-based component (ES6)
class SearchBar extends Component {
  render() { //method definition in ES6
    return <input />;
  }
}

export default SearchBar;

Refactor to ES6 syntactic sugar


/**
 * Global settings that do not change in a production environment
 */

if (typeof window === 'undefined') {
    // For tests
    global.window = {}
}

window.appSettings = {
    headerHeight: 60,
    drawerWidth: 250,
    drawerAnimationDuration:500,

    icons: {
        navbarArchive: 'ios-box',
        navbarEye:     'ios-eye',
        navbarChevron: 'ios-arrow-right',
        navbarSearch:  'ios-search-strong',
        navbarCompose: 'compose',
        navbarRefresh: 'ios-reload',

        threadPostMenu: 'more',
    }
}

/**
 * Global settings that do not change in a production environment
 */

if (typeof window === 'undefined') {
    // For tests
    global.window = {}
}

window.appSettings = {
    boardOuterMargin: 30,
    boardPostMargin: 25,

    // The following settings can't be changed explcitly.
    // Needs to be kept in sync with src/styles/base/variables
    headerHeight: 60,
    drawerWidth: 250,
    drawerAnimationDuration: 500,

    homeViewID: 'HomeView',
    contentViewID: 'ContentView',
    settingsViewID: 'SettingsView',

    icons: {
        navbarArchive: 'ios-box',
        navbarEye:     'ios-eye',
        navbarChevron: 'ios-arrow-right',
        navbarSearch:  'ios-search-strong',
        navbarCompose: 'ios-plus-outline',
        navbarRefresh: 'ios-reload',

        threadPostMenu: 'more',
    }
}

Add view ID's and board layout options


'use strict';
angular.module('eehMenuBs3').directive('eehMenuBs3MenuItemContent', MenuItemContentDirective);

/** @ngInject */
function MenuItemContentDirective(eehNavigation) {
    return {
        restrict: 'A',
        scope: {
            menuItem: '=eehMenuBs3MenuItemContent'
        },
        templateUrl: 'template/eeh-menu/menu-item-content/menu-item-content.html',
        link: function (scope) {
            scope.iconBaseClass = function () {
                return eehNavigation.iconBaseClass();
            };
        }
    };
}

'use strict';
angular.module('eehMenuBs3').directive('eehMenuBs3MenuItemContent', MenuItemContentDirective);

/** @ngInject */
function MenuItemContentDirective(eehMenu) {
    return {
        restrict: 'A',
        scope: {
            menuItem: '=eehMenuBs3MenuItemContent'
        },
        templateUrl: 'template/eeh-menu-bs3/menu-item-content/menu-item-content.html',
        link: function (scope) {
            scope.iconBaseClass = function () {
                return eehMenu.iconBaseClass();
            };
        }
    };
}

Fix names and paths in menu item content directive


'use strict';

angular.module('alienUiApp').factory('restTechnicalErrorInterceptor', ['$rootScope', '$q', '$window', 'toaster', '$translate',
  function($rootScope, $q, $window, toaster, $translate) {

    var extractErrorMessage = function(rejection) {
      if (UTILS.isDefinedAndNotNull(rejection.data)) {
        if (UTILS.isDefinedAndNotNull(rejection.data.error)) {
          var error = rejection.data.error;
          // Redirect to homepage when the user is not authenticated
          if (error.code === 100) {
            $window.location.href = '/';
          } else {
            return {
              status: rejection.status,
              data: 'ERRORS.' + error.code
            };
          }
        } else {
          // Error not defined ==> return the data part
          return {
            status: rejection.status,
            data: rejection.data
          };
        }
      } else {
        // rejection.data not defined ==> unknown error
        return {
          status: rejection.status,
          data: 'ERRORS.UNKNOWN'
        };
      }
    };

    return {
      'responseError': function(rejection) {
        var error = extractErrorMessage(rejection);
        // Display the toaster message on top with 4000 ms display timeout
        // Don't shot toaster for "tour" guides
        if (error.data.indexOf('/data/guides') < 0) {
          toaster.pop('error', $translate('ERRORS.INTERNAL') + ' - ' + error.status, $translate(error.data), 4000, 'trustedHtml', null);
        }
        return $q.reject(rejection);
      }
    };
  }
]);

'use strict';

angular.module('alienUiApp').factory('restTechnicalErrorInterceptor', ['$rootScope', '$q', '$window', 'toaster', '$translate',
  function($rootScope, $q, $window, toaster, $translate) {

    var extractErrorMessage = function(rejection) {
      if (UTILS.isDefinedAndNotNull(rejection.data)) {
        if (UTILS.isDefinedAndNotNull(rejection.data.error)) {
          var error = rejection.data.error;
          // Redirect to homepage when the user is not authenticated
          if (error.code === 100) {
            $window.location.href = '/';
          } else {
            return {
              status: rejection.status,
              data: 'ERRORS.' + error.code
            };
          }
        } else {
          // Error not defined ==> return the data part
          return {
            status: rejection.status,
            data: rejection.data
          };
        }
      } else {
        // rejection.data not defined ==> unknown error
        return {
          status: rejection.status,
          data: 'ERRORS.UNKNOWN'
        };
      }
    };

    return {
      'responseError': function(rejection) {
        var error = extractErrorMessage(rejection);
        // Display the toaster message on top with 4000 ms display timeout
        // Don't shot toaster for "tour" guides
        if (rejection.config.url.indexOf('data/guides') < 0) {
          toaster.pop('error', $translate('ERRORS.INTERNAL') + ' - ' + error.status, $translate(error.data), 4000, 'trustedHtml', null);
        }
        return $q.reject(rejection);
      }
    };
  }
]);

Fix double error message when "tour" is missing


'use strict';

angular.module('arethusa.core').directive('langSpecific', [
  'languageSettings',
  function(languageSettings) {
    return {
      restrict: 'A',
      link: function(scope, element, attrs) {
        var settings = languageSettings.getFor('treebank');
        if (settings) {
          element.attr('lang', settings.lang);
          element.attr('dir', settings.leftToRight ? 'ltr' : 'rtl');
          element.css('font-family', settings.font);
        }
      }
    };
  }
]);

'use strict';

angular.module('arethusa.core').directive('langSpecific', [
  'languageSettings',
  function(languageSettings) {
    return {
      restrict: 'A',
      link: function(scope, element, attrs) {
        var settings = languageSettings.getFor('treebank') || languageSettings.getFor('hebrewMorph');
        if (settings) {
          element.attr('lang', settings.lang);
          element.attr('dir', settings.leftToRight ? 'ltr' : 'rtl');
          element.css('font-family', settings.font);
        }
      }
    };
  }
]);

Check hebrew document in langSpecific


var path 		= require('path');
var rootPath 	= path.normalize(__dirname + '/../../');

module.exports 	= {
    local: {
        baseUrl: 'http://localhost:3051',
        db: 'mongodb://localhost/rp_local',
        rootPath: rootPath,
        port: process.env.PORT || 3051
    },
    staging : {
        baseUrl: 'http://dev.resourceprojects.org',
        //db: '@aws-us-east-1-portal.14.dblayer.com:10669/rp_dev?ssl=true',
        db: '@aws-us-east-1-portal.14.dblayer.com:10669,aws-us-east-1-portal.13.dblayer.com:10499/rp_dev?ssl=true',
        rootPath: rootPath,
        port: process.env.PORT || 80
    }
};

var path 		= require('path');
var rootPath 	= path.normalize(__dirname + '/../../');

module.exports 	= {
    local: {
        baseUrl: 'http://localhost:3030',
        db: 'mongodb://localhost/rp_local',
        rootPath: rootPath,
        port: process.env.PORT || 3030
    },
    staging : {
        baseUrl: 'http://dev.resourceprojects.org',
        //db: '@aws-us-east-1-portal.14.dblayer.com:10669/rp_dev?ssl=true',
        db: '@aws-us-east-1-portal.14.dblayer.com:10669,aws-us-east-1-portal.13.dblayer.com:10499/rp_dev?ssl=true',
        rootPath: rootPath,
        port: process.env.PORT || 80
    }
};

Add to download links ng-csv


module.exports = function(app, express) {

  // Facebook OAuth
  app.get('/auth/facebook', function(req, res) {
    res.send('Facebook OAuth');
  });
  app.get('/auth/facebook/callback', function(req, res) {
    res.send('Callback for Facebook OAuth');
  });

  // User Creation
  app.route('/api/users')
    .post(function(req, res) {
      res.send('Create a new user');
    })
    .put(function(req, res) {
      res.send('Update user info');
    })
    .delete(function(req, res) {
      res.send('Delete the user');
    });
};

module.exports = function(app, express) {

  // Facebook OAuth
  app.get('/auth/facebook', function(req, res) {
    res.send('Facebook OAuth');
  });
  app.get('/auth/facebook/callback', function(req, res) {
    res.send('Callback for Facebook OAuth');
  });

  // User Creation
  app.route('/api/users')
    .post(function(req, res) {
      res.send('Create a new user');
    })
    .put(function(req, res) {
      res.send('Update user info');
    })
    .delete(function(req, res) {
      res.send('Delete the user');
    });

  // Room Creation
  app.post('/api/rooms', function(req, res) {
    res.send('Create a room');
  });

  // Note Creation
  app.post('/api/notes/new', function(req, res) {
    res.send('End of lecture, and create all new notes for each user');
  });

  // Note Editing
  app.route('/api/notes')
    .get(function(req, res) {
      /*
       * /api/notes/?userId=id&roomId=id // compare all notes for user
       * /api/notes/?userId=id&roomId=id&filter=show // get all notes for that lecture
       */
      res.send('Compare all notes for the user/room');
    })
    .put(function(req, res) {
      res.send('Edit existing notes (save button)');
    })
    .post(function(req, res) {
      res.send('Add new notes (save button)');
    });
};

Add endpoints for Room creation (start of lecture), Note Creation (end of lecture), Editing Notes


define(['backbone.marionette', './ExamView', 'hbs!../templates/exams'],
  function (Marionette, ExamView, template) {
    'use strict';

    return Marionette.CompositeView.extend({
      tagName: 'table',
      className: 'table table-bordered table-condensed',
      itemView: ExamView,
      itemViewContainer: 'tbody',
      template: template,

      collectionEvents: {
        'add remove': function() {
          $('#clash').toggleClass('hidden', !this.collection.clashCount);
        }
      },

      appendHtml: function (compositeView, itemView, index) {
        var childrenContainer = compositeView.$itemViewContainer;
        var children = childrenContainer.children();
        if (children.size() <= index) {
          childrenContainer.append(itemView.el);
        } else {
          children.eq(index).before(itemView.el);
        }
      }
    });
  });

define(['backbone.marionette', './ExamView', 'hbs!../templates/exams'],
  function (Marionette, ExamView, template) {
    'use strict';

    return Marionette.CompositeView.extend({
      tagName: 'table',
      className: 'table table-bordered table-condensed',
      itemView: ExamView,
      itemViewContainer: 'tbody',
      template: template,

      collectionEvents: {
        'add remove': function() {
          $('#clash').toggleClass('hidden', !this.collection.clashCount);
        }
      },

      appendHtml: function (compositeView, itemView, index) {
        var childrenContainer = this.getItemViewContainer(compositeView);
        var children = childrenContainer.children();
        if (children.size() <= index) {
          childrenContainer.append(itemView.el);
        } else {
          children.eq(index).before(itemView.el);
        }
      }
    });
  });

Use getItemViewContainer() for safety - it ensures $itemViewContainer exists


class ObjectLocator {

    constructor(fs, objectName) {
        this.fs = fs;
        this.objectName = objectName;
        this.objects = [];
        this.objectDirectoryName = "objects";
    }

    run() {
        this.loadAllObjects(this.objectName);
        return this.objects;
    }

    loadAllObjects(dependencyName) {
        let fileData = this.fs.readFileSync(process.cwd() + `/${this.objectDirectoryName}/${dependencyName}.json`);
        let fileJson = {};
        if (fileData != null || fileData.toString().length == 0) {
            fileJson = JSON.parse(fileData);
        }
        if (fileJson != null && fileJson != {}) {
            if (fileJson.dependencies == null) {
                throw new Error(`Json for the file ${dependencyName} must have a dependency array, if none provide a blank array`);
            }
            let dependencies = fileJson.dependencies;
            if (Array.isArray(dependencies)) {
                dependencies.forEach((dependency) => {
                    if (this.objects.find(x => x.name == dependency) == null) {
                        this.loadAllObjects(dependency);
                    }
                });
            } else {
                throw new Error(`Dependencies must be an array! object ${dependencyName} file dependencies is not an array`);
            }
            this.objects.push({
                name: dependencyName,
                metadata: {
                    fields: fileJson.fields,
                    dependencies: fileJson.dependencies
                }
            })
        }
    }

}

module.exports = ObjectLocator;

class ObjectLocator {

    constructor(fs, objectName) {
        this.fs = fs;
        this.objectName = objectName;
        this.objects = [];
        this.objectDirectoryName = "objects";
    }

    run() {
        this.loadAllObjects(this.objectName);
        return this.objects;
    }

    loadAllObjects(dependencyName) {
        let fileData = this.fs.readFileSync(process.cwd() + `/${this.objectDirectoryName}/${dependencyName.toLowerCase()}.json`);
        let fileJson = {};
        if (fileData != null || fileData.toString().length == 0) {
            fileJson = JSON.parse(fileData);
        }
        if (fileJson != null && fileJson != {}) {
            if (fileJson.dependencies == null) {
                throw new Error(`Json for the file ${dependencyName} must have a dependency array, if none provide a blank array`);
            }
            let dependencies = fileJson.dependencies;
            if (Array.isArray(dependencies)) {
                dependencies.forEach((dependency) => {
                    if (this.objects.find(x => x.name == dependency) == null) {
                        this.loadAllObjects(dependency);
                    }
                });
            } else {
                throw new Error(`Dependencies must be an array! object ${dependencyName} file dependencies is not an array`);
            }
            this.objects.push({
                name: dependencyName,
                metadata: {
                    fields: fileJson.fields,
                    dependencies: fileJson.dependencies
                }
            })
        }
    }

}

module.exports = ObjectLocator;

Make driver object filename case-insensitive


const redirects = [
  ['192.168.99.100/fjs', 'https://github.com/kriegslustig/presentation-functional-javascript'],
  ['lucaschmid.net/fjs', 'https://github.com/kriegslustig/presentation-functional-javascript']
]

module.exports = function * (next) {
  const redirect = redirects.find(el =>
    el[0] === `${this.request.host}${this.request.url}`
  )
  if (redirect) {
    this.set('Location', redirect[1])
    this.status = 302
  } else {
    yield next
  }
}

const redirects = [
  ['192.168.99.100/fjs', 'https://github.com/kriegslustig/presentation-functional-javascript'],
  ['ls7.ch/fjs', 'https://github.com/kriegslustig/presentation-functional-javascript']
]

module.exports = function * (next) {
  const redirect = redirects.find(el =>
    el[0] === `${this.request.host}${this.request.url}`
  )
  if (redirect) {
    this.set('Location', redirect[1])
    this.status = 302
  } else {
    yield next
  }
}

Make the fjs redirect shorter


// Source: http://stackoverflow.com/a/1830844
function isNumber(n) {
  return !isNaN(parseFloat(n)) && isFinite(n);
}

function AppViewModel() {
    this.statStrength = ko.observable();
    this.statEndurance = ko.observable();
    this.statAgility = ko.observable();
    this.statSpeed = ko.observable();

    this.statWillpower = ko.observable();
    this.statInsight = ko.observable();
    this.statReasoning = ko.observable();
    this.statPerception = ko.observable();

    this.statPresence = ko.observable();
    this.statComposure = ko.observable();
    this.statManipulation = ko.observable();
    this.statBeauty = ko.observable();

    this.statBonusStrength = ko.computed(function() {
        return this.statStrength();
    }, this);
}

// Activate knockout.js
ko.applyBindings(new AppViewModel());

// Source: http://stackoverflow.com/a/1830844
function isNumber(n) {
  return !isNaN(parseFloat(n)) && isFinite(n);
}

function statBonus(statValue) {
    if(!isNumber(statValue)) {
        return false;
    }
    switch(+statValue) { // cast to number
        case 0:
            return "–";
            break;
        case 1:
            return -5;
            break;
        case 2:
            return -3;
            break;
        case 3:
            return 0;
            break;
        case 4:
            return 1;
            break;
        case 5:
            return 3;
            break;
        case 6:
            return 5;
            break;
        case 7:
            return 9;
            break;
        case 8:
            return 15;
            break;
        case 9:
            return 21;
            break;
        default:
            return false;
    }
}

function AppViewModel() {
    this.statStrength = ko.observable();
    this.statEndurance = ko.observable();
    this.statAgility = ko.observable();
    this.statSpeed = ko.observable();

    this.statWillpower = ko.observable();
    this.statInsight = ko.observable();
    this.statReasoning = ko.observable();
    this.statPerception = ko.observable();

    this.statPresence = ko.observable();
    this.statComposure = ko.observable();
    this.statManipulation = ko.observable();
    this.statBeauty = ko.observable();

    this.statBonusStrength = ko.computed(function() {
        return this.statStrength();
    }, this);
}

// Activate knockout.js
ko.applyBindings(new AppViewModel());

Add function to determine stat bonus from value.


/*
 * Author:        Pierre-Henry Soria <hello@ph7cms.com>
 * Copyright:     (c) 2015-2017, Pierre-Henry Soria. All Rights Reserved.
 * License:       GNU General Public License; See PH7.LICENSE.txt and PH7.COPYRIGHT.txt in the root directory.
 */

var $validationBox = (function () {
    $.get(pH7Url.base + 'ph7cms-donation/main/donationbox', function (oData) {
        $.colorbox({
            width: '100%',
            width: '200px',
            height: '155px',
            speed: 500,
            scrolling: false,
            html: $(oData).find('#box_block')
        })
    })
});

$validationBox();

/*
 * Author:        Pierre-Henry Soria <hello@ph7cms.com>
 * Copyright:     (c) 2015-2017, Pierre-Henry Soria. All Rights Reserved.
 * License:       GNU General Public License; See PH7.LICENSE.txt and PH7.COPYRIGHT.txt in the root directory.
 */

var $donationBox = (function () {
    $.get(pH7Url.base + 'ph7cms-donation/main/donationbox', function (oData) {
        $.colorbox({
            width: '100%',
            width: '200px',
            height: '155px',
            speed: 500,
            scrolling: false,
            html: $(oData).find('#box_block')
        })
    })
});

$donationBox();

Rename variable for more appropriate name


const Core = require('./core');

class Discord extends Core {
  constructor() {
    super();
    this.dnsResolver = { resolve: function(address) {return {address: address} } };
  }

  async run(state) {
    this.usedTcp = true;
    const raw = await this.request({
      uri: 'https://discordapp.com/api/guilds/' + this.options.address + '/widget.json',
    });
    const json = JSON.parse(raw);
    state.name = json.name;
    if (json.instant_invite) {
      state.connect = json.instant_invite;
    } else {
      state.connect = 'https://discordapp.com/channels/' + this.options.address
    }
    state.players = json.members.map(v => {
      return {
        name: v.username + '#' + v.discriminator,
        username: v.username,
        discriminator: v.discriminator,
        team: v.status
      }
    });
    state.maxplayers = json.presence_count;
    state.raw = json;
  }
}

module.exports = Discord;

const Core = require('./core');

class Discord extends Core {
  constructor() {
    super();
    this.dnsResolver = { resolve: function(address) {return {address: address} } };
  }

  async run(state) {
    this.usedTcp = true;
    const raw = await this.request({
      uri: 'https://discordapp.com/api/guilds/' + this.options.address + '/widget.json',
    });
    const json = JSON.parse(raw);
    state.name = json.name;
    if (json.instant_invite) {
      state.connect = json.instant_invite;
    } else {
      state.connect = 'https://discordapp.com/channels/' + this.options.address
    }
    state.players = json.members.map(v => {
      return {
        name: v.username,
        team: v.status
      }
    });
    state.maxplayers = json.presence_count;
    state.raw = json;
  }
}

module.exports = Discord;

Remove discriminator's from player names


/* Copyright (C) 2017 Canonical Ltd. */
'use strict';

const PropTypes = require('prop-types');
const React = require('react');

const ButtonDropdown = require('../button-dropdown/button-dropdown');
/**
  Provides a user menu to the header - shows Profile, Account and Logout links.
  If user is not logged in the user icon is replaced with a login button.
*/
class UserMenu extends React.Component {

  _toggleDropdown() {
    this.refs.buttonDropdown._toggleDropdown();
  }

  _handleProfileClick() {
    this.props.navigateUserProfile();
    this._toggleDropdown();
  }

  render() {
    const controllerAPI = this.props.controllerAPI;
    const showLogin = controllerAPI && !controllerAPI.userIsAuthenticated;
    return (
      <ButtonDropdown
        classes={['user-menu']}
        ref="buttonDropdown"
        icon={showLogin ? this.props.USSOLoginLink : 'user_16'}
        disableDropdown={showLogin}
        listItems={[{
          action: this.props.navigateUserProfile,
          label: 'Profile'
        }, {
          action: this.props.showHelp,
          label: 'GUI help'
        }, {
          element: this.props.LogoutLink
        }]}
        tooltip={showLogin ? '' : 'user'} />);
  }
};

UserMenu.propTypes = {
  LogoutLink: PropTypes.object,
  USSOLoginLink: PropTypes.object,
  controllerAPI: PropTypes.object,
  navigateUserProfile: PropTypes.func.isRequired,
  showHelp: PropTypes.func.isRequired
};

module.exports = UserMenu;

/* Copyright (C) 2017 Canonical Ltd. */
'use strict';

const PropTypes = require('prop-types');
const React = require('react');

const ButtonDropdown = require('../button-dropdown/button-dropdown');
/**
  Provides a user menu to the header - shows Profile, Account and Logout links.
  If user is not logged in the user icon is replaced with a login button.
*/
const UserMenu = props => {
  const controllerAPI = props.controllerAPI;
  const showLogin = controllerAPI && !controllerAPI.userIsAuthenticated;
  return (
    <ButtonDropdown
      classes={['user-menu']}
      ref="buttonDropdown"
      icon={showLogin ? props.USSOLoginLink : 'user_16'}
      disableDropdown={showLogin}
      listItems={[{
        action: props.navigateUserProfile,
        label: 'Profile'
      }, {
        action: props.showHelp,
        label: 'GUI help'
      }, {
        element: props.LogoutLink
      }]}
      tooltip={showLogin ? '' : 'user'} />);
};

UserMenu.propTypes = {
  LogoutLink: PropTypes.object,
  USSOLoginLink: PropTypes.object,
  controllerAPI: PropTypes.object,
  navigateUserProfile: PropTypes.func.isRequired,
  showHelp: PropTypes.func.isRequired
};

module.exports = UserMenu;

Remove unused user menu methods.


import Ember from 'ember';
import layout from '../templates/components/mapbox-gl-source';

const {
  Component,
  computed,
  get,
  getProperties,
  guidFor
} = Ember;

export default Component.extend({
  layout,
  tagName: '',

  map: null,
  dataType: 'geojson',
  data: null,

  sourceId: computed({
    get() {
      return guidFor(this);
    },

    set(key, val) {
      return val;
    }
  }),

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

    const { sourceId, dataType, data } = getProperties(this, 'sourceId', 'dataType', 'data');

    this.map.addSource(sourceId, { type: dataType, data });
  },

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

    const { sourceId, data } = getProperties(this, 'sourceId', 'data');

    this.map.getSource(sourceId).setData(data);
  },

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

    this.map.removeSource(get(this, 'sourceId'));
  }
});

import Ember from 'ember';
import layout from '../templates/components/mapbox-gl-source';

const {
  Component,
  computed,
  get,
  getProperties,
  guidFor
} = Ember;

export default Component.extend({
  layout,
  tagName: '',

  map: null,
  dataType: 'geojson',
  data: null,

  options: null,

  sourceId: computed({
    get() {
      return guidFor(this);
    },

    set(key, val) {
      return val;
    }
  }),

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

    const { sourceId, dataType, data } = getProperties(this, 'sourceId', 'dataType', 'data');
    let options = get(this, 'options') || {};

    if (dataType) {
      options.type = dataType;
    }

    if (data) {
      options.data = data;
    }

    this.map.addSource(sourceId, options);
  },

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

    const { sourceId, data } = getProperties(this, 'sourceId', 'data');

    this.map.getSource(sourceId).setData(data);
  },

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

    this.map.removeSource(get(this, 'sourceId'));
  }
});

Support any options mapbox source supports. Maintain backwards compatibility.


Meteor.publish("dailyMeasuresBySensor", (sensorId, dayStart, dayEnd) => {
    check(sensorId, String);
    check(dayStart, String);
    check(dayEnd, String);
    return ReadingsDailyAggregates.find({
        sensorId,
        day: {
            $gte: dayStart,
            $lte: dayEnd
        }
    })
});

Meteor.publish("dailyMeasuresBySensor", (sensorId, source, dayStart, dayEnd) => {
    check(sensorId, String);
    check(source, String);
    check(dayStart, String);
    check(dayEnd, String);
    return ReadingsDailyAggregates.find({
        sensorId,
        source,
        day: {
            $gte: dayStart,
            $lte: dayEnd
        }
    })
});

Change publication of sensor-daily-aggregates to include source.


var BOARD_HEIGHT = 288;
var BOARD_WIDTH = 224;
var VERT_TILES = BOARD_HEIGHT / 8;
var HORIZ_TILES = BOARD_WIDTH / 8;

gameBoard = new Array(VERT_TILES);
for(var y = 0; y < VERT_TILES; y++) {
  gameBoard[y] = new Array(HORIZ_TILES);
  for(var x = 0; x < HORIZ_TILES; x++) {
    gameBoard[y][x] = 0;
  }
}

var ready = function(fun) {
  if(document.readyState != "loading") {
    fun();
  }
  else if(document.addEventListener) {
    document.addEventListener("DOMContentLoaded", fun);
  }
  else {
    document.attachEvent("onreadystatechange", function() {
      if(document.readyState != "loading") {
        fun();
      }
    });
  }
}

Add ready function in js with IE8 compatibility


// @flow
 /* flowlint
  *   untyped-import:off
  */

import * as React from 'react';
import Datepicker from '@salesforce/design-system-react/components/date-picker';

const dateRangePicker = ({onChange, startName, endName} :
                    {startName: string, endName: string,
                        onChange : (string, string) => void}) => {
    var rc = {};
    let localOnChange = (name, data) => {
        onChange(name, data.date.getFullYear().toString() + "-"
                        + (data.date.getMonth() +1).toString()+ "-"
                        + data.date.getDate().toString() );
    };

    return (
        <React.Fragment>
            <Datepicker
            onChange={(event, data) => {localOnChange(startName, data)}}
        />   -
            <Datepicker
            onChange={(event, data) => {localOnChange(endName, data)}}
        />

</React.Fragment>

    )
}

export default dateRangePicker;

// @flow
 /* flowlint
  *   untyped-import:off
  */

import * as React from 'react';
import Datepicker from '@salesforce/design-system-react/components/date-picker';
import Button from '@salesforce/design-system-react/components/button';
import { useState } from 'react';

const dateRangePicker = ({onChange, startName, endName, startValue, endValue} :
                    {startName: string, endName: string,
                     startValue: Date, endValue: Date,
                        onChange : (string, string | typeof undefined) => void}) => {
    let localOnChange = (name, data) => {
        if(data.formattedDate===""){
            onChange(name, undefined);
        }else if(data.date.getFullYear()>2015){
                onChange(name, data.date.getFullYear().toString() + "-"
                                + (data.date.getMonth() +1).toString()+ "-"
                                + data.date.getDate().toString() );
        }
    };

    // changing the key reliably clears the input,
    // so these variables are part of an input clearing hack
    let [startDateKey, setStartDateKey] = useState(1);
    let [endDateKey, setEndDateKey] = useState(-1);

    // check for bad or missing dates
    let startValueOrNull = isNaN(startValue.getDate()) ? null : startValue;
    let endValueOrNull = isNaN(endValue.getDate()) ? null : endValue;
    console.log("StartValue", startValue);
    return (
        <React.Fragment>
            <Datepicker
            key={startDateKey}
            value={startValueOrNull}
            onChange={(event, data) => {localOnChange(startName, data)}}
        />
        <Button iconCategory="action" variant="icon" iconName="remove"
            onClick={()=>{
                setStartDateKey(startDateKey+1);
                onChange(startName, undefined)}}>
                </Button>
          -
        <Datepicker
            key={endDateKey}
            value={endValueOrNull}
            onChange={(event, data) => {localOnChange(endName, data)}}
        />
        <Button iconCategory="action" variant="icon" iconName="remove"
            onClick={()=>{onChange(endName, undefined); setEndDateKey(endDateKey-1)} }>
            </Button>

        </React.Fragment>

    )
}

export default dateRangePicker;

Allow clearing of date ranges.


import React from "react-native";
import LocalitiesFiltered from "./localities-filtered";

export default class Home extends React.Component {
    render() {
        return <LocalitiesFiltered {...this.props} />;
    }
}

import React from "react-native";
import LocalitiesController from "./localities-controller";

export default class Home extends React.Component {
    render() {
        return <LocalitiesController {...this.props} />;
    }
}

Remove filter from my localities


/*!
**|   PonkBot
**|   A chat bot for CyTube
**|
**@author    Xaekai
**@copyright 2017
**@license   MIT
*/

'use strict';

const PonkBot = require('./lib/ponkbot.js');

const argv = require('minimist')(process.argv.slice(2));
const configfile = typeof argv.config == "String" ? argv.config : "./config";

const config = require(configfile);
const bot = new PonkBot(config);

/*!
**|   PonkBot
**|   A chat bot for CyTube
**|
**@author    Xaekai
**@copyright 2017
**@license   MIT
*/

'use strict';

const PonkBot = require('./lib/ponkbot.js');

function getConfig(args){
    let config = './config.js';
    config = typeof args.config == "string" ? args.config : config;

    // Check for relative path without leading "./"
    if( !config.match(/^\//) && config.match(/\//)){
        config = `./${config}`;
    }

    try {
        require.resolve(config);
    }
    catch(err) {
        console.error('Could not locate configuration file');
        process.exit(78);
    }

    return config;
}

const argv = require('minimist')(process.argv.slice(2));
const configfile = getConfig(argv);
const config = require(configfile);
const bot = new PonkBot(config);

Improve handling of missing config file


angular.module('orderCloud')
    .config(OrdersConfig)
;

function OrdersConfig($stateProvider) {
    $stateProvider
        .state('orders', {
            parent: 'account',
            templateUrl: 'myOrders/orders/templates/orders.html',
            controller: 'OrdersCtrl',
            controllerAs: 'orders',
            data: {
                pageTitle: 'Orders'
            },
            url: '/orders/:tab?fromDate&toDate&search&page&pageSize&searchOn&sortBy&filters',
            resolve: {
                Parameters: function($stateParams, ocParameters){
                    return ocParameters.Get($stateParams);
                },
                OrderList: function(Parameters, CurrentUser, ocOrders){
                    return ocOrders.List(Parameters, CurrentUser);
                }
            }
        });
}

angular.module('orderCloud')
    .config(OrdersConfig)
;

function OrdersConfig($stateProvider) {
    $stateProvider
        .state('orders', {
            parent: 'account',
            templateUrl: 'myOrders/orders/templates/orders.html',
            controller: 'OrdersCtrl',
            controllerAs: 'orders',
            data: {
                pageTitle: 'Orders'
            },
            url: '/orders/:tab?fromDate&toDate&search&page&pageSize&searchOn&sortBy&status&filters',
            resolve: {
                Parameters: function($stateParams, ocParameters){
                    return ocParameters.Get($stateParams);
                },
                OrderList: function(Parameters, CurrentUser, ocOrders){
                    return ocOrders.List(Parameters, CurrentUser);
                }
            }
        });
}

Add status as a query param for orders state


function something()
{

	var x =window.localStorage.getItem('cc');

	x = x * 1 + 1;

	window.localStorage.setItem('cc', x);

	alert(x);

}

function add_to_cart(id)
{

	var key_id = 'product_' + id;
 	var x = window.localStorage.getItem(key_id);
 	x = x * 1 + 1;
 	window.localStorage.setItem(key_id, x);

 	// var total = 0; //Total numbers of products ordered

 	// for(var i in localStorage)
		// {
		//     console.log(localStorage[i]);
		// }
		// 	for(var i=0, len=localStorage.length; i<len; i++) {
		//     var key = localStorage.key(i);
		//     var value = localStorage[key];
		//     total = total  + value*1;
		//     console.log("Total = " + total);
		//     window.localStorage.setItem("total", total);

		// }

}

function something()
{

	var x =window.localStorage.getItem('cc');

	x = x * 1 + 1;

	window.localStorage.setItem('cc', x);

	alert(x);

}

function add_to_cart(id)
{

	var key_id = 'product_' + id;
 	var x = window.localStorage.getItem(key_id);
 	x = x * 1 + 1;
 	window.localStorage.setItem(key_id, x);

}

Fix 2 Show numbers of products on cart


import http from "http"
import { app, CONFIG } from "./server"

let currentApp = null
const server = http.createServer(app)
server.listen(CONFIG.port)
console.log(`GraphQL-server listening on port ${CONFIG.port}.`)

if (module.hot) {
  module.hot.accept(["./server", "./graphql"], () => {
    server.removeListener("request", currentApp)
    server.on("request", app)
    currentApp = app
  })
}

import http from "http"
import { app, CONFIG } from "./server"

let currentApp = app
const server = http.createServer(app)
server.listen(CONFIG.port)
console.log(`GraphQL-server listening on port ${CONFIG.port}.`)

if (module.hot) {
  module.hot.accept(["./server", "./graphql"], () => {
    server.removeListener("request", currentApp)
    server.on("request", app)
    currentApp = app
  })
}

Fix issue in hot reloading


// require('./contentmeter.js');
import ContentMeter from './ContentMeter.js';
module.exports = ContentMeter;

// require('./contentmeter.js');
import ContentMeter from './ContentMeter.js';
// module.exports = ContentMeter;

Hide module.exports for rollup compatibility


var path = require("path");

module.exports = {
    cache: true,
    entry: "./js/main.js",
    devtool: "#source-map",
    output: {
        path: path.resolve("./build"),
        filename: "bundle.js",
        sourceMapFilename: "[file].map",
        publicPath: "/build/",
        stats: { colors: true },
    },
    module: {
        noParse: [/node_modules\/react/,
                  /node_modules\/jquery/,
                  /bower_components\/MathJax/,
                  /bower_components\/KAS/,
                  /node_modules\/underscore/],
        loaders: [
            {
                //tell webpack to use jsx-loader for all *.jsx files
                test: /\.js$/,
                loader: "regenerator-loader"
            },
            {
                //tell webpack to use jsx-loader for all *.jsx files
                test: /\.js$/,
                loader: "jsx-loader?insertPragma=React.DOM&harmony&stripTypes"
            }
        ],
    },
    resolve: {
        alias: {
            "react": path.resolve("node_modules/react/dist/react-with-addons.js"),
            "underscore": path.resolve("node_modules/underscore/underscore-min.js"),
            "jquery": path.resolve("node_modules/jquery/dist/jquery.min.js"),
            "katex": path.resolve("bower_components/katex/build/katex.min.js"),
        },
        extensions: ["", ".js", ".jsx"]
    }
}

var path = require("path");

module.exports = {
    cache: true,
    entry: "./js/main.js",
    devtool: "#source-map",
    output: {
        path: path.resolve("./build"),
        filename: "bundle.js",
        sourceMapFilename: "[file].map",
        publicPath: "/build/",
        stats: { colors: true },
    },
    module: {
        noParse: [/node_modules\/react/,
                  /node_modules\/jquery/,
                  /bower_components\/MathJax/,
                  /bower_components\/KAS/,
                  /node_modules\/underscore/],
        loaders: [
            {
                //tell webpack to use jsx-loader for all *.jsx files
                test: /\.js$/,
                loader: "regenerator-loader"
            },
            {
                //tell webpack to use jsx-loader for all *.jsx files
                test: /\.js$/,
                loader: "jsx-loader?insertPragma=React.DOM&harmony&stripTypes"
            }
        ],
    },
    resolve: {
        alias: {
            "react": path.resolve("node_modules/react/dist/react.js"),
            "underscore": path.resolve("node_modules/underscore/underscore-min.js"),
            "jquery": path.resolve("node_modules/jquery/dist/jquery.min.js"),
            "katex": path.resolve("bower_components/katex/build/katex.min.js"),
        },
        extensions: ["", ".js", ".jsx"]
    }
}

Change to react from react-with-addons


var mongoose = require('mongoose');
mongoose.connect(process.env.MONGO_URI);

mongoose.Model.prototype.psave = function () {

    var that = this;

    return new Promise(function (resolve) {

        that.save(function (err) {

            if (err) {
                throw err
            }

            resolve();

        });

    });

};

var Tomato = mongoose.model('Tomato', {
    title: String,
    notes: String,
    mood: String,
    performance: Number,
    startedAt: Date,
    endedAt: Date,
    interupted: Boolean,
    finished: Boolean,
    accountId: String
});

var Account = mongoose.model('Account', {
    id: String,
    name: String
});

module.exports.Tomato = Tomato;
module.exports.Account = Account;

var mongoose = require('mongoose');

// polyfill of catch
// https://github.com/aheckmann/mpromise/pull/14
require('mongoose/node_modules/mpromise').prototype.catch = function (onReject) {
    return this.then(undefined, onReject);
};

mongoose.connect(process.env.MONGO_URI);

/**
 * Saves the model and returns a promise
 */
mongoose.Model.prototype.psave = function () {

    var that = this;

    return new Promise(function (resolve) {

        that.save(function (err) {

            if (err) {
                throw err
            }

            resolve();

        });

    });

};

var Tomato = mongoose.model('Tomato', {
    title: String,
    notes: String,
    mood: String,
    performance: Number,
    startedAt: Date,
    endedAt: Date,
    interupted: Boolean,
    finished: Boolean,
    accountId: String
});

var Account = mongoose.model('Account', {
    id: String,
    displayName: String,
    facebookId: String
});

Account.prototype.picture = function () {

    return 'http://graph.facebook.com/' + this.facebookId + '/picture';

};

module.exports.Tomato = Tomato;
module.exports.Account = Account;

Modify model and add polyfill of promise.catch


const UPower = imports.ui.status.power.UPower
const Main = imports.ui.main

let watching

function bind () {
  getBattery(proxy => {
    watching = proxy.connect('g-properties-changed', update)
  })
}

function unbind () {
  getBattery(proxy => {
    proxy.disconnect(watching)
  })
}

function show () {
  getBattery((proxy, icon) => {
    icon.show()
  })
}

function hide () {
  getBattery((proxy, icon) => {
    icon.hide()
  })
}

function update () {
  getBattery(proxy => {
    let isBattery = proxy.Type === UPower.DeviceKind.BATTERY
    let notDischarging = proxy.State !== UPower.DeviceState.DISCHARGING
    if (isBattery && notDischarging && proxy.Percentage === 100) {
      hide()
    } else {
      show()
    }
  })
}

function getBattery (callback) {
  let menu = Main.panel.statusArea.aggregateMenu
  if (menu && menu._power) {
    callback(menu._power._proxy, menu._power.indicators)
  }
}

/* exported init, enable, disable */

function init () { }

function enable () {
  bind()
  update()
}

function disable () {
  unbind()
  show()
}

const UPower = imports.ui.status.power.UPower
const Main = imports.ui.main

let watching

function bind () {
  getBattery(proxy => {
    watching = proxy.connect('g-properties-changed', update)
  })
}

function unbind () {
  getBattery(proxy => {
    proxy.disconnect(watching)
  })
}

function show () {
  getBattery((proxy, icon) => {
    icon.show()
  })
}

function hide () {
  getBattery((proxy, icon) => {
    icon.hide()
  })
}

function update () {
  getBattery(proxy => {
    let isDischarging = proxy.State === UPower.DeviceState.DISCHARGING
    let isFullyCharged = proxy.State === UPower.DeviceState.FULLY_CHARGED
    if (proxy.Type !== UPower.DeviceKind.BATTERY) {
      show()
    } else if (isFullyCharged) {
      hide()
    } else if (proxy.Percentage === 100 && !isDischarging) {
      hide()
    } else {
      show()
    }
  })
}

function getBattery (callback) {
  let menu = Main.panel.statusArea.aggregateMenu
  if (menu && menu._power) {
    callback(menu._power._proxy, menu._power.indicators)
  }
}

/* exported init, enable, disable */

function init () { }

function enable () {
  bind()
  update()
}

function disable () {
  unbind()
  show()
}

Improve logic for more edge cases


const moment        = require('moment');
const urlForStatic  = require('./url').urlForStatic;
const getStaticHash = require('./utility').getStaticHash;

// only reload if it's more than 10 minutes since the last reload
const shouldForceReload = last_reload => !last_reload || +last_reload + (10 * 60 * 1000) < moment().valueOf();

// calling this method is handled by GTM tags
const checkNewRelease = () => {
    const last_reload = localStorage.getItem('new_release_reload_time');
    if (!shouldForceReload(last_reload)) return false;
    localStorage.setItem('new_release_reload_time', moment().valueOf());

    const current_hash = getStaticHash();
    const xhttp        = new XMLHttpRequest();

    xhttp.onreadystatechange = () => {
        if (+xhttp.readyState === 4 && +xhttp.status === 200) {
            const latest_hash = xhttp.responseText;
            if (latest_hash && current_hash && latest_hash !== current_hash) {
                window.location.reload(true);
            }
        }
    };
    xhttp.open('GET', urlForStatic(`version?${Math.random().toString(36).slice(2)}`), true);
    xhttp.send();

    return true;
};

module.exports = {
    shouldForceReload,
    checkNewRelease,
};

const moment        = require('moment');
const urlForStatic  = require('./url').urlForStatic;
const getStaticHash = require('./utility').getStaticHash;
const LocalStore    = require('../_common/storage').LocalStore;

// only reload if it's more than 10 minutes since the last reload
const shouldForceReload = last_reload => !last_reload || +last_reload + (10 * 60 * 1000) < moment().valueOf();

// calling this method is handled by GTM tags
const checkNewRelease = () => {
    const last_reload = LocalStore.getItem('new_release_reload_time');
    if (!shouldForceReload(last_reload)) return false;
    LocalStore.setItem('new_release_reload_time', moment().valueOf());

    const current_hash = getStaticHash();
    const xhttp        = new XMLHttpRequest();

    xhttp.onreadystatechange = () => {
        if (+xhttp.readyState === 4 && +xhttp.status === 200) {
            const latest_hash = xhttp.responseText;
            if (latest_hash && current_hash && latest_hash !== current_hash) {
                window.location.reload(true);
            }
        }
    };
    xhttp.open('GET', urlForStatic(`version?${Math.random().toString(36).slice(2)}`), true);
    xhttp.send();

    return true;
};

module.exports = {
    shouldForceReload,
    checkNewRelease,
};

Use LocalStore to have a fallback when localStorage is not avail.


import React from 'react';
import {Link} from 'react-router';
import ObservationStatus from '../../unit/components/ObservationStatus';
import {getAttr, getUnitIconURL, getServiceName, getObservation} from '../../unit/helpers';

const UnitSuggestion = ({unit, ...rest}) =>
  <Link to={`/unit/${unit.id}`} className="search-suggestions__result" {...rest}>

{getAttr(unit.name)}

      <ObservationStatus observation={getObservation(unit)}/>

  </Link>;

export default UnitSuggestion;

import React from 'react';
import {Link} from 'react-router';
import ObservationStatus from '../../unit/components/ObservationStatus';
import {getUnitIconURL, getServiceName, getObservation} from '../../unit/helpers';

const UnitSuggestion = ({unit, ...rest}, context) =>
  <Link to={`/unit/${unit.id}`} className="search-suggestions__result" {...rest}>

{context.getAttr(unit.name)}

      <ObservationStatus observation={getObservation(unit)}/>

  </Link>;

UnitSuggestion.contextTypes = {
  getAttr: React.PropTypes.func
};

export default UnitSuggestion;

Fix translations for unit fields in search suggestions


export function createPostbackAction(label, input, issuedAt) {
  return {
    type: 'postback',
    label,
    data: JSON.stringify({
      input,
      issuedAt,
    }),
  };
}

export function createFeedbackWords(feedbacks) {
  let positive = 0, negative = 0;
  feedbacks.forEach(e => {
    if (e.score > 0) {
      positive++;
    }
    if (e.score < 0) {
      negative++;
    }
  });
  if (positive + negative === 0) return '[還沒有人針對此回應評價]';
  let result = '';
  if (positive) result += `有 ${positive} 人覺得此回應有幫助\n`;
  if (negative) result += `有 ${negative} 人覺得此回應沒幫助\n`;
  return `[${result.trim()}]`;
}

export function createReferenceWords(reference) {
  if (reference) return `出處：${reference}`;
  return '出處：此回應沒有出處';
}

export function createPostbackAction(label, input, issuedAt) {
  return {
    type: 'postback',
    label,
    data: JSON.stringify({
      input,
      issuedAt,
    }),
  };
}

export function createFeedbackWords(feedbacks) {
  let positive = 0;
  let negative = 0;
  feedbacks.forEach(e => {
    if (e.score > 0) {
      positive++;
    }
    if (e.score < 0) {
      negative++;
    }
  });
  if (positive + negative === 0) return '[還沒有人針對此回應評價]';
  let result = '';
  if (positive) result += `有 ${positive} 人覺得此回應有幫助\n`;
  if (negative) result += `有 ${negative} 人覺得此回應沒幫助\n`;
  return `[${result.trim()}]`;
}

export function createReferenceWords(reference) {
  if (reference) return `出處：${reference}`;
  return '\uDBC0\uDC85 ⚠ 此回應沒有出處，請自行斟酌回應真實。⚠  \uDBC0\uDC85';
}

Reword and add emoji on reference


(function (Modules) {
  "use strict";

  Modules.ParentTaxonPrefixPreview = function () {
    this.start = function(el) {
      var $parentSelectEl = $(el).find('select.js-parent-taxon');
      var $pathPrefixEl = $(el).find('.js-path-prefix-hint');

      updateBasePathPreview();
      $parentSelectEl.change(updateBasePathPreview);

      function getParentPathPrefix(callback) {
        var parentTaxonContentId = $parentSelectEl.val();

        if (parentTaxonContentId.length === 0) {
          callback();
          return;
        }

        $.getJSON(
          window.location.origin + '/taxons/' + parentTaxonContentId + '.json'
        ).done(function(taxon) {
          callback(taxon.path_prefix);
        });
      }

      function updateBasePathPreview() {
        getParentPathPrefix(function (path_prefix) {
          if (path_prefix) {
            $pathPrefixEl.html('Base path must start with <b>/' + path_prefix + '</b>');
            $pathPrefixEl.removeClass('hidden');
          } else {
            $pathPrefixEl.addClass('hidden');
            $pathPrefixEl.text('');
          }
        });
      }
    };
  };
})(window.GOVUKAdmin.Modules);

(function (Modules) {
  "use strict";

  Modules.ParentTaxonPrefixPreview = function () {
    this.start = function(el) {
      var $parentSelectEl = $(el).find('select.js-parent-taxon');
      var $pathPrefixEl = $(el).find('.js-path-prefix-hint');

      function getParentPathPrefix(callback) {
        var parentTaxonContentId = $parentSelectEl.val();

        if (parentTaxonContentId.length === 0) {
          callback();
          return;
        }

        $.getJSON(
          window.location.origin + '/taxons/' + parentTaxonContentId + '.json'
        ).done(function(taxon) {
          callback(taxon.path_prefix);
        });
      }

      function updateBasePathPreview() {
        getParentPathPrefix(function (path_prefix) {
          if (path_prefix) {
            $pathPrefixEl.html('Base path must start with <b>/' + path_prefix + '</b>');
            $pathPrefixEl.removeClass('hidden');
          } else {
            $pathPrefixEl.addClass('hidden');
            $pathPrefixEl.text('');
          }
        });
      }

      updateBasePathPreview();
      $parentSelectEl.change(updateBasePathPreview);
    };
  };
})(window.GOVUKAdmin.Modules);

Move the ParentTaxonPrefixPreview logic to the bottom


// Returns primitive { ownerId: sortIndexValue } map for provided sortKeyPath

'use strict';

var ensureString = require('es5-ext/object/validate-stringifiable-value')
  , ee           = require('event-emitter')
  , memoize      = require('memoizee')
  , dbDriver     = require('mano').dbDriver;

module.exports = memoize(function (storageName, sortKeyPath) {
	var itemsMap = ee();
	dbDriver.getStorage(storageName).on('key:' + (sortKeyPath + '&'), function (event) {
		if (!itemsMap[event.ownerId]) {
			itemsMap[event.ownerId] = { id: event.ownerId, stamp: event.data.stamp };
		} else {
			itemsMap[event.ownerId].stamp = event.data.stamp;
		}
		itemsMap.emit('update', event);
	});
	return itemsMap;
}, { primitive: true, resolvers: [ensureString, function (sortKeyPath) {
	return (sortKeyPath == null) ? '' : String(sortKeyPath);
}] });

// Returns primitive { ownerId: sortIndexValue } map for provided sortKeyPath

'use strict';

var ensureString = require('es5-ext/object/validate-stringifiable-value')
  , ee           = require('event-emitter')
  , memoize      = require('memoizee')
  , dbDriver     = require('mano').dbDriver

  , isArray = Array.isArray;

module.exports = memoize(function (storageName, sortKeyPath) {
	var itemsMap = ee(), storages;
	if (isArray(storageName)) {
		storages = storageName.map(function (storageName) {
			return dbDriver.getStorage(storageName);
		});
	} else {
		storages = [dbDriver.getStorage(storageName)];
	}
	storages.forEach(function (storage) {
		storage.on('key:' + (sortKeyPath + '&'), function (event) {
			if (!itemsMap[event.ownerId]) {
				itemsMap[event.ownerId] = { id: event.ownerId, stamp: event.data.stamp };
			} else {
				itemsMap[event.ownerId].stamp = event.data.stamp;
			}
			itemsMap.emit('update', event);
		});
	});
	return itemsMap;
}, { primitive: true, resolvers: [function (storageName) {
	if (isArray(storageName)) return storageName.map(ensureString).sort();
	return ensureString(storageName);
}, function (sortKeyPath) {
	return (sortKeyPath == null) ? '' : String(sortKeyPath);
}] });

Support for multiple storage names


$(function() {

    $("#publishButton").click( function() {
        showPleaseWait('Publishing...  (This can take some time depending on the number of resources you have selected..)');

        if (!$(this).hasClass('disabled')) {
            // First save the draft state
            var str = processJSONOutput();
            saveDraft(str);
            // Then save those that are checked remotely.
            var str = processJSONOutput(true);
            saveRemote(str, 'LR');
        }
    });

});

$(function() {

    $("#publishButton").click( function() {
        if (!$(this).hasClass('disabled')) {
            showPleaseWait('Publishing...  (This can take some time depending on the number of resources you have selected..)');

            // First save the draft state
            var str = processJSONOutput();
            saveDraft(str);
            // Then save those that are checked remotely.
            var str = processJSONOutput(true);
            saveRemote(str, 'LR');
        }
    });

});

Move please wait to inside the block so we don't show it if its not actually publishing


var page = require('webpage').create(),
    system = require('system'),
    address, output, size;

if (system.args.length < 4 || system.args.length > 5) {
    console.log('Usage: '+system.args[0]+' URL selector filename [zoom]');
    phantom.exit(1);
} else {
    address = system.args[1];
    selector = system.args[2];
    output = system.args[3];
    if (system.args.length > 4) {
        page.zoomFactor = system.args[4];
    }
    page.open(address, function (status) {
        if (status !== 'success') {
            console.log('Unable to load the address!');
            phantom.exit(1);
        } else {
            window.setTimeout(function () {
                var rect = page.evaluate(function(selector) {
                  var el = document.querySelector(selector);
                  if (!el) {
                    return null;
                  }
                  return el.getBoundingClientRect();
                }, selector);
                if (!rect) {
                  console.log('Unabled to find the element!');
                  phantom.exit(2);
                  return false;
                }
                for (k in rect) {
                    rect[k] *= page.zoomFactor;
                }
                page.clipRect = rect;
                page.render(output);
                phantom.exit();
            }, 500);
        }
    });
}

var page = require('webpage').create(),
    system = require('system'),
    address, output, size;

page.viewportSize = {
  width: 640,
  height: 480
};

if (system.args.length < 4 || system.args.length > 5) {
    console.log('Usage: '+system.args[0]+' URL selector filename [zoom]');
    phantom.exit(1);
} else {
    address = system.args[1];
    selector = system.args[2];
    output = system.args[3];
    if (system.args.length > 4) {
        page.zoomFactor = system.args[4];
    }
    page.open(address, function (status) {
        if (status !== 'success') {
            console.log('Unable to load the address!');
            phantom.exit(1);
        } else {
            window.setTimeout(function () {
                var rect = page.evaluate(function(selector) {
                  var el = document.querySelector(selector);
                  if (!el) {
                    return null;
                  }
                  return el.getBoundingClientRect();
                }, selector);
                if (!rect) {
                  console.log('Unabled to find the element!');
                  phantom.exit(2);
                  return false;
                }
                for (k in rect) {
                    rect[k] *= page.zoomFactor;
                }
                page.clipRect = rect;
                page.render(output);
                phantom.exit();
            }, 500);
        }
    });
}

Set viewport size to 640 x 480


const path = require('path');
const express = require('express');
const router = express.Router();
const log = require('../logger');
const wordsRepository = new (require("../repositories/word.repository"))();

router.get('/', (req, res) => {
  wordsRepository.getRandomWords(6)
    .then(words => {
      res.render("index", {
        "words": words.map(word => word.dataValues)
      });
    })
    .catch(error => {
      log.error(error);
      res.status(500).json(error);
    })
});

router.get(['/chat', '/account', '/leaderboards'], (req, res) => {
  const mainFilePath = path.join(__dirname, '..', '..', 'front', 'dist', 'index.html');
  res.sendFile(path.join(mainFilePath));
});

module.exports = router;

const path = require('path');
const express = require('express');
const router = express.Router();
const log = require('../logger');
const wordsRepository = new (require("../repositories/word.repository"))();

router.get('/', (req, res) => {
  wordsRepository.getRandomWords(6)
    .then(words => {
      res.render("index", {
        "words": words.map(word => word.dataValues)
      });
    })
    .catch(error => {
      log.error(error);
      res.status(500).json(error);
    })
});

router.get([
  '/chat',
  '/account',
  '/leaderboards',
  '/signin',
  '/signup',
  '/score'], (req, res) => {
    const mainFilePath = path.join(__dirname, '..', '..', 'front', 'dist', 'index.html');
    res.sendFile(path.join(mainFilePath));
});

module.exports = router;

Add mapping to angular routes


$(function() {

  // toggle on/off switch
  $(document).on('click', '[data-toggle]', function() {
    targetHide = $(this).data('toggle-hide')
    targetShow = $(this).data('toggle-show')
    $('[data-toggle-name="' + targetHide  + '"]').addClass('hide')
    $('[data-toggle-name="' + targetShow  + '"]').removeClass('hide')
                                                 .find('input:text, textarea').first().focus()
  });

  // AJAX request utility
  AJAXRequest = function(url, method, data, success, error)
  {
    return $.ajax({
      url      : url,
      type     : method,
      data     : data,
      dataType : 'JSON',
      cache    : false,
      success  : function(json)
      {
        success(json)
      },
      error    : function()
      {
        error()
      }
    });
  }
});

$(function() {

  // toggle on/off switch
  $(document).on('click', '[data-toggle]', function() {
    targetHide = $(this).data('toggle-hide')
    targetShow = $(this).data('toggle-show')
    $('[data-toggle-name="' + targetHide  + '"]').addClass('hide')
    $('[data-toggle-name="' + targetShow  + '"]').removeClass('hide')
  });

  // AJAX request utility
  AJAXRequest = function(url, method, data, success, error)
  {
    return $.ajax({
      url      : url,
      type     : method,
      data     : data,
      dataType : 'JSON',
      cache    : false,
      success  : function(json)
      {
        success(json)
      },
      error    : function()
      {
        error()
      }
    });
  }
});

Remove the auto focus on element toggle feature


/**
 * This bundle logs the map click coordinates to the console. This is a demonstration of using DefaultExtension.
 *
 * @class Oskari.routing.bundle.inapprouting.InAppRoutingBundleInstance
 */
Oskari.clazz.define('Oskari.routing.bundle.inapprouting.InAppRoutingBundleInstance',
  /**
   * @method create called automatically on construction
   * @static
   */
  function () {
    // Best practice is to initialize instance variables here.
    this.myVar = undefined;
  }, {
    /**
     * @static
     * @property __name
     */
    __name : 'inapprouting',
    /**
     * Module protocol method
     *
     * @method getName
     */
    getName : function () {
      return this.__name;
    },
    start: function() {
      var sandbox = Oskari.getSandbox();
      console.log('Starting bundle with sandbox: ', sandbox);
    },
    eventHandlers: {
      'MapClickedEvent': function (event) {
        console.log('Map clicked at', event.getLonLat());
      }
    },
    /**
     * DefaultExtension method for doing stuff after the bundle has started.
     *
     * @method afterStart
     */
    afterStart: function (sandbox) {
      console.log('Bundle', this.getName(), 'started');
      this.myVar = 'foobar';
    }
  }, {
    "extend" : ["Oskari.userinterface.extension.DefaultExtension"]
  });

/**
 * This bundle logs the map click coordinates to the console. This is a demonstration of using DefaultExtension.
 *
 * @class Oskari.routing.bundle.inapprouting.InAppRoutingBundleInstance
 */
Oskari.clazz.define('Oskari.routing.bundle.inapprouting.InAppRoutingBundleInstance',
  /**
   * @method create called automatically on construction
   * @static
   */
  function () {
    // Best practice is to initialize instance variables here.
    this.myVar = undefined;
  }, {
    /**
     * @static
     * @property __name
     */
    __name : 'inapprouting',
    /**
     * Module protocol method
     *
     * @method getName
     */
    getName : function () {
      return this.__name;
    },
    eventHandlers: {
      'MapClickedEvent': function (event) {
        console.log('Map clicked at ' + event.getLonLat());
        var sandbox = Oskari.getSandbox();
        var url = sandbox.getAjaxUrl() + 'action_route=CalculateRoute';
        jQuery.getJSON(url, function(data) { console.log('Great success: ', data) });
      }
    },
    /**
     * DefaultExtension method for doing stuff after the bundle has started.
     *
     * @method afterStart
     */
    afterStart: function (sandbox) {
      console.log('Bundle', this.getName(), 'started');
      this.myVar = 'foobar';
    }
  }, {
    "extend" : ["Oskari.userinterface.extension.DefaultExtension"]
  });

Make backend call for calculating route


load('bower_components/qunit/qunit/qunit.js');
load('bower_components/qunit-tap/lib/qunit-tap.js');
load('bower_components/power-assert-formatter/lib/power-assert-formatter.js');
load('bower_components/empower/lib/empower.js');

(function () {
    qunitTap(QUnit, print, {
        showModuleNameOnFailure: true,
        showTestNameOnFailure: true,
        showExpectationOnFailure: true,
        showSourceOnFailure: false
    });
    empower(QUnit.assert, powerAssertFormatter(), {destructive: true});
})();

QUnit.config.autorun = false;

load('sandbox/qunit_rhino_espowered.js');

QUnit.load();

load('bower_components/qunit/qunit/qunit.js');
load('bower_components/qunit-tap/lib/qunit-tap.js');
load('bower_components/estraverse/estraverse.js');
load('bower_components/power-assert-formatter/lib/power-assert-formatter.js');
load('bower_components/empower/lib/empower.js');

(function () {
    qunitTap(QUnit, print, {
        showModuleNameOnFailure: true,
        showTestNameOnFailure: true,
        showExpectationOnFailure: true,
        showSourceOnFailure: false
    });
    empower(QUnit.assert, powerAssertFormatter(), {destructive: true});
})();

QUnit.config.autorun = false;

load('sandbox/qunit_rhino_espowered.js');

QUnit.load();

Update Rhino example since power-assert-formatter requires estraverse as runtime dependency.


/*
 * Copyright (C) 2013 salesforce.com, inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
({
/*
 * These helper methods are in the renderer due to the many ways
 * that abstractList can be implemented.  Since controller methods
 * can be overridden, and component creation can be dynamic, putting
 * the relevant helper method call in the renderer ensures that the
 * emptyListContent is handled no matter how the list is implemented.
 */
    afterRender : function(component, helper){
        this.superAfterRender();
        helper.updateEmptyListContent(component);
    },
    rerender : function(component, helper){
        this.superRerender();
        if (component.getConcreteComponent().isDirty('v.items')) {
            helper.updateEmptyListContent(component);
        }
    }
})// eslint-disable-line semi

/*
 * Copyright (C) 2013 salesforce.com, inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
({
/*
 * These helper methods are in the renderer due to the many ways
 * that abstractList can be implemented.  Since controller methods
 * can be overridden, and component creation can be dynamic, putting
 * the relevant helper method call in the renderer ensures that the
 * emptyListContent is handled no matter how the list is implemented.
 */
    afterRender : function(component, helper){
        this.superAfterRender();
        helper.updateEmptyListContent(component);
    },
    rerender : function(component){
        this.superRerender();
        var concreteCmp = component.getConcreteComponent();
        if (concreteCmp.isDirty('v.items')) {
            concreteCmp.getDef().getHelper().updateEmptyListContent(component);
        }
    }
})// eslint-disable-line semi

Make renderer polymorphically call updateEmptyListContent


/*global TodoMVC */
'use strict';

TodoMVC.module('Todos', function (Todos, App, Backbone) {
	// Todo Model
	// ----------
	Todos.Todo = Backbone.Model.extend({
		defaults: {
			title: '',
			completed: false,
			created: 0
		},

		initialize: function () {
			if (this.isNew()) {
				this.set('created', Date.now());
			}
		},

		toggle: function () {
			return this.set('completed', !this.isCompleted());
		},

		isCompleted: function () {
			return this.get('completed');
		},

		matchesFilter: function (filter) {
			if (filter == 'all') {
				return true;
			}

			if (filter == 'active') {
				return !this.isCompleted();
			}

			return this.isCompleted();
		}
	});

	// Todo Collection
	// ---------------
	Todos.TodoList = Backbone.Collection.extend({
		model: Todos.Todo,

		localStorage: new Backbone.LocalStorage('todos-backbone-marionette'),

		getCompleted: function () {
			return this.filter(this._isCompleted);
		},

		getActive: function () {
			return this.reject(this._isCompleted);
		},

		comparator: 'created',

		_isCompleted: function (todo) {
			return todo.isCompleted();
		}
	});
});

/*global TodoMVC */
'use strict';

TodoMVC.module('Todos', function (Todos, App, Backbone) {
	// Todo Model
	// ----------
	Todos.Todo = Backbone.Model.extend({
		defaults: {
			title: '',
			completed: false,
			created: 0
		},

		initialize: function () {
			if (this.isNew()) {
				this.set('created', Date.now());
			}
		},

		toggle: function () {
			return this.set('completed', !this.isCompleted());
		},

		isCompleted: function () {
			return this.get('completed');
		},

		matchesFilter: function (filter) {
			if (filter == 'all') {
				return true;
			}

			if (filter == 'active') {
				return !this.isCompleted();
			}

			return this.isCompleted();
		}
	});

	// Todo Collection
	// ---------------
	Todos.TodoList = Backbone.Collection.extend({
		model: Todos.Todo,

		localStorage: new Backbone.LocalStorage('todos-backbone-marionette'),

		comparator: 'created',

		getCompleted: function () {
			return this.filter(this._isCompleted);
		},

		getActive: function () {
			return this.reject(this._isCompleted);
		},

		_isCompleted: function (todo) {
			return todo.isCompleted();
		}
	});
});

Move attribute comparator with others


$(function() {

    $('#side-menu').metisMenu();

});

//Loads the correct sidebar on window load,
//collapses the sidebar on window resize.
$(function() {
    $(window).bind("load resize", function() {
        console.log($(this).width())
        if ($(this).width() < 768) {
            $('div.sidebar-collapse').addClass('collapse')
        } else {
            $('div.sidebar-collapse').removeClass('collapse')
        }
    })
})

$(function() {

    $('#side-menu').metisMenu();

});

//Loads the correct sidebar on window load,
//collapses the sidebar on window resize.
$(function() {
    $(window).bind("load resize", function() {
        if (this.window.innerWidth < 768) {
            $('div.sidebar-collapse').addClass('collapse')
        } else {
            $('div.sidebar-collapse').removeClass('collapse')
        }
    })
})

Use innerWindow as conditional comparison for appending/removing collapse class


// ==UserScript==
// @name                Selfish Youtube
// @namespace           https://github.com/ASzc/selfish-youtube
// @description         On the watch page, prevent the share panel from being switched to after pressing Like
// @include             http://youtube.com/*
// @include             http://*.youtube.com/*
// @include             https://youtube.com/*
// @include             https://*.youtube.com/*
// ==/UserScript==

var oldPanelName = "action-panel-share";
var newPanelName = "action-panel-share-foo";

// Change share panel id so Google's JS can't reference it properly unless we give it the new name
var actionPanelShare = document.getElementById(oldPanelName);
actionPanelShare.id = newPanelName;

// Change target of Share UI button so it points at the new panel ID
var buttons = document.body.getElementsByTagName("button");
for (var i = 0; i < buttons.length; i++) {
    var button = buttons[i];
    var buttonDTF = button.getAttribute("data-trigger-for");

    if (buttonDTF != null && buttonDTF == oldPanelName) {
        button.setAttribute("data-trigger-for", newPanelName);
    }
}

// ==UserScript==
// @name                Selfish Youtube
// @namespace           https://github.com/ASzc/selfish-youtube
// @description         On the watch page, remove the share panel.
// @include             http://youtube.com/*
// @include             http://*.youtube.com/*
// @include             https://youtube.com/*
// @include             https://*.youtube.com/*
// ==/UserScript==

var panelName = "action-panel-share";

// Remove panel
var actionPanelShare = document.getElementById(panelName);
actionPanelShare.parentNode.removeChild(actionPanelShare);

// Remove buttons targeting the panel
var buttons = document.body.getElementsByTagName("button");
for (var i = 0; i < buttons.length; i++) {
    var button = buttons[i];
    var buttonDTF = button.getAttribute("data-trigger-for");

    if (buttonDTF != null && buttonDTF == panelName) {
        button.parentNode.removeChild(button);
    }
}

Change to simple removal of the share panel and button


({
    block : 'page',
    title : 'bem-components: link',
    mods : { theme : 'normal' },
    head : [
        { elem : 'css', url : '_simple.css' },
        { elem : 'js', url : '_simple.js' }
    ],
    content : [
        {
            block : 'link',
            content : 'Empty link 1'
        },
        {
            block : 'link',
            url : {
                block : 'link-content',
                tag : '',
                content : '/action'
            },
            content : 'url with bemjson'
        },
        {
            block : 'link',
            url : 'http://yandex.ru',
            content : 'yandex.ru'
        },
        {
            block : 'link',
            url : 'http://yandex.com',
            title : 'One of the largest internet companies in Europe',
            content : 'yandex.com'
        },
        {
            block : 'link',
            url : 'http://yandex.com.tr',
            title : 'One of the largest internet companies in Europe',
            target : '_blank',
            content : 'yandex.com.tr'
        },
        {
            block : 'link',
            mods : { pseudo : true },
            content : 'Pseudo link'
        },
        {
            block : 'link',
            tabIndex : -1,
            content : 'yandex.ru'
        }
    ].map(function(l) { return { tag : 'div', content : l } })
})

({
    block : 'page',
    title : 'bem-components: link',
    mods : { theme : 'normal' },
    head : [
        { elem : 'css', url : '_simple.css' },
        { elem : 'js', url : '_simple.js' }
    ],
    content : ['default', 'simple', 'normal'].map(function(theme, i) {
        var content = [
                { block : 'link', content : 'with no url' },
                { block : 'link', url : 'http://example.com/', content : 'plain url' },
                {
                    block : 'link',
                    url : {
                        block : 'link-content',
                        tag : '',
                        content : '/action'
                    },
                    content : 'bemjson url'
                },
                {
                    block : 'link',
                    url : 'http://example.com/',
                    title : 'One of the largest internet companies in Europe',
                    content : 'with title'
                },
                {
                    block : 'link',
                    url : 'http://example.com/',
                    title : 'One of the largest internet companies in Europe',
                    target : '_blank',
                    content : 'blank target'
                },
                { block : 'link', mods : { pseudo : true }, content : 'pseudo link' },
                {
                    block : 'link',
                    url : 'http://example.com/',
                    tabIndex : -1,
                    content : 'out of tab order'
                }
            ].map(function(link, j) {
                link.mods || (link.mods = {});
                i && (link.mods.theme = theme);
                return [
                    j > 0 && { tag : 'br' },
                    link
                ]
            });

        content.unshift({ tag : 'h2', content : theme });
        i && content.unshift({ tag : 'hr' });

        return content;
    })
})

Add example cases for block:link


var assert = chai.assert;

describe('LMLayer', function () {
  describe('[[constructor]]', function () {
    it('should construct with zero arguments', function () {
      let lmLayer = new LMLayer();
      assert.instanceOf(lmLayer, LMLayer);
    });
  });

  describe('#initialize()', function () {
    // TODO: implement configuration negotiation?
    it.skip('should yield a reasonable configuration', function () {
      let maxLeftContext = 64;
      let lmLayer = new LMLayer();
      return lmLayer.initialize(
        { maxLeftContextCodeUnits: maxLeftContext },
        { model: { type: 'dummy' } }
      ).then(function (configuration) {
        assert.isAtMost(configuration.leftContextCodeUnits, maxLeftContext);
        assert.propertyVal(configuration, 'rightContextCodeUnits', 0);
      });
    });
  });

  describe('#asBlobURI()', function () {
    // #asBlobURI() requires browser APIs, hence why it cannot be tested headless in Node.
    it('should take a function and convert it into a blob function', function (done) {
      let uri = LMLayer.asBlobURI(function dummyHandler() {
        // Post something weird, so we can be reasonably certain the Web Worker is...
        // well, working.
        // WARNING: Do NOT refactor this string as a variable. It **MUST** remain a string
        // in this function body, because the code in this function's body gets
        // stringified!
        postMessage('fhqwhgads');
      });
      assert.match(uri, /^blob:/);

      let worker = new Worker(uri);
      worker.onmessage = function thisShouldBeCalled(event) {
        assert.propertyVal(event, 'data', 'fhqwhgads');
        done();
      };
    })
  })
});

var assert = chai.assert;

describe('LMLayer', function () {
  describe('[[constructor]]', function () {
    it('should construct with zero arguments', function () {
      let lmLayer = new LMLayer();
      assert.instanceOf(lmLayer, LMLayer);
    });
  });

  describe('#asBlobURI()', function () {
    // #asBlobURI() requires browser APIs, hence why it cannot be tested headless in Node.
    it('should take a function and convert it into a blob function', function (done) {
      let uri = LMLayer.asBlobURI(function dummyHandler() {
        // Post something weird, so we can be reasonably certain the Web Worker is...
        // well, working.
        // WARNING: Do NOT refactor this string as a variable. It **MUST** remain a string
        // in this function body, because the code in this function's body gets
        // stringified!
        postMessage('fhqwhgads');
      });
      assert.match(uri, /^blob:/);

      let worker = new Worker(uri);
      worker.onmessage = function thisShouldBeCalled(event) {
        assert.propertyVal(event, 'data', 'fhqwhgads');
        done();
      };
    })
  })
});

Remove test for configuration-negotiation (not applicable to dummy model).


var express = require('express');

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

var router = express.Router();

/**
 * Render the home page.
 */
router.get('/', function(req, res) {
  res.render('index.jade');
});

/**
 * Render the dashboard page.
 */
router.get('/dashboard', utils.requireLogin, function(req, res) {
  res.render('dashboard.jade');
});

router.get('/user/:filename', function(req, res) {
  res.render('user/'+req.params.filename+'.jade');
});

module.exports = router;

var express = require('express');

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

var router = express.Router();

/**
 * Render the home page.
 */
router.get('/', function(req, res) {
  res.render('user/index.jade');
});

/**
 * Render the dashboard page.
 */
router.get('/dashboard', utils.requireLogin, function(req, res) {
  res.render('dashboard.jade');
});

router.get('/user/:filename', function(req, res) {
  res.render('user/'+req.params.filename+'.jade');
});

module.exports = router;

Change the index default jade file


/**

 Copyright 2015 Covistra Technologies Inc.

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */
var SocketIO = require('socket.io'),
    _ = require('lodash');

"use strict";

exports.register = function (server, options, next) {
    server.log(['plugin', 'info'], "Registering the Socket plugin");

    var io = SocketIO.listen(server.listener);

    var log = server.plugins['covistra-system'].systemLog;
    var config = server.plugins['hapi-config'].CurrentConfiguration;

    var socketManager = require('./lib/socket-manager')(server, io, log.child({service: 'socket-manager'}), config);

    io.on('connection', function(socket) {
        server.log(['plugin', 'socket', 'debug'], "Client is connected on our socket");
        socketManager.clientConnected(socket);
    });

    server.expose('socketManager', socketManager);

    next();
};

exports.register.attributes = {
    pkg: require('./package.json')
};

/**

 Copyright 2015 Covistra Technologies Inc.

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */
var SocketIO = require('socket.io'),
    _ = require('lodash');

"use strict";

exports.register = function (server, options, next) {

    var log = server.plugins['covistra-system'].systemLog;
    var config = server.plugins['hapi-config'].CurrentConfiguration;

    log.info("Registering the socket plugin", server.select('api').listener);

    var io = SocketIO(server.select('api').listener);

    log.debug("Socket.IO instance has been successfully created");

    var socketManager = require('./lib/socket-manager')(server, io, log.child({service: 'socket-manager'}), config);

    io.on('connection', function(socket) {
        server.log(['plugin', 'socket', 'debug'], "Client is connected on our socket");
        socketManager.clientConnected(socket);
    });

    server.expose('socketManager', socketManager);

    next();
};

exports.register.attributes = {
    pkg: require('./package.json')
};

Connect the socket.io instance to the API connection only


import $ from 'jquery';

export default function plugit( pluginName, pluginClass ) {
	let dataName = `plugin_${pluginName}`;

	$.fn[pluginName] = function( options ) {
		return this.each( function() {
			let $this = $( this );
			let data = $this.data( dataName );

			if ( ! data ) {
				$this.data( dataName, ( data = new pluginClass( this, options ) ) );
			}
		} );
	};

}

import $ from 'jquery';

export default function plugit( pluginName, pluginClass ) {
	let dataName = `plugin_${pluginName}`;

	$.fn[pluginName] = function( opts ) {
		return this.each( function() {
			let $this = $( this );
			let data = $this.data( dataName );
			let options = $.extend( {}, this._defaults, opts );

			pluginClass.element = this;
			pluginClass.$element = $this;

			if ( ! data ) {
				$this.data( dataName, ( data = new pluginClass( this, options ) ) );
			}
		} );
	};

}

Add cached element and props before construction, merge defaults.


var gulp = require('gulp');
var elixir = require('laravel-elixir');
var urlAdjuster = require('gulp-css-url-adjuster');

/*
 |----------------------------------------------------------------
 | adjust css urls for images and stuff
 |----------------------------------------------------------------
 |
 | A wrapper for gulp-css-url-adjuster.
 |
 */

elixir.extend('urlAdjuster', function(input, options, output) {

    gulp.task('urlAdjuster', function() {
        gulp.src(input)
            .pipe(urlAdjuster(options))
            .pipe(gulp.dest(output));
    });

    return this.queueTask('urlAdjuster');
});

var gulp = require('gulp');
var elixir = require('laravel-elixir');
var urlAdjuster = require('gulp-css-url-adjuster');
var files = [];

/*
 |----------------------------------------------------------------
 | adjust css urls for images and stuff
 |----------------------------------------------------------------
 |
 | A wrapper for gulp-css-url-adjuster.
 |
 */

elixir.extend('urlAdjuster', function(input, options, output) {

    files.push({
        input: input,
        options: options,
        output: output
    });

    var stream;

    gulp.task('urlAdjuster', function() {
        files.forEach(function(toUrlAdjust) {
            stream = gulp.src(toUrlAdjust.input)
                         .pipe(urlAdjuster(toUrlAdjust.options))
                         .pipe(gulp.dest(toUrlAdjust.output));
        });

        return stream;
    });

    return this.queueTask('urlAdjuster');
});

Allow for multiple files to be urlAdjsted.


'use strict'

const express = require('express')
const bodyParser = require('body-parser')
const config = require('./config')
const app = express()
const publisher = require('./publisher')
var cronJob = require('cron').CronJob

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

app.use(bodyParser.urlencoded({extended: false}))

app.use(bodyParser.json())

app.get('/', function (req, res) {
  res.send('Meeaaww')
})

app.get('/meaw', function (req, res) {
  res.status(200).send('Meaaawww 200')
})

app.listen(app.get('port'), () => {
  console.log(`Running Crypty on port : ${app.get('port')}`)

  var cryptoJob = new cronJob({
    cronTime: '0 */20 * * * *',
    onTick: function () {
      // Publish every 20 min
      publisher.publish()
    },
    start: false,
    timeZone: 'Asia/Bangkok'
  })

  cryptoJob.start()
})

'use strict'

const express = require('express')
const bodyParser = require('body-parser')
const config = require('./config')
const app = express()
const publisher = require('./publisher')
var cronJob = require('cron').CronJob

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

app.use(bodyParser.urlencoded({extended: false}))

app.use(bodyParser.json())

app.get('/', function (req, res) {
  res.send('Meeaaww')
})

app.get('/meaw', function (req, res) {
  res.status(200).send('Meaaawww 200')
})

app.listen(app.get('port'), () => {
  console.log(`Running Crypty on port : ${app.get('port')}`)

  var cryptoJob = new cronJob({
    cronTime: '0 */60 * * * *',
    onTick: function () {
      // Publish every 60 min
      publisher.publish()
    },
    start: false,
    timeZone: 'Asia/Bangkok'
  })

  cryptoJob.start()
})

Change time frequency to publish message


'use strict';

var nomnom = require('nomnom');

var signal = require('./lib/signal');
var tree = require('./lib/tree');

var task = function (name, callback, options) {
    var command = nomnom.command(name);

    options.forEach(function (option) {
        command = command.option(option.name, {
            abbr: option.abbr,
            flag: option.flag,
            default: option.default
        });
    });

    command.callback(function (opts) {
        var args = options.map(function (option) {
            return opts[option.name];
        });
        callback.apply(null, args);
    });
};

var combine = function (/* ...signals */) {
    var args = Array.prototype.slice.call(arguments);
    var combinator = tree.merge;
    args.unshift(combinator);
    return signal.combine.apply(null, args);
};

module.exports = {
    task: task,
    combine: combine,
    files: require('./base/files'),
    write: require('./base/write'),
    noop: require('./base/noop')
};

'use strict';

var nomnom = require('nomnom');

var signal = require('./lib/signal');
var tree = require('./lib/tree');

var task = function (name, callback, options) {
    options = options || [];

    var command = nomnom.command(name);

    options.forEach(function (option) {
        command = command.option(option.name, {
            abbr: option.abbr,
            flag: option.flag,
            default: option.default
        });
    });

    command.callback(function (opts) {
        var args = options.map(function (option) {
            return opts[option.name];
        });
        callback.apply(null, args);
    });
};

var combine = function (/* ...signals */) {
    var args = Array.prototype.slice.call(arguments);
    var combinator = tree.merge;
    args.unshift(combinator);
    return signal.combine.apply(null, args);
};

module.exports = {
    task: task,
    combine: combine,
    files: require('./base/files'),
    write: require('./base/write'),
    noop: require('./base/noop')
};

Allow task third parameter to be optional


'use strict';

var toStr = Number.prototype.toString;

module.exports = function isNumberObject(value) {
	if (typeof value === 'number') { return true; }
	if (typeof value !== 'object') { return false; }
	try {
		toStr.call(value);
		return true;
	} catch (e) {
		return false;
	}
};

'use strict';

var toStr = Number.prototype.toString;
var tryNumberObject = function tryNumberObject(value) {
	try {
		toStr.call(value);
		return true;
	} catch (e) {
		return false;
	}
};

module.exports = function isNumberObject(value) {
	if (typeof value === 'number') { return true; }
	if (typeof value !== 'object') { return false; }
	return tryNumberObject(value);
};

Improve optimizability of the non-try/catch part.


#!/usr/bin/env node

/**
 * index.js
 *
 * for `character-map` command
 */

var opts = require('minimist')(process.argv.slice(2));
var opentype = require('opentype.js');

if (!opts.f || typeof opts.f !== 'string') {
	console.log('use -f to specify your font path, TrueType and OpenType supported');
	return;
}

opentype.load(opts.f, function(err, font) {
	if (err) {
		console.log(err);
		return;
	}

	if (!font.glyphs || font.glyphs.length === 0) {
		console.log('no glyphs found in this font');
		return;
	}

	var table = '';
	font.glyphs.forEach(function(glyph) {
		if (!glyph.unicode) {
			return;
		}

		table += String.fromCharCode(glyph.unicode);
	});

	console.log(table);
});

#!/usr/bin/env node

/**
 * index.js
 *
 * for `character-map` command
 */

var opts = require('minimist')(process.argv.slice(2));
var opentype = require('opentype.js');

if (!opts.f || typeof opts.f !== 'string') {
	console.log('use -f to specify your font path, TrueType and OpenType supported');
	return;
}

opentype.load(opts.f, function(err, font) {
	if (err) {
		console.log(err);
		return;
	}

	var glyphs = font.glyphs.glyphs;
	if (!glyphs || glyphs.length === 0) {
		console.log('no glyphs found in this font');
		return;
	}

	var table = '';
	Object.keys(glyphs).forEach(function(key) {
		var glyph = glyphs[key];
		if (!glyph.unicode) {
			return;
		}

		table += String.fromCharCode(glyph.unicode);
	});

	console.log(table);
});

Fix broken iteration of items in Opentype.js slyphs object


'use strict';

export default class Fragment {
    render () {
        return null;
    }

    /**
     * Generic event handler. Turns class methods into closures that can be used as event handlers. If you need to use
     * data from the event object, or have access to the event object in general, use handleEvent instead.
     *
     * For non class methods (or methods that call into a different `this`, use framework/utils/handle).
     */
    handle (fn, final=false, args) {
        var self = this;

        return function (ev) {
            fn.apply(self, args);

            if (final) {
                ev.preventDefault();
                ev.stopPropagation();
                return false;
            }
        }
    }
}

'use strict';

export default class Fragment {
    render () {
        return null;
    }

    /**
     * Generic event handler. Turns class methods into closures that can be used as event handlers. If you need to use
     * data from the event object, or have access to the event object in general, use handleEvent instead.
     *
     * For non class methods (or methods that call into a different `this`, use framework/utils/handle).
     */
    handle (fn, args, final=false) {
        var self = this;

        return function (ev) {
            fn.apply(self, args);

            if (final) {
                ev.preventDefault();
                ev.stopPropagation();
                return false;
            }
        }
    }
}

Change order of arguments in Fragment.handle


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

/**
 * Internal curryN function.
 *
 * @private
 * @category Function
 * @param {Number} length The arity of the curried function.
 * @return {array} An array of arguments received thus far.
 * @param {Function} fn The function to curry.
 */
module.exports = function _curryN(length, received, fn) {
  return function() {
    var combined = [];
    var argsIdx = 0;
    var left = length;
    var combinedIdx = 0;
    while (combinedIdx < received.length || argsIdx < arguments.length) {
      var result;
      if (combinedIdx < received.length &&
          (received[combinedIdx] == null ||
           received[combinedIdx]['@@functional/placeholder'] !== true ||
           argsIdx >= arguments.length)) {
        result = received[combinedIdx];
      } else {
        result = arguments[argsIdx];
        argsIdx += 1;
      }
      combined[combinedIdx] = result;
      if (result == null || result['@@functional/placeholder'] !== true) {
        left -= 1;
      }
      combinedIdx += 1;
    }
    return left <= 0 ? fn.apply(this, combined) : _arity(left, _curryN(length, combined, fn));
  };
};

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

/**
 * Internal curryN function.
 *
 * @private
 * @category Function
 * @param {Number} length The arity of the curried function.
 * @param {Array} An array of arguments received thus far.
 * @param {Function} fn The function to curry.
 * @return {Function} The curried function.
 */
module.exports = function _curryN(length, received, fn) {
  return function() {
    var combined = [];
    var argsIdx = 0;
    var left = length;
    var combinedIdx = 0;
    while (combinedIdx < received.length || argsIdx < arguments.length) {
      var result;
      if (combinedIdx < received.length &&
          (received[combinedIdx] == null ||
           received[combinedIdx]['@@functional/placeholder'] !== true ||
           argsIdx >= arguments.length)) {
        result = received[combinedIdx];
      } else {
        result = arguments[argsIdx];
        argsIdx += 1;
      }
      combined[combinedIdx] = result;
      if (result == null || result['@@functional/placeholder'] !== true) {
        left -= 1;
      }
      combinedIdx += 1;
    }
    return left <= 0 ? fn.apply(this, combined) : _arity(left, _curryN(length, combined, fn));
  };
};

Fix param/return documentation for internal curryN