import ExtendableError from 'es6-error';

import makeWclUrl from './makeWclUrl';

export class ApiDownError extends ExtendableError {}
export class LogNotFoundError extends ExtendableError {}

function tryParseMessage(message) {
  try {
    const errorMessage = JSON.parse(message);
    if (errorMessage.error) {
      return errorMessage.error;
    }
  } catch (error) {
    // We don't care about an error parsing the error, message's default value is fine
  }
  return null;
}

export default async function fetchWcl(endpoint, queryParams) {
  const response = await fetch(makeWclUrl(endpoint, queryParams));

  if (response.status === 521) {
    throw new ApiDownError('The API is currently down. This is usually for maintenance which should only take about 10 seconds. Please try again in a moment.');
  }
  const json = await response.json();
  if (response.status === 400 || response.status === 401) {
    const message = tryParseMessage(json.message);
    if (message === 'This report does not exist or is private.') {
      throw new LogNotFoundError();
    }
    throw new Error(message || json.error);
  }
  return json;
}

import ExtendableError from 'es6-error';

import makeWclUrl from './makeWclUrl';

export class ApiDownError extends ExtendableError {}
export class LogNotFoundError extends ExtendableError {}
export class UnknownApiError extends ExtendableError {}

function tryParseMessage(message) {
  try {
    const errorMessage = JSON.parse(message);
    if (errorMessage.error) {
      return errorMessage.error;
    }
  } catch (error) {
    // We don't care about an error parsing the error, message's default value is fine
  }
  return null;
}

const HTTP_CODES = {
  OK: 200,
  BAD_REQUEST: 400,
  UNAUTHORIZED: 401,
  CLOUDFLARE: {
    UNKNOWN_ERROR: 520,
    WEB_SERVER_IS_DOWN: 521,
    CONNECTION_TIMED_OUT: 522,
    ORIGIN_IS_UNREACHABLE: 523,
    A_TIMEOUT_OCCURED: 524,
  },
};

export default async function fetchWcl(endpoint, queryParams) {
  const response = await fetch(makeWclUrl(endpoint, queryParams));

  if (Object.values(HTTP_CODES.CLOUDFLARE).includes(response.status)) {
    throw new ApiDownError('The API is currently down. This is usually for maintenance which should only take about 10 seconds. Please try again in a moment.');
  }
  const json = await response.json();
  if ([HTTP_CODES.BAD_REQUEST, HTTP_CODES.UNAUTHORIZED].includes(response.status)) {
    const message = tryParseMessage(json.message);
    if (message === 'This report does not exist or is private.') {
      throw new LogNotFoundError();
    }
    throw new Error(message || json.error);
  }
  if (response.status !== HTTP_CODES.OK) {
    throw new UnknownApiError(`Received HTTP code ${response.status}`);
  }
  return json;
}

Handle all Cloudflare errors as API down errors Handle all non 200 HTTP codes as fatal API errors


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

var H = require('Header');
var React = require('React');

function getVersionedGithubPath(path, version='next') {
  return [
    'https://github.com/facebook/react-native/blob',
    version === 'next' ? 'master' : version + '-stable',
    path
    ].join('/')
}

var HeaderWithGithub = React.createClass({

  contextTypes: {
    version: React.PropTypes.string
  },

  render: function() {
    return (

              <H level={this.props.level || 3} toSlug={this.props.title}>
                {this.props.title}
              </H>

                Edit on GitHub

    );
  }
});

module.exports = HeaderWithGithub;

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

var H = require('Header');
var React = require('React');

function getVersionedGithubPath(path, version) {
  version = version || 'next';
  return [
    'https://github.com/facebook/react-native/blob',
    version === 'next' ? 'master' : version + '-stable',
    path
  ].join('/');
}

var HeaderWithGithub = React.createClass({

  contextTypes: {
    version: React.PropTypes.string
  },

  render: function() {
    return (

              <H level={this.props.level || 3} toSlug={this.props.title}>
                {this.props.title}
              </H>

                Edit on GitHub

    );
  }
});

module.exports = HeaderWithGithub;

Remove default argument syntax on getVersionedGithubPath()


const path = require('path')

module.exports = {
    http: {
        port: process.env.DEV ? 8080 : 8050,
        favicon: path.join(__dirname, '../assets/favicon.ico')
    },
    server: {
        SSR: true, // Server side rendering
        certificate: path.join(__dirname, '../../fullchain.pem'),
        certificate_key: path.join(__dirname, '../../privkey.pem'),
    },
    static: [
        {
            url: '/build',
            path: path.join(__dirname, '../../build')
        },
        {
            url: '/assets',
            path: path.join(__dirname, '../assets')
        },
        {
            url: '/static',
            path: path.join(__dirname, '../docs')
        }
    ]
}

const path = require('path')

module.exports = {
    http: {
        port: process.env.DEV ? 8080 : 8050,
        favicon: path.join(__dirname, '../assets/favicon.ico')
    },
    server: {
        SSR: false, // Server side rendering
        certificate: path.join(__dirname, '../../fullchain.pem'),
        certificate_key: path.join(__dirname, '../../privkey.pem'),
    },
    static: [
        {
            url: '/build',
            path: path.join(__dirname, '../../build')
        },
        {
            url: '/assets',
            path: path.join(__dirname, '../assets')
        },
        {
            url: '/static',
            path: path.join(__dirname, '../docs')
        }
    ]
}

Disable SSR for speed testing


import replaceScript from './replaceScript';

/*
 * runs an array of async functions in sequential order
 */
export function seq(arr, index) {
  // first call, without an index
  if (typeof index === 'undefined') {
    index = 0;
  }

  function callback() {
    index++;
    if (index < arr.length) {
      seq(arr, index);
    } else {
      // If finished trigger all the DOM Content Loaded event
      const DOMContentLoadedEvent = document.createEvent('Event');
      DOMContentLoadedEvent.initEvent('DOMContentLoaded', true, true);
      document.dispatchEvent(DOMContentLoadedEvent);
    }
  }

  if (arr.length) {
    const fn = arr[index];

    fn(callback);
  }
}

export function setContent(contents) {
  document.documentElement.innerHTML = contents;

  // First generate the callbacks for all script tags.
  // This will create insertion operations for all script tags
  const $scripts = document.querySelectorAll('script');

  const runList = [].map.call($scripts, function($script) {
    return function(callback) {
      replaceScript($script, callback);
    };
  });

  // insert the script tags sequentially
  // to preserve execution order
  seq(runList);
}

export default function loadCurrentPage() {
  const req = new XMLHttpRequest();
  req.addEventListener('load', () => {
    setContent(req.responseText);
  });
  req.open('GET', window.location.href);
  req.send();
}

import replaceScript from './replaceScript';

/*
 * runs an array of async functions in sequential order
 */
export function seq(arr, index) {
  // first call, without an index
  if (typeof index === 'undefined') {
    index = 0;
  }

  function callback() {
    index++;
    if (index < arr.length) {
      seq(arr, index);
    } else {
      // If finished trigger all the DOM Content Loaded event
      const DOMContentLoadedEvent = document.createEvent('Event');
      DOMContentLoadedEvent.initEvent('DOMContentLoaded', true, true);
      document.dispatchEvent(DOMContentLoadedEvent);
      const $body = document.querySelector('body');
      if ($body && $body.onload) {
        $body.onload();
      }
    }
  }

  if (arr.length) {
    const fn = arr[index];

    fn(callback);
  }
}

export function setContent(contents) {
  document.documentElement.innerHTML = contents;

  // First generate the callbacks for all script tags.
  // This will create insertion operations for all script tags
  const $scripts = document.querySelectorAll('script');

  const runList = [].map.call($scripts, function($script) {
    return function(callback) {
      replaceScript($script, callback);
    };
  });

  // insert the script tags sequentially
  // to preserve execution order
  seq(runList);
}

export default function loadCurrentPage() {
  const req = new XMLHttpRequest();
  req.addEventListener('load', () => {
    setContent(req.responseText);
  });
  req.open('GET', window.location.href);
  req.send();
}

Fix African Storybook loading by ensuring Hashi runs body.onload handlers.


'use strict';

/**
 * Module dependencies
 */

// Native
const path = require('path');

// Externals
const co = require('co');
const render = require('koa-ejs');

/**
 * EJS hook
 */

module.exports = function(strapi) {
  const hook = {
    /**
     * Default options
     */

    defaults: {
      root: path.join(strapi.config.appPath, strapi.config.paths.views),
      layout: 'layout',
      viewExt: 'ejs',
      cache: true,
      debug: true,
    },

    /**
     * Initialize the hook
     */

    initialize: () => {
      // Force cache mode in production
      if (strapi.config.environment === 'production') {
        strapi.config.hook.settings.ejs.cache = true;
      }

      render(strapi.app, strapi.config.hook.settings.ejs);

      strapi.app.context.render = co.wrap(strapi.app.context.render);
    },
  };

  return hook;
};

'use strict';

/**
 * Module dependencies
 */

// Native
const path = require('path');

// Externals
const co = require('co');
const render = require('koa-ejs');

/**
 * EJS hook
 */

module.exports = function(strapi) {
  const hook = {
    /**
     * Default options
     */

    defaults: {
      root: path.join(strapi.config.appPath, strapi.config.paths.views),
      layout: 'layout',
      viewExt: 'ejs',
      cache: true,
      debug: true,
    },

    /**
     * Initialize the hook
     */

    initialize () {
      // Force cache mode in production
      if (strapi.config.environment === 'production') {
        strapi.config.hook.settings.ejs.cache = true;
      }

      render(strapi.app, Object.assign(this.defaults, strapi.config.hook.settings.ejs));

      strapi.app.context.render = co.wrap(strapi.app.context.render);
    },
  };

  return hook;
};

Fix default config usage ejs


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

// Styles
import './styles.scss';

function ExploreDetailFooterComponent(props) {
  const { setSidebarAnchor } = props;

  return (
    <div className="c-explore-detail-footer">
      <a
        onClick={() => setSidebarAnchor('overview')}
        onKeyPress={() => setSidebarAnchor('overview')}
        role="button"
        tabIndex={0}
      >
             OVERVIEW
      </a>
      <a
        onClick={() => setSidebarAnchor('layers')}
        onKeyPress={() => setSidebarAnchor('layers')}
        role="button"
        tabIndex={0}
      >
             LAYERS
      </a>
      <a
        onClick={() => setSidebarAnchor('visualization')}
        onKeyPress={() => setSidebarAnchor('visualization')}
        role="button"
        tabIndex={0}
      >
             VISUALIZATION
      </a>
      <a
        onClick={() => setSidebarAnchor('further_information')}
        onKeyPress={() => setSidebarAnchor('further_information')}
        role="button"
        tabIndex={0}
      >
             FURTHER INFORMATION
      </a>
    </div>
  );
}

ExploreDetailFooterComponent.propTypes = { setSidebarAnchor: PropTypes.func.isRequired };

export default ExploreDetailFooterComponent;

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

// Utils
import { logEvent } from 'utils/analytics';

// Styles
import './styles.scss';

function ExploreDetailFooterComponent(props) {
  const { setSidebarAnchor } = props;

  return (
    <div className="c-explore-detail-footer">
      <a
        onClick={() => {
          setSidebarAnchor('overview');
          logEvent('Explore (Detail)', 'Clicks to Scroll', 'Overview');
        }}
        onKeyPress={() => {
          setSidebarAnchor('overview');
          logEvent('Explore (Detail)', 'Clicks to Scroll', 'Overview');
        }}
        role="button"
        tabIndex={0}
      >
             OVERVIEW
      </a>
      <a
        onClick={() => {
          setSidebarAnchor('layers');
          logEvent('Explore (Detail)', 'Clicks to Scroll', 'Layers');
        }}
        onKeyPress={() => {
          setSidebarAnchor('layers');
          logEvent('Explore (Detail)', 'Clicks to Scroll', 'Layers');
        }}
        role="button"
        tabIndex={0}
      >
             LAYERS
      </a>
      <a
        onClick={() => {
          setSidebarAnchor('visualization');
          logEvent('Explore (Detail)', 'Clicks to Scroll', 'Visualization');
        }}
        onKeyPress={() => {
          setSidebarAnchor('visualization');
          logEvent('Explore (Detail)', 'Clicks to Scroll', 'Visualization');
        }}
        role="button"
        tabIndex={0}
      >
             VISUALIZATION
      </a>
      <a
        onClick={() => {
          setSidebarAnchor('further_information');
          logEvent('Explore (Detail)', 'Clicks to Scroll', 'Further information');
        }}
        onKeyPress={() => {
          setSidebarAnchor('further_information');
          logEvent('Explore (Detail)', 'Clicks to Scroll', 'Further information');
        }}
        role="button"
        tabIndex={0}
      >
             FURTHER INFORMATION
      </a>
    </div>
  );
}

ExploreDetailFooterComponent.propTypes = { setSidebarAnchor: PropTypes.func.isRequired };

export default ExploreDetailFooterComponent;

Add GA events for Explore detail footer links


import React, { Component } from 'react'
import PostItemContainer from '../../containers/PostItemContainer'
import debounce from '../../util/debounce'

class PostList extends Component {
  constructor (props) {
    super(props)

    this.onScroll = debounce(this.onScroll, 350).bind(this)
  }

  componentDidMount () {
    this.props.actions.requestPosts(
      this.props.r,
      this.props.from
    )

    window.addEventListener('scroll', this.onScroll)
  }

  componentWillUnmount () {
    window.removeEventListener('scroll', this.onScroll)
  }

  onScroll () {
    if (this.props.posts.length === 0) {
      // Sub does not contain any post yet
      // so we do not need to fetch more
      return
    }
    const [lastPost] = this.props.posts.reverse()
    const scrollBottom = document.body.scrollHeight
      - document.body.scrollTop
      - window.innerHeight

    if (scrollBottom / window.innerHeight < 0.2) {
      this.props.actions.requestPosts(
        this.props.r,
        this.props.from,
        lastPost.id
      )
    }
  }

  render () {
    return (
      <div className='subreddit__post-list'>
        {this.props.posts.map(p => (
          <PostItemContainer
            key={p.id}
            {...p}
          />
        ))}
      </div>
    )
  }
}

export default PostList

import React, { Component } from 'react'
import PostItemContainer from '../../containers/PostItemContainer'
import debounce from '../../util/debounce'

class PostList extends Component {
  constructor (props) {
    super(props)

    this.onScroll = debounce(this.onScroll, 350).bind(this)
  }

  componentDidMount () {
    this.props.actions.requestPosts(
      this.props.r,
      this.props.from
    )

    window.addEventListener('scroll', this.onScroll)
  }

  componentWillReceiveProps (nextProps) {
    if (this.props.r !== nextProps.r) {
      this.props.actions.requestPosts(
        nextProps.r,
        nextProps.from
      )
    }
  }

  componentWillUnmount () {
    window.removeEventListener('scroll', this.onScroll)
  }

  onScroll () {
    if (this.props.posts.length === 0) {
      // Sub does not contain any post yet
      // so we do not need to fetch more
      return
    }
    const [lastPost] = this.props.posts.reverse()
    const scrollBottom = document.body.scrollHeight
      - document.body.scrollTop
      - window.innerHeight

    if (scrollBottom / window.innerHeight < 0.2) {
      this.props.actions.requestPosts(
        this.props.r,
        this.props.from,
        lastPost.id
      )
    }
  }

  render () {
    return (
      <div className='subreddit__post-list'>
        {this.props.posts.map(p => (
          <PostItemContainer
            key={p.id}
            {...p}
          />
        ))}
      </div>
    )
  }
}

export default PostList

Add post fetching on props update


var TagsSettingsMenuView = Ember.View.extend({
    saveText: Ember.computed('controller.model.isNew', function () {
        return this.get('controller.model.isNew') ?
            'Add Tag' :
            'Save Tag';
    }),

    // This observer loads and resets the uploader whenever the active tag changes,
    // ensuring that we can reuse the whole settings menu.
    updateUploader: Ember.observer('controller.activeTag.image', 'controller.uploaderReference', function () {
        var uploader = this.get('controller.uploaderReference'),
            image = this.get('controller.activeTag.image');

        if (uploader && uploader[0]) {
            if (image) {
                uploader[0].uploaderUi.initWithImage();
            } else {
                uploader[0].uploaderUi.initWithDropzone();
            }
        }
    })
});

export default TagsSettingsMenuView;

var TagsSettingsMenuView = Ember.View.extend({
    saveText: Ember.computed('controller.model.isNew', function () {
        return this.get('controller.model.isNew') ?
            'Add Tag' :
            'Save Tag';
    }),

    // This observer loads and resets the uploader whenever the active tag changes,
    // ensuring that we can reuse the whole settings menu.
    updateUploader: Ember.observer('controller.activeTag.image', 'controller.uploaderReference', function () {
        var uploader = this.get('controller.uploaderReference'),
            image = this.get('controller.activeTag.image');

        if (uploader && uploader[0]) {
            if (image) {
                uploader[0].uploaderUi.initWithImage();
            } else {
                uploader[0].uploaderUi.reset();
            }
        }
    })
});

export default TagsSettingsMenuView;

Reset upload component on tag switch


import mysql from './mysql';
import postgresql from './postgresql';
import sqlserver from './sqlserver';
import cassandra from './cassandra';

/**
 * List of supported database clients
 */
export const CLIENTS = [
  {
    key: 'mysql',
    name: 'MySQL',
    defaultPort: 3306,
  },
  {
    key: 'postgresql',
    name: 'PostgreSQL',
    defaultDatabase: 'postgres',
    defaultPort: 5432,
  },
  {
    key: 'sqlserver',
    name: 'Microsoft SQL Server',
    defaultPort: 1433,
  },
  {
    key: 'cassandra',
    name: 'Cassandra',
    defaultPort: 9042,
  },
];

export default {
  mysql,
  postgresql,
  sqlserver,
  cassandra,
};

import mysql from './mysql';
import postgresql from './postgresql';
import sqlserver from './sqlserver';
import cassandra from './cassandra';

/**
 * List of supported database clients
 */
export const CLIENTS = [
  {
    key: 'mysql',
    name: 'MySQL',
    defaultPort: 3306,
  },
  {
    key: 'postgresql',
    name: 'PostgreSQL',
    defaultDatabase: 'postgres',
    defaultPort: 5432,
  },
  {
    key: 'sqlserver',
    name: 'Microsoft SQL Server',
    defaultPort: 1433,
  },
  {
    key: 'cassandra',
    name: 'Cassandra',
    defaultPort: 9042,
    disabledFeatures: [
      'scriptCreateTable',
    ],
  },
];

export default {
  mysql,
  postgresql,
  sqlserver,
  cassandra,
};

Add property for disabled features


const handleErrors = (response) => {
  if (!response.ok) {
    if (response.message) {
      throw Error(response.message);
    }
    throw Error(response.statusText);
  }
  return response;
};

export const getRepos = (username) => {
  let name = username.trim().toLowerCase();
  if (name === '') {
    return [];
  }
  return fetch(`https://api.github.com/users/${name}/repos`)
    .then(handleErrors)
    .then((response) => {return response.json()})
    .catch((error) => {
      console.error(error);
    })
};

const handleErrors = (response) => {
  if (!response.ok) {
    if (response.message) {
      throw Error(response.message);
    }
    throw Error(response.statusText);
  }
  return response;
};

export const getRepos = (username) => {
  let name = username.trim().toLowerCase();
  if (name === '') {
    return [];
  }
  return fetch(`https://api.github.com/users/${name}/repos`)
    .then(handleErrors)
    .then((response) => {return response.json()})
    .catch((error) => {
      console.error(error);
    });
};

export const getReadme = (url) => {
  let headers = new Headers();
  headers.append('Accept', 'application/vnd.github.VERSION.html');
  return fetch(`${url}/readme`, {headers})
    .then(handleErrors)
    .then((response => {return response.text()}))
    .catch((error) => {
      console.error(error);
    });
}

Add function to get readme Simple function that does a get request to get a GitHub's repo README file and receive the data as html


const path = require('path');

function resolvePath(pathToResolve) {
  return path.resolve(__dirname, pathToResolve);
}

module.exports = {
  lintOnSave: false,

  pages: {
    'index': {
      entry: 'src/main.js',
      chunks: ['chunk-vendors', 'chunk-common', 'index', 'preload'],
    },
    'options/options': {
      entry: 'src/options/options.js',
    },
  },

  pluginOptions: {
    browserExtension: {
      components: {
        background: true,
        options: true,
        standalone: true,
      },
      api: 'browser',
      usePolyfill: true,
      autoImportPolyfill: true,
      componentOptions: {
        background: {
          entry: 'src/browser/background.js',
        },
      },
    },
  },

  chainWebpack: (config) => {
    config.resolve.alias.set('styles', resolvePath('src/styles'));
    config.entry('preload').add(resolvePath('src/styles/preload.scss'));
  },
};

const path = require('path');

function resolvePath(pathToResolve) {
  return path.resolve(__dirname, pathToResolve);
}

module.exports = {
  lintOnSave: false,

  pages: {
    'index': {
      entry: 'src/main.js',
      chunks: ['chunk-vendors', 'chunk-common', 'index', 'preload'],
    },
    'options/options': {
      entry: 'src/options/options.js',
    },
  },

  pluginOptions: {
    browserExtension: {
      components: {
        background: true,
        options: true,
        standalone: true,
      },
      api: 'browser',
      usePolyfill: true,
      autoImportPolyfill: true,
      componentOptions: {
        background: {
          entry: 'src/browser/background.js',
        },
      },
    },
  },

  chainWebpack: (config) => {
    config.resolve.alias.set('styles', resolvePath('src/styles'));
    config.entry('preload').add(resolvePath('src/styles/preload.scss'));
  },

  configureWebpack: {
    node: {
      global: true,
    },
  },
};

Fix 'global is not defined' error


// Ionic Starter App

// angular.module is a global place for creating, registering and retrieving Angular modules
// 'starter' is the name of this angular module example (also set in a <body> attribute in index.html)
// the 2nd parameter is an array of 'requires'
angular.module('starter', ['ionic'])

.run(function($ionicPlatform) {
    $ionicPlatform.ready(function() {
        if (window.cordova && window.cordova.plugins.Keyboard) {
            // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
            // for form inputs)
            cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);

            // Don't remove this line unless you know what you are doing. It stops the viewport
            // from snapping when text inputs are focused. Ionic handles this internally for
            // a much nicer keyboard experience.
            cordova.plugins.Keyboard.disableScroll(true);
        }
        if (window.StatusBar) {
            StatusBar.styleDefault();
        }
    });
})

.controller('ClientController', ['$scope', function($scope) {

    $scope.sendGetRequest = function() {

    }

    $scope.sendGetRequestNews = function() {

    }

    $scope.sendGetRequestNewsWithAmount = function(amount) {

    }

    $scope.sendPostRequest = function(keyword) {

    }

}])

// Ionic Starter App

// angular.module is a global place for creating, registering and retrieving Angular modules
// 'starter' is the name of this angular module example (also set in a <body> attribute in index.html)
// the 2nd parameter is an array of 'requires'
angular.module('starter', ['ionic'])

.run(function($ionicPlatform) {
    $ionicPlatform.ready(function() {
        if (window.cordova && window.cordova.plugins.Keyboard) {
            // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
            // for form inputs)
            cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);

            // Don't remove this line unless you know what you are doing. It stops the viewport
            // from snapping when text inputs are focused. Ionic handles this internally for
            // a much nicer keyboard experience.
            cordova.plugins.Keyboard.disableScroll(true);
        }
        if (window.StatusBar) {
            StatusBar.styleDefault();
        }
    });
})

.controller('ClientController', ['$scope', '$http', function($scope, $http) {

    $scope.sendGetRequest = function() {
        var url = "http://localhost:8888/myapi/";

        $http.get(url)
            .success(function(data) {
                console.log('OK: ' + data);
            })
            .error(function(error, status) {
                console.error(error + ' ' + status);
            });
    }

    $scope.sendGetRequestNews = function() {

    }

    $scope.sendGetRequestNewsWithAmount = function(amount) {

    }

    $scope.sendPostRequest = function(keyword) {

    }

}])

Implement get request in controller


var respond = require("./respond");

// Retrieve the requested stored resource
// Such resources may contain unsafe data depending on their source

function storeRequest(request, response) {
    var apiRequest = request.body;

    var requestType = apiRequest.type;

    if (requestType === "store") return storeRequest(apiRequest, response);

    if (requestType === "fetch") return fetchRequest(apiRequest, response);

    respond.fail(response, "Unsupported store api request: " + requestType);
}

function storeRequest(apiRequest, response) {
    var basket = apiRequest.basket;
    if (respond.failIfUndefined(response, basket, "basket")) return;

}

function fetchRequest(apiRequest, response) {
    var basket = apiRequest.basket;
    if (respond.failIfUndefined(response, basket, "basket")) return;

}

module.exports = storeRequest;

var respond = require("./respond");
var StoreMemory = require("./pointrel20151212/StoreMemory");

// Retrieve the requested stored resource
// Such resources may contain unsafe data depending on their source

function storeRequest(request, response) {
    var apiRequest = request.body;

    var requestType = apiRequest.type;

    if (requestType === "store") return storeRequest(apiRequest, response);

    if (requestType === "fetch") return fetchRequest(apiRequest, response);

    respond.fail(response, "Unsupported store api request: " + requestType);
}

var baskets = {};

// TODO: Support storing either string with content type or an object
function storeRequest(apiRequest, response) {
    var content = apiRequest.content;
    if (respond.failIfUndefined(response, content, "content")) return;

    // TODO: Assuming for now content is an object to be converted

    var basketName = apiRequest.basket;
    if (respond.failIfUndefined(response, basketName, "basket")) return;

    var basket = baskets[basketName];
    if (!basket) {
        basket = new StoreMemory();
        baskets[basketName] = baskets;
    }

    basket.storeDataObject(content).then(function (sha256) {
        respond.success(response, {sha256: sha256});
    });
}

// TODO: Support multiple baskets in search
function fetchRequest(apiRequest, response) {
    var sha256 = apiRequest.sha256;
    if (respond.failIfUndefined(response, sha256, "sha256")) return;

    var basketName = apiRequest.basket;
    if (respond.failIfUndefined(response, basketName, "basket")) return;

    var basket = baskets[basketName];
    if (!basket) return respond.fail(response, "No such basket: " + basketName);

    basket.fetchDataObject(sha256).then(function (result) {
        respond.success(response, {content: result});
    });
}

module.exports = storeRequest;

Improve storyRequest.js to use MemoryStore


import glob from 'glob';

export default function(importPattern) {
  return glob.sync(importPattern)
    .filter(fileName => fileName.match(/\.js$/))
    .map(fileName => {
      console.log(fileName);
      return require(fileName);
    })
    .filter(module => module.__esModule)
    .reduce((acc, module) => {
      console.log(module);
      return [...acc, ...Object.values(module)];
    }, []);
}

import glob from 'glob';

export default function(importPattern) {
  return glob.sync(importPattern)
    .filter(fileName => fileName.match(/\.js$/))
    .map(fileName => require(fileName))
    .filter(module => module.__esModule)
    .reduce((acc, module) => [...acc, ...Object.values(module)], []);
}

Remove console logs from importGlob


class KasenIterator {
  constructor(iter, func) {
    this.iter = iter;
    this.func = func;
  }

  // eslint-disable-next-line class-methods-use-this
  next() {
    throw new Error("not implemented");
  }

  // eslint-disable-next-line class-methods-use-this
  prev() {
    throw new Error("not implemented");
  }
}

export class MapIterator extends KasenIterator {
  __base(direction) {
    const result = this.iter[direction]();
    if (!result.done) {
      result.value = this.func(result.value, result.key);
    }
    return result;
  }

  next() {
    return this.__base("next");
  }

  prev() {
    return this.__base("prev");
  }
}

export class FilterIterator extends KasenIterator {
  __base(direction) {
    let result;
    // eslint-disable-next-line no-cond-assign
    while (!(result = this.iter[direction]()).done) {
      if (this.func(result.value, result.key)) {
        // TODO: Shrink "key" if Array
        break;
      }
    }
    return result;
  }

  next() {
    return this.__base("next");
  }

  prev() {
    return this.__base("prev");
  }
}

export class ReverseIterator extends KasenIterator {
  next() {
    return this.iter.prev();
  }

  prev() {
    return this.iter.next();
  }
}

class KasenIterator {
  constructor(iter, func) {
    this.iter = iter;
    this.func = func;
  }

  // eslint-disable-next-line class-methods-use-this, no-unused-vars
  __base(_direction) {
    throw new Error("not implemented");
  }

  next() {
    return this.__base("next");
  }

  prev() {
    return this.__base("prev");
  }
}

export class MapIterator extends KasenIterator {
  __base(direction) {
    const result = this.iter[direction]();
    if (!result.done) {
      result.value = this.func(result.value, result.key);
    }
    return result;
  }
}

export class FilterIterator extends KasenIterator {
  __base(direction) {
    let result;
    // eslint-disable-next-line no-cond-assign
    while (!(result = this.iter[direction]()).done) {
      if (this.func(result.value, result.key)) {
        // TODO: Shrink "key" if Array
        break;
      }
    }
    return result;
  }
}

export class ReverseIterator extends KasenIterator {
  next() {
    return this.iter.prev();
  }

  prev() {
    return this.iter.next();
  }
}

Implement default next() and prev()


function daysInMonth(month,year) {
  return new Date(year, month + 1, 0).getDate();
}

function kmhToDegsPerH(kmh) {
  return kmh / 111;
}

module.exports.daysInMonth = daysInMonth;
module.exports.kmhToDegsPerH = kmhToDegsPerH;

var moment = require('moment');

function daysInMonth(month,year) {
  return new Date(year, month + 1, 0).getDate();
}

function kmhToDegsPerH(kmh) {
  return kmh / 111;
}

function timeRangeInMonths(timeRange) {
  return Math.round(moment(timeRange[1]*1000).diff(moment(timeRange[0]*1000), 'months', true));
}

module.exports.daysInMonth = daysInMonth;
module.exports.kmhToDegsPerH = kmhToDegsPerH;
module.exports.timeRangeInMonths = timeRangeInMonths;

Add function for calculating time range in months


module.exports = o => `<head>
    <title>Tennisvuorot Helsingissä</title>
    <meta charset="utf-8"/>
    <meta name="apple-mobile-web-app-capable" content="yes"/>
    <link rel="apple-touch-icon" href="tennisBall.png"/>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
    <meta name="google" content="notranslate" />
    <meta name="Description" content="Helsingin tenniskenttien varaustilanne ja yhteystiedot.">
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <meta name="google-site-verification" content="KcIYdY4YdFSZqc7HCEJnLyFBDBvbEOxDq_1K9drmUPk" />
</head>`

module.exports = o => `<head>
    <title>Tennisvuorot Helsingissä</title>
    <meta charset="utf-8"/>
    <meta name="apple-mobile-web-app-capable" content="yes"/>
    <link rel="apple-touch-icon" href="tennisBall.png"/>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
    <meta name="google" content="notranslate" />
    <meta name="Description" content="Helsingin tenniskenttien varaustilanne ja yhteystiedot.">
    <link rel="stylesheet" type="text/css" href="styles.css"/>
</head>`

Remove meta tag for google


import React from "react";
import ReactDOMServer from "react-dom/server";
import { Route, StaticRouter } from "react-router";
import config from "config"; // Aliased through webpack
import paths from "../paths";
import BodyContent from "../BodyContent";

// TODO: what if a route isn't found?
module.exports = function renderPage(location, cb) {
  const allPages = paths.getAllPages(config);
  const page = paths.getPageForPath(location, allPages);

  (config.renderPage || renderDefault)(
    {
      location,
      content: BodyContent(page, allPages),
    },
    (err, { html, context }) => {
      if (err) {
        return cb(err);
      }

      return cb(null, { html, page, context });
    }
  );
};

function renderDefault({ location, content }, cb) {
  cb(null, {
    html: ReactDOMServer.renderToStaticMarkup(
      <StaticRouter location={location} context={{}}>
        <Route component={content} />
      </StaticRouter>
    ),
  });
}

import React from "react";
import ReactDOMServer from "react-dom/server";
import { Route, StaticRouter } from "react-router";
import config from "config"; // Aliased through webpack
import paths from "../paths";
import BodyContent from "../BodyContent";

// TODO: what if a route isn't found?
module.exports = function renderPage(location, cb) {
  const allPages = paths.getAllPages(config);
  const page = paths.getPageForPath(location, allPages);

  (config.renderPage || renderDefault)(
    {
      location,
      content: BodyContent(page, allPages),
    },
    (err, { html, context } = {}) => {
      if (err) {
        return cb(err);
      }

      return cb(null, { html, page, context });
    }
  );
};

function renderDefault({ location, content }, cb) {
  cb(null, {
    html: ReactDOMServer.renderToStaticMarkup(
      <StaticRouter location={location} context={{}}>
        <Route component={content} />
      </StaticRouter>
    ),
  });
}

Make sure an object is being destructured at getPage


$(function () {

  // show tooltip on hover
  tooltip();

  // do avoid clickable table cell if there is an action within
  $('table.selectable-table td').click(function () {

    var href = $(this).find('a').attr('href');
    if (href) {
      window.location = href;
      return false;
    }

  });

});

function tooltip() {
  $('[data-title]').attr('data-placement', 'bottom').tooltip();
}

$(function () {

  // show tooltip on hover
  tooltip();

  // do avoid clickable table cell if there is an action within
  $('table.selectable-table td').click(function (event) {

    var target = event.target;
    var href = $(target).attr('href');

    if (href) {
      window.location = href;
      return false;
    }

  });

});

function tooltip() {
  $('[data-title]').attr('data-placement', 'bottom').tooltip();
}

Fix issue with not working reject application for leave shortcut


var gulp = require('gulp');
var gulpif = require('gulp-if');
var svg2png = require('gulp-svg2png');
var config = require('../config.js');

gulp.task('svg2png', function() {
    return gulp.src(config.src + 'svg/*.svg')
        .pipe(gulpif(config.svgFallback, svg2png()))
        .pipe(gulp.dest(config.build + 'img/svg-fallbacks/'));
});

var gulp = require('gulp');
var gulpif = require('gulp-if');
var svg2png = require('gulp-svg2png');
var config = require('../config.js');

gulp.task('svg2png', function() {
    return gulp.src(config.src + 'svg/*.svg')
        .pipe(gulpif(config.svgFallback, svg2png()))
        .pipe(gulpif(config.svgFallback, gulp.dest(config.build + 'img/svg-fallbacks/')));
});

Fix svg fallbacks gulp task


import test from 'ava'

import { ConfigService } from '../../../src/services/config'

test('test', t => {
  t.deepEqual([1, 2], [1, 2])
})

import test, { beforeEach, afterEach } from 'ava'
import fs from 'fs'
import path from 'path'
import rimraf from 'rimraf'

import { ConfigService, ConfigurationFileNotExist } from '../../../src/services/config'

const tmpData = path.join(__dirname, './data')
let configService

beforeEach('init temp files for tests', t => {
  fs.mkdirSync(tmpData)
})

beforeEach('init service', t => {
  configService = new ConfigService(path.join(tmpData, 'configfile'))
})

afterEach.always('clean temp files', t => {
  rimraf.sync(tmpData)
})

test('configFileExist() should return false when main configuration file does not exist', t => {
  t.false(configService.configFileExist())
})

test('configFileExist() should return true when main configuration file exist', t => {
  fs.writeFileSync(configService.configPath, JSON.stringify({}))
  t.true(configService.configFileExist())
})

test('configData() should return good data when main configuration file exist', t => {
  fs.writeFileSync(configService.configPath, JSON.stringify({ 'repo_url': 'https://', 'folder_path': './' }))
  t.deepEqual(configService.configData(), { 'folder_path': './', 'repo_url': 'https://' })
})

test('configData() should throw an error when main configuration file exist', t => {
  t.throws(() => configService.configData(), ConfigurationFileNotExist)
})

Add tests for configFileExist & configData methods


'use strict'
var express = require('express'),
    app = express(),
    config = require('./libs/config'),
    router = require('./routes'),
    SerialPort = require('serialport').SerialPort,
    serialPort = new SerialPort(config.serial.path, {
      baudrate: config.serial.buadRate
    }, false),
    cards = require('./routes/cards'),
    door = require('./libs/door'),
    bodyParser = require('body-parser');

app.use(bodyParser.json())

app.get('/', function(req, res){
  res.send({
    active: true,
    timestamp: new Date().getTime()
  })
})

app.use('/api/v1', router)

// Server Listener
app.listen(config.app.port, config.app.host, function(e) {
  console.log('Listening on http://%s:%s', config.app.host, config.app.port)
  door.setup(function() {
    serialPort.open(function (error) {
      if ( error ) {
        console.log('failed to open: '+ error);
      } else {
        serialPort.on('data', function(data) {
          cards.isUIDAllowed(data, function(err, test) {
            if (err) throw err;
            door.open();
          });
        });
      }
    });
  });
})

'use strict'
var express = require('express'),
    app = express(),
    config = require('./libs/config'),
    router = require('./routes'),
    SerialPort = require('serialport').SerialPort,
    serialPort = new SerialPort(config.serial.path, {
      baudrate: config.serial.buadRate
    }, false),
    cards = require('./routes/cards'),
    door = require('./libs/door'),
    bodyParser = require('body-parser');

app.use(bodyParser.json())

app.get('/', function(req, res){
  res.send({
    active: true,
    timestamp: new Date().getTime()
  })
})

app.use('/api/v1', router)

// Server Listener
app.listen(config.app.port, config.app.host, function(e) {
  console.log('Listening on http://%s:%s', config.app.host, config.app.port)
  if(process.env.PROD) {
    door.setup(function() {
      serialPort.open(function (error) {
        if ( error ) {
          console.log('failed to open: '+ error);
        } else {
          serialPort.on('data', function(data) {
            cards.isUIDAllowed(data, function(err, test) {
              if (err) throw err;
              door.open();
            });
          });
        }
      });
    });
  }
})

Allow to work in DEV or PROD


/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * 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
 */

foam.CLASS({
  package: 'foam.u2',
  name: 'HTMLValidator',
  extends: 'foam.u2.DefaultValidator',

  axioms: [ foam.pattern.Singleton.create() ],

  methods: [
    function sanitizeText(text) {
      // TODO: validate text
      return text;
    }
  ]
});

// An Element which does not escape HTML content
foam.CLASS({
  package: 'foam.u2',
  name: 'HTMLElement',
  extends: 'foam.u2.Element',

  requires: [ 'foam.u2.HTMLValidator' ],

  exports: [ 'validator as elementValidator' ],

  properties: [
    {
      class: 'Proxy',
      of: 'foam.u2.DefaultValidator',
      name: 'validator',
      factory: function() { return this.HTMLValidator.create() }
    }
  ]
});

/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * 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
 */

foam.CLASS({
  package: 'foam.u2',
  name: 'HTMLValidator',
  extends: 'foam.u2.DefaultValidator',

  axioms: [ foam.pattern.Singleton.create() ],

  methods: [
    function sanitizeText(text) {
      // TODO: validate text
      return text;
    }
  ]
});

// An Element which does not escape HTML content
foam.CLASS({
  package: 'foam.u2',
  name: 'HTMLElement',
  extends: 'foam.u2.Element',

  requires: [ 'foam.u2.HTMLValidator' ],

  exports: [ 'validator as elementValidator' ],

  properties: [
    {
      class: 'Proxy',
      of: 'foam.u2.DefaultValidator',
      name: 'validator',
      factory: function() {
        // Note that HTMLValidator is a singleton so only once instance of
        // HTMLValidator should ever be created here.
        return this.HTMLValidator.create()
      }
    }
  ]
});

Add comment to HTMLValidator factory.


'use strict';

angular.module('config', [])
  .constant('config', {
    'endpoint'    : "http://"+window.location.hostname+":8080",
    'provider'    : "basic", // google, github, gitlab, keycloak, pingfederate, saml2 or basic
    'client_id'   : "INSERT-CLIENT-ID-HERE",
    'github_url'  : null,  // replace with your enterprise github server
    'gitlab_url'  : "https://gitlab.com",  // replace with your gitlab server
    'keycloak_url': "https://keycloak.example.org",  // replace with your keycloak server
    'keycloak_realm': "master",  // replace with your keycloak realm
    'pingfederate_url' : "https://pingfederate.example.org" // replace with your pingfederate server
    'colors'      : {}, // use default colors
    'severity'    : {}, // use default severity codes
    'audio'       : {}, // no audio
    'tracking_id' : ""  // Google Analytics tracking ID eg. UA-NNNNNN-N
  });

'use strict';

angular.module('config', [])
  .constant('config', {
    'endpoint'        : "http://"+window.location.hostname+":8080",
    'provider'        : "basic", // google, github, gitlab, keycloak, pingfederate, saml2 or basic
    'client_id'       : "INSERT-CLIENT-ID-HERE",
    'github_url'      : null,  // replace with your enterprise github server
    'gitlab_url'      : "https://gitlab.com",  // replace with your gitlab server
    'keycloak_url'    : "https://keycloak.example.org",  // replace with your keycloak server
    'keycloak_realm'  : "master",  // replace with your keycloak realm
    'pingfederate_url': "https://pingfederate.example.org", // replace with your pingfederate server
    'colors'          : {}, // use default colors
    'severity'        : {}, // use default severity codes
    'audio'           : {}, // no audio
    'tracking_id'     : ""  // Google Analytics tracking ID eg. UA-NNNNNN-N
  });

Add missing comma and fix alignment


// Mithril component which embeds four iframes with HTML videos

const MyComponent = {
    view(controller, args) {
        return m("div.ba.ma3.pa3.bg-light-purple", [
            // Passengers 2016 - Starship Avalon Slingshot Around The Red Giant Star Arcturus
            m("iframe", {src: "//www.youtube.com/embed/1jGRpuzrkpk", width: 800, height: 600, frameborder: "0", allowfullscreen: true}),
            // Alan Kay at OOPSLA 1997 - The computer revolution hasn't happened yet
            m("iframe", {src: "//www.youtube.com/embed/oKg1hTOQXoY", width: 800, height: 600, frameborder: "0", allowfullscreen: true}),
            // The Mother of All Demos, presented by Douglas Engelbart (1968)
            m("iframe", {src: "//www.youtube.com/embed/yJDv-zdhzMY", width: 800, height: 600, frameborder: "0", allowfullscreen: true}),
            // Twirlip Civic Sensemaking Project Overview
            m("iframe", {src: "//www.youtube.com/embed/_mRy4sGK7xk", width: 800, height: 600, frameborder: "0", allowfullscreen: true}),
        ])
    }
}

Twirlip7.show(MyComponent, { title: "Videos on the future, the present, and the past" } )

// Mithril component which embeds four iframes with HTML videos

const MyComponent = {
    view(controller, args) {
        return m("div.ba.ma3.pa3.bg-light-purple", [
            // The Mother of All Demos, presented by Douglas Engelbart (1968)
            m("iframe", {src: "//www.youtube.com/embed/yJDv-zdhzMY", width: 800, height: 600, frameborder: "0", allowfullscreen: true}),

            // Alan Kay at OOPSLA 1997 - The computer revolution hasn't happened yet
            m("iframe", {src: "//www.youtube.com/embed/oKg1hTOQXoY", width: 800, height: 600, frameborder: "0", allowfullscreen: true}),

            // The Lively Kernel presented by Dan Ingalls aa a Google Tech Talks  (2008)
            m("iframe", {src: "//www.youtube.com/embed/gGw09RZjQf8", width: 800, height: 600, frameborder: "0", allowfullscreen: true}),

            // Twirlip Civic Sensemaking Project Overview (2011)
            m("iframe", {src: "//www.youtube.com/embed/_mRy4sGK7xk", width: 800, height: 600, frameborder: "0", allowfullscreen: true}),
        ])
    }
}

Twirlip7.show(MyComponent, { title: "Videos on the future, the present, and the past" } )

Replace a video in example because previous one disappeared


var path = require('path')
  , fs = require('fs')
  , clone = require('clone')
  , data = require('./data')
  , getset = require('deep-get-set')
  , DeepMerge = require('deep-merge')
  , merge = DeepMerge(function(a, b) {
      return b
    });

getset.p = true;

function File(path) {
  this._path = path;
  this._obj = {};
}

File.prototype.open = function() {
  if (fs.existsSync(this._path)) {
    var text = fs.readFileSync(this._path, 'utf8')
      , format = data.createFormat(path.extname(this._path));

    this._obj = format.parse(text);
  }
}

File.prototype.get = function(path) {
  path = path.replace(/\//g, '.');
  return clone(getset(this._obj, path));
}

File.prototype.toObject = function() {
  return clone(this._obj);
}

module.exports = File;

var path = require('path')
  , fs = require('fs')
  , clone = require('clone')
  , getset = require('deep-get-set')
  , data = require('./data');

getset.p = true;

function File(path) {
  this._path = path;
  this._obj = {};
}

File.prototype.open = function() {
  if (fs.existsSync(this._path)) {
    var text = fs.readFileSync(this._path, 'utf8')
      , format = data.createFormat(path.extname(this._path));

    this._obj = format.parse(text);
  }
}

File.prototype.get = function(path) {
  path = path.replace(/\//g, '.');
  return clone(getset(this._obj, path));
}

File.prototype.toObject = function() {
  return clone(this._obj);
}

module.exports = File;

Remove unused deep-merge dependency from File.


'use strict';

var _ = require('lodash');

module.exports = {
  launch: _.once(function () {
    window.app = this;
  })
};

module.exports.launch();

'use strict';

var _ = require('lodash');

module.exports = {
  launch: _.once(function () {
    window.app = this;
    document.title = 'Altceva!';
  })
};

module.exports.launch();

Change document.title to test out if prerendering works.


import { Kinvey } from 'kinvey-javascript-sdk-core';
import { NetworkRack } from 'kinvey-javascript-sdk-core/build/rack/rack';
import { SerializeMiddleware } from 'kinvey-javascript-sdk-core/build/rack/middleware/serialize';
import { HttpMiddleware } from './http';
import { Push } from './push';

export class KinveyProvider {
  constructor() {
    // Use Http middleware after the Serialize middleware
    const networkRack = NetworkRack.sharedInstance();
    networkRack.useAfter(SerializeMiddleware, new HttpMiddleware());

    // Add Push module to Kinvey
    Kinvey.Push = Push;
  }

  init(options) {
    // Initialize Kinvey
    return Kinvey.init(options);
  }

  $get() {
    return Kinvey;
  }
}

import { Kinvey } from 'kinvey-javascript-sdk-core';
import { NetworkRack } from 'kinvey-javascript-sdk-core/build/rack/rack';
import { SerializeMiddleware } from 'kinvey-javascript-sdk-core/build/rack/middleware/serialize';
import { HttpMiddleware } from './http';
import { Push } from './push';

export class KinveyProvider {
  constructor() {
    // Use Http middleware after the Serialize middleware
    const networkRack = NetworkRack.sharedInstance();
    networkRack.useAfter(SerializeMiddleware, new HttpMiddleware());
  }

  init(options) {
    // Initialize Kinvey
    const client = Kinvey.init(options);

    // Add Push module to Kinvey
    Kinvey.Push = new Push();

    // Return the client
    return client;
  }

  $get() {
    return Kinvey;
  }
}

Add Push module during SDK init


module.exports = asyncMap
function asyncMap (list, fn, cb_) {
  var n = list.length
    , results = []
    , errState = null
  function cb (er, data) {
    if (errState) return
    if (er) return cb_(errState = er)
    results.push(data)
    if (-- n === 0)
      return cb_(null, results)
  }
  if (list.length === 0) return cb_(null, [])
  list.forEach(function (l) {
    fn(l, cb)
  })
}

/*
usage:

// do something to a list of things
asyncMap(myListOfStuff, function (thing, cb) { doSomething(thing.foo, cb) }, cb)
// do more than one thing to each item
asyncMap(list, fooFn, barFn, cb)

*/

module.exports = asyncMap

function asyncMap () {
  var steps = Array.prototype.slice.call(arguments)
    , list = steps.shift() || []
    , cb_ = steps.pop()
  if (typeof cb_ !== "function") throw new Error(
    "No callback provided to asyncMap")
  if (!list) return cb_(null, [])
  if (!Array.isArray(list)) list = [list]
  var n = steps.length
    , data = [] // 2d array
    , errState = null
    , l = list.length
    , a = l * n
  if (!a) return cb_(null, [])
  function cb (er) {
    if (errState) return
    var argLen = arguments.length
    for (var i = 1; i < argLen; i ++) if (arguments[i] !== undefined) {
      data[i - 1] = (data[i - 1] || []).concat(arguments[i])
    }
    // see if any new things have been added.
    if (list.length > l) {
      var newList = list.slice(l)
      a += (list.length - l) * n
      l = list.length
      process.nextTick(function () {
        newList.forEach(function (ar) {
          steps.forEach(function (fn) { fn(ar, cb) })
        })
      })
    }

    if (er || --a === 0) {
      errState = er
      cb_.apply(null, [errState].concat(data))
    }
  }
  // expect the supplied cb function to be called
  // "n" times for each thing in the array.
  list.forEach(function (ar) {
    steps.forEach(function (fn) { fn(ar, cb) })
  })
}

Allow for more than one step, like npm does


Add small v in front of version


const {assert} = require('./common');
const Passwords = require('../passwords');

describe('Passwords', () => {
  it('should be able to generate and compare hashes', async () => {
    const pass = 'apple';
    const passFake = 'orange';

    const passHash = await Passwords.hash(pass);
    const passHashSync = Passwords.hashSync(pass);
    const passFakeHashSync = Passwords.hashSync(passFake);

    assert(Passwords.compareSync(pass, passHash),
      'hash should compare correctly to original password');
    assert(Passwords.compareSync(pass, passHashSync),
      'hashSync should compare correctly to original password');
    assert(!Passwords.compareSync(passFake, passHash),
      'hash should not match fake password');
    assert(!Passwords.compareSync(pass, passFakeHashSync),
      'fake hash sync should not match original password');
  });
})

const {assert} = require('./common');
const Passwords = require('../passwords');

describe('Passwords', () => {
  it('should be able to generate and compare hashes', async () => {
    const pass = 'apple';
    const passFake = 'orange';

    const passHash = await Passwords.hash(pass);
    const passHashSync = Passwords.hashSync(pass);
    const passFakeHashSync = Passwords.hashSync(passFake);

    assert(Passwords.compareSync(pass, passHash),
      'hash should compare correctly to original password');
    const compareAsync = await Passwords.compare(pass, passHash);
    assert(compareAsync,
      'hash should async compare correctly to original password');
    assert(Passwords.compareSync(pass, passHashSync),
      'hashSync should compare correctly to original password');
    assert(!Passwords.compareSync(passFake, passHash),
      'hash should not match fake password');
    assert(!Passwords.compareSync(pass, passFakeHashSync),
      'fake hash sync should not match original password');
  });
})

Add test of async compare


/*
 * Code style test
 */

/**
 * Module dependencies
 */
var helper = require('./helper');

/**
 * Lint own code
 *
 * @param test
 */
exports.MakeLint = function (test) {
  helper.testConsoleOutput(
    'make',
    ['lint'],
    {
      stdout: './node_modules/.bin/nodelint ./lib/ ./test/\n',
      stderr: '0 errors\n',
      exitCode: 0
    },
    test
  );
};

/*
 * Code style test
 */

/**
 * Module dependencies
 */
var helper = require('./helper');

/**
 * Lint own code
 *
 * @param test
 */
exports.MakeLint = function (test) {
  helper.testConsoleOutput(
    './node_modules/.bin/nodelint',
    ['./lib/', './test/'],
    {
      stdout: '',
      stderr: '0 errors\n',
      exitCode: 0
    },
    test
  );
};

Fix 'make test' for quiet make


import {element} from 'deku';

import Editor from './editor';
import Menu from './menu';
import Panel from './panel';

function render({props, state}, setState) {
  console.log({props, state});

  if (!state.trees) {
    state.trees = props.globals.trees;
  }

  const updateTrees = (newTrees) => {
    setState({trees: newTrees});
  };

  return (

      <Panel
        trees={state.trees}
        updateTrees={updateTrees} />

      <Menu />
      <Editor />

  );
}

export default {render};

import {element} from 'deku';

import Editor from './editor';
import Menu from './menu';
import Panel from './panel';

function initialState(props) {
  return {
    trees: props.globals.trees
  };
}

function render({props, state}, setState) {
  console.log({props, state});

  const updateTrees = (newTrees) => {
    setState({trees: newTrees});
  };

  return (

      <Panel
        trees={state.trees}
        updateTrees={updateTrees} />

      <Menu />
      <Editor />

  );
}

export default {initialState, render};

Make good use of initialState


(function (root, factory) {
  if(typeof define === 'function' && define.amd) {
    define(['knockout'], factory);
  } else {
    factory(root.ko);
  }
}(this, function(ko) {
  ko.bindingHandlers.bindHtml = {
    update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
      ko.unwrap(valueAccessor());
      ko.applyBindingsToDescendants(bindingContext.$data, element);
    }
  };

  ko.bindingHandlers.bindHtml.preprocess = function(value, name, addBinding) {
      addBinding('html', value);
      return value;
  };
}));

(function (root, factory) {
  if(typeof define === 'function' && define.amd) {
    // Support AMD modules
    define(['knockout'], factory);
  } else {
    factory(root.ko);
  }
}(this, function(ko) {
  ko.bindingHandlers.bindHtml.preprocess = function(value, name, addBinding) {
    addBinding('html', value);
    return value;
  };

  ko.bindingHandlers.bindHtml = {
    update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
      ko.unwrap(valueAccessor());
      ko.applyBindingsToDescendants(bindingContext.$data, element);
    }
  };
}));

Add a code comment. Reorganized code for clarity.


/**
 * @overview A module that initializes the Gallery with Flickity
 * @module Gallery.js
*/

import Flickity from 'flickity';
import { ImgixSettings } from '../media/ImgixSettings';

export const Gallery = {

  /**
   * Sets up any galleries on the page
   * @returns {void}
  */
  init() {
    const galleryEls = document.querySelectorAll(`.js-gallery`);
    this.setupGalleries(galleryEls);
    this.setupImageGalleries();
  },

  /**
   * Instantiates Flickity on each gallery, with their respective options
   * @param {object} Gallery DOM elements to setup
   * @returns {void}
  */
  setupGalleries(galleryEls) {
    [...galleryEls].forEach((el) => {
      const options = JSON.parse(el.getAttribute(`data-options`));
      new Flickity(el, options);
    });
  },

  /**
   * Sets custom imgix settings for gallery images, instantiating Flickity and
   * fading in the gallery once the first image has loaded
   * @returns {void}
  */
  setupImageGalleries() {
    ImgixSettings.init({
      fluidClass: `imgix-fluid--gallery`,
      onLoad: (el) => {
        const currentGallery = el.closest(`.js-gallery--images`);

        if (el === currentGallery.firstElementChild) {
          this.setupGalleries([currentGallery]);
          currentGallery.classList.add(`is-loaded`);
        }
      },
    });
  },
};

/**
 * @overview A module that initializes the Gallery with Flickity
 * @module Gallery.js
*/

import Flickity from 'flickity';
import { ImgixSettings } from '../media/ImgixSettings.js';

export const Gallery = {

  /**
   * Sets up any galleries on the page
   * @returns {void}
  */
  init() {
    const galleryEls = document.querySelectorAll(`.js-gallery`);
    this.setupGalleries(galleryEls);
    this.setupImageGalleries();
  },

  /**
   * Instantiates Flickity on each gallery, with their respective options
   * @param {object} Gallery DOM elements to setup
   * @returns {void}
  */
  setupGalleries(galleryEls) {
    [...galleryEls].forEach((el) => {
      const options = JSON.parse(el.getAttribute(`data-options`));
      new Flickity(el, options);
    });
  },

  /**
   * Sets custom imgix settings for gallery images, instantiating Flickity and
   * fading in the gallery once the first image has loaded
   * @returns {void}
  */
  setupImageGalleries() {
    ImgixSettings.init({
      fluidClass: `imgix-fluid--gallery`,
      onLoad: (el) => {
        const currentGallery = el.closest(`.js-gallery--images`);

        if (el === currentGallery.firstElementChild) {
          this.setupGalleries([currentGallery]);
          currentGallery.classList.add(`is-loaded`);
        }
      },
    });
  },
};

Add js extension required for proper jspm importing


describe("About Expects", function() {

  //We shall contemplate truth by testing reality, via spec expectations.
  it("should expect true", function() {
    expect(true).toBeTruthy(); //This should be true
  });

  //To understand reality, we must compare our expectations against reality.
  it("should expect equality", function () {
	  var expectedValue = 2;
	  var actualValue = 1 + 1;

	  expect(actualValue === expectedValue).toBeTruthy();
  });

  //Some ways of asserting equality are better than others.
  it("should assert equality a better way", function () {
	  var expectedValue = FILL_ME_IN;
	  var actualValue = 1 + 1;

  // toEqual() compares using common sense equality.
	  expect(actualValue).toEqual(expectedValue);
  });

  //Sometimes you need to be really exact about what you "type".
  it("should assert equality with ===", function () {
	  var expectedValue = FILL_ME_IN;
	  var actualValue = (1 + 1).toString();

  // toBe() will always use === to compare.
	  expect(actualValue).toBe(expectedValue);
  });

  //Sometimes we will ask you to fill in the values.
  it("should have filled in values", function () {
	  expect(1 + 1).toEqual(FILL_ME_IN);
  });
});

describe("About Expects", function() {

  //We shall contemplate truth by testing reality, via spec expectations.
  it("should expect true", function() {
    expect(true).toBeTruthy(); //This should be true
  });

  //To understand reality, we must compare our expectations against reality.
  it("should expect equality", function () {
	  var expectedValue = 2;
	  var actualValue = 1 + 1;

	  expect(actualValue === expectedValue).toBeTruthy();
  });

  //Some ways of asserting equality are better than others.
  it("should assert equality a better way", function () {
	  var expectedValue = 2;
	  var actualValue = 1 + 1;

  // toEqual() compares using common sense equality.
	  expect(actualValue).toEqual(expectedValue);
  });

  //Sometimes you need to be really exact about what you "type".
  it("should assert equality with ===", function () {
	  var expectedValue = FILL_ME_IN;
	  var actualValue = (1 + 1).toString();

  // toBe() will always use === to compare.
	  expect(actualValue).toBe(expectedValue);
  });

  //Sometimes we will ask you to fill in the values.
  it("should have filled in values", function () {
	  expect(1 + 1).toEqual(FILL_ME_IN);
  });
});

Fix 'should assert equality a better way'


//the list of servers we can access
const serverPaths = [
  'http://malmo.lizenn.net:8080',
  'http://localhost:8080',
  'http://localhost:8081',
  'http://malmo.lizenn.net:8082',
]

//the adress used internally in the application to proxy
const proxyURL = '/proxiedOCCIServer';

//the adress of the initial backend Server
const backendURL = serverPaths[0];

//the prefix to append to categories queries
const backendCategoriesPrefix_erocci = '/categories/';
const backendCategoriesPrefix_mart = '/';

const initialState =  {
  currentPath: '/-/',
  currentJson: {},
  errorMessage: {
    simple: '',
    detailed: ''
  },
  okMessage: '',
  currentURLServer: backendURL,
  // readings (toolified), or write (edit)
  codeRights: 'read',
  schemes: {schemeName: [{title: '', term: ''}]}
};

//this const will be changed when deploying on standalone. Must be false by default
const integratedVersion = false;

module.exports = {initialState: initialState, proxyURL: proxyURL, backendURL: backendURL, serverPaths: serverPaths, integratedVersion: integratedVersion, backendCategoriesPrefix_erocci: backendCategoriesPrefix_erocci, backendCategoriesPrefix_mart: backendCategoriesPrefix_mart};

//the list of servers we can access
const serverPaths = [
  'http://malmo.lizenn.net:8080',
  'http://localhost:8080',
  'http://localhost:8081',
  'http://malmo.lizenn.net:8082',
  'http://martserver-linkeddata-1:8081'
]

//the adress used internally in the application to proxy
const proxyURL = '/proxiedOCCIServer';

//the adress of the initial backend Server
const backendURL = serverPaths[0];

//the prefix to append to categories queries
const backendCategoriesPrefix_erocci = '/categories/';
const backendCategoriesPrefix_mart = '/';

const initialState =  {
  currentPath: '/-/',
  currentJson: {},
  errorMessage: {
    simple: '',
    detailed: ''
  },
  okMessage: '',
  currentURLServer: backendURL,
  // readings (toolified), or write (edit)
  codeRights: 'read',
  schemes: {schemeName: [{title: '', term: ''}]}
};

//this const will be changed when deploying on standalone. Must be false by default
const integratedVersion = false;

module.exports = {initialState: initialState, proxyURL: proxyURL, backendURL: backendURL, serverPaths: serverPaths, integratedVersion: integratedVersion, backendCategoriesPrefix_erocci: backendCategoriesPrefix_erocci, backendCategoriesPrefix_mart: backendCategoriesPrefix_mart};

Add martserver-linkeddata-1:8081 to default target servers


// At max, each shard should have ~2500 servers
// This is used for when you get your bot on a ton of servers.

const Discord = require("discord.js");
const Manager = new Discord.ShardingManager("./swgohBot.js");   // Tell it what the main file we'd otherwise run the bot with is
Manager.spawn(5);   // Tell it that we want 5 shards (Approx. 1100 servers per shard)

// At max, each shard can have 2500 servers
const config = require("./config.js");

const Discord = require("discord.js");
const Manager = new Discord.ShardingManager("./swgohBot.js",{
    totalShards: config.shardCount  // Tell it how many shards we want (Approx. 1100 servers per shard)
});
Manager.spawn();

Manager.on("launch", (shard) => {
    console.log(`Launching Shard ${shard.id} (${shard.id + 1}/${Manager.totalShards})`);
});

Update the sharding file to use the config


/*
  Simple example that takes a command line provided serial port destination and routes the output to a file of the same name with .log appended to the port name.

  usage: node logger.js /dev/tty.usbserial <baudrate>

*/

var SerialPort = require("serialport");
var fs = require("fs");
var port = process.argv[2];
var baudrate = process.argv[3];
if (!port) {
  console.log("You must specify a serial port location.");
} else {
  var target = port.split("/");
  target = target[target.length-1]+".log";
  if (!baudrate) {
    baudrate = 115200;
  }
  fs.open("./"+target, 'w', function (err, fd) {
    fs.write(fd, "\n------------------------------------------------------------\nOpening SerialPort: "+target+" at "+Date.now()+"\n------------------------------------------------------------\n");
    var serialPort = new SerialPort.SerialPort(port, {
      baudrate: baudrate
    });
    serialPort.on("data", function (data) {
      fs.write(fd, data.toString());
    });
    serialPort.on("close", function (data) {
      fs.write(fd, "\n------------------------------------------------------------\nClosing SerialPort: "+target+" at "+Date.now()+"\n------------------------------------------------------------\n");
    });
  });
}

/*
  Simple example that takes a command line provided serial port destination and routes the output to a file of the same name with .log appended to the port name.

  usage: node logger.js /dev/tty.usbserial <baudrate>

*/

var SerialPort = require("serialport");
var fs = require("fs");
var port = process.argv[2];
var baudrate = process.argv[3];
var active = false;

function attemptLogging(fd, port, baudrate) {
  fs.write(fd, "\n------------------------------------------------------------\nOpening SerialPort: "+target+" at "+Date.now()+"\n------------------------------------------------------------\n");
  var serialPort = new SerialPort.SerialPort(port, {
    baudrate: baudrate
  });
  serialPort.on("data", function (data) {
    fs.write(fd, data.toString());
  });
  serialPort.on("close", function (data) {
    active = false;
    fs.write(fd, "\n------------------------------------------------------------\nClosing SerialPort: "+target+" at "+Date.now()+"\n------------------------------------------------------------\n");
  });
}

if (!port) {
  console.log("You must specify a serial port location.");
} else {
  var target = port.split("/");
  target = target[target.length-1]+".log";
  if (!baudrate) {
    baudrate = 115200;
  }
  fs.open("./"+target, 'w', function (err, fd) {
    setInterval(function () {
      if (!active) {
        try {
          attemptLogging(fd, port, baudrate);
        } catch (e) {
          // Error means port is not available for listening.
          active = false;
        }
      }
    }, 1000);
  });
}

Make it run regardless of if the port is present (and restart if it is present)


var assert = require('assert');

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

describe('Filename normalization', function() {
    it('should normalize spaces', function() {
        assert.equal(filename('1 2 3'), '1_2_3');
    });

    it('should ignore trailing spaces', function() {
        assert.equal(filename('a b c '), 'a_b_c');
    });

    it('should remove illegal chars', function() {
        assert.equal(filename('ABC 38 ./.#$#@!/'), 'ABC_38');
    });

    it('should strip dots', function() {
        assert.equal(filename('a.b.c'), 'abc');
    });

    it('should convert to lowecase', function() {
        assert.equal(filename('AbC DeF gHi'), 'abc_def_ghi');
    });

});

var assert = require('assert');

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

describe('Filename normalization', function() {
    it('should normalize spaces', function() {
        assert.equal(filename('1 2 3'), '1_2_3');
    });

    it('should ignore trailing spaces', function() {
        assert.equal(filename('a b c '), 'a_b_c');
    });

    it('should remove illegal chars', function() {
        assert.equal(filename('abc 38 ./.#$#@!/'), 'abc_38');
    });

    it('should strip dots', function() {
        assert.equal(filename('a.b.c'), 'abc');
    });

    it('should convert to lowecase', function() {
        assert.equal(filename('AbC DeF gHi'), 'abc_def_ghi');
    });

});

Fix lowercase conflict on previous test


import thunk from "redux-thunk";
import { combineReducers, createStore, applyMiddleware, compose } from "redux";
import _gluestick from "./reducers";
import promiseMiddleware from "../lib/promiseMiddleware";

export default function (client, customRequire, customMiddleware, hotCallback, devMode) {
  const reducer = combineReducers(Object.assign({}, {_gluestick}, customRequire()));

  let middleware = [
    promiseMiddleware(client),
    thunk,
  ];

  // Include middleware that will warn when you mutate the state object
  // but only include it in dev mode
  if (devMode) {
    middleware.push(require("redux-immutable-state-invariant")());
  }

  // When `customMiddleware` is of type `function`, pass it a shallow
  // copy of the current array of `middlewares` and expect a new value
  // in return. Fallback to default behaviour.
  middleware = typeof customMiddleware === 'function'
    ? customMiddleware([...middleware])
    : middleware.concat(customMiddleware);

  const composeArgs = [
    applyMiddleware.apply(this, middleware),
    typeof window === "object" && typeof window.devToolsExtension !== "undefined" ? window.devToolsExtension() : f => f
  ];

  const finalCreateStore = compose.apply(null, composeArgs)(createStore);
  const store = finalCreateStore(reducer, typeof window !== "undefined" ? window.__INITIAL_STATE__ : {});

  if (hotCallback) {
    hotCallback(() => {
      const nextReducer = combineReducers(Object.assign({}, {_gluestick}, customRequire()));
      store.replaceReducer(nextReducer);
    });
  }

  return store;
}

import thunk from "redux-thunk";
import { combineReducers, createStore, applyMiddleware, compose } from "redux";
import _gluestick from "./reducers";
import promiseMiddleware from "../lib/promiseMiddleware";

export default function (client, customRequire, customMiddleware, hotCallback, devMode) {
  const reducer = combineReducers(Object.assign({}, {_gluestick}, customRequire()));

  let middleware = [
    promiseMiddleware(client),
    thunk,
  ];

  // Include middleware that will warn when you mutate the state object
  // but only include it in dev mode
  if (devMode) {
    middleware.push(require("redux-immutable-state-invariant")());
  }

  // When `customMiddleware` is of type `function`, pass it current
  // array of `middlewares` and expect a new value in return.
  // Fallback to default behaviour.
  middleware = typeof customMiddleware === 'function'
    ? customMiddleware([...middleware])
    : middleware.concat(customMiddleware);

  const composeArgs = [
    applyMiddleware.apply(this, middleware),
    typeof window === "object" && typeof window.devToolsExtension !== "undefined" ? window.devToolsExtension() : f => f
  ];

  const finalCreateStore = compose.apply(null, composeArgs)(createStore);
  const store = finalCreateStore(reducer, typeof window !== "undefined" ? window.__INITIAL_STATE__ : {});

  if (hotCallback) {
    hotCallback(() => {
      const nextReducer = combineReducers(Object.assign({}, {_gluestick}, customRequire()));
      store.replaceReducer(nextReducer);
    });
  }

  return store;
}

Clarify since it's not a shallow clone hehe


const LEVELS = {
  NOTHING: 0,
  ERROR: 1,
  WARN: 2,
  INFO: 4,
  DEBUG: 5,
}

const createLevel = (label, level, currentLevel, loggerFunction) => (message, extra = {}) => {
  if (level > currentLevel) return
  const logData = Object.assign(
    {
      level: label,
      timestamp: new Date().toISOString(),
      message,
    },
    extra
  )

  loggerFunction(JSON.stringify(logData))
}

const createLogger = ({ level = parseInt(process.env.LOG_LEVEL, 10) || LEVELS.INFO } = {}) => ({
  info: createLevel('INFO', LEVELS.INFO, level, console.info),
  error: createLevel('ERROR', LEVELS.ERROR, level, console.error),
  warn: createLevel('WARN', LEVELS.WARN, level, console.warn),
  debug: createLevel('DEBUG', LEVELS.DEBUG, level, console.log),
})

module.exports = {
  LEVELS,
  createLogger,
}

const LEVELS = {
  NOTHING: 0,
  ERROR: 1,
  WARN: 2,
  INFO: 4,
  DEBUG: 5,
}

const createLevel = (label, level, currentLevel, loggerFunction) => (message, extra = {}) => {
  if (level > currentLevel) return
  const logData = Object.assign(
    {
      level: label,
      timestamp: new Date().toISOString(),
      message,
    },
    extra
  )

  loggerFunction(JSON.stringify(logData))
}

const createLogger = ({ level = LEVELS.INFO } = {}) => {
  const logLevel = parseInt(process.env.LOG_LEVEL, 10) || level
  return {
    info: createLevel('INFO', LEVELS.INFO, logLevel, console.info),
    error: createLevel('ERROR', LEVELS.ERROR, logLevel, console.error),
    warn: createLevel('WARN', LEVELS.WARN, logLevel, console.warn),
    debug: createLevel('DEBUG', LEVELS.DEBUG, logLevel, console.log),
  }
}

module.exports = {
  LEVELS,
  createLogger,
}

Allow env variable LOG_LEVEL to override the log level


function sockets(){
    var socket = new WebSocket("ws://localhost:8765");
    window.frequency = 1;
    window.height = 0;
    window.width = 0;
    var hero = {};
    window.heroId = '';
    var units = [];

    socket.onopen = function(event) {
        console.log("Connected.");
    };

    socket.onclose = function(event) {
        if (event.wasClean) {
            console.log('Connection closed');
        } else {
            console.log('disconnection');
        }
            console.log('code: ' + event.code + ' reason: ' + event.reason);
    };

    socket.onmessage = function(event) {
        var answer = JSON.parse(event.data);
        console.log(answer);
        //if (answer.id)
        //    document.cookie = "hero_id=" + answer.id;
        if (answer.hasOwnProperty('frequency')) {
            frequency = answer.frequency;
            height = answer.field.height;
            width = answer.field.width;
            heroId = answer.id;
        }
        else if (units.length == 0) {
            units = answer;
            hero = units[heroId];
            restart(hero, units);
        }
        //else if (answer.hasOwnProperty('update')){
        //    unitUpdate(answer['update']);
        //}
        else {
            units = answer;
            hero = units[heroId];
            unitsUpdate(hero, units);
        }
    };

    socket.onerror = function(error) {
        console.log("Error " + error.message);
    };
}

function sockets(){
    var socket = new WebSocket("ws://localhost:8765");
    window.frequency = 1;
    window.height = 0;
    window.width = 0;
    var hero = {};
    window.heroId = '';
    var units = [];

    socket.onopen = function(event) {
        console.log("Connected.");
    };

    socket.onclose = function(event) {
        if (event.wasClean) {
            console.log('Connection closed');
        } else {
            console.log('disconnection');
        }
            console.log('code: ' + event.code + ' reason: ' + event.reason);
    };

    socket.onmessage = function(event) {
        var answer = JSON.parse(event.data);
        console.log(answer);
        //if (answer.id)
        //    document.cookie = "hero_id=" + answer.id;
        if (answer.hasOwnProperty('frequency')) {
            frequency = answer.frequency;
            height = answer.field.height;
            width = answer.field.width;
            document.getElementById('gameCanvas').width=width;
            document.getElementById('gameCanvas').height=height;
            heroId = answer.id;
        }
        else if (units.length == 0) {
            units = answer;
            hero = units[heroId];
            restart(hero, units);
        }
        //else if (answer.hasOwnProperty('update')){
        //    unitUpdate(answer['update']);
        //}
        else {
            units = answer;
            hero = units[heroId];
            unitsUpdate(hero, units);
        }
    };

    socket.onerror = function(error) {
        console.log("Error " + error.message);
    };
}

Set parameters for game field from server


'use strict';

// Configuration for the New Relic Node.js agent

exports.config = {
    app_name: process.env.NEW_RELIC_APP_NAME || 'app',
    logging: {
        filepath: process.env.NEW_RELIC_LOG || 'stdout'
    }
};

'use strict';

// Configuration for the New Relic Node.js agent

module.exports.config = {
    app_name: process.env.NEW_RELIC_APP_NAME || 'app',
    logging: {
        filepath: process.env.NEW_RELIC_LOG || 'stdout'
    }
};

Use always "module.exports", not the alias "exports"


/**
 * New Relic agent configuration.
 *
 * See lib/config.defaults.js in the agent distribution for a more complete
 * description of configuration variables and their potential values.
 */
exports.config = {
  /**
   * Array of application names.
   */
  app_name : ['Nitro Server'],
  /**
   * Your New Relic license key.
   */
  license_key : process.env.NEW_RELIC_LICENSE_KEY,
  logging : {
    /**
     * Level at which to log. 'trace' is most useful to New Relic when diagnosing
     * issues with the agent, 'info' and higher will impose the least overhead on
     * production applications.
     */
    level : 'trace'
  }
};

/**
 * New Relic agent configuration.
 *
 * See lib/config.defaults.js in the agent distribution for a more complete
 * description of configuration variables and their potential values.
 */
exports.config = {
  /**
   * Array of application names.
   */
  app_name : ['Nitro Server'],
  /**
   * Your New Relic license key.
   */
  license_key : process.env.NEW_RELIC_LICENSE_KEY,
  logging : {
    /**
     * Level at which to log. 'trace' is most useful to New Relic when diagnosing
     * issues with the agent, 'info' and higher will impose the least overhead on
     * production applications.
     */
    level : 'info'
  }
};

Switch new relic to ‘info’ logging


var expect = require('expect.js');
var seeds = require('../seeds');
var Feature = require('../feature');

describe('Feature', function () {
  before(function (done) {
    seeds(done);
  });

  describe('schema', function () {
    it('successfully creates a valid document');
    it('fails at creating an invalid document');
  });

  describe('.search()', function () {
    it('performs an empty search, returning all commands', function (done) {
      Feature.search('', function (docs) {
        expect(docs.length).to.be(7);
        done();
      });
    });

    it('performs a case-insensitive search for a command', function (done) {
      Feature.search('git ADD', function (docs) {
        expect(docs).to.eql([{
          name: 'add files',
          examples: [
            { technology: 'Git', snippets: ['git add'] },
            { technology: 'Mercurial', snippets: ['hg add'] },
            { technology: 'Subversion', snippets: ['svn add'] }
          ]
        }]);

        done();
      });
    });

    it('discards extra hidden fields created by MongoDB');

    it('performs a search for a command that does not exist', function (done) {
      Feature.search('git yolo', function (docs) {
        expect(docs.length).to.be(0);
        done();
      });
    });
  });
});

var expect = require('expect.js');
var seeds = require('../seeds');
var Feature = require('../feature');

describe('Feature', function () {
  before(seeds);

  describe('schema', function () {
    it('successfully creates a valid document');
    it('fails at creating an invalid document');
  });

  describe('.search()', function () {
    it('performs an empty search, returning all commands', function (done) {
      Feature.search('', function (docs) {
        expect(docs.length).to.be(7);
        done();
      });
    });

    it('performs a case-insensitive search for a command', function (done) {
      Feature.search('git ADD', function (docs) {
        expect(docs).to.eql([{
          name: 'add files',
          examples: [
            { technology: 'Git', snippets: ['git add'] },
            { technology: 'Mercurial', snippets: ['hg add'] },
            { technology: 'Subversion', snippets: ['svn add'] }
          ]
        }]);

        done();
      });
    });

    it('discards extra hidden fields created by MongoDB');

    it('performs a search for a command that does not exist', function (done) {
      Feature.search('git yolo', function (docs) {
        expect(docs.length).to.be(0);
        done();
      });
    });
  });
});

Remove an extra anonymous function


var assert = require('assert');
var seeds = require('../seeds');
var Feature = require('../feature');

describe('Feature', function () {
  before(function (done) {
    seeds(done);
  });

  describe('schema', function () {
    it('successfully creates a valid document');
    it('fails at creating an invalid document');
  });

  describe('.search()', function () {
    it('performs an empty search, returning all commands', function (done) {
      Feature.search('', function (docs) {
        assert.equal(7, docs.length);
        done();
      });
    });

    it('performs a case-insensitive search for a command', function (done) {
      Feature.search('git ADD', function (docs) {
        assert.equal(1, docs.length);

        var doc = docs[0];
        assert.equal('add files', doc.name);
        assert.deepEqual({
          Git: 'git add',
          Mercurial: 'hg add',
          Subversion: 'svn add'
        }, doc.examples);

        done();
      });
    });

    it('discards extra hidden fields created by MongoDB');

    it('performs a search for a command that does not exist', function (done) {
      Feature.search('git yolo', function (docs) {
        assert.equal(0, docs.length);
        done();
      });
    });
  });
});

var assert = require('assert');
var seeds = require('../seeds');
var Feature = require('../feature');

describe('Feature', function () {
  before(function (done) {
    seeds(done);
  });

  describe('schema', function () {
    it('successfully creates a valid document');
    it('fails at creating an invalid document');
  });

  describe('.search()', function () {
    it('performs an empty search, returning all commands', function (done) {
      Feature.search('', function (docs) {
        assert.equal(docs.length, 7);
        done();
      });
    });

    it('performs a case-insensitive search for a command', function (done) {
      Feature.search('git ADD', function (docs) {
        assert.equal(docs.length, 1);

        var doc = docs[0];
        assert.equal(doc.name, 'add files');
        assert.deepEqual(doc.examples, {
          Git: 'git add',
          Mercurial: 'hg add',
          Subversion: 'svn add'
        });

        done();
      });
    });

    it('discards extra hidden fields created by MongoDB');

    it('performs a search for a command that does not exist', function (done) {
      Feature.search('git yolo', function (docs) {
        assert.equal(docs.length, 0);
        done();
      });
    });
  });
});

Fix the order of parameters for assert.equals()


import $ from 'jquery';
import getScrubbedData from './dataUtils';
import testData from '../../test/data/test-data';
import apiKey from '../../test/apiKey';

const getWeatherData = location => {
  if (parseInt(location, 10)) {
    return $.get(`http://api.wunderground.com/api/${apiKey}/hourly/forecast10day/conditions/q/${location}.json`)
      .then(data => {
        return getScrubbedData(data);
      })
      .catch(error => console.log('catch', error));
  } else {
    const cityStateArr = location.split(', ');
    return $.get(`http://api.wunderground.com/api/${apiKey}/hourly/forecast10day/conditions/q/${cityStateArr[1]}/${cityStateArr[0]}.json`)
      .then(data => {
        return getScrubbedData(data);
      })
      .catch(error => console.log('catch', error));
  }
};

const getTestWeatherData = () => {
  return getScrubbedData(testData);
};

export { getWeatherData, getTestWeatherData };

import $ from 'jquery';
import getScrubbedData from './dataUtils';
import { testData } from '../../test/data/test-data';
import apiKey from '../../test/apiKey';

const getWeatherData = location => {
  if (parseInt(location, 10)) {
    return $.get(`http://api.wunderground.com/api/${apiKey}/hourly/forecast10day/conditions/q/${location}.json`)
      .then(data => {
        return getScrubbedData(data);
      })
      .catch(error => console.log('catch', error));
  } else {
    const cityStateArr = location.split(', ');
    return $.get(`http://api.wunderground.com/api/${apiKey}/hourly/forecast10day/conditions/q/${cityStateArr[1]}/${cityStateArr[0]}.json`)
      .then(data => {
        return getScrubbedData(data);
      })
      .catch(error => console.log('catch', error));
  }
};

const getTestWeatherData = () => {
  return getScrubbedData(testData);
};

export { getWeatherData, getTestWeatherData };

Fix test data coming back as undefined.


/* Prettyify */
$( document ).ready(function() {
    prettyPrint();
});

/* Scrollspy */
var navHeight = $('.navbar').outerHeight(true) + 10

$('body').scrollspy({
    target: '.bs-sidebar',
    offset: navHeight
})

/* Prevent disabled links from causing a page reload */
$("li.disabled a").click(function() {
    event.preventDefault();
});

/* Adjust the scroll height of anchors to compensate for the fixed navbar */
window.disableShift = false;
var shiftWindow = function() {
    if (window.disableShift) {
        window.disableShift = false;
    } else {
        /* If we're at the bottom of the page, don't erronously scroll up */
        var scrolledToBottomOfPage = (
            (window.innerHeight + window.scrollY) >= document.body.offsetHeight
        );
        if (!scrolledToBottomOfPage) {
            scrollBy(0, -60);
        };
    };
};
if (location.hash) {shiftWindow();}
window.addEventListener("hashchange", shiftWindow);

/* Deal with clicks on nav links that do not change the current anchor link. */
$("ul.nav a" ).click(function() {
    var href = this.href;
    var suffix = location.hash;
    var matchesCurrentHash = (href.indexOf(suffix, href.length - suffix.length) !== -1);
    if (location.hash && matchesCurrentHash) {
        /* Force a single 'hashchange' event to occur after the click event */
        window.disableShift = true;
        location.hash='';
    };
});

/* Prettyify */
$( document ).ready(function() {
    prettyPrint();
    $('table').addClass('table');
});

/* Scrollspy */
var navHeight = $('.navbar').outerHeight(true) + 10

$('body').scrollspy({
    target: '.bs-sidebar',
    offset: navHeight
})

/* Prevent disabled links from causing a page reload */
$("li.disabled a").click(function() {
    event.preventDefault();
});

/* Adjust the scroll height of anchors to compensate for the fixed navbar */
window.disableShift = false;
var shiftWindow = function() {
    if (window.disableShift) {
        window.disableShift = false;
    } else {
        /* If we're at the bottom of the page, don't erronously scroll up */
        var scrolledToBottomOfPage = (
            (window.innerHeight + window.scrollY) >= document.body.offsetHeight
        );
        if (!scrolledToBottomOfPage) {
            scrollBy(0, -60);
        };
    };
};
if (location.hash) {shiftWindow();}
window.addEventListener("hashchange", shiftWindow);

/* Deal with clicks on nav links that do not change the current anchor link. */
$("ul.nav a" ).click(function() {
    var href = this.href;
    var suffix = location.hash;
    var matchesCurrentHash = (href.indexOf(suffix, href.length - suffix.length) !== -1);
    if (location.hash && matchesCurrentHash) {
        /* Force a single 'hashchange' event to occur after the click event */
        window.disableShift = true;
        location.hash='';
    };
});

Apply 'table' css class to all tables


var FileRenderer = require('../filerenderer.js');
var FileRevisions = require('../fileRevisions.js');

if (window.contextVars.renderURL !== undefined) {
    FileRenderer.start(window.contextVars.renderURL, '#fileRendered');
}

new FileRevisions(
    '#fileRevisions',
    window.contextVars.node,
    window.contextVars.file,
    window.contextVars.currentUser.canEdit
);

FileRenderer.start(window.contextVars.renderURL, '#fileRendered');

Remove extra call to to FileRenderer.start


var sFormat = require( 'stringformat' );
var transformTools = require( 'browserify-transform-tools' );
var path = require( 'path' );

var options = {
  excludeExtensions: [ '.json' ],
  evaluateArguments: true
};

module.exports = transformTools.makeRequireTransform( "requireTransform", options, function ( args, opts, cb ) {
  var configData = opts.configData || {};
  var config = configData.config || {};

  var key = args[ 0 ];

  var foundDep = config[ key ];

  if ( foundDep ) {
    return cb( null, sFormat( '({0})', foundDep ));
  }

  cb();
} );

var sFormat = require( 'stringformat' );
var transformTools = require( 'browserify-transform-tools' );
var path = require( 'path' );

var options = {
  excludeExtensions: [ '.json' ],
  evaluateArguments: true
};

module.exports = transformTools.makeRequireTransform( 'requireTransform', options, function ( args, opts, cb ) {
  var configData = opts.configData || {};
  var config = configData.config || {};

  var key = args[ 0 ];

  var foundDep = config[ key ];

  if ( foundDep ) {
    return cb( null, sFormat( '({0})', foundDep ));
  }

  cb();
} );

Make sure we use single quotes


'use strict';

module.exports = function (t, a) {
	a(t(), false, "Undefined");
	a(t(1), false, "Primitive");
	a(t({}), false, "Objectt");
	a(t({ toString: function () { return '[object Error]'; } }), false,
		"Fake error");
	a(t(new Error()), true, "Error");
};

'use strict';

module.exports = function (t, a) {
	a(t(), false, "Undefined");
	a(t(1), false, "Primitive");
	a(t({}), false, "Objectt");
	a(t({ toString: function () { return '[object Error]'; } }), false,
		"Fake error");
	a(t(new Error()), true, "Error");
	a(t(new EvalError()), true, "EvalError");
	a(t(new RangeError()), true, "RangeError");
	a(t(new ReferenceError()), true, "ReferenceError");
	a(t(new SyntaxError()), true, "SyntaxError");
	a(t(new TypeError()), true, "TypeError");
	a(t(new URIError()), true, "URIError");
};

Test against all types of errors


const express = require('express');
const router 	= express.Router();
const multer  = require('multer');
const upload  = multer({dest: 'uploads/'});
const path    = require('path');
const compile = require('../latex/latex');

// POST method route
router.post('/', upload.single('zip_file'), function (req, res) {
  const filename = req.file.filename;
  const compiler = req.file.compiler || 'pdflatex';
  compile(compiler, filename)
    .then((file) => sendResultingFile(file, res))
    .catch((error) => errorHandler(error, res));
});

function sendResultingFile(file, res) {
  res.sendFile(path.resolve(file), function (err) {
    if(err) {
      console.error('Error sending the file: ' + err);
      res.status(400).send(err);
    }
  });
}

function errorHandler(error, res) {
  console.error(error);
  res.status(400).send(error);
}

module.exports = router;

const express = require('express');
const router 	= express.Router();
const multer  = require('multer');
const upload  = multer({dest: 'uploads/'});
const path    = require('path');
const compile = require('../latex/latex');

// POST method route
router.post('/', upload.single('zip_file'), function (req, res) {
  const filename = req.file.filename;
  let compiler = req.file.compiler;
  // Just making sure to run one of this allowed exes
  if(compiler != 'pdflatex'
    || compiler != 'latexmk'
    || compiler != 'xelatex') {
    compiler = 'pdflatex';
  }
  compile(compiler, filename)
    .then((file) => sendResultingFile(file, res))
    .catch((error) => errorHandler(error, res));
});

function sendResultingFile(file, res) {
  res.sendFile(path.resolve(file), function (err) {
    if(err) {
      console.error('Error sending the file: ' + err);
      res.status(400).send(err);
    }
  });
}

function errorHandler(error, res) {
  console.error(error);
  res.status(400).send(error);
}

module.exports = router;

Make sure the compiler post parameter is an allowed one


import r from 'ramda';
import products from '../mock-data/products.json';

const getBrands = r.compose(r.uniq, r.map(r.prop('brand')));
const brands = getBrands(products);

console.log(brands);

import r from 'ramda';
import products from '../mock-data/products.json';

const getBrands = r.compose(r.uniq, r.map(r.prop('brand')));
const sortByPriceAsc = r.sortBy(r.prop('price'));
const sortByPriceDesc = r.compose(r.reverse, sortByPriceAsc);
const isFeatured = r.propEq('featured', true);
const getFeatured = r.filter(isFeatured);

const featured = getFeatured(products);

console.log(featured);

Add functions for sorting and finding featured products


#!/usr/bin/env node

const fs = require('fs')
const program = require('commander')
const meta = require(`${process.cwd()}/package.json`)

try {
  let ver = meta.version

  program
    .version(meta.version)
    .arguments('<version>')
    .action(v => { ver = v })
    .parse(process.argv)

  if (ver) {
    let fileStr = fs.readFileSync('./bower.json', 'utf-8')
    const verPtn = /(.*"version"\s*:\s*")[^"]*(".*)/

    if (verPtn.test(fileStr)) {
      fileStr = fileStr.replace(verPtn, '$1' + ver + '$2')
      fs.writeFileSync('./bower.json', fileStr, 'utf-8')
    }
  }

  process.exit(0)
} catch (err) {
  console.error(err)
  process.exit(1)
}

#!/usr/bin/env node

const fs = require('fs')
const program = require('commander')
const meta = require(`${process.cwd()}/package.json`)
const path = require('path')

const DEFAULT_FILE_PATHS = [ './bower.json' ]

function collect (val, memo) {
  memo.push(val)
  return memo
}

function updateVersion (file, ver) {
  const resolved = path.resolve(file)

  let fileStr = fs.readFileSync(resolved, 'utf-8')
  const verPtn = /(.*"version"\s*:\s*")[^"]*(".*)/

  if (verPtn.test(fileStr)) {
    fileStr = fileStr.replace(verPtn, '$1' + ver + '$2')
    fs.writeFileSync(resolved, fileStr, 'utf-8')
  }
}

try {
  let ver = meta.version

  program
    .version(meta.version)
    .option('-f, --file [file]', 'The target file to sync. Defaults to bower.json', collect, DEFAULT_FILE_PATHS)
    .arguments('<version>')
    .action(v => { ver = v })
    .parse(process.argv)

  if (ver) {
    program.file.forEach(
      file => updateVersion(file, ver)
    )
  }

  process.exit(0)
} catch (err) {
  console.error(err)
  process.exit(1)
}

Add a way to specify which files to target


export {
  formatDate,
  formatDateAndTime,
  parsePositiveInteger,
  truncateString,
  parsePositiveFloat,
  formatExpiryDate,
  parseExpiryDate,
  formatPlural,
} from 'sussol-utilities';
export { formatStatus } from './formatStatus';
export { sortDataBy } from './sortDataBy';
export { compareVersions } from './compareVersions';
export { createReducer, REHYDRATE } from './createReducer';
export { getAllPeriodsForProgram, getAllPrograms } from './byProgram';
export { requestPermission } from './requestPermission';
export { backupValidation } from './fileSystem';
export { debounce } from './underscoreMethods';

export {
  formatDate,
  formatDateAndTime,
  parsePositiveInteger,
  truncateString,
  parsePositiveFloat,
  formatExpiryDate,
  parseExpiryDate,
  formatPlural,
} from 'sussol-utilities';
export { formatStatus } from './formatStatus';
export { sortDataBy, newSortDataBy } from './sortDataBy';
export { compareVersions } from './compareVersions';
export { createReducer, REHYDRATE } from './createReducer';
export { getAllPeriodsForProgram, getAllPrograms } from './byProgram';
export { requestPermission } from './requestPermission';
export { backupValidation } from './fileSystem';
export { debounce } from './underscoreMethods';

Add export and import newSortDataBy