/**
 * Modules dependencies
 */

var mocha = require('mocha'),
	assert = require('chai').assert,
	libPath = process.env['SCRAPINODE_COV'] ? '../lib-cov' : '../lib',
	scrapinode = require( libPath + '/scrapinode'),
	ScrapinodeError = require(libPath + '/error/scrapinode-error'),
	express = require('express'),
	filed = require('filed');

// Web server
var app = express();

app.get('/',function(req,res){
	setTimeout(function(){
		req.pipe(filed(__dirname + '/resources/index.html')).pipe(res);
	},1000);
});

app.listen(3040);

// Test suite

describe('scrapinode#createScraper(options,callback)',function(){
	beforeEach(function(){
		scrapinode.clearRouter();
	});
	describe('when the options.timeout is defined and the timeout is reached',function(){
		it('should return an error Object in the callback as first argument',function(done){
			scrapinode.use('localhost:3040','title',function(window){
				return window.$('title').text();
			});
			var options = {
				url : 'http://localhost:3040/',
				timeout : 200,
				retries : 0
			};
			scrapinode.createScraper(options,function(err,scraper){
				assert.instanceOf(err,Error);
				done();
			});
		});
	});
});

/**
 * Modules dependencies
 */

var mocha = require('mocha'),
	assert = require('chai').assert,
	libPath = process.env['SCRAPINODE_COV'] ? '../lib-cov' : '../lib',
	scrapinode = require( libPath + '/scrapinode'),
	ScrapinodeError = require(libPath + '/error/scrapinode-error'),
	express = require('express'),
	filed = require('filed');

// Web server
var app = express();

app.get('/',function(req,res){
	setTimeout(function(){
		req.pipe(filed(__dirname + '/resources/index.html')).pipe(res);
	},1000);
});

app.listen(3080);

// Test suite

describe('scrapinode#createScraper(options,callback)',function(){
	beforeEach(function(){
		scrapinode.clearRouter();
	});
	describe('when the options.timeout is defined and the timeout is reached',function(){
		it('should return an error Object in the callback as first argument',function(done){
			scrapinode.use('localhost:3040','title',function(window){
				return window.$('title').text();
			});
			var options = {
				url : 'http://localhost:3080/',
				timeout : 200,
				retries : 0
			};
			scrapinode.createScraper(options,function(err,scraper){
				assert.instanceOf(err,Error);
				done();
			});
		});
	});
});

Use another port number because this port is already bound by another test somewhere.


$(document).ready(function() {
  $('.toggle').click(function(event) {
    event.stopPropagation();
    event.preventDefault();

    $('.menu-wrapper').toggleClass('visible');
  });

  $(document).click(function() {
    $('.menu-wrapper').removeClass('visible');
  });
});

$(document).ready(function() {
  $('.menu-title .toggle').click(function(event) {
    event.stopPropagation();
    event.preventDefault();

    $('.menu-wrapper').toggleClass('visible');
  });

  $(document).click(function() {
    $('.menu-wrapper').removeClass('visible');
  });
});

Fix menu conflict with share


function simplestyle_factory(feature) {

    var sizes = {
      small: [20, 50],
      medium: [30, 70],
      large: [35, 90]
    };

    var fp = feature.properties || {};

    var size = fp['marker-size'] || 'medium';
    var symbol = (fp['marker-symbol']) ? '-' + fp['marker-symbol'] : '';
    var color = fp['marker-color'] || '7e7e7e';
    color = color.replace('#', '');

    var d = document.createElement('div');
    d.className = 'simplestyle-marker';
    var ds = d.style;
    ds.position = 'absolute';
    ds.width = sizes[size][0] + 'px';
    ds.height = sizes[size][1] + 'px';
    ds.marginTop = -(sizes[size][1] / 2) + 'px';
    ds.marginLeft = -(sizes[size][0] / 2) + 'px';

    ds.backgroundImage = 'url(http://a.tiles.mapbox.com/v3/marker/' +
      'pin-' + size[0] + symbol + '+' + color + '.png)';

    ds.textIndent = '-10000px';
    d.innerHTML = fp.title || '';

    return d;
}

function simplestyle_factory(feature) {

    var sizes = {
      small: [20, 50],
      medium: [30, 70],
      large: [35, 90]
    };

    var fp = feature.properties || {};

    var size = fp['marker-size'] || 'medium';
    var symbol = (fp['marker-symbol']) ? '-' + fp['marker-symbol'] : '';
    var color = fp['marker-color'] || '7e7e7e';
    color = color.replace('#', '');

    var d = document.createElement('div');
    d.className = 'simplestyle-marker';
    var ds = d.style;
    ds.position = 'absolute';
    ds.width = sizes[size][0] + 'px';
    ds.height = sizes[size][1] + 'px';
    ds.marginTop = -(sizes[size][1] / 2) + 'px';
    ds.marginLeft = -(sizes[size][0] / 2) + 'px';
    ds.cursor = 'pointer';

    ds.backgroundImage = 'url(http://a.tiles.mapbox.com/v3/marker/' +
      'pin-' + size[0] + symbol + '+' + color + '.png)';

    ds.textIndent = '-10000px';
    d.innerHTML = fp.title || '';

    return d;
}

Set cursor to hand when over simplestyle points.


/* eslint-disable object-shorthand */
module.exports = {
  // We need to make sure that the transpiler hit the linoleum source since we are
  // not building to npm or simliar.
  ignore: function(filename) {
    return ((/node_modules/.test(filename))
        && !(/linoleum(-[^/]*)?\/(electron|src|tasks|Gulpfile)/.test(filename)))
        || (/\$.*\$/.test(filename));
  },
  sourceMap: 'inline',
  auxiliaryCommentBefore: 'istanbul ignore start',
  auxiliaryCommentAfter: 'istanbul ignore end',
  presets: [
    require.resolve('babel-preset-react'),
    require.resolve('babel-preset-es2015'),
    require.resolve('babel-preset-stage-0')
  ]
};

/* eslint-disable object-shorthand */
module.exports = {
  // We need to make sure that the transpiler hit the linoleum source since we are
  // not building to npm or simliar.
  ignore: function(filename) {
    return ((/node_modules/.test(filename))
        && !(/linoleum(-[^/]*)?\/(electron|src|tasks|Gulpfile)/.test(filename)))
        || (/\$.*\$/.test(filename));
  },
  sourceMap: 'inline',
  presets: [
    require.resolve('babel-preset-react'),
    require.resolve('babel-preset-es2015'),
    require.resolve('babel-preset-stage-0')
  ]
};

Make aux comments opt in


import React from "react";
import PropTypes from "prop-types";
import SVG from "./assets/_sprite.svg";
import s4e from "svg4everybody";

export default class Sprite extends React.Component {
  static loaded = false;

  static propTypes = {
    id: PropTypes.string,
    viewBox: PropTypes.string
  };

  state = {
    ready: Sprite.loaded
  };

  req = typeof XMLHttpRequest !== "undefined" && new XMLHttpRequest();

  componentWillMount() {
    if (Sprite.loaded || !this.req) {
      return;
    }

    Sprite.loaded = true;

    this.req.open("GET", SVG, true);
    this.req.send();
    this.req.onload = () => {
      const div = document.createElement("div");
      div.innerHTML = this.req.responseText;
      div.style.display = "none";
      document.body.insertBefore(div, document.body.childNodes[0]);
      this.setState({ ready: true }, s4e);
    };
  }

  render() {
    const { viewBox, id } = this.props;
    const { ready } = this.state;

    if (!ready) {
      return <svg/>;
    }

    return (
      <svg role="img" viewBox={viewBox} className="svg-sprite">
        <use xlinkHref={"#" + id} />
      </svg>
    );
  }
}

import React from "react";
import PropTypes from "prop-types";
import SVG from "./assets/_sprite.svg";
import s4e from "svg4everybody";

export default class Sprite extends React.Component {
  static loaded = false;

  static propTypes = {
    id: PropTypes.string,
    viewBox: PropTypes.string
  };

  state = {
    ready: Sprite.loaded
  };

  req = typeof XMLHttpRequest !== "undefined" && new XMLHttpRequest();

  componentWillMount() {
    if (Sprite.loaded || !this.req) {
      return;
    }

    Sprite.loaded = true;

    this.req.open("GET", SVG, true);
    this.req.send();
    this.req.onload = () => {
      const div = document.createElement("div");
      div.innerHTML = this.req.responseText;
      div.style.display = "none";
      document.body.insertBefore(div, document.body.childNodes[0]);
      this.setState({ ready: true }, s4e);
    };
  }

  render() {
    const { viewBox, id } = this.props;
    const { ready } = this.state;

    if (!ready) {
      return <svg viewBox={viewBox} className={"svg-sprite"}/>;
    }

    return (
      <svg role="img" viewBox={viewBox} className="svg-sprite">
        <use xlinkHref={"#" + id} />
      </svg>
    );
  }
}

Add viewbox to empty svg


var AllItemsFolder = Backbone.Model.extend({
	initialize: function() {
		this.items = new ItemCollection();
		this.items.url = '/item';

		this.set({
			name: LANG.all_items_folder,
			route: 'item',
			iconclass: 'icon-asterisk'
		});
	},
	getNextInList: function() {
		return folders.first();
    },
	getPreviousInList: function() {
		return this;
    }
});

var AllItemsFolder = Backbone.Model.extend({
	initialize: function() {
		this.items = new ItemCollection();
		this.items.url = '/item';
		this.listenTo(this.items, 'itemRead', this.itemRead);
		this.listenTo(this.items, 'itemUnread', this.itemUnread);

		this.set({
			name: LANG.all_items_folder,
			route: 'item',
			iconclass: 'icon-asterisk'
		});
	},
	getNextInList: function() {
		return folders.first();
    },
	getPreviousInList: function() {
		return this;
    },
	itemRead: function(feed_id) {
		folders.each(function(folder) {
			var feed = folder.feeds.get(feed_id);
			if(feed) {
				feed.decrementReadCount();
			}
		});
	},
	itemUnread: function(feed_id) {
		folders.each(function(folder) {
			var feed = folder.feeds.get(feed_id);
			if(feed) {
				feed.incrementReadCount();
			}
		});
	}
});

Add code to update the read count when reading items in the "all items" folder


'use strict';

import Sprite from './sprite';

export default class SpriteSheet {
	constructor(texture, width, height) {
		this.texture = texture;
		this.width = width;
		this.height = height;
		this.spriteCoordinates = {};
	}
	register(name, x, y, width, height) {
		this.spriteCoordinates[name] = {
			x: x,
			y: y,
			width: width,
			height: height
		};
	}
	registerMany(map) {
		for (var name in map) {
			var coords = map[name];
			this.register(name, coords[0], coords[1], coords[2], coords[3]);
		}
	}
	createSprite(name) {
		let info = this.spriteCoordinates[name];
		let sprite = new Sprite(info.width, info.height, this.texture);
		sprite.setTextureCoordinates(
			info.x / this.width,
			info.y / this.height,
			(info.x + info.width) / this.width,
			(info.y + info.height) / this.height
		);
		return sprite;
	}
}

'use strict';

import Sprite from './sprite';

export default class SpriteSheet {
	constructor(texture, width, height) {
		this.texture = texture;
		this.width = width;
		this.height = height;
		this.spriteCoordinates = {};
	}
	register(name, x, y, width, height) {
		this.spriteCoordinates[name] = {
			x: x,
			y: y,
			width: width,
			height: height
		};
	}
	registerMany(map) {
		for (var name in map) {
			var coords = map[name];
			this.register(name, coords[0], coords[1], coords[2], coords[3]);
		}
	}
	createSprite(name) {
		let info = this.spriteCoordinates[name];
		let sprite = new Sprite(info.width, info.height, this.texture);
		setSpriteTextureFromCoordinates(sprite, info);
		return sprite;
	}
	setSpriteTexture(sprite, name) {
		let info = this.spriteCoordinates[name];
		setSpriteTextureFromCoordinates(sprite, info);
	}
	setSpriteTextureFromCoordinates(sprite, info) {
		sprite.setTextureCoordinates(
			info.x / this.width,
			info.y / this.height,
			(info.x + info.width) / this.width,
			(info.y + info.height) / this.height
		);
	}
}

Add setSpriteTexture to the SpriteSheet class


'use strict'

module.exports =
class StateStore {
  constructor () {
    this.dbPromise = new Promise((resolve, reject) => {
      let dbOpenRequest = indexedDB.open('AtomEnvironments', 1)
      dbOpenRequest.onupgradeneeded = (event) => {
        let db = event.target.result
        db.createObjectStore('states')
        resolve(db)
      }
      dbOpenRequest.onsuccess = () => {
        resolve(dbOpenRequest.result)
      }
      dbOpenRequest.onerror = reject
    })
  }

  save (key, value) {
    return this.dbPromise.then(db => {
      return new Promise((resolve, reject) => {
        var request = db.transaction(['states'], 'readwrite')
          .objectStore('states')
          .put(value, key)

        request.onsuccess = resolve
        request.onerror = reject
      })
    })
  }

  load (key) {
    return this.dbPromise.then(db => {
      return new Promise((resolve, reject) => {
        var request = db.transaction(['states'])
          .objectStore('states')
          .get(key)

        request.onsuccess = (event) => resolve(event.target.result)
        request.onerror = (event) => reject(event)
      })
    })
  }
}

'use strict'

module.exports =
class StateStore {
  constructor () {
    this.dbPromise = new Promise((resolve, reject) => {
      let dbOpenRequest = indexedDB.open('AtomEnvironments', 1)
      dbOpenRequest.onupgradeneeded = (event) => {
        let db = event.target.result
        db.createObjectStore('states')
        resolve(db)
      }
      dbOpenRequest.onsuccess = () => {
        resolve(dbOpenRequest.result)
      }
      dbOpenRequest.onerror = reject
    })
  }

  save (key, value) {
    return this.dbPromise.then(db => {
      value.storedAt = new Date().toString()
      return new Promise((resolve, reject) => {
        var request = db.transaction(['states'], 'readwrite')
          .objectStore('states')
          .put(value, key)

        request.onsuccess = resolve
        request.onerror = reject
      })
    })
  }

  load (key) {
    return this.dbPromise.then(db => {
      return new Promise((resolve, reject) => {
        var request = db.transaction(['states'])
          .objectStore('states')
          .get(key)

        request.onsuccess = (event) => resolve(event.target.result)
        request.onerror = (event) => reject(event)
      })
    })
  }
}

Add storedAt date for serialized environment state


module.exports = {
  normalizeEntityName: function() {
    // this prevents an error when the entityName is
    // not specified (since that doesn't actually matter
    // to us
  },

  afterInstall: function() {
    var addonContext = this;

    return this.addBowerPackageToProject('qunit', '~1.16.0')
      .then(function() {
        return addonContext.addBowerPackageToProject('stefanpenner/ember-cli-shims', '0.0.3');
      })
      .then(function() {
        return addonContext.addBowerPackageToProject('ember-qunit-notifications', '0.0.4');
      })
      .then(function() {
        return addonContext.addBowerPackageToProject('ember-qunit', '0.1.8');
      });
  }
};

module.exports = {
  normalizeEntityName: function() {
    // this prevents an error when the entityName is
    // not specified (since that doesn't actually matter
    // to us
  },

  afterInstall: function() {
    var addonContext = this;

    return this.addBowerPackageToProject('qunit', '~1.17.0')
      .then(function() {
        return addonContext.addBowerPackageToProject('stefanpenner/ember-cli-shims', '0.0.3');
      })
      .then(function() {
        return addonContext.addBowerPackageToProject('ember-qunit-notifications', '0.0.5');
      })
      .then(function() {
        return addonContext.addBowerPackageToProject('ember-qunit', '0.1.8');
      });
  }
};

Update blueprint installed package versions.


'use strict'

module.exports = function (animations) {
  let index = 0
  let running = false

  const api = {
    set index (i) {
      console.log(animations[i].name)
      index = i
    },

    next: () => { api.index = ++index % animations.length },
    previous: () => { api.index = index > 0 ? index - 1 : animations.length - 1 },

    select: name => {
      for (let i = 0; i < animations.length; i++) {
        if (animations[i].name.toUpperCase() === name.toUpperCase()) {
          api.index = i
          break
        }
      }
    },

    get running () { return running },
    resume: () => { running = true },
    pause: () => { running = false },

    update: dt => {
      if (running) animations[index].update(dt)
    }
  }

  animations = animations.map(Animation => new Animation(api))

  return api
}

'use strict'

module.exports = function (animations) {
  let index = 0
  let running = false

  const api = {
    set index (i) {
      console.log(animations[i].name)
      index = i
    },

    next: () => { api.index = ++index % animations.length },
    previous: () => { api.index = index > 0 ? index - 1 : animations.length - 1 },

    select: name => {
      for (let i = 0; i < animations.length; i++) {
        if (animations[i].name.toUpperCase() === name.toUpperCase()) {
          api.index = i
          return true
        }
      }

      throw ReferenceError(`Cannot find animation '${name}'`)
    },

    get running () { return running },
    resume: () => { running = true },
    pause: () => { running = false },

    update: dt => {
      if (running) animations[index].update(dt)
    }
  }

  animations = animations.map(Animation => new Animation(api))

  return api
}

Throw a ReferenceError on animations.select(unregisterName)


(function (window, document, $) {
  var app = window.devsite;

  app.pages.signup = function () {
    var $btn = $('.notify-btn');

    $btn.on('click', function() {
      $btn.addClass('disabled');
      $.post('/api/developer-plus/coming-soon', {
        email: $('input[name="email"]').val()
      }, function(data, status, xhr) {
        $btn.addClass('btn-success')
          .removeClass('btn-primary')
          .text('Success!');

        setTimeout(function () {
          $('input[name="email"]').val('');
          $btn.addClass('btn-primary')
            .removeClass('btn-success disabled')
            .text('Notify Me!');
        }, 2500);
      })
        .fail(function(e) {
          $btn.addClass('btn-danger')
            .removeClass('btn-primary')
            .text('Error');

          setTimeout(function() {
            $btn.addClass('btn-primary')
              .removeClass('btn-danger disabled')
              .text('Notify Me!');
          }, 2500);
        });
    });
  };
}(window, document, jQuery));

(function (window, document, $) {
  var app = window.devsite;

  app.pages.signup = function () {
    var $btn = $('.notify-btn');

    $btn.on('click', function() {
      $btn.addClass('disabled');
      $.post('/api/developer-plus/coming-soon', {
        email: $('input[name="email"]').val().trim()
      }, function(data, status, xhr) {
        $btn.addClass('btn-success')
          .removeClass('btn-primary')
          .text('Success!');

        setTimeout(function () {
          $('input[name="email"]').val('');
          $btn.addClass('btn-primary')
            .removeClass('btn-success disabled')
            .text('Notify Me!');
        }, 2500);
      })
        .fail(function(e) {
          $btn.addClass('btn-danger')
            .removeClass('btn-primary')
            .text('Error');

          setTimeout(function() {
            $btn.addClass('btn-primary')
              .removeClass('btn-danger disabled')
              .text('Notify Me!');
          }, 2500);
        });
    });
  };
}(window, document, jQuery));

Trim email address before submitting to API.


// Requires
var _ = require('underscore');

var express = require('express');

function setup(options, imports, register) {
    // Import
    var app = imports.server.app;
    var workspace = imports.workspace;

    // Apply middlewares
    app.use(express.cookieParser());
    app.use(express.cookieSession({
        key: ['sess', workspace.id].join('.'),
        secret: workspace.secret,
    }));
    // Get User and set it to res object
    app.use(function getUser(req, res, next) {
        var uid = req.session.userId;
        if(uid) {
            return workspace.getUser(uid)
            .then(function(user) {
                // Set user
                res.user = user;
                next();
            })
            .fail(function(err) {
                res.user = null;
                next();
            });
        }
        return next();

    });
    // Block queries for unAuthenticated users
    var AUTHORIZED_PATHS = ['/', '/auth/join', '/users/list'];
    app.use(function filterAuth(req, res, next) {
        if(_.contains(AUTHORIZED_PATHS, req.path) || res.user) {
            return next();
        }
        // Unauthorized
        return res.send(403, {
            ok: false,
            data: {},
            error: "Could not run RPC request because user has not authenticated",
            method: req.path,
        });
    });

    // Register
    register(null, {});
}

// Exports
module.exports = setup;

// Requires
var _ = require('underscore');

var express = require('express');

function setup(options, imports, register) {
    // Import
    var app = imports.server.app;
    var workspace = imports.workspace;

    // Apply middlewares
    app.use(express.cookieParser());
    app.use(express.cookieSession({
        key: ['sess', workspace.id].join('.'),
        secret: workspace.secret,
    }));
    // Get User and set it to res object
    app.use(function getUser(req, res, next) {
        // Pause request stream
        req.pause();

        var uid = req.session.userId;
        if(uid) {
            return workspace.getUser(uid)
            .then(function(user) {
                // Set user
                res.user = user;
                next();
            })
            .fail(function(err) {
                res.user = null;
                next();
            });
        }
        return next();

    });
    // Block queries for unAuthenticated users
    var AUTHORIZED_PATHS = ['/', '/auth/join', '/users/list'];
    app.use(function filterAuth(req, res, next) {
        // Resume request now
        // So our handlers can use it as a stream
        req.resume();

        if(_.contains(AUTHORIZED_PATHS, req.path) || res.user) {
            return next();
        }
        // Unauthorized
        return res.send(403, {
            ok: false,
            data: {},
            error: "Could not run RPC request because user has not authenticated",
            method: req.path,
        });
    });

    // Register
    register(null, {});
}

// Exports
module.exports = setup;

Fix req stream race condition due to middleware


export default {
  pan: {
    id: 'pan',
    name: 'Pan',
    title: 'Pan Tool'
  },
  text: {
    id: 'text',
    name: 'Text',
    title: 'Text Label Tool',
    options: {
      text: 'Text here'
    }
  },
  marker: {
    id: 'marker',
    dataId: 'Point',
    name: 'Marker',
    title: 'Marker Tool'
  },
  polyline: {
    id: 'polyline',
    dataId: 'LineString',
    name: 'PolyLine',
    title: 'Multi segmented line tool'
  },
  polygon: {
    id: 'polygon',
    dataId: 'Polygon',
    name: 'Polygon',
    title: 'Polygon Tool'
  },
  circle: {
    id: 'circle',
    dmId: google.maps.drawing.OverlayType.CIRCLE,
    name: 'Circle',
    title: 'Circle Tool'
  },
  rectangle: {
    id: 'rectangle',
    dmId: google.maps.drawing.OverlayType.RECTANGLE,
    name: 'Rectangle',
    title: 'Rectangle Tool'
  }
};

export default {
  pan: {
    id: 'pan',
    name: 'Pan',
    title: 'Pan Tool'
  },
  text: {
    id: 'text',
    name: 'Text',
    title: 'Text Label Tool',
    options: {
      text: 'Text here'
    }
  },
  marker: {
    id: 'marker',
    dataId: 'Point',
    name: 'Marker',
    title: 'Marker Tool'
  },
  polyline: {
    id: 'polyline',
    dataId: 'LineString',
    name: 'Line',
    title: 'Multi segmented line tool'
  },
  polygon: {
    id: 'polygon',
    dataId: 'Polygon',
    name: 'Polygon',
    title: 'Polygon Tool'
  },
  circle: {
    id: 'circle',
    dmId: google.maps.drawing.OverlayType.CIRCLE,
    name: 'Circle',
    title: 'Circle Tool'
  },
  rectangle: {
    id: 'rectangle',
    dmId: google.maps.drawing.OverlayType.RECTANGLE,
    name: 'Rectangle',
    title: 'Rectangle Tool'
  }
};

Rename polyline, it was too technical


import _ from 'underscore';
import { HTTP } from 'meteor/http'

/**
 *  SAP B1 Integration Methods
 */
Meteor.methods({
    'sapB1integration/testConnectionToWindowsService': (sapServerIpAddress) => {
        if (!this.userId && Core.hasPayrollAccess(this.userId)) {
            throw new Meteor.Error(401, "Unauthorized");
        }
        let userId = Meteor.userId();
        this.unblock()

        let connectionStatusResponse = Meteor.http.call("GET", `${sapServerIpAddress}:9080/api/payrun`);
        return connectionStatusResponse
    },
    'sapB1integration/postPayrunResults': (payRunResult, periodMonth, periodYear, sapServerIpAddress) => {
        if (!this.userId && Core.hasPayrollAccess(this.userId)) {
            throw new Meteor.Error(401, "Unauthorized");
        }
        let userId = Meteor.userId();
        this.unblock()

        // if(sapServerIpAddress.startsWith("http://")) {
        //
        // }
        //--
        let postData = JSON.stringify(payRunResult)
        postData = "=" + postData;       // A quirk of the C# REST API on the windows service

        // HTTP.call('POST', `${sapServerIpAddress}:9080/api/payrun`, {data: postData}, () => (error, result) {
        //     if (!error) {
        //         console.log(`Payrun batch result: \n${JSON.stringify(result)}`)
        //
        //     }
        // });
    }
});

import _ from 'underscore';
import { HTTP } from 'meteor/http'

/**
 *  SAP B1 Integration Methods
 */
Meteor.methods({
    'sapB1integration/testConnectionToWindowsService': (sapServerIpAddress) => {
        if (!this.userId && Core.hasPayrollAccess(this.userId)) {
            throw new Meteor.Error(401, "Unauthorized");
        }
        let userId = Meteor.userId();
        this.unblock()

        let connectionStatusResponse = Meteor.http.call("GET", `${sapServerIpAddress}:9080/api/payrun`);
        return connectionStatusResponse
    },
    'sapB1integration/postPayrunResults': (payRunResult, periodMonth, periodYear, sapServerIpAddress) => {
        if (!this.userId && Core.hasPayrollAccess(this.userId)) {
            throw new Meteor.Error(401, "Unauthorized");
        }
        let userId = Meteor.userId();
        this.unblock()

        // if(sapServerIpAddress.startsWith("http://")) {
        //
        // }
        //--
        let postData = JSON.stringify(payRunResult)
        postData = "=" + postData;       // A quirk of the C# REST API on the windows service

        HTTP.call('POST', `${sapServerIpAddress}:9080/api/payrun`, {data: postData}, (error, result) => {
            if (!error) {
                console.log(`Payrun batch result: \n${JSON.stringify(result)}`)

            }
        });
    }
});

Fix typo bug in sap b1 http POST to sap


'use strict';

module.exports = {
  root: true,
  parser: 'babel-eslint',
  parserOptions: {
    ecmaVersion: 2018,
    sourceType: 'module',
    ecmaFeatures: {
      legacyDecorators: true
    }
  },
  plugins: [
    'ember'
  ],
  extends: [
    'eslint:recommended',
    'plugin:ember/recommended'
  ],
  env: {
    browser: true
  },
  rules: {
    "no-control-regex": "off",
    "no-console": ["error", { allow: ["warn", "debug", "error"] }],
    "ember/no-observers": "warn",
    "ember/no-jquery": "error",
    "ember/no-string-prototype-extensions": "warn",
    "ember/no-controller-access-in-routes": "warn",
    "ember/no-classic-classes": "warn",
    "ember/no-computed-properties-in-native-classes": "warn",
    "ember/require-tagless-components": "warn",
    "ember/no-assignment-of-untracked-properties-used-in-tracking-contexts": "warn",
    "ember/no-side-effects": "warn"
  },
  overrides: [
    // node files
    {
      files: [
        '.eslintrc.js',
        '.template-lintrc.js',
        'ember-cli-build.js',
        'testem.js',
        'config/**/*.js',
        'lib/*/index.js',
        'server/**/*.js'
      ],
      parserOptions: {
        sourceType: 'script'
      },
      env: {
        browser: false,
        node: true
      },
      rules: {
        // this can be removed once the following is fixed
        // https://github.com/mysticatea/eslint-plugin-node/issues/77
        'node/no-unpublished-require': 'off'
      }
    }
  ]
};

'use strict';

module.exports = {
  root: true,
  parser: 'babel-eslint',
  parserOptions: {
    ecmaVersion: 2018,
    sourceType: 'module',
    ecmaFeatures: {
      legacyDecorators: true
    }
  },
  plugins: [
    'ember'
  ],
  extends: [
    'eslint:recommended',
    'plugin:ember/recommended'
  ],
  env: {
    browser: true
  },
  rules: {
    "no-control-regex": "off",
    "no-console": ["error", { allow: ["warn", "debug", "error"] }]
  },
  overrides: [
    // node files
    {
      files: [
        '.eslintrc.js',
        '.template-lintrc.js',
        'ember-cli-build.js',
        'testem.js',
        'config/**/*.js',
        'lib/*/index.js',
        'server/**/*.js'
      ],
      parserOptions: {
        sourceType: 'script'
      },
      env: {
        browser: false,
        node: true
      },
      rules: {
        // this can be removed once the following is fixed
        // https://github.com/mysticatea/eslint-plugin-node/issues/77
        'node/no-unpublished-require': 'off'
      }
    }
  ]
};

Remove custom eslint configs for ember rules


module.exports = {
  extends: "airbnb",
  globals: {
    Urls: true
  },
  env: {
    browser: true
  },
  rules: {
    "react/jsx-filename-extension": [
      1,
      {
        extensions: [
          ".js",
          ".jsx"
        ]
      }
    ]
  },
  settings: {
    "import/resolver": {
      webpack: {
        config: "webpack.config.js"
      }
    }
  }
}

module.exports = {
  extends: "airbnb",
  globals: {
    Urls: true
  },
  env: {
    browser: true,
    jest: true
  },
  rules: {
    "react/jsx-filename-extension": [
      1,
      {
        extensions: [
          ".js",
          ".jsx"
        ]
      }
    ]
  },
  settings: {
    "import/resolver": {
      webpack: {
        config: "webpack.config.js"
      }
    }
  }
}

Add jest to eslint env


describe("Alert unsupported browser: ", function(){
  beforeEach(function(){
    loadFixtures('browser-unsupported-bar_fixture.html');
  });

  describe("When userAgent is MSIE", function(){
    it("should add .ls-browser-unsupported class on <html>", function(){
      $.cookie = function (arg) {
        return false;
      };

      var __originalLowerCase = String.prototype.toLowerCase;
      String.prototype.toLowerCase = function () {
        return "mozilla/4.0 (compatible; msie 8.0; windows nt 6.0; trident/4.0)";
      }

      locastyle.browserUnsupportedBar.init();
      expect($('html').hasClass('ls-browser-unsupported')).toEqual(true);

      String.prototype.toLowerCase = __originalLowerCase;
      $(".ls-alert-blocker").remove();
    })
  });

});

describe("Alert unsupported browser: ", function(){
  beforeEach(function(){
    loadFixtures('browser-unsupported-bar_fixture.html');
  });

  describe("When userAgent is MSIE", function(){
    it("should add .ls-browser-unsupported class on ", function(){
      $.cookie = function (arg) {
        return false;
      };

      var __originalLowerCase = String.prototype.toLowerCase;
      String.prototype.toLowerCase = function () {
        return "mozilla/4.0 (compatible; msie 8.0; windows nt 6.0; trident/4.0)";
      }

      locastyle.browserUnsupportedBar.init();
      expect($('html').hasClass('ls-browser-unsupported')).toEqual(true);

      String.prototype.toLowerCase = __originalLowerCase;
      $(".ls-alert-blocker").remove();
    })
  });

  describe("When click to close", function() {
    it("should remove .ls-browser-unsupported class on ", function() {
      $('body').prepend('
&timesAtualize seu navegador para visualizar corretamente este painel! Baixe a versão mais recente: Firefox, Chrome, Internet Explorer ou Safari
');

      locastyle.browserUnsupportedBar.init();
      $('.ls-alert-blocker .ls-dismiss[data-ls-module=dismiss]').trigger('click');

      expect($('html').hasClass('ls-browser-unsupported')).toEqual(false);
      $(".ls-alert-blocker").remove();
    })
  })

});

Create test when click to close alert browser unsupported.


payex.hostedView.consumer({
    container: 'checkout',
    culture: 'nb-NO',
    onConsumerIdentified: function(consumerIdentifiedEvent) {
        var request = new XMLHttpRequest();
        request.addEventListener('load', function() {
            response = JSON.parse(this.responseText);

            var script = document.createElement('script');
            script.setAttribute('src', response.operation.href);
            script.onload = function() {
                payex.hostedView.paymentMenu({
                    container: 'checkout',
                    culture: 'nb-NO',
                    onPaymentCompleted: function(e) {

                    }
                }).open();
            };
            var head = document.getElementsByTagName('head')[0];
            head.appendChild(script);
        });
        request.open('POST', window.location.href, true);
        request.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
        request.send(JSON.stringify(consumerIdentifiedEvent));
    }
}).open();

payex.hostedView.consumer({
    container: 'checkout',
    culture: 'nb-NO',
    onConsumerIdentified: function (consumerIdentifiedEvent) {
        var request = new XMLHttpRequest();
        request.addEventListener('load', function () {
            response = JSON.parse(this.responseText);

            var script = document.createElement('script');
            script.setAttribute('src', response.operation.href);
            script.onload = function () {
                var iframe = document.getElementsByTagName('iframe')[0];
                iframe.parentElement.removeChild(iframe);

                payex.hostedView.paymentMenu({
                    container: 'checkout',
                    culture: 'nb-NO',
                    onPaymentCompleted: function (paymentCompletedEvent) {
                    }
                }).open();
            };
            var head = document.getElementsByTagName('head')[0];
            head.appendChild(script);
        });
        request.open('POST', window.location.href, true);
        request.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
        request.send(JSON.stringify(consumerIdentifiedEvent));
    }
}).open();

Remove consumer hosted view before initializing payment menu


(function() {
    window.stop();
    document.documentElement.innerHTML = null;

    const IS_LOCAL = false,
        URL_REMOTE = "https://rawgit.com/ultratype/UltraTypeBot/master/OUT/OUT.js",
        URL_OUT = IS_LOCAL ? chrome.extension.getURL('OUT/OUT.js') : URL_REMOTE,
        SCRIPT_OUT = "<script src='" + URL_OUT + "'></script>\n";
    let loader = new XMLHttpRequest();
    loader.open("GET", location.href, true);
    loader.onreadystatechange = function() {
        if (loader.readyState == 4) {
            const doc = `${SCRIPT_OUT}${this.responseText}`;
            document.open();
            document.write(doc);
            document.close();
        }
    }
    loader.send();
})();

(function() {
    window.stop();
    document.documentElement.innerHTML = null;

    const IS_LOCAL = !!(localStorage["ultratypedev"]),
        URL_REMOTE = "https://rawgit.com/ultratype/UltraTypeBot/master/OUT/OUT.js",
        URL_OUT = IS_LOCAL ? chrome.extension.getURL('OUT/OUT.js') : URL_REMOTE,
        SCRIPT_OUT = "<script src='" + URL_OUT + "'></script>\n";
    let loader = new XMLHttpRequest();
    loader.open("GET", location.href, true);
    loader.onreadystatechange = function() {
        if (loader.readyState == 4) {
            const doc = `${SCRIPT_OUT}${this.responseText}`;
            document.open();
            document.write(doc);
            document.close();
        }
    }
    loader.send();
})();

Load locally based on localStorage value


import { PureComponent } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';

import actions from './geolocation-provider-actions';
import reducers, { initialState } from './geolocation-provider-reducers';

class GeolocationProvider extends PureComponent {
  componentDidMount() {
    const { requestLocation, locationProvider } = this.props;
    requestLocation(locationProvider);
  }

  render() {
    return null;
  }
}

GeolocationProvider.propTypes = {
  locationProvider: PropTypes.string.isRequired,
  requestLocation: PropTypes.func.isRequired
};

GeolocationProvider.defaultProps = {
  locationProvider: 'https://freegeoip.net/json/'
};

export { actions, reducers, initialState };
export default connect(null, actions)(GeolocationProvider);

import { PureComponent } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';

import actions from './geolocation-provider-actions';
import reducers, { initialState } from './geolocation-provider-reducers';

const { IPSTACK_GEOLOCATION_API_KEY } = process.env;

class GeolocationProvider extends PureComponent {
  componentDidMount() {
    const { requestLocation, locationProvider } = this.props;
    requestLocation(locationProvider);
  }

  render() {
    return null;
  }
}

GeolocationProvider.propTypes = {
  locationProvider: PropTypes.string.isRequired,
  requestLocation: PropTypes.func.isRequired
};

GeolocationProvider.defaultProps = {
  locationProvider: `http://api.ipstack.com/check?access_key=${IPSTACK_GEOLOCATION_API_KEY}&format=1`
};

export { actions, reducers, initialState };
export default connect(null, actions)(GeolocationProvider);

Update geolocation provider to follow new API


/**
 * @TODO "Donate now" instead of Contribute.
 * @TODO "Join Now"
 */
(function($) {
  var alvCustom = {
    init: function() {
      $('form#Main.CRM_Contribute_Form_Contribution_Main').on('submit', function(ev) {
        // On contribute form submit, do a JS pre-check for empty
        // fields and simulate a failed validation without submission.
        var firstEl = true;
        $('input.required, input.creditcard, input.cvv2', this).each(function(i, el) {
          if ($(this).val() == '') {
            $(this).addClass('error');
            // $(this).css({backgroundColor: '#fcc'});
            // $(this).css({border: '4px solid red'});
            $(this).after('This field is required');
            $(this).closest('.crm-section').find('label').addClass('crm-error');
            if (firstEl) {
              $('html, body').animate({scrollTop: $(this).offset().top - 50}, 1000);
              ev.preventDefault();
              firstEl = false;
            }
          }
        });
      });
    }
  };

  // Bootstrap on page load.
  $('body').ready(function() {
    alvCustom.init();
  });
})(jQuery);

/**
 * @TODO "Donate now" instead of Contribute.
 * @TODO "Join Now"
 */
(function($) {
  var alvCustom = {
    init: function() {
      $('form#Main.CRM_Contribute_Form_Contribution_Main').on('submit', function(ev) {
        // On contribute form submit, do a JS pre-check for empty
        // fields and simulate a failed validation without submission.
        var firstEl = true;
        $('input.required, input.creditcard, input#cvv2', this).each(function(i, el) {
          if ($(this).val() == '') {
            $(this).addClass('error');
            $(this).after('This field is required');
            $(this).closest('.crm-section').find('label').addClass('crm-error');
            if (firstEl) {
              $('html, body').animate({scrollTop: $(this).offset().top - 50}, 1000);
              ev.preventDefault();
              firstEl = false;
            }
          }
        });
      });
    }
  };

  // Bootstrap on page load.
  $('body').ready(function() {
    alvCustom.init();
  });
})(jQuery);

Correct selector for CVV field


Spree.ready(function() {
  // Fix sortable helper
  var fixHelper = function(e, ui) {
      ui.children().each(function() {
          $(this).width($(this).width());
      });
      return ui;
  };

  var td_count = $(this).find('tbody tr:first-child td').length
  $('table.sortable tbody').sortable({
    handle: '.handle',
    helper: fixHelper,
    placeholder: 'ui-sortable-placeholder',
    update: function(event, ui) {
      $("#progress").show();
      var tableEl = $(ui.item).closest("table.sortable")
      var positions = {};
      $.each(tableEl.find('tbody tr'), function(position, obj){
        var idAttr = $(obj).prop('id');
        if (idAttr) {
          var objId = idAttr.split('_').slice(-1);
          if (!isNaN(objId)) {
            positions['positions['+objId+']'] = position+1;
          }
        }
      });
      Spree.ajax({
        type: 'POST',
        dataType: 'script',
        url: tableEl.data("sortable-link"),
        data: positions,
        success: function(data){ $("#progress").hide(); }
      });
    },
    start: function (event, ui) {
      // Set correct height for placehoder (from dragged tr)
      ui.placeholder.height(ui.item.height())
      // Fix placeholder content to make it correct width
      ui.placeholder.html("		")
    },
    stop: function (event, ui) {
      var tableEl = $(ui.item).closest("table.sortable")
      // Fix odd/even classes after reorder
      tableEl.find("tr:even").removeClass("odd even").addClass("even");
      tableEl.find("tr:odd").removeClass("odd even").addClass("odd");
    }
  });
});

//= require solidus_admin/Sortable

Spree.ready(function() {
  var sortable_tables = document.querySelectorAll('table.sortable');

  _.each(sortable_tables, function(table) {
    var url = table.getAttribute('data-sortable-link');
    var tbody = table.querySelector('tbody');
    var sortable = Sortable.create(tbody,{
      handle: ".handle",
      onEnd: function(e) {
        var positions = {};
        _.each(e.to.querySelectorAll('tr'), function(el, index) {
          var idAttr = el.id;
          if (idAttr) {
            var objId = idAttr.split('_').slice(-1);
            positions['positions['+objId+']'] = index + 1;
          }
        });
        Spree.ajax({
          type: 'POST',
          dataType: 'script',
          url: url,
          data: positions,
        });
      }
    });
  });
});

Convert sortable tables to Sortable.js


import App from '../../src/app';

describe.only('App StateModel', function() {
  describe('when passing StateModel on Toolkit.App definition', function() {
    before(function() {
      this.MyModel = Backbone.Model.extend({
        foo: 'bar'
      });

      this.myApp = new App({
        StateModel: this.MyModel
      });
    });

    it('should successfully attach StateModel definition', function() {
      expect(this.myApp.StateModel).to.eq(this.MyModel);
    });
  });
});

import App from '../../src/app';

describe('App StateModel', function() {
  describe('when passing StateModel on Toolkit.App definition', function() {
    before(function() {
      this.MyModel = Backbone.Model.extend({
        foo: 'bar'
      });

      this.myApp = new App({
        StateModel: this.MyModel
      });
    });

    it('should successfully attach StateModel definition', function() {
      expect(this.myApp.StateModel).to.eq(this.MyModel);
    });
  });
});

Remove `only` from unit tests


var cornerstoneTools = (function ($, cornerstone, cornerstoneTools) {

    "use strict";

    if(cornerstoneTools === undefined) {
        cornerstoneTools = {};
    }
    if(cornerstoneTools.referenceLines === undefined) {
        cornerstoneTools.referenceLines = {};
    }

    // projects a patient point to an image point
    function projectPatientPointToImagePlane(patientPoint, imagePlane)
    {
        var point = patientPoint.clone().sub(imagePlane.imagePositionPatient);
        var x = imagePlane.columnCosines.dot(point) / imagePlane.columnPixelSpacing;
        var y = imagePlane.rowCosines.dot(point) / imagePlane.rowPixelSpacing;
        var imagePoint = {x: x, y: y};
        return imagePoint;
    }

    // projects an image point to a patient point
    function imagePointToPatientPoint(imagePoint, imagePlane)
    {
        var x = imagePlane.columnCosines.clone().multiplyScalar(imagePoint.x);
        x.multiplyScalar(imagePlane.columnPixelSpacing);
        var y = imagePlane.rowCosines.clone().multiplyScalar(imagePoint.y);
        y.multiplyScalar(imagePlane.rowPixelSpacing);
        var patientPoint = x.add(y);
        patientPoint.add(imagePlane.imagePositionPatient);
        return patientPoint;
    }

    // module/private exports
    cornerstoneTools.projectPatientPointToImagePlane = projectPatientPointToImagePlane;
    cornerstoneTools.imagePointToPatientPoint = imagePointToPatientPoint;

    return cornerstoneTools;
}($, cornerstone, cornerstoneTools));

var cornerstoneTools = (function ($, cornerstone, cornerstoneTools) {

    "use strict";

    if(cornerstoneTools === undefined) {
        cornerstoneTools = {};
    }
    if(cornerstoneTools.referenceLines === undefined) {
        cornerstoneTools.referenceLines = {};
    }

    // projects a patient point to an image point
    function projectPatientPointToImagePlane(patientPoint, imagePlane)
    {
        var point = patientPoint.clone().sub(imagePlane.imagePositionPatient);
        var x = imagePlane.rowCosines.dot(point) / imagePlane.columnPixelSpacing;
        var y = imagePlane.columnCosines.dot(point) / imagePlane.rowPixelSpacing;
        var imagePoint = {x: x, y: y};
        return imagePoint;
    }

    // projects an image point to a patient point
    function imagePointToPatientPoint(imagePoint, imagePlane)
    {
        var x = imagePlane.rowCosines.clone().multiplyScalar(imagePoint.x);
        x.multiplyScalar(imagePlane.columnPixelSpacing);
        var y = imagePlane.columnCosines.clone().multiplyScalar(imagePoint.y);
        y.multiplyScalar(imagePlane.rowPixelSpacing);
        var patientPoint = x.add(y);
        patientPoint.add(imagePlane.imagePositionPatient);
        return patientPoint;
    }

    // module/private exports
    cornerstoneTools.projectPatientPointToImagePlane = projectPatientPointToImagePlane;
    cornerstoneTools.imagePointToPatientPoint = imagePointToPatientPoint;

    return cornerstoneTools;
}($, cornerstone, cornerstoneTools));

Revert my previous 'fix' for point projection


// Local web server running on port 9001 with livereload functionality
module.exports = function(grunt) {
    grunt.config('browserSync', {
        options: {
            watchTask: true,
            server: {
                baseDir: './'
            }
        },
        bsFiles: {
            src: [
                '*.html',
                'styles/*.css',
                'scripts/*.js'
            ]
        }
    });
};

// Local web server running on port 9001 with livereload functionality
module.exports = function(grunt) {
    grunt.config('browserSync', {
        options: {
            watchTask: true,
            tunnel: '<%= pkg.name %>',
            server: {
                baseDir: './'
            }
        },
        bsFiles: {
            src: [
                '*.html',
                'styles/*.css',
                'scripts/*.js'
            ]
        }
    });
};

Add localtunnel option to browserSync


import React from 'react';
import autobind from 'core-decorators/es/autobind';
import { Button, Intent, Collapse } from '@blueprintjs/core';
import MarkdownRenderer from '../../../../../../lib/react/components/MarkdownRenderer';

import '../../scss/components/_instruction-panel.scss';

@autobind
class InstructionCollapse extends React.Component {
	state = {
		isOpen: false,
	}

	toggleOpen() {
		this.setState((prevState) => {
			return { isOpen: !prevState.isOpen }
		});
	}

	render() {
		const { title, content } = this.props.article;

		return (

				<Button className='pt-fill ' text={title}
					iconName={this.state.isOpen?'remove':'add'} onClick={this.toggleOpen}/>
				<Collapse isOpen={this.state.isOpen}>

						<MarkdownRenderer className='markdown-content' src={content}/>

				</Collapse>

		);
	}
}

export default InstructionCollapse;

import React from 'react';
import autobind from 'core-decorators/es/autobind';
import { Button, Intent, Collapse } from '@blueprintjs/core';
import MarkdownRenderer from '../../../../../../lib/react/components/MarkdownRenderer';

import '../../scss/components/_instruction-panel.scss';

@autobind
class InstructionCollapse extends React.Component {
	state = {
		isOpen: false,
	}

	toggleOpen() {
		this.setState((prevState) => {
			return { isOpen: !prevState.isOpen }
		});
	}

	render() {
		const { title, content } = this.props.article;

		return (

				<Button className='pt-fill ' text={title}
					iconName={this.state.isOpen?'chevron-up':'chevron-down'} onClick={this.toggleOpen}/>
				<Collapse isOpen={this.state.isOpen}>

						<MarkdownRenderer className='markdown-content' src={content}/>

				</Collapse>

		);
	}
}

export default InstructionCollapse;

Change the icon on instructions view to chevrons


var gulp = require('gulp');
var config = require('../config');

gulp.task('watch', function() {
    gulp.watch(config.css.watch,                ['css']);
    gulp.watch(config.coffeelint.watch,         ['coffeelint']);
    gulp.watch(config.browserify.app.watch,     ['browserify-app']);
    gulp.watch(config.browserify.iframe.watch,  ['browserify-iframe']);
    gulp.watch(config.browserify.datasetSyncWrapper.watch, ['browserify-wrapper']);
    gulp.watch(config.browserify.saver.watch,   ['browserify-saver']);
    gulp.watch(config.browserify.globals.watch, ['browserify-globals']);
    gulp.watch(config.assets.watch,             ['assets']);
    gulp.watch(config.vendor.watch,             ['vendor']);
});

gulp.task('build-all', ['coffeelint', 'browserify-app', 'browserify-globals',
  'browserify-iframe', 'browserify-dataset-sync-wrapper', 'browserify-saver', 'css', 'assets', 'vendor']);

gulp.task('default', ['build-all', 'watch']);

var gulp = require('gulp');
var config = require('../config');

gulp.task('watch', function() {
    gulp.watch(config.css.watch,                ['css']);
    gulp.watch(config.coffeelint.watch,         ['coffeelint']);
    gulp.watch(config.browserify.app.watch,     ['browserify-app']);
    gulp.watch(config.browserify.iframe.watch,  ['browserify-iframe']);
    gulp.watch(config.browserify.datasetSyncWrapper.watch, ['browserify-dataset-sync-wrapper']);
    gulp.watch(config.browserify.saver.watch,   ['browserify-saver']);
    gulp.watch(config.browserify.globals.watch, ['browserify-globals']);
    gulp.watch(config.assets.watch,             ['assets']);
    gulp.watch(config.vendor.watch,             ['vendor']);
});

gulp.task('build-all', ['coffeelint', 'browserify-app', 'browserify-globals',
  'browserify-iframe', 'browserify-dataset-sync-wrapper', 'browserify-saver', 'css', 'assets', 'vendor']);

gulp.task('default', ['build-all', 'watch']);

Fix some leftover issues from renaming browserify-dataset-sync-wrapper


Upgrade to 1.3.1, add Zepto support


var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  res.status(200).send('This is the Journey app.');
});

module.exports = router;

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  res.status(200).json({
    success: true,
    message: 'This is the Journey API.'
  });
});

module.exports = router;

Update default response on root API endpoint


(function () {

  Accounts.oauth.registerService('twitter', 1, function(oauthBinding) {
    var identity = oauthBinding.get('https://api.twitter.com/1/account/verify_credentials.json');

    return {
      serviceData: {
        id: identity.id,
        screenName: identity.screen_name,
        accessToken: oauthBinding.accessToken,
        accessTokenSecret: oauthBinding.accessTokenSecret
      },
      options: {
        profile: {
          name: identity.name
        }
      }
    };
  });
}) ();

(function () {

  Accounts.oauth.registerService('twitter', 1, function(oauthBinding) {
    var identity = oauthBinding.get('https://api.twitter.com/1/account/verify_credentials.json').data;

    return {
      serviceData: {
        id: identity.id,
        screenName: identity.screen_name,
        accessToken: oauthBinding.accessToken,
        accessTokenSecret: oauthBinding.accessTokenSecret
      },
      options: {
        profile: {
          name: identity.name
        }
      }
    };
  });
}) ();

Fix capturing the twitter identity information


// ==========================================================================
// Project:  Ember Runtime
// Copyright: ©2011 Strobe Inc. and contributors.
// License:   Licensed under MIT license (see license.js)
// ==========================================================================

require('ember-runtime/mixins/observable');
require('ember-runtime/system/core_object');
require('ember-runtime/system/set');

Ember.CoreObject.subclasses = new Ember.Set();

/**
  @class
  @extends Ember.CoreObject
  @extends Ember.Observable
*/
Ember.Object = Ember.CoreObject.extend(Ember.Observable);

// ==========================================================================
// Project:  Ember Runtime
// Copyright: ©2011 Strobe Inc. and contributors.
// License:   Licensed under MIT license (see license.js)
// ==========================================================================

require('ember-runtime/mixins/observable');
require('ember-runtime/system/core_object');
require('ember-runtime/system/set');

Ember.CoreObject.subclasses = new Ember.Set();

/**
  @class

  `Ember.Object` is the main base class for all Ember objects. It is a subclass
  of `Ember.CoreObject` with the `Ember.Observable` mixin applied. For details,
  see the documentation for each of these.

  @extends Ember.CoreObject
  @extends Ember.Observable
*/
Ember.Object = Ember.CoreObject.extend(Ember.Observable);

Add brief documentation for Ember.Object


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

/**
 * WordPress dependencies
 */
import { addFilter } from '@wordpress/hooks';
import { __ } from '@wordpress/i18n';

/**
 * Internal dependencies
 */
import { isFeatureEnabled } from '../../features';

export { registerStore } from './datastore';

if ( isFeatureEnabled( 'ga4setup' ) ) {
	addFilter(
		'googlesitekit.SetupWinNotification-analytics',
		'googlesitekit.OptimizeSetupWinNotification',
		( winData ) => {
			winData.description = __(
				'You’ll only see Universal Analytics data for now.',
				'google-site-kit'
			);
			winData.learnMore.label = 'Learn more';
			winData.learnMore.url =
				'https://sitekit.withgoogle.com/documentation/ga4-analytics-property/';
			return winData;
		}
	);
}

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

export { registerStore } from './datastore';

Remove non existent filter from Analytics.


import React from 'react';
import WeatherIcons from '../weather_icons/WeatherIcons';

const TenDay = ({ tenDayForecast }) => {
  if(!tenDayForecast) {
    return(

    )
  }
  const forecastArray = tenDayForecast.simpleforecast.forecastday;
  const icons = new WeatherIcons();

  return(

      {forecastArray.map((day, i) => {
        return(

{day.date.weekday_short}

Low {day.low.fahrenheit}

Hi {day.high.fahrenheit}

        );
      })}

  )
};

export default TenDay;

Add class to render for ten day.


/* eslint-env node */

module.exports = {
  normalizeEntityName: function() {}, // no-op since we're just adding deps

  afterInstall: function() {
      return this.addBowerPackagesToProject([{
      name: 'polymer',
      target: '1.9.1'
    }, {
      name: 'webcomponentsjs',
      target: '0.7.24'
    }]);
  }
};

/* eslint-env node */

module.exports = {
  normalizeEntityName: function() {}, // no-op since we're just adding deps

  afterInstall: function() {
      return this.addBowerPackagesToProject([{
      name: 'polymer',
      target: '^2.2.0'
    }, {
      name: 'webcomponentsjs',
      target: '^1.0.17'
    }]);
  }
};

Fix blueprint polymer version 🔨


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

/**
 * A tag function that escapes URI components in a template string.
 *
 * @param {string[]} strings The array of static strings in the template.
 * @param {...any} values The array of expressions used in the template.
 * @return {string} Escaped URI string.
 */
export function escapeURI( strings, ...values ) {
	return strings.reduce( ( acc, string, idx ) => {
		return acc + string + encodeURIComponent( values[ idx ] || '' );
	}, '' );
}

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

/**
 * A tag function that escapes URI components in a template string.
 *
 * @since n.e.x.t
 *
 * @param {string[]} strings The array of static strings in the template.
 * @param {...any} values The array of expressions used in the template.
 * @return {string} Escaped URI string.
 */
export function escapeURI( strings, ...values ) {
	return strings.reduce( ( acc, string, idx ) => {
		return acc + string + encodeURIComponent( values[ idx ] || '' );
	}, '' );
}

Add @since for escapeURI function.


'use strict';

/**
 * @ngdoc function
 * @name conferenceAppApp.controller:QuestionlistCtrl
 * @description
 * # QuestionlistCtrl
 * Controller of the conferenceAppApp
 */
angular.module('meanDemoApp')
  .controller('QuestionlistCtrl', function ($scope, Question) {

    Question.query(null,
      function(questions) {
        $scope.questions = questions;
      });

  });

'use strict';

/**
 * @ngdoc function
 * @name conferenceAppApp.controller:QuestionlistCtrl
 * @description
 * # QuestionlistCtrl
 * Controller of the conferenceAppApp
 */
angular.module('meanDemoApp')
  .controller('QuestionlistCtrl', function ($scope, $timeout, Question) {

    var pollInterval = 20000; // in milliseconds

    function getQuestions() {
      Question.query(null,
        function(questions) {
          $scope.questions = questions;
          pollInterval = 20000;
        }, function() {
          // if we fail for some reason, exponentially back off on the polling
          pollInterval *= 2;
        }).$promise.finally(function() {
          $timeout(getQuestions, pollInterval); // poll for new questions
        });
    }

    getQuestions();
  });

Add polling for new questions


module.exports = {
  getViewPortInfo: function getViewPort () {
    var e = document.documentElement
    var g = document.getElementsByTagName('body')[0]
    var x = window.innerWidth || e.clientWidth || g.clientWidth
    var y = window.innerHeight || e.clientHeight || g.clientHeight

    return {
      width: x,
      height: y
    }
  },

  mergeObject: function (o1, o2) {
    var a
    var o3 = {}

    for (a in o1) {
      o3[a] = o1[a]
    }

    for (a in o2) {
      o3[a] = o2[a]
    }

    return o3
  },

  isUndefined: function (obj) {
    return (typeof obj) === 'undefined'
  }
}

module.exports = {
  getViewPortInfo: function getViewPort () {
    var e = document.documentElement
    var g = document.getElementsByTagName('body')[0]
    var x = window.innerWidth || e.clientWidth || g.clientWidth
    var y = window.innerHeight || e.clientHeight || g.clientHeight

    return {
      width: x,
      height: y
    }
  },

  mergeObject: function (o1, o2) {
    var a
    var o3 = {}

    for (a in o1) {
      o3[a] = o1[a]
    }

    for (a in o2) {
      o3[a] = o2[a]
    }

    return o3
  },

  isUndefined: function (obj) {
    return (typeof obj) === 'undefined'
  },

  getCurrentScript: function () {
    // Source http://www.2ality.com/2014/05/current-script.html
    return document.currentScript || (function () {
        var scripts = document.getElementsByTagName('script')
        return scripts[scripts.length - 1]
      })()
  }

}

Add utility method to getCurrentScript


var socket = io.connect(window.location.href);

$(document).ready(function() {

});

var socket = io.connect(window.location.href);

$(document).ready(function() {

  $('#song-count').text($('.track').length + ' tracks')

  // Get total time
  var total = 0;
  $('.time').each(function(index) {
    total += $(this).data('time');
  });
  var time = moment.duration({s: total });
  var hours = ' hours ';
  var minutes = ' minutes';

  if (time.hours() === 1) hours = ' hour ';
  if (time.minutes() === 1) minutes = ' minute';

  $('#total-time').text(time.hours() + hours + time.minutes() + minutes);

});

Update song count and total track play time


var _ = require('underscore');
var layouts = require('log4js').layouts;
var sprintf = require("sprintf-js").sprintf;
var stringify = require('json-stringify-safe');
var NestedError = require('nested-error-stacks');
var isPlainObject = require('is-plain-object');

var Log4jsJsonLayout = {
  process: function(logEvent) {
    var logEntry = {
      timestamp: logEvent.startTime.toISOString(),
      level: logEvent.level && logEvent.level.levelStr.toLowerCase() || null
    };

    try {
      _.extend(logEntry, this.extractParams(logEvent.data));
      return stringify(logEntry);
    } catch (e) {
      return JSON.stringify(new NestedError('Error converting log message to JSON', e));
    }
  },

  extractParams: function(logData) {
    var messages = [];
    var params = {};
    logData.forEach(function(element) {
      if (isPlainObject(element)) {
        params = _.extend(params, element);
      } else {
        messages.push(element);
      }
    });
    var message = this.parseMessages(messages);
    return _.extend({message: message}, params);
  },

  parseMessages: function(messages) {
    if (messages.length == 1) {
      return messages[0];
    }
    if (typeof messages[0] === 'string' && messages[0].indexOf('%') >= 0) {
      return sprintf.apply(this, messages);
    }
    return messages.join('');
  }
};

layouts.addLayout('json', function() {
  return Log4jsJsonLayout.process.bind(Log4jsJsonLayout);
});

var _ = require('underscore');
var layouts = require('log4js').layouts;
var sprintf = require("sprintf-js").sprintf;
var stringify = require('json-stringify-safe');
var NestedError = require('nested-error-stacks');
var isPlainObject = require('is-plain-object');

var Log4jsJsonLayout = {
  process: function(logEvent) {
    var logEntry = {
      timestamp: logEvent.startTime.toISOString(),
      level: logEvent.level && logEvent.level.levelStr.toLowerCase() || null
    };

    try {
      _.extend(logEntry, this.extractParams(logEvent.data));
      return stringify(logEntry);
    } catch (e) {
      return JSON.stringify(new NestedError('Error converting log message to JSON', e));
    }
  },

  extractParams: function(logData) {
    var messages = [];
    var params = {};
    logData.forEach(function(element) {
      if (isPlainObject(element)) {
        params = _.extend(params, element);
      } else {
        messages.push(element);
      }
    });
    var message = this.parseMessages(messages);
    return {message: message, janus: params};
  },

  parseMessages: function(messages) {
    if (messages.length == 1) {
      return messages[0];
    }
    if (typeof messages[0] === 'string' && messages[0].indexOf('%') >= 0) {
      return sprintf.apply(this, messages);
    }
    return messages.join('');
  }
};

layouts.addLayout('json', function() {
  return Log4jsJsonLayout.process.bind(Log4jsJsonLayout);
});

Move extra log params under 'janus' namespace


import { getDomNodePath } from './getDomNodePath';

export function getDomNodeProfile(el) {
  return {
    action: el.action,
    class: el.className,
    href: getElementProps(el, 'href'),
    id: getElementProps(el, 'id'),
    method: el.method,
    name: el.name,
    node_name: el.nodeName,
    selector: getDomNodePath(el),
    text: getElementProps(el, 'text'),
    title: getElementProps(el, 'title'),
    type: el.type,
    x_position: el.offsetLeft || el.clientLeft || null,
    y_position: el.offsetTop || el.clientTop || null
  };
}

const getElementProps = (el, prop) => {
  if (el[prop]) {
    return el[prop];
  }
  if (el.parentNode) {
    return getElementProps(el.parentNode, prop);
  }
  return null;
};

import { getDomNodePath } from './getDomNodePath';

export function getDomNodeProfile(el) {
  return {
    action: el.action,
    class: el.className,
    href: getElementProps(el, 'href'),
    id: getElementProps(el, 'id'),
    event_key: getElementProps(el, 'data-event-key'),
    method: el.method,
    name: el.name,
    node_name: el.nodeName,
    selector: getDomNodePath(el),
    text: getElementProps(el, 'text'),
    title: getElementProps(el, 'title'),
    type: el.type,
    x_position: el.offsetLeft || el.clientLeft || null,
    y_position: el.offsetTop || el.clientTop || null
  };
}

const getElementProps = (el, prop) => {
  if (el[prop]) {
    return el[prop];
  }
  if (el.parentNode) {
    return getElementProps(el.parentNode, prop);
  }
  return null;
};

Add support for data-event-key attribute to avoid polluting standard attributes and be more explicit


module.exports = {
  "parser": "babel-eslint",
  "env": {
    "browser": true,
    "jest": true,
    "node": true,
    "es6": true
  },
  "extends": ["eslint:recommended", "plugin:react/recommended"],
  "parserOptions": {
    "ecmaFeatures": {
      "experimentalObjectRestSpread": true,
      "jsx": true
    },
    "sourceType": "module"
  },
  "plugins": [
    "react", "flowtype"
  ],
  "settings": {
    "react": {
      "version": "16.5",
      "flowVersion": "0.81"
    }
  },
  "rules": {
    "flowtype/define-flow-type": 1,
    "flowtype/require-valid-file-annotation": ["error", "always"],

    "indent": ["error", 2],
    "linebreak-style": ["error", "unix"],
    "quotes": ["error", "single", "avoid-escape"],
    "semi": ["error", "always"],
    "no-var": ["error"],
    "brace-style": ["error"],
    "array-bracket-spacing": ["error", "never"],
    "block-spacing": ["error", "always"],
    "no-spaced-func": ["error"],
    "no-whitespace-before-property": ["error"],
    "space-before-blocks": ["error", "always"],
    "keyword-spacing": ["error"],

    "no-unused-vars": ["error", {"args": "none"}]
  }
};

module.exports = {
  "parser": "babel-eslint",
  "env": {
    "browser": true,
    "jest": true,
    "node": true,
    "es6": true
  },
  "extends": ["eslint:recommended", "plugin:react/recommended"],
  "parserOptions": {
    "ecmaFeatures": {
      "experimentalObjectRestSpread": true,
      "jsx": true
    },
    "sourceType": "module"
  },
  "plugins": [
    "react", "flowtype"
  ],
  "settings": {
    "react": {
      "version": "16.5",
      "flowVersion": "0.81"
    }
  },
  "rules": {
    "flowtype/define-flow-type": 1,
    "flowtype/require-valid-file-annotation": ["error", "always"],

    "indent": ["error", 2],
    "linebreak-style": ["error", "unix"],
    "quotes": ["error", "single", "avoid-escape"],
    "semi": ["error", "always"],
    "no-var": ["error"],
    "brace-style": ["error"],
    "array-bracket-spacing": ["error", "never"],
    "block-spacing": ["error", "always"],
    "no-spaced-func": ["error"],
    "no-whitespace-before-property": ["error"],
    "space-before-blocks": ["error", "always"],
    "keyword-spacing": ["error"],

    "no-unused-vars": ["error", {"args": "none", "ignoreRestSiblings": true}]
  }
};

Allow { unused, ...rest } in ESLint rules


// utils -> chooseBestURL

import {
  slugify,
} from 'bellajs';

import stringComparision from 'string-comparison';

import {
  error,
} from './logger';

export default (candidates = [], title) => {
  let theBest = candidates.reduce((prev, curr) => {
    return curr.length < prev.length ? curr : prev;
  }, candidates[0]);

  try {
    const ls = stringComparision.levenshtein;
    const titleHashed = slugify(title);
    let g = ls.similarity(theBest, titleHashed);

    candidates.forEach((url) => {
      const k = ls.similarity(url, titleHashed);
      if (k > g) {
        g = k;
        theBest = url;
      }
    });
  } catch (err) {
    error(err);
  }

  return theBest;
};

// utils -> chooseBestURL

import {
  slugify,
} from 'bellajs';

import stringComparision from 'string-comparison';

import {getParserOptions} from '../config';

import {
  error,
} from './logger';

export default (candidates = [], title) => {
  let theBest = candidates.reduce((prev, curr) => {
    return curr.length < prev.length ? curr : prev;
  }, candidates[0]);

  try {
    const opts = getParserOptions();
    const alg = opts['urlsCompareAlgorithm'];
    const comparer = stringComparision[alg];

    const titleHashed = slugify(title);
    let g = comparer.similarity(theBest, titleHashed);

    candidates.forEach((url) => {
      const k = comparer.similarity(url, titleHashed);
      if (k > g) {
        g = k;
        theBest = url;
      }
    });
  } catch (err) {
    error(err);
  }

  return theBest;
};

Load string comparison algorithm from config


/**
 * Handling of relationship input fields.
 *
 * @mixin
 * @namespace Bolt.fields.relationship
 *
 * @param {Object} bolt - The Bolt module.
 * @param {Object} $ - jQuery.
 */
(function (bolt, $) {

    /**
     * Bolt.fields.relationship mixin container.
     *
     * @private
     * @type {Object}
     */
    var relationship = {};

    /**
     * Bind relationship field.
     *
     * @static
     * @function init
     * @memberof Bolt.fields.relationship
     *
     * @param {Object} fieldset
     * @param {FieldConf} fconf
     */
    relationship.init = function (fieldset, fconf) {
        var select = $(fieldset).find('select'),
            templateSelection;

        if (fconf.groupBy) {
            templateSelection = function (item) {
                return $(item.element).parent().attr('label') + ': ' + item.text;
            };
        }

        select.select2({
            placeholder: bolt.data('field.relationship.text.placeholder'),
            allowClear: true,
            templateSelection: templateSelection
        });
    };

    // Apply mixin container
    bolt.fields.relationship = relationship;

})(Bolt || {}, jQuery);

/**
 * Handling of relationship input fields.
 *
 * @mixin
 * @namespace Bolt.fields.relationship
 *
 * @param {Object} bolt - The Bolt module.
 * @param {Object} $ - jQuery.
 */
(function (bolt, $) {

    /**
     * Bolt.fields.relationship mixin container.
     *
     * @private
     * @type {Object}
     */
    var relationship = {};

    /**
     * Bind relationship field.
     *
     * @static
     * @function init
     * @memberof Bolt.fields.relationship
     *
     * @param {Object} fieldset
     * @param {FieldConf} fconf
     */
    relationship.init = function (fieldset, fconf) {
        var select = $(fieldset).find('select'),
            templateSelection;

        if (fconf.groupBy) {
            templateSelection = function (item) {
                var label = $(item.element).parent().attr('label');

                return (label ? label + ': ' : '') + item.text;
            };
        }

        select.select2({
            placeholder: bolt.data('field.relationship.text.placeholder'),
            allowClear: true,
            templateSelection: templateSelection
        });
    };

    // Apply mixin container
    bolt.fields.relationship = relationship;

})(Bolt || {}, jQuery);

Change how templateSelection is build


(function( $ ){

    $.fn.placeholder = function(restoreOverride, removeOverride){

        return $(this).each(function(){

            var $this = $(this)

            original_color = $this.css('color')

            function remove(){
                $this.css('color', original_color)
                $this.val('')
            }

            function restore(){
                $this.css('color', '#999')
                $this.val($this.attr('placeholder'))
            }

            $this.parent('form').bind('submit', function(e){
                if($this.val() == $this.attr('placeholder')) remove()
            })

            $this.bind('focus', function(){
                if(!$this.val() || $this.val() == $this.attr('placeholder')){
                    removeOverride ? removeOverride() : remove()
                }
            })

            $this.bind('blur', function(){
                if(!$this.val()){
                    restoreOverride ? restoreOverride() : restore()
                }
            })

            restore()

        })

    }

})( jQuery )

(function( $ ){

    $.fn.placeholder = function(restoreOverride, removeOverride){

        return $(this).each(function(){

            var $this = $(this)

            original_color = $this.css('color')

            function remove(){
                $this.css('color', original_color)
                $this.val('')
            }

            function restore(){
                $this.css('color', '#999')
                $this.val($this.attr('placeholder'))
            }

            $this.parent('form').bind('submit', function(e){
                if($this.val() == $this.attr('placeholder')) remove()
            })

            $this.bind('focus', function(){
                if(!$this.val() || $this.val() == $this.attr('placeholder')){
                    removeOverride ? removeOverride() : remove()
                }
            })

            $this.bind('blur', function(){
                if(!$this.val()){
                    restoreOverride ? restoreOverride() : restore()
                }
            })

            restoreOverride ? restoreOverride() : restore()

        })

    }

})( jQuery )

Fix a bug that caused improper initialization with overrides


/* eslint-env node */
'use strict';

const EmberAddon = require('ember-cli/lib/broccoli/ember-addon');

module.exports = function(defaults) {
  let app = new EmberAddon(defaults, {
    // Add options here
  });

  /*
    This build file specifies the options for the dummy test app of this
    addon, located in `/tests/dummy`
    This build file does *not* influence how the addon or the app using it
    behave. You most likely want to be modifying `./index.js` or app's build file
  */

  return app.toTree();
};

/* eslint-env node */
'use strict';

const EmberAddon = require('ember-cli/lib/broccoli/ember-addon');

module.exports = function(defaults) {
  let app = new EmberAddon(defaults, {
    'ember-cli-babel': {
      includePolyfill: true
    }
  });

  /*
    This build file specifies the options for the dummy test app of this
    addon, located in `/tests/dummy`
    This build file does *not* influence how the addon or the app using it
    behave. You most likely want to be modifying `./index.js` or app's build file
  */

  return app.toTree();
};

Make async functions work in workflow package


/*jslint browser: true, undef: true, white: false, laxbreak: true *//*global Ext,Slate*/
Ext.define('SlateAdmin.model.person.ProgressReport', {
    extend: 'Ext.data.Model',
    fields: [
        'AuthorUsername',
        'Subject',
        {
            name: 'ID',
            type: 'integer',
            useNull: true,
            defaultValue: null
        }, {
            name: 'Class',
            defaultValue: 'ProgressNote'
        }, {
            name: 'Type',
            convert: function (v, r) {
                switch (r.get('Class'))
                {
                    case 'InterimReport':
                        return 'Interim';
                    case 'NarrativeReport':
                        return 'Narrative';
                    case 'ProgressNote':
                        return 'Note';
                    case 'Standards':
                        return 'Standards';
                }
            }
        }, {
            name: 'Date',
            type: 'date',
            dateFormat: 'Y-m-d H:i:s'
        }, {
            name: 'StudentID',
            type: 'integer'
        }, {
            name: 'CourseSectionID',
            type: 'integer'
        }, {
            name: 'TermID',
            type: 'integer'
        }
    ],
    proxy: {
        type: 'slaterecords',
        url: '/progress'
    }
});

/*jslint browser: true, undef: true, white: false, laxbreak: true *//*global Ext,Slate*/
Ext.define('SlateAdmin.model.person.ProgressReport', {
    extend: 'Ext.data.Model',
    fields: [
        'AuthorUsername',
        'Subject',
        {
            name: 'ID',
            type: 'integer',
            useNull: true,
            defaultValue: null
        }, {
            name: 'Class',
            defaultValue: 'Slate\\Progress\\Note'
        }, {
            name: 'Type',
            depends: [
                'Class'
            ],
            calculate: function (data) {
                switch (data.Class) {
                    case 'Slate\\Progress\\SectionInterimReport':
                        return 'InterimReport';
                    case 'Slate\\Progress\\SectionTermReport':
                    case 'NarrativeReport':
                        return 'TermReport';

                    case 'Slate\\Progress\\Note':
                    default:
                        return 'ProgressNote';
                    // case 'Standards':
                    //     return 'Standard';
                }
            }
        }, {
            name: 'Date',
            type: 'date',
            dateFormat: 'Y-m-d H:i:s'
        }, {
            name: 'StudentID',
            type: 'integer'
        }, {
            name: 'CourseSectionID',
            type: 'integer'
        }, {
            name: 'TermID',
            type: 'integer'
        }
    ],
    proxy: {
        type: 'slaterecords',
        url: '/progress'
    }
});

Reconfigure progress report record type calculation


import React from 'react/addons';

export class ChatForm extends React.Component {
    static propTypes = {
        onSend: React.PropTypes.func.isRequired
    }

    constructor(props) {
        super(props);
        this.state = {text: ''};
    }

    handleSubmit = (e) => {
        this.props.onSend(this.state.text);
        this.setState({text: ''});
        let input = React.findDOMNode(this.refs.message);
        input.focus();
        e.preventDefault();
    }

    handleChange = (e) => {
        this.setState({text: e.target.value});
    }

    render() {
        return

        <form onSubmit={this.handleSubmit}>

                <input
                    type="text"
                    className="form-control"
                    ref="message"
                    onChange={this.handleChange}
                    value={this.state.text}
                    autoFocus />

                    <button className="btn btn-primary">Send!</button>

        </form>

;
    }
}

import React from 'react/addons';

export class ChatForm extends React.Component {
    static propTypes = {
        onSend: React.PropTypes.func.isRequired
    }

    constructor(props) {
        super(props);
        this.state = {text: ''};
    }

    handleSubmit = (e) => {
        this.props.onSend(this.state.text);
        this.setState({text: ''});
        let input = React.findDOMNode(this.refs.message);
        input.focus();
        e.preventDefault();
    }

    handleChange = (e) => {
        this.setState({text: e.target.value});
    }

    render() {
        return

        <form onSubmit={this.handleSubmit}>

                <input
                    type="text"
                    className="form-control"
                    ref="message"
                    onChange={this.handleChange}
                    value={this.state.text}
                    autoFocus />

                    <button className="btn btn-primary" disabled={this.state.text.length === 0}>Send!</button>

        </form>

;
    }
}

Enable send button when user enters text.


var Compile = require('../base-compiler');

function compileISPC(info, env) {
    var compiler = new Compile(info, env);
    compiler.optionsForFilter = function (filters, outputFilename) {
        return ['--emit-asm', '-g', '-o', this.filename(outputFilename)];
    };
    return compiler.initialise();
}

module.exports = compileISPC;

var Compile = require('../base-compiler');

function compileISPC(info, env) {
    var compiler = new Compile(info, env);
    compiler.optionsForFilter = function (filters, outputFilename) {
        return ['--target=sse2-i32x4', '--emit-asm', '-g', '-o', this.filename(outputFilename)];
    };
    return compiler.initialise();
}

module.exports = compileISPC;

Make ISPC default to SSE2 if no target is specified


import * as _ from 'lodash'
import React from 'react'
import {connect} from 'react-redux'
import {compose} from 'recompose'

import {withDatabaseSubscribe} from '../hocs'
import {refreshVideo} from '../../actions/videos'

const mapStateToProps = ({videos}) => ({
  videos
})

const enhance = compose(
  connect(mapStateToProps),
  withDatabaseSubscribe(
    'value',
    (props) => (`videos/${props.videoId}`),
    (props) => (snapshot) => (
      props.dispatch(refreshVideo(
        {
          videoId: props.videoId,
          videoSnapshot: snapshot.val()
        })
      )
    )
  )
)

const VideoPreviewCard = ({videoId, videos}) => (
  <div>
    {JSON.stringify(_.get(videos, videoId, {}))}
  </div>
)

export default enhance(VideoPreviewCard)

import * as _ from 'lodash'
import React from 'react'
import {connect} from 'react-redux'
import {Link} from 'react-router-dom'
import {compose} from 'recompose'

import {refreshVideo} from '../../actions/videos'
import {withDatabaseSubscribe} from '../hocs'

const mapStateToProps = ({videos}) => ({
  videos
})

const enhance = compose(
  connect(mapStateToProps),
  withDatabaseSubscribe(
    'value',
    (props) => (`videos/${props.videoId}`),
    (props) => (snapshot) => (
      props.dispatch(refreshVideo(
        {
          videoId: props.videoId,
          videoSnapshot: snapshot.val()
        })
      )
    )
  )
)

const VideoPreviewCard = ({videoId, videos}) => (
  <div>
    <Link to={`/videos/${videoId}`}>
      {JSON.stringify(_.get(videos, videoId, {}))}
    </Link>
  </div>
)

export default enhance(VideoPreviewCard)

Make video card a link


import React from 'react';
import Relay from 'react-relay';
import {Styles} from 'material-ui';
import MuiThemeProvider from 'material-ui/lib/MuiThemeProvider';
import TopNav from './TopNav';
import {BernieTheme} from './styles/bernie-theme';
import {BernieColors} from './styles/bernie-css';

class AdminDashboard extends React.Component {
  tabs = [
    {
      value: '/admin/call-assignments',
      label: 'Call Assignments'
    },
    {
      value: '/admin/events',
      label: 'Events'
    }
  ];

  render() {
    return (
      <MuiThemeProvider muiTheme={Styles.getMuiTheme(BernieTheme)}>
        <div>
          <TopNav
            barColor={BernieColors.blue}
            tabColor={BernieColors.lightBlue}
            selectedTabColor={Styles.Colors.white}
            title="Ground Control Admin"
            logoColors={{
              primary: Styles.Colors.white,
              swoosh: BernieColors.gray
            }}
            tabs={this.tabs}
            history={this.props.history}
            location={this.props.location}
          />
          {this.props.children}
        </div>
      </MuiThemeProvider>
    )
  }
}

// We only do this to auth protect this component.  Otherwise it is unnecessary.
export default Relay.createContainer(AdminDashboard, {
  fragments: {
    listContainer: () => Relay.QL`
      fragment on ListContainer {
        id
      }
    `
  }
})

import React from 'react';
import Relay from 'react-relay';
import {Styles} from 'material-ui';
import MuiThemeProvider from 'material-ui/lib/MuiThemeProvider';
import TopNav from './TopNav';
import {BernieTheme} from './styles/bernie-theme';
import {BernieColors} from './styles/bernie-css';

class AdminDashboard extends React.Component {
  tabs = [
    {
      value: '/admin/call-assignments',
      label: 'Calls'
    },
    {
      value: '/admin/constituent-lookup',
      label: 'People'
    },
    {
      value: '/admin/events',
      label: 'Events'
    }
  ];

  render() {
    return (
      <MuiThemeProvider muiTheme={Styles.getMuiTheme(BernieTheme)}>
        <div>
          <TopNav
            barColor={BernieColors.blue}
            tabColor={BernieColors.lightBlue}
            selectedTabColor={Styles.Colors.white}
            title="Ground Control Admin"
            logoColors={{
              primary: Styles.Colors.white,
              swoosh: BernieColors.gray
            }}
            tabs={this.tabs}
            history={this.props.history}
            location={this.props.location}
          />
          {this.props.children}
        </div>
      </MuiThemeProvider>
    )
  }
}

// We only do this to auth protect this component.  Otherwise it is unnecessary.
export default Relay.createContainer(AdminDashboard, {
  fragments: {
    listContainer: () => Relay.QL`
      fragment on ListContainer {
        id
      }
    `
  }
})

Add constituent lookup tab, shorten tab labels


import { ipcRenderer, remote } from 'electron';
import du from 'du';

import { getServicePartitionsDirectory } from '../../helpers/service-helpers.js';

const debug = require('debug')('Franz:LocalApi');

const { session } = remote;

export default class LocalApi {
  // Settings
  getAppSettings(type) {
    return new Promise((resolve) => {
      ipcRenderer.once('appSettings', (event, resp) => {
        debug('LocalApi::getAppSettings resolves', resp.type, resp.data);
        resolve(resp);
      });

      ipcRenderer.send('getAppSettings', type);
    });
  }

  async updateAppSettings(type, data) {
    debug('LocalApi::updateAppSettings resolves', type, data);
    ipcRenderer.send('updateAppSettings', {
      type,
      data,
    });
  }

  // Services
  async getAppCacheSize() {
    const partitionsDir = getServicePartitionsDirectory();
    return new Promise((resolve, reject) => {
      du(partitionsDir, (err, size) => {
        if (err) reject(err);

        debug('LocalApi::getAppCacheSize resolves', size);
        resolve(size);
      });
    });
  }

  async clearCache(serviceId) {
    const s = session.fromPartition(`persist:service-${serviceId}`);

    debug('LocalApi::clearCache resolves', serviceId);
    return new Promise(resolve => s.clearCache(resolve));
  }

  async clearAppCache() {
    const s = session.defaultSession;

    debug('LocalApi::clearCache clearAppCache');
    return new Promise(resolve => s.clearCache(resolve));
  }
}

import { ipcRenderer, remote } from 'electron';
import du from 'du';

import { getServicePartitionsDirectory } from '../../helpers/service-helpers.js';

const debug = require('debug')('Franz:LocalApi');

const { session } = remote;

export default class LocalApi {
  // Settings
  getAppSettings(type) {
    return new Promise((resolve) => {
      ipcRenderer.once('appSettings', (event, resp) => {
        debug('LocalApi::getAppSettings resolves', resp.type, resp.data);
        resolve(resp);
      });

      ipcRenderer.send('getAppSettings', type);
    });
  }

  async updateAppSettings(type, data) {
    debug('LocalApi::updateAppSettings resolves', type, data);
    ipcRenderer.send('updateAppSettings', {
      type,
      data,
    });
  }

  // Services
  async getAppCacheSize() {
    const partitionsDir = getServicePartitionsDirectory();
    return new Promise((resolve, reject) => {
      du(partitionsDir, (err, size) => {
        if (err) reject(err);

        debug('LocalApi::getAppCacheSize resolves', size);
        resolve(size);
      });
    });
  }

  async clearCache(serviceId) {
    const s = session.fromPartition(`persist:service-${serviceId}`);

    debug('LocalApi::clearCache resolves', serviceId);
    return s.clearCache();
  }

  async clearAppCache() {
    const s = session.defaultSession;

    debug('LocalApi::clearCache clearAppCache');
    return s.clearCache();
  }
}

Update clear cache to electron promise APIs


/**
 * Given a binary tree, return the inorder traversal of its nodes' values.
 *
 * For example:
 * Given binary tree [1,null,2,3],
 *    1
 *     \
 *      2
 *     /
 *    3
 * return [1,3,2].
 *
 * Note: Recursive solution is trivial, could you do it iteratively?
 */
const inorderTraversal = root => {
  const res = [];

  const helper = (root, res) => {
    if (!root) {
      return;
    }

    helper(root.left, res);
    res.push(root.val);
    helper(root.right, res);
  };

  helper(root, res);
  return res;
};

export default inorderTraversal;

/**
 * Given a binary tree, return the inorder traversal of its nodes' values.
 *
 * For example:
 * Given binary tree [1,null,2,3],
 *    1
 *     \
 *      2
 *     /
 *    3
 * return [1,3,2].
 *
 * Note: Recursive solution is trivial, could you do it iteratively?
 */
const inorderTraversal = root => {
  const result = [];
  const stack = [];
  let current = root;

  while (stack.length || current) {
    if (current) {
      stack.push(current);
      current = current.left;
    } else {
      current = stack.pop();
      result.push(current.val);
      current = current.right;
    }
  }

  return result;
};

export default inorderTraversal;

Use iterative way to solve inorder traversal.


import React from 'react'
import Login from './login'
import Signup from './signup'
import Classes from './teacher/classes/Classes'
import Lessons from './teacher/classes/lessons/lessonData'
import Students from './teacher/classes/students/studentData'

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

    this.state = {
      classes: [
        {
          id: {
            type: 1,
          },
          name: 'CS201'
        },
        {
          id: {
            type: 1,
          },
          name: 'CS101'
        },
      ]
    };
  }

  //events here

  render(){
    return (

Thumbroll

        <Classes teacherData={this.state.classes}/>

    );
  }

  componentWillMount(){
    //fetch classes from the DB and update the state to be passed down to Classes
  }
}

module.exports = App;

import React from 'react'
import Login from './login'
import Signup from './signup'
import Classes from './teacher/classes/Classes'
import Lessons from './teacher/classes/lessons/Lessons'
import LessonsData from './teacher/classes/lessons/lessonData'
import Students from './teacher/classes/students/studentData'

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

    this.state = {
      classes: [
        {
          id: {
            type: 1,
          },
          name: 'CS201'
        },
        {
          id: {
            type: 1,
          },
          name: 'CS101'
        },
      ]
    };
  }

  //events here

  render(){
    return (

Thumbroll

        <Classes teacherData={this.state.classes}/>

    );
  }

  componentWillMount(){
    //fetch classes from the DB and update the state to be passed down to Classes
  }
}

//////////////////////////
// FOR DEBUGGING EVENTS //
//////////////////////////

// console.shallowCloneLog = function(){
//   var typeString = Function.prototype.call.bind(Object.prototype.toString)
//   console.log.apply(console, Array.prototype.map.call(arguments, function(x){
//     switch (typeString(x).slice(8, -1)) {
//       case 'Number': case 'String': case 'Undefined': case 'Null': case 'Boolean': return x;
//       case 'Array': return x.slice();
//       default:
//         var out = Object.create(Object.getPrototypeOf(x));
//         out.constructor = x.constructor;
//         for (var key in x) {
//           out[key] = x[key];
//         }
//         Object.defineProperty(out, 'constructor', {value: x.constructor});
//         return out;
//     }
//   }));
// }

var Nav = () => (

Settings

Logout

);

module.exports = App;

Split LessonsData into Lessons and LessonsData components


(function () {
    'use strict';

    angular
        .module('GVA.Common')
        .directive('truncatedText', truncatedText);

    truncatedText.$inject = ['$parse'];

    function truncatedText() {

        function truncateText(text, limit) {

            var words = text.split(' ');

            var truncatedtext = words.reduce(function (prev, curr) {
                if (curr.length + prev.length >= limit) {
                    return prev;
                } else {
                    return prev + ' ' + curr;
                }
            });

            return truncatedtext;
        }

        function link(scope) {

            activate();

            function activate() {

                scope.maxChars = parseInt(scope.maxChars) || 160;
                scope.fullText = '';
                scope.truncatedText = '';

                scope.$watch('ngModel', function () {
                    scope.fullText = scope.ngModel;
                    if (scope.fullText) {
                        scope.truncatedText = truncateText(scope.fullText, scope.maxChars);
                        scope.truncated = scope.fullText !== scope.truncatedText;
                    } else {
                        scope.truncated = false;
                    }
                });
            }
        }

        return {
            restrict: 'A',
            scope: {
                ngModel: '=',
                maxChars: '@',
            },
            link: link,
            template: '{{fullText}}' +
                      '{{truncatedText}}' +
                      '... Show More'
        };
    }
})();

Fix for truncated text 'see more' click triggering text click events.


var default_opacity = 0.2;
var animate_time = 1000;
var num_images = 152;

function fix_resolutions() {
  var img = $('#imgloader').get(0);
  var img_ratio = img.naturalWidth / img.naturalHeight;
  var wnd_ratio = window.innerWidth / window.innerHeight;
  $("#back").css("background-size", img_ratio > wnd_ratio ? "auto 100%" : "100% auto");
}

function set_random_img() {
  var img = $('#imgloader').attr('src');
  var back = $('#back');

  back.animate({opacity: 0}, animate_time, 'swing', function() {
    back.css("background-image", "url('" + img + "')");
    fix_resolutions();
    back.animate({opacity: default_opacity}, animate_time);
  });
}

function select_random_img() {
  $('#imgloader').attr('src', "static/imgs/" + (Math.floor(Math.random() * num_images) + 1) + ".jpg");
}

$( document ).ready(function() {
  $('#imgloader').attr('src', '').load(set_random_img);
  select_random_img();

  $(window).resize(fix_resolutions);
  window.setInterval(select_random_img, 10000);
});

var default_opacity = 0.2;
var animate_time = 0;
var default_animate_time = 1000;
var num_images = 152;

function fix_resolutions() {
  var img = $('#imgloader').get(0);
  var img_ratio = img.naturalWidth / img.naturalHeight;
  var wnd_ratio = window.innerWidth / window.innerHeight;
  $("#back").css("background-size", img_ratio > wnd_ratio ? "auto 100%" : "100% auto");
}

function set_random_img() {
  var img = $('#imgloader').attr('src');
  var back = $('#back');

  back.animate({opacity: 0}, animate_time, 'swing', function() {
    if(animate_time == 0) { animate_time = default_animate_time; }

    back.css("background-image", "url('" + img + "')");
    fix_resolutions();
    back.animate({opacity: default_opacity}, animate_time);
  });
}

function select_random_img() {
  $('#imgloader').attr('src', "static/imgs/" + (Math.floor(Math.random() * num_images) + 1) + ".jpg");
}

$( document ).ready(function() {
  $('#imgloader').attr('src', '').load(set_random_img);
  select_random_img();

  $(window).resize(fix_resolutions);
  window.setInterval(select_random_img, 10000);
});

Load background faster first time


function createSanity({roles}) {
  return {
    getRole(roleName) {
      return roles[roleName]
    }
  }
}

export default createSanity

function createSanity({roles}) {
  return {
    getRole(roleName) {
      const fulfiller = roles[roleName]
      if (!fulfiller) {
        return null
      }

      return fulfiller.module.__esModule && fulfiller.module.default
        ? fulfiller.module.default
        : fulfiller.module
    },

    getPluginForRole(roleName) {
      return roles[roleName] && roles[roleName].plugin
    }
  }
}

export default createSanity

Add support for ES5/ES6 module styles in role getter