(function(){
  var tilingEl = document.getElementById('penrose-tiling')
    , outerRadius = 0.9
    , innerRadius = outerRadius * Math.sin(18 * Math.PI / 180)
    , top = innerRadius
    , bottom = -innerRadius
    , left = -outerRadius
    , right = outerRadius
  ;
  tilingEl.innerHTML="<path d='M " + left + " 0 L 0 " + top + " L " + right + " 0 L 0 " + bottom + " z' fill='orange' />";
})();

(function(){
  var a18 = 18 * Math.PI / 180
    , sin18 = Math.sin(a18)
    , narrowDiamondMarkup =
        "<path d='M -1 0 L 0 " + sin18 + " L 1 0 L 0 -" + sin18 + " z' fill='orange' vector-effect='non-scaling-stroke' stroke='black' />"
    , tilingEl = document.getElementById('penrose-tiling')
  ;
  tilingEl.innerHTML = narrowDiamondMarkup;
})();

Add border stroke to shape


/* jshint node: true */

module.exports = function(environment) {
  var ENV = {
    modulePrefix: 'dummy',
    environment: environment,
    baseURL: '/',
    locationType: 'auto',
    EmberENV: {
      FEATURES: {
        // Here you can enable experimental features on an ember canary build
        // e.g. 'with-controller': true
      }
    },

    APP: {
      // Here you can pass flags/options to your application instance
      // when it is created
    },

    'ember-cli-toggle': {
      //includedThemes: ['light', 'ios', 'flip'],
      //excludedThemes: ['flip'],
      //defaultTheme: 'light',
      //defaultSize: 'small'
    }
  };

  if (environment === 'development') {
    // ENV.APP.LOG_RESOLVER = true;
    ENV.APP.LOG_ACTIVE_GENERATION = true;
    // ENV.APP.LOG_TRANSITIONS = true;
    // ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
    ENV.APP.LOG_VIEW_LOOKUPS = true;
  }

  if (environment === 'test') {
    // Testem prefers this...
    ENV.baseURL = '/';
    ENV.locationType = 'auto';

    // keep test console output quieter
    ENV.APP.LOG_ACTIVE_GENERATION = false;
    ENV.APP.LOG_VIEW_LOOKUPS = false;

    ENV.APP.rootElement = '#ember-testing';
  }

  if (environment === 'production') {

  }

  return ENV;
};

/* jshint node: true */

module.exports = function(environment) {
  var ENV = {
    modulePrefix: 'dummy',
    environment: environment,
    baseURL: '/ember-cli-toggle',
    locationType: 'auto',
    EmberENV: {
      FEATURES: {
        // Here you can enable experimental features on an ember canary build
        // e.g. 'with-controller': true
      }
    },

    APP: {
      // Here you can pass flags/options to your application instance
      // when it is created
    },

    'ember-cli-toggle': {
      //includedThemes: ['light', 'ios', 'flip'],
      //excludedThemes: ['flip'],
      //defaultTheme: 'light',
      //defaultSize: 'small'
    }
  };

  if (environment === 'development') {
    // ENV.APP.LOG_RESOLVER = true;
    ENV.APP.LOG_ACTIVE_GENERATION = true;
    // ENV.APP.LOG_TRANSITIONS = true;
    // ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
    ENV.APP.LOG_VIEW_LOOKUPS = true;
  }

  if (environment === 'test') {
    // Testem prefers this...
    ENV.baseURL = '/';
    ENV.locationType = 'auto';

    // keep test console output quieter
    ENV.APP.LOG_ACTIVE_GENERATION = false;
    ENV.APP.LOG_VIEW_LOOKUPS = false;

    ENV.APP.rootElement = '#ember-testing';
  }

  if (environment === 'production') {

  }

  return ENV;
};

Change base path for dummy


'use strict'
const config = require(__dirname + '/config.js');
const Twit = require('twit');
const HC = require('node-hipchat');

let twitter = new Twit({
  consumer_key: config['twitter_consumer_key'],
  consumer_secret: config['twitter_consumer_secret'],
  access_token: config['twitter_access_token'],
  access_token_secret: config['twitter_access_token_secret'],
  timeout_ms: 60*1000  // optional HTTP request timeout to apply to all requests.
});

let hipchat = new HC(config['hipchat_key']);
const params = {
  'room': config['hipchat_room'],
  'from': config['hipchat_bot'],
  'color': 'gray',
  'notify': 0
};

// Listen to TEA twitter accounts
let stream = twitter.stream('statuses/filter', { track: config['twitter_keywords'] });

console.log('Listening to Twitter, tracking ' + config['twitter_keywords'].join(', ') + '.');
stream.on('tweet', function (tweet) {
  // posting to HipChat
  let content = params;
  content.message = tweet.text;
  hipchat.postMessage(params, null);
});

'use strict'
const config = require(__dirname + '/config.js');
const Twit = require('twit');
const HC = require('node-hipchat');

let twitter = new Twit({
  consumer_key: config['twitter_consumer_key'],
  consumer_secret: config['twitter_consumer_secret'],
  access_token: config['twitter_access_token'],
  access_token_secret: config['twitter_access_token_secret'],
  timeout_ms: 60*1000  // optional HTTP request timeout to apply to all requests.
});

let hipchat = new HC(config['hipchat_key']);
const params = {
  'room': config['hipchat_room'],
  'from': config['hipchat_bot'],
  'color': 'gray',
  'notify': 0
};

// Listen to TEA twitter accounts
let stream = twitter.stream('statuses/filter', { track: config['twitter_keywords'] });

console.log('Listening to Twitter, tracking ' + config['twitter_keywords'].join(', ') + '.');
stream.on('tweet', function (tweet) {
  // posting to HipChat
  let content = params;
  const url = `http://twitter.com/${tweet.user.screen_name}/status/${tweet.id_str}`;
  content.message = `<a href="${url}">${url}</a>`;
  hipchat.postMessage(params, null);
});

Change tweet post message: post url instead


angular.module('ngCordova.plugins.deviceOrientation', [])

.factory('$cordovaDeviceOrientation', ['$q', function($q) {

  return {
    watchHeading: function(options) {
      var q = $q.defer();

      navigator.compass.watchHeading(function(result) {
        q.notify(result);
      }, function(err) {
        q.reject(err);
      }, options);

      return q.promise;
    }
  }
}]);

angular.module('ngCordova.plugins.deviceOrientation', [])

.factory('$cordovaDeviceOrientation', ['$q', function($q) {

  return {
    getCurrentHeading: function() {
      var q = $q.defer();

      navigator.compass.getCurrentHeading(function(heading) {
        q.resolve(heading);
      }, function(err) {
        q.reject(err);
      });

      return q.promise;
    },
    watchHeading: function(options) {
      var q = $q.defer();

      var watchID = navigator.compass.watchHeading(function(result) {
        q.resolve(watchID);
        q.notify(result);
      }, function(err) {
        q.reject(err);
      }, options);

      return q.promise;
    }
    clearWatch: function(watchID) {
      navigator.compass.clearWatch();
    }
  }
}]);

Complete the compass methods and handle watchID


const parseTime = (s) => {
  const re = /(\d{2}):(\d{2}):(\d{2}),(\d{3})/;
  const [, hours, mins, seconds, ms] = re.exec(s);
  return 3600*(+hours) + 60*(+mins) + (+seconds) + 0.001*(+ms);
};

export const parseSRT = (text) => {
  const normText = text.replace(/\r\n/g, '\n'); // normalize newlines

  const re = /(\d+)\n(\d{2}:\d{2}:\d{2},\d{3}) --> (\d{2}:\d{2}:\d{2},\d{3})\n((?:.+\n)+)/g;
  const subs = [];
  let found;

  while (true) {
    found = re.exec(normText);
    if (!found) {
      break;
    }
    const [full, , beginStr, endStr, lines] = found;
    const begin = parseTime(beginStr);
    const end = parseTime(endStr);
    // TODO: Should verify that end time is >= begin time
    // NOTE: We could check that indexes and/or time are in order, but don't really care
    subs.push({
      begin,
      end,
      lines: lines.trim(),
    });
    re.lastIndex = found.index + full.length;
  }

  return subs;
};

const parseTime = (s) => {
  const re = /(\d{2}):(\d{2}):(\d{2}),(\d{3})/;
  const [, hours, mins, seconds, ms] = re.exec(s);
  return 3600*(+hours) + 60*(+mins) + (+seconds) + 0.001*(+ms);
};

const cleanText = (s) => {
  const BREAK_RE = /(<br>)/ig; // SRT files shouldn't have these, but some do
  const TAG_RE = /(<([^>]+)>)/ig;
  return s.trim().replace(BREAK_RE, '\n').replace(TAG_RE, '');
};

export const parseSRT = (text) => {
  const normText = text.replace(/\r\n/g, '\n'); // normalize newlines

  const re = /(\d+)\n(\d{2}:\d{2}:\d{2},\d{3}) --> (\d{2}:\d{2}:\d{2},\d{3})\n((?:.+\n)+)/g;
  const subs = [];
  let found;

  while (true) {
    found = re.exec(normText);
    if (!found) {
      break;
    }
    const [full, , beginStr, endStr, lines] = found;
    const begin = parseTime(beginStr);
    const end = parseTime(endStr);
    // TODO: Should verify that end time is >= begin time
    // NOTE: We could check that indexes and/or time are in order, but don't really care
    subs.push({
      begin,
      end,
      lines: cleanText(lines),
    });
    re.lastIndex = found.index + full.length;
  }

  return subs;
};

Clean up tags in SRT files on import


import ACTIVITIY_STATUS from './activityStatus'
const IPCAdapter = require('electron-ipc-adapter')

const ipcRenderer = window.require('electron').ipcRenderer

class ClientIPCAdapter extends IPCAdapter {
  constructor (ipcRenderer) {
    super(ipcRenderer.send.bind(ipcRenderer), ipcRenderer.on.bind(ipcRenderer))
  }

  getHubs () {
    return this.ask('getHubs').then((response) => response.hubs)
  }

  getActivities (hubUuid) {
    return Promise.all([
      this.ask('getActivities', { hubUuid }),
      this.ask('getCurrentActivityForHub', { hubUuid })
    ])
      .then((results) => {
        const activities = results[0].activities
        const currentActivityId = results[1].activityId

        return activities.map((activity) => {
          if (activity.id === currentActivityId) {
            activity.activityStatus = ACTIVITIY_STATUS.STARTED
          } else {
            activity.activityStatus = ACTIVITIY_STATUS.OFF
          }

          return activity
        })
      })
  }

  startActivityForHub (activityId, hubUuid) {
    return this.tell('startActivityForHub', { hubUuid, activityId })
  }
}

const ipcAdapter = new ClientIPCAdapter(ipcRenderer)
export default ipcAdapter

import ACTIVITIY_STATUS from './activityStatus'
const IPCAdapter = require('electron-ipc-adapter')

const ipcRenderer = window.require('electron').ipcRenderer

class ClientIPCAdapter extends IPCAdapter {
  constructor (ipcRenderer) {
    super(ipcRenderer.send.bind(ipcRenderer), ipcRenderer.on.bind(ipcRenderer))
  }

  getHubs () {
    return this.ask('getHubs').then((response) => response.hubs)
  }

  getActivities (hubUuid) {
    return Promise.all([
      this.ask('getActivities', { hubUuid }),
      this.ask('getCurrentActivityForHub', { hubUuid })
    ])
      .then((results) => {
        const activities = results[0].activities
        const currentActivityId = results[1].activityId

        return activities.map((activity) => {
          if (activity.id !== '-1' && activity.id === currentActivityId) {
            activity.activityStatus = ACTIVITIY_STATUS.STARTED
          } else {
            activity.activityStatus = ACTIVITIY_STATUS.OFF
          }

          return activity
        })
      })
  }

  startActivityForHub (activityId, hubUuid) {
    return this.tell('startActivityForHub', { hubUuid, activityId })
  }
}

const ipcAdapter = new ClientIPCAdapter(ipcRenderer)
export default ipcAdapter

Set default activity state better when load activities


import styled from 'styled-components'
import { Link } from 'gatsby'

export const PostItemTitle = styled.h1`
  ${props => props.theme.Home_PostTitle}
  margin-bottom: 0.8rem;
  color: var(--text);
  transition: color 0.3s;
`

export const PostItemLink = styled(Link)`
  display: block;
  width: 100%;
  color: var(--text);
  text-decoration: none;

  @media (hover: hover) {
    &:hover ${PostItemTitle} {
      color: var(--accent);
    }
  }
`

export const PostItemWrapper = styled.article`
  display: flex;
  flex-direction: column;
  align-items: flex-start;
`

export const PostItemDateAndReadTime = styled.small`
  ${props => props.theme.Home_PostDateAndReadTime}
  display: block;
  margin-bottom: 1.2rem;
`

export const PostItemDescription = styled.p`
  ${props => props.theme.Home_PostDescription}
  margin-bottom: 0.8rem;
`

export const PostItemTags = styled.small`
  ${props => props.theme.General_Tag}
`

export const PostItemTag = styled(Link)`
  color: var(--text);
  transition: color 0.3s;
  text-decoration: underline;

  @media (hover: hover) {
    &:hover {
      color: var(--accent);
    }
  }
`

import styled from 'styled-components'
import { Link } from 'gatsby'

export const PostItemTitle = styled.h1`
  ${props => props.theme.Home_PostTitle}
  margin-bottom: 0.8rem;
  color: var(--text);
  transition: color 0.3s;
`

export const PostItemLink = styled(Link)`
  display: block;
  width: 100%;
  color: var(--text);
  text-decoration: none;
  transition: color 0.3s;

  @media (hover: hover) {
    &:hover ${PostItemTitle} {
      color: var(--accent);
    }
  }
`

export const PostItemWrapper = styled.article`
  display: flex;
  flex-direction: column;
  align-items: flex-start;
`

export const PostItemDateAndReadTime = styled.small`
  ${props => props.theme.Home_PostDateAndReadTime}
  display: block;
  margin-bottom: 1.2rem;
`

export const PostItemDescription = styled.p`
  ${props => props.theme.Home_PostDescription}
  margin-bottom: 0.8rem;
`

export const PostItemTags = styled.small`
  ${props => props.theme.General_Tag}
`

export const PostItemTag = styled(Link)`
  color: var(--text);
  transition: color 0.3s;
  text-decoration: underline;

  @media (hover: hover) {
    &:hover {
      color: var(--accent);
    }
  }
`

Add transition on PostItem component


var onReady = require('kwf/on-ready');

if (window.devicePixelRatio && window.devicePixelRatio > 1) {
    onReady.onRender('.kwfReplaceImageDpr2', function(el) {
        if (el.get(0).tagName.toLowerCase() == 'img') {
            if (el.get(0).src.indexOf('dpr2') == -1) {
                el.get(0).src = el.get(0).src.replace('/images/', '/images/dpr2/');
            }
        } else {
            var url = el.getStyle('background-image');
            if (url.indexOf('dpr2') == -1) {
                url = url.replace('/images/', '/images/dpr2/');
            }
            el.css('background-image', url);
        }
    });
}

var onReady = require('kwf/on-ready');

if (window.devicePixelRatio && window.devicePixelRatio > 1) {
    onReady.onRender('.kwfReplaceImageDpr2', function(el) {
        if (el.get(0).tagName.toLowerCase() == 'img') {
            el.get(0).src = el.get(0).src.replace('/images/', '/images/dpr2/');
        } else {
            var url = el.getStyle('background-image');
            url = url.replace('/images/', '/images/dpr2/');
            el.css('background-image', url);
        }
    });
}

Revert "Quick fix for wrong image urls in e18 templates"


describe('Users', function() {

  describe('Creation', function () {

    context('When creating a new user', function() {
      it('Should have an id', function() {
        some_guy = new mocks.user();
      });
    });
  });
});

describe('Users', function() {

  describe('Creation', function () {

    context('When creating a new user', function() {
      it('Should have an id', function() {
        var some_guy = jsf(mocks.user);
        // ADD SHOULD.JS
        some_guy.should.have.property('id');
        some_guy.id.should.be.ok();
        some_guy.id.should.be.String();
      });
    });
  });
});

Add first example of shouldJS


/**
 * @class SMITHY/DojoBorderView
 * View implementation for Dojo Tab Container to support smithy "tabs"
 * layout mode.
 */
define([
    "../declare",
    "dijit/layout/TabContainer"
], function (
    declare,
    TabContainer
) {
    var module = declare(TabContainer, {
        constructor: function (config) {
            this.implementsMode = "tabs";
        },

        addChild: function (view) {
            this.inherited(arguments);
        },

        removeChild: function (childView) {
            this.inherited(arguments);
        },

        hasChild: function (childView) {
            var cidx = this.getIndexOfChild(childView);
            return (cidx > -1);
        },

        startup: function () {
            this.inherited(arguments);
        }

    });

    return module;

});

/**
 * @class SMITHY/DojoBorderView
 * View implementation for Dojo Tab Container to support smithy "tabs"
 * layout mode.
 */
define([
    "../declare",
    "dijit/layout/TabContainer"
], function (
    declare,
    TabContainer
) {
    var module = declare(TabContainer, {
        constructor: function (config) {
            this.implementsMode = "tabs";
        },

        addChild: function (view) {
            this.inherited(arguments);
            this.set("title", view.title);
        },

        removeChild: function (childView) {
            this.inherited(arguments);
        },

        hasChild: function (childView) {
            var cidx = this.getIndexOfChild(childView);
            return (cidx > -1);
        },

        startup: function () {
            this.inherited(arguments);
        }

    });

    return module;

});

Use view "title" as default tab title.


import { load as configLoader } from '../config';

const config = configLoader();

export const originMiddleware = async (ctx, next) => {
  ctx.response.set('Access-Control-Allow-Origin', config.origin);
  ctx.response.set('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
  ctx.response.set('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, X-Authentication-Token, Access-Control-Request-Method');
  ctx.response.set('Access-Control-Expose-Headers', 'Date, X-Freefeed-Server');

  await next();
};

import { load as configLoader } from '../config';

const config = configLoader();

export const originMiddleware = async (ctx, next) => {
  ctx.response.set('Access-Control-Allow-Origin', config.origin);
  ctx.response.set('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
  ctx.response.set('Access-Control-Allow-Headers', [
    'Origin',
    'X-Requested-With',
    'Content-Type',
    'Accept',
    'X-Authentication-Token',
    'Access-Control-Request-Method',
    'Authorization',
  ].join(', '));
  ctx.response.set('Access-Control-Expose-Headers', 'Date, X-Freefeed-Server');

  await next();
};

Add Authorization header to Access-Control-Allow-Headers


module.exports = {
  stylePrefix: 'comp-',

  wrapperId: 'wrapper',

  wrapperName: 'Body',

  // Default wrapper configuration
  wrapper: {
    removable: false,
    copyable: false,
    draggable: false,
    components: [],
    traits: [],
    stylable: [
      'background',
      'background-color',
      'background-image',
      'background-repeat',
      'background-attachment',
      'background-position',
      'background-size'
    ]
  },

  // Could be used for default components
  components: [],

  // Class for new image component
  imageCompClass: 'fa fa-picture-o',

  // Open assets manager on create of image component
  oAssetsOnCreate: true,

  // Generally, if you don't edit the wrapper in the editor, like
  // custom attributes, you don't need the wrapper stored in your JSON
  // structure, but in case you need it you can use this option.
  // If you have `config.avoidInlineStyle` disabled the wrapper will be stored
  // as we need to store inlined style.
  storeWrapper: 1,

  // List of void elements
  voidElements: [
    'area',
    'base',
    'br',
    'col',
    'embed',
    'hr',
    'img',
    'input',
    'keygen',
    'link',
    'menuitem',
    'meta',
    'param',
    'source',
    'track',
    'wbr'
  ]
};

module.exports = {
  stylePrefix: 'comp-',

  wrapperId: 'wrapper',

  wrapperName: 'Body',

  // Default wrapper configuration
  wrapper: {
    removable: false,
    copyable: false,
    draggable: false,
    components: [],
    traits: [],
    stylable: [
      'background',
      'background-color',
      'background-image',
      'background-repeat',
      'background-attachment',
      'background-position',
      'background-size'
    ]
  },

  // Could be used for default components
  components: [],

  // Class for new image component
  imageCompClass: 'fa fa-picture-o',

  // Open assets manager on create of image component
  oAssetsOnCreate: true,

  // Generally, if you don't edit the wrapper in the editor, like
  // custom attributes, you don't need the wrapper stored in your JSON
  // structure, but in case you need it you can use this option.
  // If you have `config.avoidInlineStyle` disabled the wrapper will be stored
  // as we need to store inlined style.
  storeWrapper: 0,

  // List of void elements
  voidElements: [
    'area',
    'base',
    'br',
    'col',
    'embed',
    'hr',
    'img',
    'input',
    'keygen',
    'link',
    'menuitem',
    'meta',
    'param',
    'source',
    'track',
    'wbr'
  ]
};

Set storeWrapper false by default


const { authenticate } = require('@feathersjs/authentication').hooks;

const { hashPassword, protect } = require('@feathersjs/authentication-local').hooks;

const gravatar = require('../../hooks/gravatar');

module.exports = {
  before: {
    all: [],
    find: [ authenticate('jwt') ],
    get: [],
    create: [hashPassword(), gravatar()],
    update: [ hashPassword() ],
    patch: [ hashPassword() ],
    remove: []
  },

  after: {
    all: [
      // Make sure the password field is never sent to the client
      // Always must be the last hook
      protect('password')
    ],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: []
  },

  error: {
    all: [],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: []
  }
};

const { authenticate } = require('@feathersjs/authentication').hooks;

const { hashPassword, protect } = require('@feathersjs/authentication-local').hooks;

const gravatar = require('../../hooks/gravatar');

module.exports = {
  before: {
    all: [],
    find: [ authenticate('jwt') ],
    get: [ authenticate('jwt') ],
    create: [hashPassword(), authenticate('jwt'), gravatar()],
    update: [ hashPassword(), authenticate('jwt') ],
    patch: [ hashPassword(), authenticate('jwt') ],
    remove: [ authenticate('jwt') ]
  },

  after: {
    all: [
      // Make sure the password field is never sent to the client
      // Always must be the last hook
      protect('password')
    ],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: []
  },

  error: {
    all: [],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: []
  }
};

Add authentication hook to all methods (except create)


module.exports = {
    assets: {
        files: [{
            expand: true,
            dot: true,
            dest: '<%= distDir %>',
            src: [
                '<%= appDir %>/images/**/*',
                '<%= appDir %>/styles/css/*.css',
                '<%= appDir %>/scripts/**/*.{html,css,jpg,jpeg,png,gif,json}',
                '<%= appDir %>/template/**/*.html',
                '<%= appDir %>/scripts/bower_components/requirejs/require.js'
            ]
        }]
    },
    js: {
        files: [{
            expand: true,
            dot: true,
            cwd: '<%= appDir %>',
            dest: '<%= distDir %>',
            src: [
                'scripts/config.js',
                'scripts/bower_components/**/*.js'
            ]
        }]
    }
};

module.exports = {
    assets: {
        files: [{
            expand: true,
            dot: true,
            cwd: '<%= appDir %>',
            dest: '<%= distDir %>',
            src: [
                'images/**/*',
                'styles/css/*.css',
                'scripts/**/*.{html,css,jpg,jpeg,png,gif,json}',
                'template/**/*.html',
                'scripts/bower_components/requirejs/require.js'
            ]
        }]
    },
    js: {
        files: [{
            expand: true,
            dot: true,
            cwd: '<%= appDir %>',
            dest: '<%= distDir %>',
            src: [
                'scripts/config.js',
                'scripts/bower_components/**/*.js'
            ]
        }]
    }
};

Fix build by reverting changes


import { combineReducers } from 'redux';

import { TemperatureBreachConfigReducer } from './TemperatureBreachConfigReducer';
import { SensorReducer } from './SensorReducer';

export const EntitiesReducer = combineReducers({
  temperatureBreachConfiguration: TemperatureBreachConfigReducer,
  sensor: SensorReducer,
});

import { combineReducers } from 'redux';

import { TemperatureBreachConfigReducer } from './TemperatureBreachConfigReducer';
import { SensorReducer } from './SensorReducer';
import { LocationReducer } from './LocationReducer';

export const EntitiesReducer = combineReducers({
  temperatureBreachConfiguration: TemperatureBreachConfigReducer,
  sensor: SensorReducer,
  location: LocationReducer,
});

Add import of location reducer


/*global $, assert, trigger*/

'use strict';

describe('Albums create dialog', function () {
  this.timeout(20000);

  it('should create new album', function (done) {
    var user;

    TEST.browser
      // Authorize
      .auth('users_album_create', function (usr) {
        user = usr;
      })

      // Navigate to albums page
      .goto(function () {
        return 'http://localhost:3000' + TEST.N.router.linkTo('users.albums_root', { user_hid: user.hid });
      })

      .evaluateAsync(function (done) {
        TEST.N.wire.once('users.album.create:shown', { priority: 999 }, function () {
          $('input[name="album_name"]').val('new test album!');
          $('.modal-dialog button[type="submit"]').click();
        });

        trigger('[data-on-click="users.albums_root.create_album"]', function () {
          assert.equal($('.user-albumlist li:last .thumb-caption__line:first').text(), 'new test album!');
          done();
        });
      })

      // Run test
      .run(done);
  });
});

/*global $, assert, trigger*/

'use strict';

describe('Albums create dialog', function () {
  this.timeout(20000);

  it('should create new album', function (done) {
    var user;

    TEST.browser
      // Authorize
      .auth('users_album_create', function (usr) {
        user = usr;
      })

      // Navigate to albums page
      .goto(function () {
        return TEST.N.router.linkTo('users.albums_root', { user_hid: user.hid });
      })

      .evaluateAsync(function (done) {
        TEST.N.wire.once('users.album.create:shown', { priority: 999 }, function () {
          $('input[name="album_name"]').val('new test album!');
          $('.modal-dialog button[type="submit"]').click();
        });

        trigger('[data-on-click="users.albums_root.create_album"]', function () {
          assert.equal($('.user-albumlist li:last .thumb-caption__line:first').text(), 'new test album!');
          done();
        });
      })

      // Run test
      .run(done);
  });
});

Fix tests for correct environment config


'use strict';

const { yup } = require('strapi-utils');

const strapiID = yup.lazy(value =>
  typeof value === 'number' ? yup.number().integer() : yup.string()
); // https://github.com/jquense/yup/issues/665

const email = yup
  .string()
  .email()
  .min(1);

const firstname = yup.string().min(1);

const lastname = yup.string().min(1);

const username = yup.string().min(1);

const password = yup
  .string()
  .min(8)
  .matches(/[a-z]/, '${path} must contain at least one lowercase character')
  .matches(/[A-Z]/, '${path} must contain at least one uppercase character')
  .matches(/\d/, '${path} must contain at least one number');

const roles = yup.array(strapiID);

module.exports = {
  email,
  firstname,
  lastname,
  username,
  password,
  roles,
  strapiID,
};

'use strict';

const { yup } = require('strapi-utils');

yup['strapiID'] = yup.lazy(value =>
  typeof value === 'number'
    ? yup.number().integer()
    : yup.string().matches(/^[a-f\d]{24}$/, '${path} must be a valid ID')
);

const email = yup
  .string()
  .email()
  .min(1);

const firstname = yup.string().min(1);

const lastname = yup.string().min(1);

const username = yup.string().min(1);

const password = yup
  .string()
  .min(8)
  .matches(/[a-z]/, '${path} must contain at least one lowercase character')
  .matches(/[A-Z]/, '${path} must contain at least one uppercase character')
  .matches(/\d/, '${path} must contain at least one number');

const roles = yup.array(yup.strapiID).min(1);

module.exports = {
  email,
  firstname,
  lastname,
  username,
  password,
  roles,
};

Add custom strapiID type to yup


var gRex = require('../../index.js'),
    Transaction = require("../../src/transaction/transaction"),
    handlers = require("../../src/transaction/actionhandler"),
    Element = require("../../src/element");

var vertexHandler, edgeHandler;
var edge, vertex, transaction;

describe('Element ActionHandlers', function() {
    before(function(done){
        gRex.connect()
            .then(function(result) {
                g = result;
                done();
            })
            .fail(function(error) {
                console.error(error);
            });
    });

    before(function() {
        edge = Element.build("edge");
        vertex = Element.build("vertex");
        transaction = g.begin();
    });

    describe('Vertex ActionHandler class', function() {
        describe('when building a vertex action handler', function() {
            it('should return a vertex ActionHandler', function() {
                vertexHandler = handlers.ActionHandler.build(vertex, transaction, []);
                vertexHandler.should.be.instanceof(handlers.VertexActionHandler);
            });
        });
    });

    describe('Edge ActionHandler class', function() {
        describe('when building a vertex action handler', function() {
            it('should return an edge ActionHandler', function() {
                edgeHandler = handlers.ActionHandler.build(edge, transaction, []);
                edgeHandler.should.be.instanceof(handlers.EdgeActionHandler);
            });
        });
    });
});

var gRex = require('../../index.js');
var Transaction = require("../../src/transaction/transaction");

var ActionHandlerFactory = require("../../src/transaction/actionhandlers/actionhandlerfactory");
var VertexActionHandler = require("../../src/transaction/actionhandlers/vertexactionhandler");
var EdgeActionHandler = require("../../src/transaction/actionhandlers/edgeactionhandler");

var ElementFactory = require("../../src/elementfactory");

var vertexHandler, edgeHandler;
var edge, vertex, transaction;

describe('Element ActionHandlers', function() {
    before(function(done){
        gRex.connect()
            .then(function(result) {
                g = result;
                done();
            })
            .fail(function(error) {
                console.error(error);
            });
    });

    before(function() {
        edge = ElementFactory.build("edge");
        vertex = ElementFactory.build("vertex");
        transaction = g.begin();
    });

    describe('Vertex ActionHandler class', function() {
        describe('when building a vertex action handler', function() {
            it('should return a vertex ActionHandler', function() {
                vertexHandler = ActionHandlerFactory.build(vertex, transaction, []);
                vertexHandler.should.be.instanceof(VertexActionHandler);
            });
        });
    });

    describe('Edge ActionHandler class', function() {
        describe('when building a vertex action handler', function() {
            it('should return an edge ActionHandler', function() {
                edgeHandler = ActionHandlerFactory.build(edge, transaction, []);
                edgeHandler.should.be.instanceof(EdgeActionHandler);
            });
        });
    });
});

Fix ActionHandler tests (support of Factory)


'use strict';

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

//
// TODO: THIS IS BROKEN & MUST BE FIXED BEFORE 3.0
// This should output what was previously referred to
// as "humanReadableUncaughtExceptions" by default.
//
const logger = winston.createLogger({
  transports: [
    new winston.transports.Console({ handleExceptions: true })
  ]
});

throw new Error('Hello, winston!');

'use strict';

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

//
// TODO: THIS IS BROKEN & MUST BE FIXED BEFORE 3.0
// This should output what was previously referred to
// as "humanReadableUncaughtExceptions" by default.
//
const logger = winston.createLogger({
  format: winston.format.simple(),
  transports: [
    new winston.transports.Console({ handleExceptions: true })
  ]
});

throw new Error('Hello, winston!');

Use simple format to better show that humanReadableUnhandledException is now the default message format.


var media = document.querySelectorAll('video, audio');
var rate = prompt('Speed?','');
Array.prototype.forEach.call(media, function(player) {
   player.playbackRate = rate;
});

var media = document.querySelectorAll('video, audio');
var rate = prompt('Speed? Currently playing back at ' + media[0].playbackRate + 'x.','');
Array.prototype.forEach.call(media, function(player) {
   if (rate != 0) {
      player.playbackRate = rate;
   } else {
      player.playbackRate = 1;
   }
});

Include current playback rate in prompt.


module.exports = (sequelize, Sequelize) => {
  const Comment = sequelize.define('comments', {
    id: {
      type: Sequelize.INTEGER,
      primaryKey: true,
      allowNull: false,
      autoIncrement: true
    },
    body: {
      type: Sequelize.STRING
    }
  });

  return Comment;
};

module.exports = (sequelize, Sequelize) => {
  const Comment = sequelize.define('comments', {
    id: {
      type: Sequelize.INTEGER,
      primaryKey: true,
      allowNull: false,
      autoIncrement: true
    },
    body: {
      type: Sequelize.STRING
    },
    coordinates: {
      type: Sequelize.STRING
    }
  });

  return Comment;
};

Add coordinates column to table


var http = require('http');

http.createServer(function (req, res) {
  var proxyRequest = http.request({
    'hostname': 'www.google.com'

  }, function(proxyResponse) {

    delete proxyResponse.headers['content-length'];
    res.writeHead(proxyResponse.statusCode, proxyResponse.headers);
    proxyResponse.setEncoding('utf8');
    proxyResponse.on('data', function (chunk) {
        res.write(chunk);
    });
    proxyResponse.on('end', function(){
        res.end();
    });
  }
  );
  proxyRequest.end();
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');

var http = require('http');
var url  = require('url');

http.createServer(function (req, res) {

  var host = req.headers['host'];
  delete req.headers['host'];

  var proxyRequest = http.request({
    'method': req.method,
    'hostname': host,
    'headers': req.headers,
    'path': req.url
  }, function(proxyResponse) {

    delete proxyResponse.headers['content-length'];
    res.writeHead(proxyResponse.statusCode, proxyResponse.headers);
    proxyResponse.setEncoding('utf8');
    proxyResponse.on('data', function (chunk) {
        res.write(chunk);
    });
    proxyResponse.on('end', function(){
        res.end();
    });
  }
  );
  proxyRequest.end();
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');

Use host headers to proxy request


import CustomAuthenticator from '../authenticators/custom';
import CustomAuthorizer from '../authorizers/custom';

export default {
  name: 'authentication',
  before: 'simple-auth',
  initialize: function (container) {
    container.register('authorizer:custom', CustomAuthorizer);
    container.register('authenticator:custom', CustomAuthenticator);
  }
};

import CustomAuthenticator from '../authenticators/custom';
import CustomAuthorizer from '../authorizers/custom';

export function initialize(container) {
  container.register('authenticator:custom', CustomAuthenticator);
  container.register('authorizer:custom', CustomAuthorizer);
}

export default {
  name: 'authentication',
  before: 'simple-auth',
  initialize: initialize
};

Remove back auth initialize function to make test work


'use strict';
var fs = require('fs');
var path = require('path');
var merge = require('webpack-merge');
var webpack = require('webpack');

module.exports = function(config) {
  return new Promise(function(resolve, reject) {
    var cwd = process.cwd();
    var parent = path.join(__dirname, '..');

    var siteConfig = config.webpack && config.webpack.common;
    siteConfig = siteConfig || {};

    var corePath = path.join(parent, 'components');

    var common = {
      corePath: corePath,
      parent: parent,
      resolve: {
        root: path.join(parent, 'node_modules'),
        alias: {
          'underscore': 'lodash',
          'assets': path.join(cwd, 'assets'),
          'config': path.join(cwd, 'antwar.config.js'),
          'antwar-core': corePath
        },
        extensions: [
          '',
          '.js',
          '.jsx',
          '.json'
        ],
        modulesDirectories: [
          path.join(cwd, 'node_modules'),
          'node_modules'
        ]
      },
      resolveLoader: {
        modulesDirectories: [
          path.join(parent, 'node_modules'),
          'node_modules'
        ]
      },
      plugins: [
        new webpack.DefinePlugin({
          __DEV__: JSON.stringify(JSON.parse(config.buildDev)),
          'process.env': {
            'NODE_ENV': JSON.stringify(config.buildDev ? 'dev' : 'production')
          }
        }),
        new webpack.optimize.OccurenceOrderPlugin(),
        new webpack.optimize.DedupePlugin()
      ]
    };

    resolve(merge(common, siteConfig));
  });
};

'use strict';
var fs = require('fs');
var path = require('path');
var merge = require('webpack-merge');
var webpack = require('webpack');

module.exports = function(config) {
  return new Promise(function(resolve, reject) {
    var cwd = process.cwd();
    var parent = path.join(__dirname, '..');

    var siteConfig = config.webpack && config.webpack.common;
    siteConfig = siteConfig || {};

    var corePath = path.join(parent, 'components');

    var common = {
      corePath: corePath,
      parent: parent,
      resolve: {
        root: path.join(parent, 'node_modules'),
        alias: {
          'underscore': 'lodash',
          'assets': path.join(cwd, 'assets'),
          'config': path.join(cwd, 'antwar.config.js'),
          'antwar-core': corePath
        },
        extensions: [
          '',
          '.js',
          '.jsx',
          '.json'
        ],
        modulesDirectories: [
          path.join(cwd, 'node_modules'),
          'node_modules'
        ]
      },
      resolveLoader: {
        modulesDirectories: [
          path.join(parent, 'node_modules'),
          'node_modules'
        ]
      },
      plugins: [
        new webpack.DefinePlugin({
          __DEV__: JSON.stringify(JSON.parse(config.buildDev)),
          'process.env': {
            'NODE_ENV': 'dev'
          }
        }),
        new webpack.optimize.OccurenceOrderPlugin(),
        new webpack.optimize.DedupePlugin()
      ]
    };

    resolve(merge(common, siteConfig));
  });
};

Use dev env while building for production


"use strict";

annotationApp.service('morphRetriever', function($http) {
  this.getData = function(callback) {
    var result;
    var request = $.ajax({
      url: './static/analyses.json',
      async: false
    });

    request.done(callback);
  };

  var stubData;
  this.getStubData(function(res) {
    stubData = res;
  });

  this.getData = function(string, callback) {
    var result = stubData[string] || [];
    callback(result);
  };
});

"use strict";

annotationApp.service('morphRetriever', function($http) {
  this.getStubData = function(callback) {
    var result;
    var request = $.ajax({
      url: './static/analyses.json',
      async: false
    });

    request.done(callback);
  };

  var stubData;
  this.getStubData(function(res) {
    stubData = res;
  });

  this.getData = function(string, callback) {
    var result = stubData[string] || [];
    callback(result);
  };
});

Fix error in recent rebase


const request = require('request');
const yargs = require('yargs');

// 68 Studencheskaya street Engels

const argv = yargs
  .options({
    a: {
      demand: true,
      alias: 'address',
      describe: 'Address to fetch weather for.',
      string: true
    }
  })
  .help()
  .alias('help', 'h')
  .argv;

console.log(argv);

request({
  url: 'https://maps.googleapis.com/maps/api/geocode/json?address=68%20Studencheskaya%20street%20Engels',
  json: true
}, (error, response, body) => {
  console.log(body.results[0].formatted_address);
  console.log(`Latitude: ${body.results[0].geometry.location.lat}`);
  console.log(`Longitude: ${body.results[0].geometry.location.lng}`);
});

const request = require('request');
const yargs = require('yargs');

// 68 Studencheskaya street Engels

const argv = yargs
  .options({
    a: {
      demand: true,
      alias: 'address',
      describe: 'Address to fetch weather for.',
      string: true
    }
  })
  .help()
  .alias('help', 'h')
  .argv;

var url = `https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(argv.address)}`;

request({
  url: url,
  json: true
}, (error, response, body) => {
  console.log(body.results[0].formatted_address);
  console.log(`Latitude: ${body.results[0].geometry.location.lat}`);
  console.log(`Longitude: ${body.results[0].geometry.location.lng}`);
});

Create url from user input


/* eslint prefer-arrow-callback: 0, func-names: 0, 'react/jsx-filename-extension': [1, { "extensions": [".js", ".jsx"] }] */

import React from 'react';
import assert from 'assert';
import { shallow } from 'enzyme';
import AboutHome from './about-home';

describe('AboutHome', function () {
  let wrapper;

  before(function () {
    wrapper = shallow(<AboutHome />);
  });

  it('renders without crashing', function () {
    assert.equal(wrapper, wrapper);
  });

  it('renders five markdown elements', function () {
    const markdownElements = wrapper.find('div.on-secondary-page').children();
    assert.equal(markdownElements.length, 5);
  });
});

/* eslint prefer-arrow-callback: 0, func-names: 0, 'react/jsx-filename-extension': [1, { "extensions": [".js", ".jsx"] }] */

import React from 'react';
import assert from 'assert';
import { shallow } from 'enzyme';
import AboutHome from './about-home';

describe('AboutHome', function () {
  let wrapper;

  before(function () {
    wrapper = shallow(<AboutHome />);
  });

  it('renders without crashing', function () {
  });

  it('renders five markdown elements', function () {
    const markdownElements = wrapper.find('div.on-secondary-page').children();
    assert.equal(markdownElements.length, 5);
  });
});

Test for render without crashing with empty function.


const React = require('react')
const {Card, CardActions, CardHeader, CardText} = require('material-ui/Card')
const FlatButton = require('material-ui/FlatButton').default

/* eslint-disable react/jsx-indent */
module.exports = ({ request, response = { headers: [] } }) => {
  const headerMapper = (header, index) => <li key={`${header.key}-${index}`}>{`${header.key}: ${header.value}`}

  return <Card>
           <CardHeader
             title={request.url}
             subtitle={request.method}
             actAsExpander
             showExpandableButton />
           <CardActions>
             <FlatButton label='Details' />
             <FlatButton label='Action2' />
           </CardActions>
           <CardText expandable>

Request

                {request.headers.map(headerMapper)}

Response

                {response.headers.map(headerMapper)}

           </CardText>
         </Card>
}
/* eslint-enable react/jsx-indent */

const React = require('react')
const {Card, CardHeader, CardText} = require('material-ui/Card')
const {Tabs, Tab} = require('material-ui/Tabs')

/* eslint-disable react/jsx-indent */
module.exports = ({ request, response = { headers: [] } }) => {
  const headerMapper = (header, index) => <li key={`${header.key}-${index}`}>{`${header.key}: ${header.value}`}

  console.log('response', response)
  return <Card>
           <CardHeader
             title={request.url}
             subtitle={request.method}
             actAsExpander
             showExpandableButton />
           <CardText expandable>
            <Tabs>
              <Tab label='Request'>

                  {request.headers.map(headerMapper)}

              </Tab>
              <Tab label='Response'>

                  {response.headers.map(headerMapper)}

                {request.url}
              </Tab>
            </Tabs>
           </CardText>
         </Card>
}
/* eslint-enable react/jsx-indent */

Use tabs to split request and response.


import notificationActions from '../flux/notificationActions'
window.addEventListener('error', notificationActions.logError)
notificationActions.startProgress(1, 'Testing Progress Bars', {max: 4, value: 1}, true)
notificationActions.logMessage(0, 'Testing Messages')
notificationActions.logError({message: 'Testing Errors'})

import React from 'react'
import Notifications from '../elements/notifications'
React.render(React.createElement(Notifications), document.querySelector('.notifications'))

import notificationActions from '../flux/notificationActions'
window.addEventListener('error', notificationActions.logError)
// notificationActions.startProgress(1, 'Testing Progress Bars', {max: 4, value: 1}, true)
// notificationActions.logMessage(0, 'Testing Messages')
// notificationActions.logError({message: 'Testing Errors'})

import React from 'react'
import Notifications from '../elements/notifications'
React.render(React.createElement(Notifications), document.querySelector('.notifications'))

Stop loading the notifications every time


/**
 * Default model configuration
 * (sails.config.models)
 *
 * Unless you override them, the following properties will be included
 * in each of your models.
 *
 * For more info on Sails models, see:
 * http://sailsjs.org/#!/documentation/concepts/ORM
 */

module.exports.models = {

  /***************************************************************************
  *                                                                          *
  * Your app's default connection. i.e. the name of one of your app's        *
  * connections (see `config/connections.js`)                                *
  *                                                                          *
  ***************************************************************************/
  connection: 'mongodbServer',

  /***************************************************************************
  *                                                                          *
  * How and whether Sails will attempt to automatically rebuild the          *
  * tables/collections/etc. in your schema.                                  *
  *                                                                          *
  * See http://sailsjs.org/#!/documentation/concepts/ORM/model-settings.html  *
  *                                                                          *
  ***************************************************************************/
  migrate: 'alter'

};

/**
 * Default model configuration
 * (sails.config.models)
 *
 * Unless you override them, the following properties will be included
 * in each of your models.
 *
 * For more info on Sails models, see:
 * http://sailsjs.org/#!/documentation/concepts/ORM
 */

module.exports.models = {

  /***************************************************************************
  *                                                                          *
  * Your app's default connection. i.e. the name of one of your app's        *
  * connections (see `config/connections.js`)                                *
  *                                                                          *
  ***************************************************************************/
  connection: 'mongodbServer',

  /***************************************************************************
  *                                                                          *
  * How and whether Sails will attempt to automatically rebuild the          *
  * tables/collections/etc. in your schema.                                  *
  *                                                                          *
  * See http://sailsjs.org/#!/documentation/concepts/ORM/model-settings.html  *
  *                                                                          *
  ***************************************************************************/
  migrate: 'drop'

};

Drop data on server relaunch


$(document).ready(function() {

 $("a.up-vote-answer").click(function(event) {
      event.preventDefault();
      console.log("we here");
    }
  );

});

$(document).ready(function() {

 $("a.up-vote-answer").click(function(event) {
      event.preventDefault();
      var $target = $(event.target);
      console.log($target.data("url"))
      $.ajax({
        type: "post",
        url: $target.data("url"),
        data: $target.data(),
        success:
        function(response) {
          $('#' + $target.data('answer-id')).empty();
          $('#' + $target.data('answer-id')).append(response)
        }
      });
    }
  );

});

Add ajax call for upvotes


import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';

export default class FilteredRoute extends Route {
  @service store;

  async beforeModel() {
    await this.store.update((t) => {
      const moons = [
        { type: 'moon', attributes: { name: 'Blue' } },
        { type: 'moon', attributes: { name: 'New' } }
      ];
      const operations = [];
      operations.push(t.addRecord(moons[0]));
      operations.push(t.addRecord(moons[1]));

      const plutoOperation = t.addRecord({
        type: 'planet',
        attributes: { name: 'Pluto' }
      });
      const plutoId = plutoOperation.operation.record.id;
      operations.push(plutoOperation);
      operations.push(
        t.addRecord({ type: 'planet', attributes: { name: 'Mars' } })
      );
      operations.push(
        t.addRecord({ type: 'planet', attributes: { name: 'Filtered' } })
      );
      operations.push(
        t.replaceRelatedRecords({ type: 'planet', id: plutoId }, 'moons', moons)
      );
      return operations;
    });
  }

  async model() {
    return this.store.cache.liveQuery((qb) => qb.findRecords('planet'));
  }
}

import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';

export default class FilteredRoute extends Route {
  @service store;

  async beforeModel() {
    await this.store.update((t) => {
      const blueMoonId = this.store.schema.generateId('moon');
      const newMoonId = this.store.schema.generateId('moon');
      const plutoId = this.store.schema.generateId('planet');

      return [
        t.addRecord({
          type: 'moon',
          id: blueMoonId,
          attributes: { name: 'Blue' }
        }),
        t.addRecord({
          type: 'moon',
          id: newMoonId,
          attributes: { name: 'New' }
        }),
        t.addRecord({ type: 'planet', attributes: { name: 'Mars' } }),
        t.addRecord({ type: 'planet', attributes: { name: 'Filtered' } }),
        t.addRecord({
          type: 'planet',
          id: plutoId,
          attributes: { name: 'Pluto' },
          relationships: {
            moons: {
              data: [
                { type: 'moon', id: blueMoonId },
                { type: 'moon', id: newMoonId }
              ]
            }
          }
        })
      ];
    });
  }

  async model() {
    return this.store.cache.liveQuery((qb) => qb.findRecords('planet'));
  }
}

Simplify test data creation in dummy app


var rectangle = (function (parent) {
    var rectangle = Object.create(parent);

    Object.defineProperty(rectangle, 'init', {
        value: function (xCoordinate, yCoordinate, fill, stroke) {
            parent.init.call(this, xCoordinate, yCoordinate, fill, stroke);

            return this;
        }
    });

    Object.defineProperty(rectangle, 'update', {
        value: function () {
            // TODO: Implement this method to move your circle

            // Delete this line!
            parent.update.call(this);
        }
    });

    Object.defineProperty(rectangle, 'draw', {
        value: function (width, height, canvas) {
            var canvas = canvas;
            if (canvas.getContext) {
                var ctx = canvas.getContext('2d');
                ctx.fillRect(this.xCoordinate, this.yCoordinate, width, height);
                ctx.clearRect(this.xCoordinate, this.yCoordinate, width, height);
                ctx.strokeRect(this.xCoordinate, this.yCoordinate, width, height);
            }
        }
    });

    return rectangle;
}(gameObject));

var rectangle = (function (parent) {
    var rectangle = Object.create(parent);

    Object.defineProperty(rectangle, 'init', {
        value: function (xCoordinate, yCoordinate, width, height, fill, stroke) {
            parent.init.call(this, xCoordinate, yCoordinate, fill, stroke);
            this.width = width;
            this.height = height;

            return this;
        }
    });

    Object.defineProperty(rectangle, 'width', {
        get: function () {
            return this._width;
        },
        set: function (value) {
            validator.validateIfPositiveNumber(value, 'width');
            this._width = value;
        }
    });

    Object.defineProperty(rectangle, 'height', {
        get: function () {
            return this._height;
        },
        set: function (value) {
            validator.validateIfPositiveNumber(value, 'height');
            this._height = value;
        }
    });

    Object.defineProperty(rectangle, 'update', {
        value: function () {
            // TODO: Implement this method to move your circle

            // Delete this line!
            parent.update.call(this);
        }
    });

    // This method binds rectangle with canvas! The rectangle should not know how to draw itself!
    Object.defineProperty(rectangle, 'draw', {
        value: function (width, height, canvas) {
            var canvas = canvas;
            if (canvas.getContext) {
                var ctx = canvas.getContext('2d');
                ctx.fillRect(this.xCoordinate, this.yCoordinate, width, height);
                ctx.clearRect(this.xCoordinate, this.yCoordinate, width, height);
                ctx.strokeRect(this.xCoordinate, this.yCoordinate, width, height);
            }
        }
    });

    return rectangle;
}(gameObject));

Add properties width and height to rectngle


import Socket from './socket'
import { actions } from '../store/actions'
import {
  supportsWebSockets,
  supportsGetUserMedia
} from '../utils/feature-detection'

const {
  setToken,
  setWebSocketSupport,
  setGumSupport,
  setAuthenticated
} = actions

function setSupport() {
  setWebSocketSupport(supportsWebSockets)
  setGumSupport(supportsGetUserMedia)
}

export default function connect(jwt) {
  setSupport()
  if (supportsWebSockets) {
    const socket = new Socket
    socket.connect(jwt)
    setToken(jwt)
    setAuthenticated(true)
    return socket
  } else {
    // console.warn('WebSockets not supported')
  }
}

import Socket from './socket'
import { actions } from '../store/actions'
import {
  supportsWebSockets,
  supportsGetUserMedia
} from '../utils/feature-detection'

const { setWebSocketSupport, setGumSupport } = actions

function setSupport() {
  setWebSocketSupport(supportsWebSockets)
  setGumSupport(supportsGetUserMedia)
}

export default function connect(jwt) {
  setSupport()
  try {
    if (!supportsWebSockets) throw 'WebSockets not supported'
    const socket = new Socket
    socket.connect(jwt)
    return socket
  }
  catch(err) {
    console.log(err)
  }
}

Use try/catch for initiating WebSocket


import ContainerManager from './container_manager';
import { LockModes } from '../control/constants';
import renderCrashed from '../crashed/render';
import renderPasswordlessEmail from '../passwordless-email/render';
import renderPasswordlessSMS from '../passwordless-sms/render';
import React from 'react';
import * as l from './index';

export default class Renderer {
  constructor() {
    this.containerManager = new ContainerManager();
  }

  render(locks) {
    locks.filter(l.render).forEach(lock => {
      const container = this.containerManager.ensure(l.ui.containerID(lock), l.ui.appendContainer(lock));

      if (lock.get("show")) {
        React.render(this.element(lock), container);
      } else if (container) {
        React.unmountComponentAtNode(container);
      }
    });
  }

  element(lock) {
    // TODO: mode specific renderer specs should be passed to the constructor
    const mode = lock.get("mode");
    switch(mode) {
    case LockModes.CRASHED:
      return renderCrashed(lock);
    case LockModes.PASSWORDLESS_EMAIL:
      return renderPasswordlessEmail(lock);
    case LockModes.PASSWORDLESS_SMS:
      return renderPasswordlessSMS(lock);
    default:
      throw new Error(`unknown lock mode ${mode}`);
    }
  }
}

import ContainerManager from './container_manager';
import { LockModes } from '../control/constants';
import renderCrashed from '../crashed/render';
import renderPasswordlessEmail from '../passwordless-email/render';
import renderPasswordlessSMS from '../passwordless-sms/render';
import React from 'react';
import * as l from './index';

export default class Renderer {
  constructor() {
    this.containerManager = new ContainerManager();
  }

  render(locks) {
    locks.filter(l.render).forEach(lock => {
      const container = this.containerManager.ensure(l.ui.containerID(lock), l.ui.appendContainer(lock));

      if (lock.get("show")) {
        React.render(this.element(lock), container);
      } else if (container) {
        React.unmountComponentAtNode(container);
      }
    });
  }

  element(lock) {
    // TODO: mode specific renderer specs should be passed to the constructor
    const mode = lock.get("mode");
    switch(mode) {
    case LockModes.CRASHED:
      return renderCrashed(lock);
    case LockModes.PASSWORDLESS_EMAIL:
      return renderPasswordlessEmail(lock);
    // case LockModes.PASSWORDLESS_SMS:
    //   return renderPasswordlessSMS(lock);
    default:
      throw new Error(`unknown lock mode ${mode}`);
    }
  }
}

Disable passwordless sms mode for now


//= require ./core

(function () {
"use strict";

/*
 * State object mixin
 *
 * Requires Object `state` and Array `__changeListeners` properties
 */

Marbles.State = {
	addChangeListener: function (handler) {
		this.__changeListeners.push(handler);
	},

	removeChangeListener: function (handler) {
		this.__changeListeners = this.__changeListeners.filter(function (fn) {
			return fn !== handler;
		});
	},

	setState: function (newState) {
		var state = this.state;
		Object.keys(newState).forEach(function (key) {
			state[key] = newState[key];
		});
		this.handleChange();
	},

	replaceState: function (newState) {
		this.state = newState;
		this.handleChange();
	},

	handleChange: function () {
		this.__changeListeners.forEach(function (handler) {
			handler();
		});
	}
};

})();

//= require ./core

(function () {
"use strict";

/*
 * State object mixin
 *
 * Requires Object `state` and Array `__changeListeners` properties
 */

Marbles.State = {
	addChangeListener: function (handler) {
		this.__changeListeners.push(handler);
	},

	removeChangeListener: function (handler) {
		this.__changeListeners = this.__changeListeners.filter(function (fn) {
			return fn !== handler;
		});
	},

	setState: function (newState) {
		this.willUpdate();
		var state = this.state;
		Object.keys(newState).forEach(function (key) {
			state[key] = newState[key];
		});
		this.handleChange();
		this.didUpdate();
	},

	replaceState: function (newState) {
		this.willUpdate();
		this.state = newState;
		this.handleChange();
		this.didUpdate();
	},

	handleChange: function () {
		this.__changeListeners.forEach(function (handler) {
			handler();
		});
	},

	willUpdate: function () {},

	didUpdate: function () {}
};

})();

Update Marbles.State: Add willUpdate/didUpdate hooks


var GameInfo = require('module');

'use stricts';

/*
 *  Render th first page
 */
 exports.index = function(req, res){
   res.render('index'{
     user : req.user || null
   });
 }
 exports.getGame = function(req, res){
   var id = req.params.id
   Game.findOne({_id:id},function(game,err){
     if (err){res.send(err);}
     else{res.jsonp(game);}
   });
 }
 exports.createGame = function(req, res){
   res.render('game'{
     user : req.user || null
   });
 }
 exports.deleteGame = function(req, res){
   var id = req.params.id
   Game.delete({_id:id},function(game,err){
     if (err){res.send(err);}
     else{res.jsonp(game);}
   });
 }
 exports.updateGame = function(req, res){
   res.render('game'{
     user : req.user || null
   });
 }
 exports.displayGame = function(req, res){
   var id = req.params.id
   Game.findOne({_id:id},function(game,err){
     if (err){res.send(err);}
     else{res.jsonp(game);}
   });
 }
 exports.showList = function(req, res){
   res.render('page'{
     user : req.user || null
   });
 }

var GameInfo = require('models/game-info.js');

'use stricts';

/*
 *  Render th first page
 */
 exports.index = function(req, res){
   res.render('index'{
     user : req.user || null
   });
 }
 exports.getGame = function(req, res){
   var id = req.params.id
   Game.findOne({_id:id},function(game,err){
     if (err){res.send(err);}
     else{res.jsonp(game);}
   });
 }
 exports.createGame = function(req, res){
   res.render('game'{
     user : req.user || null
   });
 }
 exports.deleteGame = function(req, res){
   var id = req.params.id
   Game.delete({_id:id},function(game,err){
     if (err){res.send(err);}
     else{res.jsonp(game);}
   });
 }
 exports.updateGame = function(req, res){
   res.render('game'{
     user : req.user || null
   });
 }
 exports.displayGame = function(req, res){
   var id = req.params.id
   Game.findOne({_id:id},function(game,err){
     if (err){res.send(err);}
     else{res.jsonp(game);}
   });
 }
 exports.showList = function(req, res){
   res.render('page'{
     user : req.user || null
   });
 }

Correct require to point to models/game-info.js.


var maxLength = 20;
var pendingRequest = undefined;

function updateResults(e) {
  if (e.which === 0) {
      return;
  }
  var query = $("#query").val();
  if (pendingRequest) {
    pendingRequest.abort();
  }
  pendingRequest = $.getJSON(
    "/search",
    {query: query},
    function processQueryResult(data) {
      $("#results").html("");
      for (var x=0; x<data.length; x++) {
        var post = data[x];
        showImage(x, post);
        if (x >= maxLength) {
          showImage(x, {
            title: "Refine your search for more results"
          });
          break;
        }
      }
      $('img.result-img').lazyload({
        effect: "fadeIn",
        threshold: 1000,
        skip_invisible: true
      });
    }
  );
}

function showImage(x, postData) {
  var postHTML = '
';
  postHTML += '
';
  if (postData.url) postHTML += '';
  postHTML += postData.title;
  if (postData.url) postHTML += '
';
  if (postData.image) postHTML += '';
  postHTML += '
';
  $("#results").append(postHTML);
}

$("#query").keypress(updateResults);

var maxLength = 20;
var pendingRequest = undefined;

function updateResults(e) {
  var query = $("#query").val();
  if (pendingRequest) {
    pendingRequest.abort();
  }
  pendingRequest = $.getJSON(
    "/search",
    {query: query},
    function processQueryResult(data) {
      $("#results").html("");
      for (var x=0; x<data.length; x++) {
        var post = data[x];
        showImage(x, post);
        if (x >= maxLength) {
          showImage(x, {
            title: "Refine your search for more results"
          });
          break;
        }
      }
      $('img.result-img').lazyload({
        effect: "fadeIn",
        threshold: 1000,
        skip_invisible: true
      });
    }
  );
}

function showImage(x, postData) {
  var postHTML = '
';
  postHTML += '
';
  if (postData.url) postHTML += '';
  postHTML += postData.title;
  if (postData.url) postHTML += '
';
  if (postData.image) postHTML += '';
  postHTML += '
';
  $("#results").append(postHTML);
}

$("#query").on('input', updateResults);

Make site be more responsive to all changes to input box


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

import MapView from 'react-native-maps';

var { width, height } = Dimensions.get('window');

const ASPECT_RATIO = width / height;
const LATITUDE = 37.7;
const LONGITUDE = -122.2;
const LATITUDE_DELTA = 1.2;
const LONGITUDE_DELTA = 1.2;

class MapScene extends Component {
      render() {
        return (
          <View>
            <MapView
              style={styles.map}
              initialRegion={{
                latitude: LATITUDE,
                longitude: LONGITUDE,
                latitudeDelta: LATITUDE_DELTA,
                longitudeDelta: LONGITUDE_DELTA,
              }}
              mapType={'standard'}
            >
            </MapView>
          </View>
      )}
}

const styles = StyleSheet.create({
  map: {
    flex: 1,
    width: width,
    height: height,
    borderWidth: 1,
  }
});

module.exports = MapScene;

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

import MapView from 'react-native-maps';

var { width, height } = Dimensions.get('window');

const ASPECT_RATIO = width / height;
const LATITUDE = 37.78825;
const LONGITUDE = -122.4324;
const LATITUDE_DELTA = 0.0922;
const LONGITUDE_DELTA = 0.0421;

class MapScene extends Component {
      render() {
        var pins = [{
          latitude: 37.78482573289199,
          longitude: -122.4023278109328
        }];
        return (
          <MapView
            annotations={pins}
            onRegionChangeComplete={this.onRegionChangeComplete}
            style={styles.map}
            initialRegion={{
              latitude: LATITUDE,
              longitude: LONGITUDE,
              latitudeDelta: LATITUDE_DELTA,
              longitudeDelta: LONGITUDE_DELTA,
            }}
            mapType={'standard'}
          >
          </MapView>
      )};
      onRegionChangeComplete(region) {
        console.log(region);
      }
};

const styles = StyleSheet.create({
  map: {
    flex: 1,
    width: width,
    height: height,
    borderWidth: 1,
  }
});

module.exports = MapScene;

Work on placing pins on map


var _ = require('underscore');

module.exports = function(opts) {

    var config = {
        base_url: '/database/:dbname/table/:table',
        grainstore: {datasource: global.environment.postgres},
        redis: global.environment.redis,
        enable_cors: global.environment.enable_cors,
        unbuffered_logging: true, // for smoother teardown from tests
        req2params: function(req, callback){

            // no default interactivity. to enable specify the database column you'd like to interact with
            req.params.interactivity = null;

            // this is in case you want to test sql parameters eg ...png?sql=select * from my_table limit 10
            req.params =  _.extend({}, req.params);
            _.extend(req.params, req.query);

            // send the finished req object on
            callback(null,req);
        },
        beforeTileRender: function(req, res, callback) {
            res.header('X-BeforeTileRender', 'called');
            callback(null);
        },
        afterTileRender: function(req, res, tile, headers, callback) {
            res.header('X-AfterTileRender','called');
            headers['X-AfterTileRender2'] = 'called';
            callback(null, tile, headers);
        }

    }

    _.extend(config,  opts || {});

    return config;
}();

var _ = require('underscore');

module.exports = function(opts) {

    var config = {
        base_url: '/database/:dbname/table/:table',
        grainstore: {datasource: global.environment.postgres},
        redis: global.environment.redis,
        enable_cors: global.environment.enable_cors,
        unbuffered_logging: true, // for smoother teardown from tests
        log_format: null, // do not log anything
        req2params: function(req, callback){

            // no default interactivity. to enable specify the database column you'd like to interact with
            req.params.interactivity = null;

            // this is in case you want to test sql parameters eg ...png?sql=select * from my_table limit 10
            req.params =  _.extend({}, req.params);
            _.extend(req.params, req.query);

            // send the finished req object on
            callback(null,req);
        },
        beforeTileRender: function(req, res, callback) {
            res.header('X-BeforeTileRender', 'called');
            callback(null);
        },
        afterTileRender: function(req, res, tile, headers, callback) {
            res.header('X-AfterTileRender','called');
            headers['X-AfterTileRender2'] = 'called';
            callback(null, tile, headers);
        }

    }

    _.extend(config,  opts || {});

    return config;
}();

Disable logging while running tests


import { UniqueViolationError } from 'objection-db-errors'
import { FeathersError, Conflict, GeneralError } from '@feathersjs/errors'

const errorHandler = (ctx) => {
  if (!ctx.error) {
    return
  }
  if (ctx.error instanceof UniqueViolationError) {
    const error = new Conflict('Unique Violation Error')
    error.errors = ctx.error.columns
    throw error
  }
  if (ctx.error instanceof FeathersError) {
    throw ctx.error
  }
  throw new GeneralError('unknown error', ctx.error.data)
}

export default errorHandler

import { UniqueViolationError } from 'objection-db-errors'
import { FeathersError, Conflict, GeneralError } from '@feathersjs/errors'
import _ from 'lodash'

const errorHandler = (ctx) => {
  if (!ctx.error) {
    return
  }
  // FIXME currently using console.log to display DB errors
  // as app.error doesn't display it
  // eslint-disable-next-line no-console
  console.log(_.omit(ctx.error, ['hook']))
  if (ctx.error instanceof UniqueViolationError) {
    const error = new Conflict('Unique Violation Error')
    error.errors = ctx.error.columns
    throw error
  }
  if (ctx.error instanceof FeathersError) {
    throw ctx.error
  }
  throw new GeneralError('unknown error', ctx.error.data)
}

export default errorHandler

Add workaround to display DB error log.


'use strict'

const config = require('./config')
const db = require('./db')
const app = require('../http/app')

db.init(config.dbUrl, 'shortlinks').then(() => {
  app.listen(config.port, () => {
    console.log('Server listening on port ' + config.port + '…')
  })
}).catch((error) => {
  console.log('>>> Error during database initialization:\n')
  console.log(error)
  process.exit(1)
})

'use strict'

const config = require('./config')
const db = require('./db')
const app = require('../http/app')

console.log('#'.repeat(40))
console.log((new Date()).toUTCString())

db.init(config.dbUrl, 'shortlinks').then(() => {
  app.listen(config.port, () => {
    console.log('Server listening on port ' + config.port + '…')
  })
}).catch((error) => {
  console.log('>>> Error during database initialization:\n')
  console.log(error)
  process.exit(1)
})

Add separator in logfile and application start time


'use strict';

/** @type {import('sequelize-cli').Migration} */
module.exports = {
  async up(queryInterface, Sequelize) {
    queryInterface.bulkInsert("Subareas", [{
      id: 1,
      name: "Alpha",
      color: Math.floor(Math.random() * 16777215).toString(16)
    }, {
      id: 2,
      name: "Bravo",
      color: Math.floor(Math.random() * 16777215).toString(16)
    }, {
      id: 3,
      name: "Charlie",
      color: Math.floor(Math.random() * 16777215).toString(16)
    }, {
      id: 4,
      name: "Delta",
      color: Math.floor(Math.random() * 16777215).toString(16)
    }, {
      id: 5,
      name: "Echo",
      color: Math.floor(Math.random() * 16777215).toString(16)
    }, {
      id: 6,
      name: "Foxtrot",
      color: Math.floor(Math.random() * 16777215).toString(16)
    }, {
      id: 7,
      name: "Onbekend",
      color: Math.floor(Math.random() * 16777215).toString(16)
    }]);
  },

  async down(queryInterface, Sequelize) {
    /**
     * Add commands to revert seed here.
     *
     * Example:
     * await queryInterface.bulkDelete('People', null, {});
     */
    await queryInterface.bulkDelete('Subareas', null, {});
  }
};

'use strict';

/** @type {import('sequelize-cli').Migration} */
module.exports = {
  async up(queryInterface, Sequelize) {
    queryInterface.bulkInsert("Subareas", [{
      id: 1,
      name: "Alpha",
      color: "DFFF00"
    }, {
      id: 2,
      name: "Bravo",
      color: "FFBF00"
    }, {
      id: 3,
      name: "Charlie",
      color: "9FE2BF"
    }, {
      id: 4,
      name: "Delta",
      color: "FF7F50"
    }, {
      id: 5,
      name: "Echo",
      color: "40E0D0"
    }, {
      id: 6,
      name: "Foxtrot",
      color: "DE3163"
    }, {
      id: 7,
      name: "Onbekend",
      color: Math.floor(Math.random() * 16777215).toString(16)
    }]);
  },

  async down(queryInterface, Sequelize) {
    /**
     * Add commands to revert seed here.
     *
     * Example:
     * await queryInterface.bulkDelete('People', null, {});
     */
    await queryInterface.bulkDelete('Subareas', null, {});
  }
};

Add better visible subarea colors


/**
 * Copyright (c) 2014 by Center Open Middleware. All Rights Reserved.
 * Titanium Appcelerator 3.3.0GA
 * Licensed under the terms of the Apache Public License
 * Please see the LICENSE included with this distribution for details.
 *
 * App Base Development Framework Team
 *
 *  Alejandro F.Carrera
 *  Carlos Blanco Vallejo
 *  Santiago Blanco Ventas
 *
 */

"use strict";

// Yaast Framework Init (Global Scope)
var Yaast = {
    "Log": function(msg, type, error, extra){
        var self = {
            'name': 'W4TLog',
            'message': msg
        };
        if(error){
            self.details = (extra) ? {
                'message': error.message,
                'info': extra
            } : error.message;
            self.source = error.sourceURL;
        }
        Ti.API[type](self);
        return self;
    },
    "MergeObject": function (obj1, obj2){
        var result = null, key;
        if (obj1 !== null && obj2 !== null){
            for (key in obj2){
                obj1[key] = obj2[key];
            }
            result = obj1;
        }
        return result;
    },
    "FontAwesome": require('fonts/FontAwesome4'),
    "API": require('lib/API'),
    "Sandbox": {
    	'currentView': null
    }
};

(function() {

    if (Yaast.API.HW.System.isTablet()) {
        var Window = require('ui/window/appWindow');
        Window.window.open();
    }
    else  alert("Wirecloud4Tablet has not compatibility with Smartphone's'");

}());

/**
 * Copyright (c) 2014 by Center Open Middleware. All Rights Reserved.
 * Titanium Appcelerator 3.3.0GA
 * Licensed under the terms of the Apache Public License
 * Please see the LICENSE included with this distribution for details.
 *
 * App Base Development Framework Team
 *
 *  Alejandro F.Carrera
 *  Carlos Blanco Vallejo
 *  Santiago Blanco Ventas
 *
 */

"use strict";

// Yaast Framework Init (Global Scope)
var Yaast = {
    "Log": function(msg, type, error, extra) {
        var self = {
            'name': 'W4TLog',
            'message': msg
        };
        if(error) {
            self.details = (extra) ? {
                'message': error.message,
                'info': extra
            } : error.message;
            self.source = error.sourceURL;
        }
        Ti.API[type](self);
        return self;
    },
    "FontAwesome": require('fonts/FontAwesome4'),
    "API": require('lib/API'),
    "Sandbox": {
    	'currentView': null
    }
};
// Merge shortcut
Yaast["MergeObject"] = Yaast.API.SW.Utils.mergeObject;

(function() {

    if (Yaast.API.HW.System.isTablet()) {
        var Window = require('ui/window/appWindow');
        Window.window.open();
    }
    else  alert("Wirecloud4Tablet has not compatibility with Smartphone's'");

}());

Use API Deep Merge in all w4t


/**
 * Created by meysamabl on 11/1/14.
 */

$(document).ready(function () {
//    $('#picture').change(function () {
//        $("#catForm").ajaxForm({ target: "#image_view" }).submit();
//        return false;
//    });

    $("#level2Parent").hide();
    $("#level1").change(function (e) {
        e.preventDefault();
        var form = $(this).closest('form');
        var url = form.attr("action");
        var id = $("#level1").val();
        if ($("#level1").val() != "") {
            $("#level2Parent").show();
            jsRoutes.controllers.Administration.getSubCategories(id).ajax({
                dataType: 'json',
                success: function (data) {
                    $('#level2').empty();
                    $('#level2').append($('<option>').text(Messages('chooseCategory')).attr('value', null));
                    console.info(data);
                    $.each(data, function(i, value) {
                        $('#level2').append($('<option>').text(value).attr('value', value));
                    });

                },
                error: function () {
                    alert("Error!")
                }
            })

        } else {
            $("#level2Parent").hide();
            $("#level2").val("");
        }
    })
});

/**
 * Created by meysamabl on 11/1/14.
 */

$(document).ready(function () {
//    $('#picture').change(function () {
//        $("#catForm").ajaxForm({ target: "#image_view" }).submit();
//        return false;
//    });

    $("#level2Parent").hide();
    $("#level1").change(function (e) {
        e.preventDefault();
        var id = $("#level1").val();
        if (id != "") {
            $("#level2Parent").show();
            jsRoutes.controllers.Administration.getSubCategories(id).ajax({
                dataType: 'json',
                success: function (data) {
                    $('#level2').empty();
                    $('#level2').append($('<option>').text(Messages('chooseCategory')).attr('value', ""));
                    $.each(data, function(i, value) {
                        $('#level2').append($('<option>').text(value).attr('value', i));
                    });

                },
                error: function () {
                    alert("Error!")
                }
            })

        } else {
            $("#level2Parent").hide();
            $("#level2").val("");
        }
    })
    $("#level2").change(function() {
        var id = $("#level2").val();
        if(id != "") {
            $("#level1").removeAttr("name");
        } else {
            $("#level1").attr('name', 'parentCategory');
        }
    })
});

Fix The bug in createCategory, Now can save subcategories with no problem


Template.postPage.onCreated( () => {
    var t = Template.instance();
    t.subscribe('listForId',FlowRouter.getParam('id'));
});

Template.postPage.helpers({
    post: () => {
        return List.findOne();
    }
});

Template.postPage.onCreated( () => {
    var t = Template.instance();
    t.post_id = FlowRouter.getParam('id');
    t.autorun(()=>{
        t.subscribe('listForId', t.post_id );
    });
});

Template.postPage.helpers({
    post() {
        return List.findOne();
    }
});

Put subscription on autorun in postPage.


import app from 'flarum/app';

import AkismetSettingsModal from './components/AkismetSettingsModal';

app.initializers.add('flarum-akismet', () => {
  app.extensionSettings['flarum-akismet'] = () => app.modal.show(new AkismetSettingsModal());
});

import app from 'flarum/app';

import AkismetSettingsModal from './components/AkismetSettingsModal';

app.initializers.add('flarum-akismet', () => {
  app.extensionSettings['flarum-akismet'] = () => app.modal.show(AkismetSettingsModal);
});

Fix extension to work with latest state changes


/**
 * Copyright 2014, Yahoo! Inc.
 * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
 */
'use strict';
var createStore = require('fluxible-app/utils/createStore');

var TimeStore = createStore({
    storeName: 'TimeStore',
    initialize: function (dispatcher) {
        this.time = new Date();
    },
    handleTimeChange: function (payload) {
        this.time = new Date();
        this.emit('change');
    },
    handlers: {
        'CHANGE_ROUTE_START': 'handleTimeChange',
        'UPDATE_TIME': 'handleTimeChange'
    },
    getState: function () {
        return {
            time: this.time.toString()
        };
    },
    dehydrate: function () {
        return this.getState();
    },
    rehydrate: function (state) {
        this.time = new Date(state.time);
    }
});

module.exports = TimeStore;

/**
 * Copyright 2014, Yahoo! Inc.
 * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
 */
'use strict';
var createStore = require('fluxible-app/utils/createStore');

var TimeStore = createStore({
    storeName: 'TimeStore',
    initialize: function (dispatcher) {
        this.time = new Date();
    },
    handleTimeChange: function (payload) {
        this.time = new Date();
        this.emitChange();
    },
    handlers: {
        'CHANGE_ROUTE_START': 'handleTimeChange',
        'UPDATE_TIME': 'handleTimeChange'
    },
    getState: function () {
        return {
            time: this.time.toString()
        };
    },
    dehydrate: function () {
        return this.getState();
    },
    rehydrate: function (state) {
        this.time = new Date(state.time);
    }
});

module.exports = TimeStore;

Use this.emitChange() instead of old API


import { tracked } from '@glimmer/tracking';
import { Resource } from 'ember-could-get-used-to-this';
export default class ResolveAsyncValueResource extends Resource {
  @tracked data;

  get value() {
    return this.data;
  }

  async setup() {
    this.data = await this.args.positional[0];
  }
}

import { tracked } from '@glimmer/tracking';
import { Resource } from 'ember-could-get-used-to-this';
export default class ResolveAsyncValueResource extends Resource {
  @tracked data;

  get value() {
    return this.data;
  }

  async setup() {
    //when a second value is passed it is the default until the promise gets resolved
    if (this.args.positional.length > 1) {
      this.data = this.args.positional[1];
    }

    this.data = await this.args.positional[0];
  }
}

Add a default value to resolve async helper


(function() {

    'use strict';

    describe('angular-feedback', function() {

        beforeEach(module('angular-feedback'));

        /*
        var feedbackProvider;

        beforeEach(function() {
            // Here we create a fake module just to intercept and store the provider
            // when it's injected, i.e. during the config phase.
            angular
                .module('fakeModule', function() {})
                .config(['feedbackProvider', function(provider) {
                    feedbackProvider = provider;
                }]);

            module('angular-feedback', 'fakeModule');

            // This actually triggers the injection into fakeModule
            inject(function(){});
        });
        */

        it('should be inactive by default', inject(function(feedback) {
            expect( feedback.isActive() ).toBeFalsy();
            expect( feedback.isLoading() ).toBeFalsy();
        }));

    });

})();

(function() {

    'use strict';

    describe('angular-feedback', function() {

        beforeEach(module('angular-feedback'));

        /*
        var feedbackProvider;

        beforeEach(function() {
            // Here we create a fake module just to intercept and store the provider
            // when it's injected, i.e. during the config phase.
            angular
                .module('fakeModule', function() {})
                .config(['feedbackProvider', function(provider) {
                    feedbackProvider = provider;
                }]);

            module('angular-feedback', 'fakeModule');

            // This actually triggers the injection into fakeModule
            inject(function(){});
        });
        */

        it('should be inactive by default', inject(function(feedback) {
            expect( feedback.isActive() ).toBeFalsy();
            expect( feedback.isLoading() ).toBeFalsy();
        }));

        it('should set loader', inject(function(feedback) {
            feedback.load();
            expect( feedback.isActive() ).toBeTruthy();
            expect( feedback.isLoading() ).toBeTruthy();
        }));

        it('should dismiss loader', inject(function(feedback, $timeout) {
            feedback.load();
            feedback.dismiss();

            $timeout.flush();

            expect( feedback.isActive() ).toBeFalsy();
            expect( feedback.isLoading() ).toBeFalsy();
        }));

        it('should set default notification to "info"', inject(function(feedback) {
            var message = 'Notification message';
            feedback.notify( message );

            expect( feedback.isActive() ).toBeTruthy();
            expect( feedback.isLoading() ).toBeFalsy();
            // expect( feedback.getType() ).toBe('info');
            expect( feedback.getMessage() ).toBe( message);
        }));

    });

})();

Add tests: load(), dismiss(), notify()


import 'instapromise';

import fs from 'fs';
import logger from 'gulplog';
import path from 'path';
import spawnAsync from '@exponent/spawn-async';

const paths = {
  template: path.resolve(__dirname, '../template'),
  templateNodeModules: path.resolve(__dirname, '../template/node_modules'),
}

let tasks = {
  async archiveTemplate() {
    await verifyNodeModulesAsync();
    await spawnAsync('zip', ['-rqy9', 'template.zip', 'template'], {
      stdio: 'inherit',
      cwd: path.resolve(__dirname, '..'),
    });
  }
};

async function verifyNodeModulesAsync() {
  let stats;
  try {
    stats = await fs.promise.stat(paths.templateNodeModules);
  } catch (e) { }

  if (stats) {
    if (!stats.isDirectory()) {
      throw new Error(
        `${paths.templateNodeModules} is not a directory; be sure to run ` +
        `"npm install" before releasing a new version of XDL`
      );
    }
  } else {
    logger.info(`Running "npm install" to set up ${paths.templateNodeModules}...`);
    await spawnAsync('npm', ['install'], {
      stdio: 'inherit',
      cwd: paths.template,
    });
  }
}

export default tasks;

import 'instapromise';

import fs from 'fs';
import logger from 'gulplog';
import path from 'path';
import spawnAsync from '@exponent/spawn-async';

const paths = {
  template: path.resolve(__dirname, '../template'),
  templateNodeModules: path.resolve(__dirname, '../template/node_modules'),
}

let tasks = {
  async archiveTemplate() {
    await verifyNodeModulesAsync();
    await spawnAsync('zip', ['-rq9', 'template.zip', 'template'], {
      stdio: 'inherit',
      cwd: path.resolve(__dirname, '..'),
    });
  }
};

async function verifyNodeModulesAsync() {
  let stats;
  try {
    stats = await fs.promise.stat(paths.templateNodeModules);
  } catch (e) { }

  if (stats) {
    if (!stats.isDirectory()) {
      throw new Error(
        `${paths.templateNodeModules} is not a directory; be sure to run ` +
        `"npm install" before releasing a new version of XDL`
      );
    }
  } else {
    logger.info(`Running "npm install" to set up ${paths.templateNodeModules}...`);
    await spawnAsync('npm', ['install'], {
      stdio: 'inherit',
      cwd: paths.template,
    });
  }
}

export default tasks;

Remove -y option from zip


const webpack = require('webpack');
const BrowserSyncPlugin = require('browsersync-webpack-plugin');

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

module.exports = {
  output: {
    pathinfo: true,
    publicPath: config.proxyUrl + config.publicPath,
  },
  devtool: '#cheap-module-source-map',
  stats: false,
  plugins: [
    new webpack.optimize.OccurrenceOrderPlugin(),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoEmitOnErrorsPlugin(),
    new BrowserSyncPlugin({
      target: config.devUrl,
      proxyUrl: config.proxyUrl,
      watch: config.watch,
      delay: 500,
    }),
  ],
};

const webpack = require('webpack');
const BrowserSyncPlugin = require('browsersync-webpack-plugin');

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

module.exports = {
  output: {
    pathinfo: true,
    publicPath: config.proxyUrl + config.publicPath,
  },
  devtool: '#cheap-module-source-map',
  stats: false,
  plugins: [
    new webpack.optimize.OccurrenceOrderPlugin(),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoEmitOnErrorsPlugin(),
    new BrowserSyncPlugin({
      target: process.env.DEVURL || config.devUrl,
      proxyUrl: config.proxyUrl,
      watch: config.watch,
      delay: 500,
    }),
  ],
};

Add environment variable check for DEVURL


var Path = require('path')
global.CONFIG = require('./index.json')
CONFIG.projectFile = (path) => Path.resolve(__dirname, '../..', path)

module.exports = function config (router) {
  for (var item of CONFIG.routerPipeline) {
    require(item).mount(router)
  }
}

var Path = require('path')
global.CONFIG = require('./index.json')
CONFIG.projectFile = (path) => Path.resolve(__dirname, '../..', path)

module.exports = function config (router) {

  //
  // Foundational config should be seldom used,
  // as it has a HIGH priority over the rest.
  //
  for (var item of (CONFIG.foundational || [])) {
    require(item).mount(router)
  }

  //
  // Run the rest of all configuration.
  //
  for (var item of CONFIG.routerPipeline) {
    require(item).mount(router)
  }
}

Add foundational config, for high priority config.


var temp = require('../lib/temp'),
    fs   = require('fs'),
    util = require('util'),
    exec = require('child_process').exec;

var myData = "foo\nbar\nfoo\nbaz";

temp.open('myprefix', function(err, info) {
  if (err) throw err;
  fs.write(info.fd, myData);
  fs.close(info.fd, function(err) {
    if (err) throw err;
    exec("grep foo '" + info.path + "' | wc -l", function(err, stdout) {
      if (err) throw err;
      util.puts(stdout.trim());
    });
  });
});

var temp = require('../lib/temp'),
    fs   = require('fs'),
    util = require('util'),
    exec = require('child_process').exec;

var myData = "foo\nbar\nfoo\nbaz";

temp.open('myprefix', function(err, info) {
  if (err) throw err;
  fs.write(info.fd, myData, function(err) {
    if (err) throw err;
    fs.close(info.fd, function(err) {
      if (err) throw err;
      exec("grep foo '" + info.path + "' | wc -l", function(err, stdout) {
        if (err) throw err;
        util.puts(stdout.trim());
      });
    });
  });
});

Update example to include callback


import React from "react";
import ReactDOM from "react-dom";
import YoutubeEmbedVideo from 'youtube-embed-video';

ReactDOM.render(<YoutubeEmbedVideo size="large" videoId="RnDC9MXSqCY" suggestions={false} />, document.getElementById("app"));

import React from "react";
import ReactDOM from "react-dom";
import YoutubeEmbedVideo from '../dist/youtube';
// import YoutubeEmbedVideo from 'youtube-embed-video';

ReactDOM.render(<YoutubeEmbedVideo size="medium" videoId="RnDC9MXSqCY" className="video-player" style={{ borderWidth: 5, borderColor: '#004ebc' }} suggestions={false} />, document.getElementById("app"));

Update example with class and style attributes


// Use the gulp-help plugin which defines a default gulp task
// which lists what tasks are available.
var gulp = require('gulp-help')(require('gulp'));

// Sequential Gulp tasks
var runSequence = require('run-sequence').use(gulp);

var cucumber = require('gulp-cucumber');

// Parse any command line arguments ignoring
// Node and the name of the calling script.
// Extract tag arguments.
var argv = require('minimist')(process.argv.slice(2));
var tags = argv.tags || false;

// Run all the Cucumber features, doesn't start server
// Hidden from gulp-help.
gulp.task('cucumber', 'Run Cucumber directly without starting the server.', function() {
  var options = {
    support: 'features-support/**/*.js',
    // Tags are optional, falsey values are ignored.
    tags: tags
  }
  return gulp.src('features/**/*.feature').pipe(cucumber(options));
}, {
  options: {'tags': 'Supports optional tags argument e.g.\n\t\t\t--tags @parsing\n\t\t\t--tags @tag1,orTag2\n\t\t\t--tags @tag1 --tags @andTag2\n\t\t\t--tags @tag1 --tags ~@andNotTag2'}
});

// Default Cucumber run requires server to be running.
gulp.task('test:features', 'Test the features.', function(done) {
  runSequence('set-envs:test',
              'server:start',
              'cucumber',
              'server:stop',
              done);
});

// Use the gulp-help plugin which defines a default gulp task
// which lists what tasks are available.
var gulp = require('gulp-help')(require('gulp'));

// Sequential Gulp tasks
var runSequence = require('run-sequence').use(gulp);

var cucumber = require('gulp-cucumber');

// Parse any command line arguments ignoring
// Node and the name of the calling script.
// Extract tag arguments.
var argv = require('minimist')(process.argv.slice(2));
var tags = argv.tags || false;

// Run all the Cucumber features, doesn't start server
// Hidden from gulp-help.
gulp.task('cucumber', 'Run Cucumber directly without starting the server.', function() {
  var options = {
    support: 'features-support/**/*.js',
    // Tags are optional, falsey values are ignored.
    tags: tags
  }
  return gulp.src('features/**/*.feature').pipe(cucumber(options));
}, {
  options: {'tags': 'Supports optional tags argument e.g.\n\t\t\t--tags @parsing\n\t\t\t--tags @tag1,orTag2\n\t\t\t--tags @tag1 --tags @andTag2\n\t\t\t--tags @tag1 --tags ~@andNotTag2'}
});

// Default Cucumber run requires server to be running.
gulp.task('test:features', 'Test the features.', function(done) {
  runSequence('set-envs:test',
              'server:start',
              'cucumber',
              'server:stop',
              done);
}, {
  options: {'tags': 'Supports same optional tags arguments as \'Cucumber\' task.'}
});

Document that the test:features Gulp task also supports tags.


const Plugin = require('imdone-api')
const _path = require('path')
const { statSync } = require('fs')

module.exports = class ExtensionPlugin extends Plugin {
  constructor(project) {
    super(project)
    console.log('loading extensions')
    this.configDir = _path.join(project.path, '.imdone')

    this.cardActionsFunction = this.loadExtensionModule(
      () => [],
      'actions',
      'card'
    )
    this.boardActionsFunction = this.loadExtensionModule(
      () => [],
      'actions',
      'board'
    )
    this.cardPropertiesFunction = this.loadExtensionModule(
      () => {
        return {}
      },
      'properties',
      'card'
    )
  }

  getCardProperties(task) {
    return this.cardPropertiesFunction(task)
  }

  getCardActions(task) {
    return this.cardActionsFunction(this.project, task)
  }

  getBoardActions() {
    return this.boardActionsFunction(this.project).map(({ title, action }) => ({
      name: title,
      action,
    }))
  }

  getConfigPath(relativePath) {
    return _path.join(this.configDir, ...relativePath)
  }

  loadExtensionModule(_default, ...path) {
    let extension = _default
    const extensionPath = this.getConfigPath(path)
    try {
      statSync(extensionPath + '.js')
      delete require.cache[require.resolve(extensionPath)]
      extension = require(extensionPath)
    } catch (e) {
      console.log('No extension found at:', extensionPath)
    }
    return extension
  }
}

const Plugin = require('imdone-api')
const _path = require('path')
const { statSync } = require('fs')

module.exports = class ExtensionPlugin extends Plugin {
  constructor(project) {
    super(project)
    console.log('loading extensions')
    this.configDir = _path.join(project.path, '.imdone')

    this.cardActionsFunction = this.loadExtensionModule(
      () => [],
      'actions',
      'card'
    ).bind(this)
    this.boardActionsFunction = this.loadExtensionModule(
      () => [],
      'actions',
      'board'
    ).bind(this)
    this.cardPropertiesFunction = this.loadExtensionModule(
      () => {
        return {}
      },
      'properties',
      'card'
    ).bind(this)
  }

  getCardProperties(task) {
    return this.cardPropertiesFunction(task)
  }

  getCardActions(task) {
    return this.cardActionsFunction(task)
  }

  getBoardActions() {
    return this.boardActionsFunction().map(({ title, action }) => ({
      name: title,
      action,
    }))
  }

  getConfigPath(relativePath) {
    return _path.join(this.configDir, ...relativePath)
  }

  loadExtensionModule(_default, ...path) {
    let extension = _default
    const extensionPath = this.getConfigPath(path)
    try {
      statSync(extensionPath + '.js')
      delete require.cache[require.resolve(extensionPath)]
      extension = require(extensionPath)
    } catch (e) {
      console.log('No extension found at:', extensionPath)
    }
    return extension
  }
}

Make actions and properties module conform to plugin-api