<commit_before>/* -*- mode: espresso; espresso-indent-level: 8; indent-tabs-mode: t -*- */
/**
 * tilelayer.js
 *
 * requirements:
 *	 tools.js
 *	 ui.js
 *	 slider.js
 *
 * @todo redo all public interfaces to use physical coordinates instead of pixel coordinates
 */

/**
 * Get the part of the tile name that consists of dimensions z, t, ...
 * For a 3D stack this will return "z/", for a 4D stack "t/z/", etc.
 *
 * @param pixelPos pixel position of the stack [x, y, z, t, ...]
 */
function getTileBaseName( pixelPos )
{
	var n = pixelPos.length;
	var dir = ""
	for ( var i = n - 1; i > 1; --i )
	{
		dir += pixelPos[ i ] + "/";
	}
	return dir;
}

function getTileBaseName3D( stack, pixelPos, adjacent )
{
  if(!adjacent) {
    adjacent = 0;
  }
  var z = pixelPos[ 2 ] + adjacent;
  if( z < 0 ) {
    z = 0;
  }
  if( z > stack.slices[stack.slices.length-1] ) {
    z = stack.slices[stack.slices.length-1];
  }
  if( $.inArray(z, stack.slices) === -1 ) {
    return null;
  }
  return z + "/";
}

/**
 *
 */
function TileLayer(
		stack,						//!< reference to the parent stack
		tileWidth,
		tileHeight,
		tileSource,
		visibility
		)
{
	/**
	 * initialise the tiles array
	 */
	var initTiles = function( rows, cols )
	{
		while ( tilesContainer.firstChild )
			tilesContainer.removeChild( tilesContainer.firstChild );

		delete tiles;
		tiles = new Array();

		for ( var i = 0; i < rows; ++i )
		{
			tiles[ i ] = new Array();
			for ( var j = 0; j < cols; ++j )
			{
				tiles[ i ][ j ] = document.createElement( "img" );
				tiles[ i ][ j ].alt = "empty";
				tiles[ i ][ j ].src = STATIC_URL_JS + "widgets/empty256.gif";

				tilesContainer.appendChild( tiles[ i ][ j ] );
			}
		}

		return;
	}

	/**
	 * align and update the tiles to be ( x, y ) in the image center
	 */
	this.redraw = function(completionCallback)
	{
		var pixelPos = [ stack.x, stack.y, stack.z ];
		var tileBaseName = getTileBaseName( pixelPos );

		var zoom = stack.s;
		var mag = 1.0;
		var artificialZoom = false;
		/* If the zoom is negative we zoom in digitally. For this
		 * we take the zero zoom level and adjust the tile properties.
		 * This way we let the browser do the zooming work.
		 */
		if (zoom < 0) {
			artificialZoom = true;
			mag = Math.pow(2, -zoom);
			zoom = 0;
		}

		effectiveTileWidth = tileWidth * mag;
		effectiveTileHeight = tileHeight * mag;

		var fr = Math.floor( stack.yc / effectiveTileHeight );
		var fc = Math.floor( stack.xc / effectiveTileWidth );

		var xd = 0;
		var yd = 0;

		if ( stack.z == stack.old_z && stack.s == stack.old_s )
		{
			var old_fr = Math.floor( stack.old_yc / effectiveTileHeight );
			var old_fc = Math.floor( stack.old_xc / effectiveTileWidth );

			xd = fc - old_fc;
			yd = fr - old_fr;

			// re-order the tiles array on demand
			if ( xd < 0 )
			{
				for ( var i = 0; i < tiles.length; ++i )
				{
					tilesContainer.removeChild( tiles[ i ].pop() );
					var img = document.createElement( "img" );
					img.alt = "empty";
					img.src = STATIC_URL_JS + "widgets/empty256.gif";
					img.style.visibility = "hidden";
					tilesContainer.appendChild( img );
					tiles[ i ].unshift( img );
				}
			}
			else if ( xd > 0 )
			{
				for ( var i = 0; i < tiles.length; ++i )
				{
					tilesContainer.removeChild( tiles[ i ].shift() );
					var img = document.createElement( "img" );
					img.alt = "empty";
					img.src = STATIC_URL_JS + "widgets/empty256.gif";
					img.style.visibility = "hidden";
					tilesContainer.appendChild( img );
					tiles[ i ].push( img );
				}
			}
			else if ( yd < 0 )
			{
				var old_row = tiles.pop();
				var new_row = new Array();
				for ( var i = 0; i < tiles[ 0 ].length; ++i )
				{
					tilesContainer.removeChild( old_row.pop() );
					var img = document.createElement( "img" );
					img.alt = "empty";
					img.src = STATIC_URL_JS + "widgets/empty256.gif";
					img.style.visibility = "hidden";
					tilesContainer.appendChild( img );
					new_row.push( img );
				}
				tiles.unshift( new_row );
			}
			else if ( yd > 0 )
			{
				var old_row = tiles.shift();
				var new_row = new Array();
				for ( var i = 0; i < tiles[ 0 ].length; ++i )
				{
					tilesContainer.removeChild( old_row.pop() );
					var img = document.createElement( "img" );
					img.alt = "empty";
					img.src = STATIC_URL_JS + "widgets/empty256.gif";
					img.style.visibility = "hidden";
					tilesContainer.appendChild( img );
					new_row.push( img );
				}
				tiles.push( new_row );
			}
		}

		if ( stack.s != stack.old_s)
		{
			if (artificialZoom)
			{
				LAST_XT = Math.floor( ( stack.dimension.x - 1 ) / tileWidth );
				LAST_YT = Math.floor( ( stack.dimension.y - 1 ) / tileHeight );
			}
			else
			{
				LAST_XT = Math.floor( ( stack.dimension.x * stack.scale - 1 ) / tileWidth );
				LAST_YT = Math.floor( ( stack.dimension.y * stack.scale - 1 ) / tileHeight );
			}
		}

		var top;
		var left;

		if ( stack.yc >= 0 )
			top  = -( stack.yc % effectiveTileHeight );
		else
			top  = -( ( stack.yc + 1 ) % effectiveTileHeight ) - effectiveTileHeight + 1;
		if ( stack.xc >= 0 )
			left = -( stack.xc % effectiveTileWidth );
		else
			left = -( ( stack.xc + 1 ) % effectiveTileWidth ) - effectiveTileWidth + 1;

		var t = top;
		var l = left;

		// update the images sources
		for ( var i = 0; i < tiles.length; ++i )
		{
			var r = fr + i;
			for ( var j = 0; j < tiles[ 0 ].length; ++j )
			{
				var c = fc + j;

				/**
				 * TODO Test if updating the URLs always was required to
				 * guarantee homogeneous update speed for modulo-changing steps
				 * and non-modulo changing steps.  Write more comments in
				 * general.
				 */
				if ( r < 0 || c < 0 || r > LAST_YT || c > LAST_XT )
				{
					tiles[ i ][ j ].alt = "";
					tiles[ i ][ j ].src = STATIC_URL_JS + "widgets/black.gif";
				}
				else
				{
					tiles[ i ][ j ].alt = "";
					tiles[ i ][ j ].src = self.tileSource.getTileURL( project, stack,
						tileBaseName, tileWidth, tileHeight, c, r, zoom);

          // prefetch tiles
          // TODO: fetch information in stack table: -2, -1, 1, 2
					/*
          var adj = [], tmpimg = new Image(), tmptileBaseName;
          for( var jj in adj ) {
            tmptileBaseName = getTileBaseName3D( stack, pixelPos, adj[jj] );
            // only prefetch for type 1
            if( tileSourceType === 1 ) {
              tmpimg.src = self.getTileURL( tmptileBaseName + r + "_" + c + "_" + zoom );
            }
          }
					*/

				}
				tiles[ i ][ j ].style.top = t + "px";
				tiles[ i ][ j ].style.left = l + "px";
				tiles[ i ][ j ].style.visibility = "visible";

				tiles[ i ][ j ].style.width = effectiveTileWidth + "px";
				tiles[ i ][ j ].style.height = effectiveTileHeight + "px";

				l += effectiveTileWidth;

			}
			l = left;
			t += effectiveTileHeight;
		}

		if (typeof completionCallback !== "undefined") {
			completionCallback();
		}

		return 2;
	}

	this.resize = function( width, height )
	{
//		alert( "resize tileLayer of stack" + stack.getId() );

		var rows = Math.floor( height / tileHeight ) + 2;
		var cols = Math.floor( width / tileWidth ) + 2;
		initTiles( rows, cols );
		self.redraw();
		return;
	}

	/**
	 * Get the width of an image tile.
	 */
	this.getTileWidth = function(){ return tileWidth; }

	/**
	 * Get the height of an image tile.
	 */
	this.getTileHeight = function(){ return tileHeight; }

	/**
	 * Get the number of tile columns.
	 */
	this.numTileColumns = function()
	{
		if ( tiles.length == 0 )
			return 0;
		else
			return tiles[ 0 ].length;
	}

	/**
	 * Get the number of tile rows.
	 */
	this.numTileColumns = function(){ return tiles.length; }

	/**
	 * Get the stack.
	 */
	this.getStack = function(){ return stack; }

	this.setOpacity = function( val )
	{
		tilesContainer.style.opacity = val+"";
		opacity = val;
	}

	this.getOpacity = function()
	{
		return opacity;
	}

	this.isolateTileLayer = function()
	{
		stack.getView().removeChild( tilesContainer );
		self.visible = false;
	}

	this.reattachTileLayer = function()
	{
		stack.getView().appendChild( tilesContainer );
		self.visible = true;
	}

	// initialise
	var self = this;

	// internal opacity variable
	var opacity = 100;
	this.visible = visibility;

	/* Contains all tiles in a 2d-array */
	var tiles = new Array();
	var tiles2 = new Array();

	var tilesContainer = document.createElement( "div" );
	tilesContainer.className = "sliceTiles";

	if( self.visible )
		stack.getView().appendChild( tilesContainer );

	var LAST_XT = Math.floor( ( stack.dimension.x * stack.scale - 1 ) / tileWidth );
	var LAST_YT = Math.floor( ( stack.dimension.y * stack.scale - 1 ) / tileHeight );

	self.tileSource = tileSource;

	var overviewLayer = tileSource.getOverviewLayer( this );
}
<commit_msg>No redrawing if tile layer not visible<commit_after>/* -*- mode: espresso; espresso-indent-level: 8; indent-tabs-mode: t -*- */
/**
 * tilelayer.js
 *
 * requirements:
 *	 tools.js
 *	 ui.js
 *	 slider.js
 *
 * @todo redo all public interfaces to use physical coordinates instead of pixel coordinates
 */

/**
 * Get the part of the tile name that consists of dimensions z, t, ...
 * For a 3D stack this will return "z/", for a 4D stack "t/z/", etc.
 *
 * @param pixelPos pixel position of the stack [x, y, z, t, ...]
 */
function getTileBaseName( pixelPos )
{
	var n = pixelPos.length;
	var dir = ""
	for ( var i = n - 1; i > 1; --i )
	{
		dir += pixelPos[ i ] + "/";
	}
	return dir;
}

function getTileBaseName3D( stack, pixelPos, adjacent )
{
  if(!adjacent) {
    adjacent = 0;
  }
  var z = pixelPos[ 2 ] + adjacent;
  if( z < 0 ) {
    z = 0;
  }
  if( z > stack.slices[stack.slices.length-1] ) {
    z = stack.slices[stack.slices.length-1];
  }
  if( $.inArray(z, stack.slices) === -1 ) {
    return null;
  }
  return z + "/";
}

/**
 *
 */
function TileLayer(
		stack,						//!< reference to the parent stack
		tileWidth,
		tileHeight,
		tileSource,
		visibility
		)
{
	/**
	 * initialise the tiles array
	 */
	var initTiles = function( rows, cols )
	{
		while ( tilesContainer.firstChild )
			tilesContainer.removeChild( tilesContainer.firstChild );

		delete tiles;
		tiles = new Array();

		for ( var i = 0; i < rows; ++i )
		{
			tiles[ i ] = new Array();
			for ( var j = 0; j < cols; ++j )
			{
				tiles[ i ][ j ] = document.createElement( "img" );
				tiles[ i ][ j ].alt = "empty";
				tiles[ i ][ j ].src = STATIC_URL_JS + "widgets/empty256.gif";

				tilesContainer.appendChild( tiles[ i ][ j ] );
			}
		}

		return;
	}

	/**
	 * align and update the tiles to be ( x, y ) in the image center
	 */
	this.redraw = function(completionCallback)
	{
		if( !self.visible )
			return;

		var pixelPos = [ stack.x, stack.y, stack.z ];
		var tileBaseName = getTileBaseName( pixelPos );

		var zoom = stack.s;
		var mag = 1.0;
		var artificialZoom = false;
		/* If the zoom is negative we zoom in digitally. For this
		 * we take the zero zoom level and adjust the tile properties.
		 * This way we let the browser do the zooming work.
		 */
		if (zoom < 0) {
			artificialZoom = true;
			mag = Math.pow(2, -zoom);
			zoom = 0;
		}

		effectiveTileWidth = tileWidth * mag;
		effectiveTileHeight = tileHeight * mag;

		var fr = Math.floor( stack.yc / effectiveTileHeight );
		var fc = Math.floor( stack.xc / effectiveTileWidth );

		var xd = 0;
		var yd = 0;

		if ( stack.z == stack.old_z && stack.s == stack.old_s )
		{
			var old_fr = Math.floor( stack.old_yc / effectiveTileHeight );
			var old_fc = Math.floor( stack.old_xc / effectiveTileWidth );

			xd = fc - old_fc;
			yd = fr - old_fr;

			// re-order the tiles array on demand
			if ( xd < 0 )
			{
				for ( var i = 0; i < tiles.length; ++i )
				{
					tilesContainer.removeChild( tiles[ i ].pop() );
					var img = document.createElement( "img" );
					img.alt = "empty";
					img.src = STATIC_URL_JS + "widgets/empty256.gif";
					img.style.visibility = "hidden";
					tilesContainer.appendChild( img );
					tiles[ i ].unshift( img );
				}
			}
			else if ( xd > 0 )
			{
				for ( var i = 0; i < tiles.length; ++i )
				{
					tilesContainer.removeChild( tiles[ i ].shift() );
					var img = document.createElement( "img" );
					img.alt = "empty";
					img.src = STATIC_URL_JS + "widgets/empty256.gif";
					img.style.visibility = "hidden";
					tilesContainer.appendChild( img );
					tiles[ i ].push( img );
				}
			}
			else if ( yd < 0 )
			{
				var old_row = tiles.pop();
				var new_row = new Array();
				for ( var i = 0; i < tiles[ 0 ].length; ++i )
				{
					tilesContainer.removeChild( old_row.pop() );
					var img = document.createElement( "img" );
					img.alt = "empty";
					img.src = STATIC_URL_JS + "widgets/empty256.gif";
					img.style.visibility = "hidden";
					tilesContainer.appendChild( img );
					new_row.push( img );
				}
				tiles.unshift( new_row );
			}
			else if ( yd > 0 )
			{
				var old_row = tiles.shift();
				var new_row = new Array();
				for ( var i = 0; i < tiles[ 0 ].length; ++i )
				{
					tilesContainer.removeChild( old_row.pop() );
					var img = document.createElement( "img" );
					img.alt = "empty";
					img.src = STATIC_URL_JS + "widgets/empty256.gif";
					img.style.visibility = "hidden";
					tilesContainer.appendChild( img );
					new_row.push( img );
				}
				tiles.push( new_row );
			}
		}

		if ( stack.s != stack.old_s)
		{
			if (artificialZoom)
			{
				LAST_XT = Math.floor( ( stack.dimension.x - 1 ) / tileWidth );
				LAST_YT = Math.floor( ( stack.dimension.y - 1 ) / tileHeight );
			}
			else
			{
				LAST_XT = Math.floor( ( stack.dimension.x * stack.scale - 1 ) / tileWidth );
				LAST_YT = Math.floor( ( stack.dimension.y * stack.scale - 1 ) / tileHeight );
			}
		}

		var top;
		var left;

		if ( stack.yc >= 0 )
			top  = -( stack.yc % effectiveTileHeight );
		else
			top  = -( ( stack.yc + 1 ) % effectiveTileHeight ) - effectiveTileHeight + 1;
		if ( stack.xc >= 0 )
			left = -( stack.xc % effectiveTileWidth );
		else
			left = -( ( stack.xc + 1 ) % effectiveTileWidth ) - effectiveTileWidth + 1;

		var t = top;
		var l = left;

		// update the images sources
		for ( var i = 0; i < tiles.length; ++i )
		{
			var r = fr + i;
			for ( var j = 0; j < tiles[ 0 ].length; ++j )
			{
				var c = fc + j;

				/**
				 * TODO Test if updating the URLs always was required to
				 * guarantee homogeneous update speed for modulo-changing steps
				 * and non-modulo changing steps.  Write more comments in
				 * general.
				 */
				if ( r < 0 || c < 0 || r > LAST_YT || c > LAST_XT )
				{
					tiles[ i ][ j ].alt = "";
					tiles[ i ][ j ].src = STATIC_URL_JS + "widgets/black.gif";
				}
				else
				{
					tiles[ i ][ j ].alt = "";
					tiles[ i ][ j ].src = self.tileSource.getTileURL( project, stack,
						tileBaseName, tileWidth, tileHeight, c, r, zoom);

          // prefetch tiles
          // TODO: fetch information in stack table: -2, -1, 1, 2
					/*
          var adj = [], tmpimg = new Image(), tmptileBaseName;
          for( var jj in adj ) {
            tmptileBaseName = getTileBaseName3D( stack, pixelPos, adj[jj] );
            // only prefetch for type 1
            if( tileSourceType === 1 ) {
              tmpimg.src = self.getTileURL( tmptileBaseName + r + "_" + c + "_" + zoom );
            }
          }
					*/

				}
				tiles[ i ][ j ].style.top = t + "px";
				tiles[ i ][ j ].style.left = l + "px";
				tiles[ i ][ j ].style.visibility = "visible";

				tiles[ i ][ j ].style.width = effectiveTileWidth + "px";
				tiles[ i ][ j ].style.height = effectiveTileHeight + "px";

				l += effectiveTileWidth;

			}
			l = left;
			t += effectiveTileHeight;
		}

		if (typeof completionCallback !== "undefined") {
			completionCallback();
		}

		return 2;
	}

	this.resize = function( width, height )
	{
//		alert( "resize tileLayer of stack" + stack.getId() );

		var rows = Math.floor( height / tileHeight ) + 2;
		var cols = Math.floor( width / tileWidth ) + 2;
		initTiles( rows, cols );
		self.redraw();
		return;
	}

	/**
	 * Get the width of an image tile.
	 */
	this.getTileWidth = function(){ return tileWidth; }

	/**
	 * Get the height of an image tile.
	 */
	this.getTileHeight = function(){ return tileHeight; }

	/**
	 * Get the number of tile columns.
	 */
	this.numTileColumns = function()
	{
		if ( tiles.length == 0 )
			return 0;
		else
			return tiles[ 0 ].length;
	}

	/**
	 * Get the number of tile rows.
	 */
	this.numTileColumns = function(){ return tiles.length; }

	/**
	 * Get the stack.
	 */
	this.getStack = function(){ return stack; }

	this.setOpacity = function( val )
	{
		tilesContainer.style.opacity = val+"";
		opacity = val;
	}

	this.getOpacity = function()
	{
		return opacity;
	}

	this.isolateTileLayer = function()
	{
		stack.getView().removeChild( tilesContainer );
		self.visible = false;
	}

	this.reattachTileLayer = function()
	{
		stack.getView().appendChild( tilesContainer );
		self.visible = true;
	}

	// initialise
	var self = this;

	// internal opacity variable
	var opacity = 100;
	this.visible = visibility;

	/* Contains all tiles in a 2d-array */
	var tiles = new Array();
	var tiles2 = new Array();

	var tilesContainer = document.createElement( "div" );
	tilesContainer.className = "sliceTiles";

	if( self.visible )
		stack.getView().appendChild( tilesContainer );

	var LAST_XT = Math.floor( ( stack.dimension.x * stack.scale - 1 ) / tileWidth );
	var LAST_YT = Math.floor( ( stack.dimension.y * stack.scale - 1 ) / tileHeight );

	self.tileSource = tileSource;

	var overviewLayer = tileSource.getOverviewLayer( this );
}
<|endoftext|>


<commit_before>import app from '../server/index';
import { expect } from 'chai';
import request from 'supertest-as-promised';
import * as utils from '../test/utils';
import roles from '../server/constants/roles';
import Promise from 'bluebird';
import models from '../server/models';

/**
 * Variables.
 */
const application = utils.data('application');
let users, collective;

/**
 * Functions
 */
const createTransactions = () => {
  const transactions = [
    {
      CreatedByUserId: users[2].id,
      FromCollectiveId: users[2].CollectiveId,
      CollectiveId: collective.id,
      amount: 2000,
      amountInHostCurrency: 2000,
      createdAt: '2016-05-07 19:52:21.203+00',
      updatedAt: '2016-05-07 19:52:21.203+00',
    },
    {
      CreatedByUserId: users[3].id,
      FromCollectiveId: users[3].CollectiveId,
      CollectiveId: collective.id,
      amount: 10000,
      amountInHostCurrency: 10000,
      createdAt: '2016-05-07 19:52:21.203+00',
      updatedAt: '2016-05-07 19:52:21.203+00',
    },
  ];

  return collective
    .addUserWithRole(users[3], roles.BACKER)
    .then(() =>
      models.Transaction.createMany(transactions, { HostCollectiveId: 1 }),
    );
};

describe('members.routes.test.js', () => {
  beforeEach(() => utils.resetTestDB());

  // Create users.
  beforeEach(() =>
    Promise.map(['user1', 'user2', 'user3', 'user4'], u =>
      models.User.createUserWithCollective(utils.data(u)),
    ).tap(results => (users = results)));

  // Create collective.
  beforeEach('create a collective', () =>
    models.Collective.create(utils.data('collective1')).tap(
      g => (collective = g),
    ),
  );

  // Add the host and a backer to the collective.
  beforeEach('add host and backer', done => {
    const promises = [
      collective.addHost(users[0].collective),
      collective.addUserWithRole(users[1], roles.ADMIN),
      collective.addUserWithRole(users[2], roles.BACKER),
    ];
    Promise.all(promises).then(() => done());
  });

  /**
   * Add user to a collective.
   */
  describe('#addUser', () => {
    it('fails adding a non-existing user to a collective', done => {
      request(app)
        .post(
          `/groups/${collective.id}/users/98765?api_key=${application.api_key}`,
        )
        .set('Authorization', `Bearer ${users[0].jwt()}`)
        .expect(404)
        .end(done);
    });

    it('fails adding a user to a non-existing collective', done => {
      request(app)
        .post(
          `/groups/98765/users/${users[1].id}?api_key=${application.api_key}`,
        )
        .set('Authorization', `Bearer ${users[0].jwt()}`)
        .expect(404)
        .end(done);
    });

    it('fails adding a user with a non-existing role', done => {
      request(app)
        .post(`/groups/${collective.id}/users/${users[1].id}`)
        .send({
          api_key: application.api_key,
          role: 'nonexistingrole',
        })
        .set('Authorization', `Bearer ${users[0].jwt()}`)
        .expect(400)
        .end(done);
    });

    it('fails adding a user to a collective if not a member of the collective', done => {
      request(app)
        .post(`/groups/${collective.id}/users/${users[2].id}`)
        .send({
          api_key: application.api_key,
          role: 'ADMIN',
        })
        .set('Authorization', `Bearer ${users[2].jwt()}`)
        .expect(403)
        .end(done);
    });

    it('fails adding a user to a collective if no host', done => {
      request(app)
        .post(`/groups/${collective.id}/users/${users[1].id}`)
        .send({
          api_key: application.api_key,
          role: 'nonexistingrole',
        })
        .set('Authorization', `Bearer ${users[2].jwt()}`)
        .expect(403)
        .end(done);
    });

    it('fails adding a host if the collective already has one', done => {
      request(app)
        .post(`/groups/${collective.id}/users/${users[1].id}`)
        .set('Authorization', `Bearer ${users[0].jwt()}`)
        .expect(400, {
          error: {
            code: 400,
            type: 'bad_request',
            message: 'Collective already has a host',
          },
        })
        .send({
          api_key: application.api_key,
          role: roles.HOST,
        })
        .end(done);
    });

    it('successfully add a user to a collective', done => {
      request(app)
        .post(
          `/groups/${collective.id}/users/${users[1].id}?api_key=${
            application.api_key
          }`,
        )
        .set('Authorization', `Bearer ${users[0].jwt()}`)
        .expect(200)
        .end((e, res) => {
          expect(e).to.not.exist;
          expect(res.body).to.have.property('success', true);
          users[1]
            .getMemberships()
            .then(memberships => {
              expect(memberships[0].CollectiveId).to.equal(collective.id);
              expect(memberships[0].role).to.equal(roles.ADMIN);
              expect(memberships[1].role).to.equal(roles.BACKER);
              done();
            })
            .catch(done);
        });
    });

    it('successfully add a user to a collective with a role', done => {
      const role = roles.ADMIN;
      request(app)
        .post(`/groups/${collective.id}/users/${users[2].id}`)
        .set('Authorization', `Bearer ${users[0].jwt()}`)
        .send({
          api_key: application.api_key,
          role,
        })
        .expect(200)
        .end((e, res) => {
          expect(e).to.not.exist;
          expect(res.body).to.have.property('success', true);

          users[1].getMemberships().then(memberships => {
            expect(memberships[0].CollectiveId).to.equal(collective.id);
            expect(memberships[0].role).to.equal(role);

            setTimeout(() => {
              models.Activity.findAndCountAll({}).then(res => {
                expect(res.count).to.equal(3);
                done();
              });
            }, 50);
          });
        });
    });
  });

  /**
   * Get a collective's users
   */
  describe('/groups/:slug/users', () => {
    beforeEach(createTransactions);

    // Create the tiers
    beforeEach('create backer tier', () =>
      models.Tier.create({
        ...utils.data('tier1'),
        CollectiveId: collective.id,
      }),
    );
    beforeEach('create sponsor tier', () =>
      models.Tier.create({
        ...utils.data('tier2'),
        CollectiveId: collective.id,
      }),
    );

    // Add users to tiers
    beforeEach('add users to tiers', () =>
      Promise.all([
        models.Member.update(
          { TierId: 1 },
          {
            where: {
              MemberCollectiveId: users[2].CollectiveId,
              CollectiveId: collective.id,
            },
          },
        ),
        models.Member.update(
          { TierId: 2 },
          {
            where: {
              MemberCollectiveId: users[3].CollectiveId,
              CollectiveId: collective.id,
            },
          },
        ),
      ]),
    );

    // Add active and non active subscription
    beforeEach('add active subscription', () =>
      Promise.all([
        models.Order.create(
          {
            FromCollectiveId: users[2].CollectiveId,
            CollectiveId: collective.id,
            CreatedByUserId: users[2].id,
            TierId: 1,
            Subscription: { isActive: true },
          },
          { include: [{ model: models.Subscription }] },
        ),
        models.Order.create(
          {
            FromCollectiveId: users[3].CollectiveId,
            CollectiveId: collective.id,
            CreatedByUserId: users[3].id,
            TierId: 2,
            Subscription: { isActive: false },
          },
          { include: [{ model: models.Subscription }] },
        ),
      ]),
    );

    it('get the list of backers with their corresponding tier', () =>
      request(app)
        .get(
          `/groups/${collective.slug}/backers?api_key=${application.api_key}`,
        )
        .expect(200)
        .toPromise()
        .tap(res => {
          const backers = res.body;
          expect(backers).to.have.length(2);
          backers.sort((a, b) => (a.firstName < b.firstName ? -1 : 1));
          expect(backers[0].role).to.equal('BACKER');
          expect(backers[1].role).to.equal('BACKER');
          expect(backers[1].tier).to.equal('backers');
          expect(backers[0].tier).to.equal('sponsors');
        }));

    it('get the list of *active* backers with their corresponding tier', done => {
      request(app)
        .get(
          `/groups/${collective.slug}/backers?filter=active&api_key=${
            application.api_key
          }`,
        )
        .expect(200)
        .expect(res => {
          const backers = res.body;
          expect(backers).to.have.length(1);
          backers.sort((a, b) => (a.firstName < b.firstName ? -1 : 1));
          expect(backers[0].tier).to.equal('backers');
        })
        .end(done);
    });

    it('get the list of backers in csv format without emails if not logged in as admin', done => {
      request(app)
        .get(
          `/groups/${collective.slug}/backers.csv?api_key=${
            application.api_key
          }`,
        )
        .expect(200)
        .expect(res => {
          const headers = res.text
            .split('\n')[0]
            .replace(/"/g, '')
            .split(',');
          const backers = res.text.split('\n').slice(1);
          const getValue = (rowIndex, colName) => {
            const row = backers[rowIndex].split(',');
            return row[headers.indexOf(colName)];
          };
          expect(backers.length).to.equal(2);
          const getColValue = (row, colName) => {
            const array = row.split(',');
            const res = array[headers.indexOf(colName)]
              .trim()
              .replace(/^\"/, '')
              .replace(/\"$/, '');
            return res;
          };
          backers.sort((a, b) => {
            return getColValue(a, 'name').substr(0, 1) <
              getColValue(b, 'name').substr(0, 1)
              ? -1
              : 1;
          });
          expect(getValue(0, 'role')).to.equal('"BACKER"');
          expect(getValue(0, 'tier')).to.equal('"sponsors"');
          expect(getValue(1, 'role')).to.equal('"BACKER"');
          expect(getValue(1, 'tier')).to.equal('"backers"');
          expect(headers).to.not.contain('"email"');
        })
        .end(done);
    });

    it('get the list of backers in csv format without emails if logged in as backer', done => {
      request(app)
        .get(
          `/groups/${collective.slug}/backers.csv?api_key=${
            application.api_key
          }`,
        )
        .set('Authorization', `Bearer ${users[2].jwt()}`) // BACKER
        .expect(200)
        .expect(res => {
          const headers = res.text.split('\n')[0];
          expect(headers).to.not.contain('"email"');
        })
        .end(done);
    });

    it('get the list of backers in csv format with emails if logged in as admin', done => {
      request(app)
        .get(
          `/groups/${collective.slug}/backers.csv?api_key=${
            application.api_key
          }`,
        )
        .set('Authorization', `Bearer ${users[1].jwt()}`) // ADMIN
        .expect(200)
        .expect(res => {
          const headers = res.text
            .split('\n')[0]
            .replace(/"/g, '')
            .split(',');
          const backers = res.text.split('\n').slice(1);
          const getValue = (rowIndex, colName) => {
            const row = backers[rowIndex].split(',');
            return row[headers.indexOf(colName)];
          };
          expect(headers).to.contain('email');
          expect(getValue(0, 'email')).to.contain('@');
        })
        .end(done);
    });
  });
});
<commit_msg>test(members.routes): avoid race condition<commit_after>import app from '../server/index';
import { expect } from 'chai';
import request from 'supertest-as-promised';
import * as utils from '../test/utils';
import roles from '../server/constants/roles';
import Promise from 'bluebird';
import models from '../server/models';

/**
 * Variables.
 */
const application = utils.data('application');
let users, collective;

/**
 * Functions
 */
const createTransactions = () => {
  const transactions = [
    {
      CreatedByUserId: users[2].id,
      FromCollectiveId: users[2].CollectiveId,
      CollectiveId: collective.id,
      amount: 2000,
      amountInHostCurrency: 2000,
      createdAt: '2016-05-07 19:52:21.203+00',
      updatedAt: '2016-05-07 19:52:21.203+00',
    },
    {
      CreatedByUserId: users[3].id,
      FromCollectiveId: users[3].CollectiveId,
      CollectiveId: collective.id,
      amount: 10000,
      amountInHostCurrency: 10000,
      createdAt: '2016-05-07 19:52:21.203+00',
      updatedAt: '2016-05-07 19:52:21.203+00',
    },
  ];

  return collective
    .addUserWithRole(users[3], roles.BACKER)
    .then(() =>
      models.Transaction.createMany(transactions, { HostCollectiveId: 1 }),
    );
};

describe('members.routes.test.js', () => {
  beforeEach(() => utils.resetTestDB());

  // Create users.
  beforeEach(() =>
    Promise.map(['user1', 'user2', 'user3', 'user4'], u =>
      models.User.createUserWithCollective(utils.data(u)),
    ).tap(results => (users = results)));

  // Create collective.
  beforeEach('create a collective', () =>
    models.Collective.create(utils.data('collective1')).tap(
      g => (collective = g),
    ),
  );

  // Add the host and a backer to the collective.
  beforeEach('add host and backer', done => {
    const promises = [
      collective.addHost(users[0].collective),
      collective.addUserWithRole(users[1], roles.ADMIN),
      collective.addUserWithRole(users[2], roles.BACKER),
    ];
    Promise.all(promises).then(() => done());
  });

  /**
   * Add user to a collective.
   */
  describe('#addUser', () => {
    it('fails adding a non-existing user to a collective', done => {
      request(app)
        .post(
          `/groups/${collective.id}/users/98765?api_key=${application.api_key}`,
        )
        .set('Authorization', `Bearer ${users[0].jwt()}`)
        .expect(404)
        .end(done);
    });

    it('fails adding a user to a non-existing collective', done => {
      request(app)
        .post(
          `/groups/98765/users/${users[1].id}?api_key=${application.api_key}`,
        )
        .set('Authorization', `Bearer ${users[0].jwt()}`)
        .expect(404)
        .end(done);
    });

    it('fails adding a user with a non-existing role', done => {
      request(app)
        .post(`/groups/${collective.id}/users/${users[1].id}`)
        .send({
          api_key: application.api_key,
          role: 'nonexistingrole',
        })
        .set('Authorization', `Bearer ${users[0].jwt()}`)
        .expect(400)
        .end(done);
    });

    it('fails adding a user to a collective if not a member of the collective', done => {
      request(app)
        .post(`/groups/${collective.id}/users/${users[2].id}`)
        .send({
          api_key: application.api_key,
          role: 'ADMIN',
        })
        .set('Authorization', `Bearer ${users[2].jwt()}`)
        .expect(403)
        .end(done);
    });

    it('fails adding a user to a collective if no host', done => {
      request(app)
        .post(`/groups/${collective.id}/users/${users[1].id}`)
        .send({
          api_key: application.api_key,
          role: 'nonexistingrole',
        })
        .set('Authorization', `Bearer ${users[2].jwt()}`)
        .expect(403)
        .end(done);
    });

    it('fails adding a host if the collective already has one', done => {
      request(app)
        .post(`/groups/${collective.id}/users/${users[1].id}`)
        .set('Authorization', `Bearer ${users[0].jwt()}`)
        .expect(400, {
          error: {
            code: 400,
            type: 'bad_request',
            message: 'Collective already has a host',
          },
        })
        .send({
          api_key: application.api_key,
          role: roles.HOST,
        })
        .end(done);
    });

    it('successfully add a user to a collective', done => {
      request(app)
        .post(
          `/groups/${collective.id}/users/${users[1].id}?api_key=${
            application.api_key
          }`,
        )
        .set('Authorization', `Bearer ${users[0].jwt()}`)
        .expect(200)
        .end((e, res) => {
          expect(e).to.not.exist;
          expect(res.body).to.have.property('success', true);
          users[1].getMemberships().then(memberships => {
            expect(memberships[0].CollectiveId).to.equal(collective.id);
            const membershipsRoles = [memberships[0].role, memberships[1].role];
            expect(membershipsRoles).to.include(roles.ADMIN);
            expect(membershipsRoles).to.include(roles.BACKER);
            done();
          });
        });
    });

    it('successfully add a user to a collective with a role', done => {
      const role = roles.ADMIN;
      request(app)
        .post(`/groups/${collective.id}/users/${users[2].id}`)
        .set('Authorization', `Bearer ${users[0].jwt()}`)
        .send({
          api_key: application.api_key,
          role,
        })
        .expect(200)
        .end((e, res) => {
          expect(e).to.not.exist;
          expect(res.body).to.have.property('success', true);

          users[1].getMemberships().then(memberships => {
            expect(memberships[0].CollectiveId).to.equal(collective.id);
            expect(memberships[0].role).to.equal(role);

            setTimeout(() => {
              models.Activity.findAndCountAll({}).then(res => {
                expect(res.count).to.equal(3);
                done();
              });
            }, 50);
          });
        });
    });
  });

  /**
   * Get a collective's users
   */
  describe('/groups/:slug/users', () => {
    beforeEach(createTransactions);

    // Create the tiers
    beforeEach('create backer tier', () =>
      models.Tier.create({
        ...utils.data('tier1'),
        CollectiveId: collective.id,
      }),
    );
    beforeEach('create sponsor tier', () =>
      models.Tier.create({
        ...utils.data('tier2'),
        CollectiveId: collective.id,
      }),
    );

    // Add users to tiers
    beforeEach('add users to tiers', () =>
      Promise.all([
        models.Member.update(
          { TierId: 1 },
          {
            where: {
              MemberCollectiveId: users[2].CollectiveId,
              CollectiveId: collective.id,
            },
          },
        ),
        models.Member.update(
          { TierId: 2 },
          {
            where: {
              MemberCollectiveId: users[3].CollectiveId,
              CollectiveId: collective.id,
            },
          },
        ),
      ]),
    );

    // Add active and non active subscription
    beforeEach('add active subscription', () =>
      Promise.all([
        models.Order.create(
          {
            FromCollectiveId: users[2].CollectiveId,
            CollectiveId: collective.id,
            CreatedByUserId: users[2].id,
            TierId: 1,
            Subscription: { isActive: true },
          },
          { include: [{ model: models.Subscription }] },
        ),
        models.Order.create(
          {
            FromCollectiveId: users[3].CollectiveId,
            CollectiveId: collective.id,
            CreatedByUserId: users[3].id,
            TierId: 2,
            Subscription: { isActive: false },
          },
          { include: [{ model: models.Subscription }] },
        ),
      ]),
    );

    it('get the list of backers with their corresponding tier', () =>
      request(app)
        .get(
          `/groups/${collective.slug}/backers?api_key=${application.api_key}`,
        )
        .expect(200)
        .toPromise()
        .tap(res => {
          const backers = res.body;
          expect(backers).to.have.length(2);
          backers.sort((a, b) => (a.firstName < b.firstName ? -1 : 1));
          expect(backers[0].role).to.equal('BACKER');
          expect(backers[1].role).to.equal('BACKER');
          expect(backers[1].tier).to.equal('backers');
          expect(backers[0].tier).to.equal('sponsors');
        }));

    it('get the list of *active* backers with their corresponding tier', done => {
      request(app)
        .get(
          `/groups/${collective.slug}/backers?filter=active&api_key=${
            application.api_key
          }`,
        )
        .expect(200)
        .expect(res => {
          const backers = res.body;
          expect(backers).to.have.length(1);
          backers.sort((a, b) => (a.firstName < b.firstName ? -1 : 1));
          expect(backers[0].tier).to.equal('backers');
        })
        .end(done);
    });

    it('get the list of backers in csv format without emails if not logged in as admin', done => {
      request(app)
        .get(
          `/groups/${collective.slug}/backers.csv?api_key=${
            application.api_key
          }`,
        )
        .expect(200)
        .expect(res => {
          const headers = res.text
            .split('\n')[0]
            .replace(/"/g, '')
            .split(',');
          const backers = res.text.split('\n').slice(1);
          const getValue = (rowIndex, colName) => {
            const row = backers[rowIndex].split(',');
            return row[headers.indexOf(colName)];
          };
          expect(backers.length).to.equal(2);
          const getColValue = (row, colName) => {
            const array = row.split(',');
            const res = array[headers.indexOf(colName)]
              .trim()
              .replace(/^\"/, '')
              .replace(/\"$/, '');
            return res;
          };
          backers.sort((a, b) => {
            return getColValue(a, 'name').substr(0, 1) <
              getColValue(b, 'name').substr(0, 1)
              ? -1
              : 1;
          });
          expect(getValue(0, 'role')).to.equal('"BACKER"');
          expect(getValue(0, 'tier')).to.equal('"sponsors"');
          expect(getValue(1, 'role')).to.equal('"BACKER"');
          expect(getValue(1, 'tier')).to.equal('"backers"');
          expect(headers).to.not.contain('"email"');
        })
        .end(done);
    });

    it('get the list of backers in csv format without emails if logged in as backer', done => {
      request(app)
        .get(
          `/groups/${collective.slug}/backers.csv?api_key=${
            application.api_key
          }`,
        )
        .set('Authorization', `Bearer ${users[2].jwt()}`) // BACKER
        .expect(200)
        .expect(res => {
          const headers = res.text.split('\n')[0];
          expect(headers).to.not.contain('"email"');
        })
        .end(done);
    });

    it('get the list of backers in csv format with emails if logged in as admin', done => {
      request(app)
        .get(
          `/groups/${collective.slug}/backers.csv?api_key=${
            application.api_key
          }`,
        )
        .set('Authorization', `Bearer ${users[1].jwt()}`) // ADMIN
        .expect(200)
        .expect(res => {
          const headers = res.text
            .split('\n')[0]
            .replace(/"/g, '')
            .split(',');
          const backers = res.text.split('\n').slice(1);
          const getValue = (rowIndex, colName) => {
            const row = backers[rowIndex].split(',');
            return row[headers.indexOf(colName)];
          };
          expect(headers).to.contain('email');
          expect(getValue(0, 'email')).to.contain('@');
        })
        .end(done);
    });
  });
});
<|endoftext|>


<commit_before>new explorer.window()
.title('Registry Editor')
.resize(600, 400)
.center()
.icon('webdows/resources/icons/rege.ico')
;<commit_msg>Created registry editor<commit_after>new explorer.window()
.title('Registry Editor')
.resize(600, 400)
.center()
.icon('webdows/resources/icons/rege.ico')
.callback(function() {
	var win = this;
	function drawReg(path, jq) {
		if(!jq.hasClass('open')) {
			jq.addClass('open');
			$.each(system.registry.get(path), function(key) {
				if(typeof this.valueOf() == 'object') {
					var name = key;
					if(path !== '') {
						key = '/'+key;
					}
					$('
'+name+'
').appendTo(jq);
				}
			});
		} else {
			jq.removeClass('open').find('div').remove();
		}
	}
	function drawVal(path) {
		win.body.find('#LSide div.c').remove();
		$.each(system.registry.get(path), function(key) {
			if(typeof this.valueOf() !== 'object') {
				$('
'+key+''+(typeof this.valueOf())+''+this+'
').appendTo(win.body.find('#LSide'));
			}
		});
	}
	function editKey(path, isNew) {
		new explorer.window()
		.title('Edit Value')
		.resize(300, 165)
		.controls([])
		.center()
		.icon('webdows/resources/icons/rege.ico')
		.closeWith(win)
		.callback(function() {
			var win2 = this;
			var disabled = '';
			var key = '';
			var value = '';
			if(!isNew) {
				disabled = 'disabled';
				key = path.split('/').pop();
				value = system.registry.get(path);
			}
			win2.body.html(`

				Value name:

				<input `+disabled+` type="text" value="`+key+`" id="key">

				Value data:

				<input type="text" value="`+value+`" id="val">

				<button id="ok">OK</button>
				<button id="can">Cancel</button>

			`);
			win2.body.find('#can').click(function() { win2.close(); });
			win2.body.find('#ok').click(function() {
				win2.close();
				var key = win2.body.find('#key').val();
				var val = win2.body.find('#val').val();
				if(isNew) {
					system.registry.set(path+'/'+key, val);
				} else {
					system.registry.set(path, val);
				}
				drawVal(win.body.find('#TSide').val());
			});
		});
	}
	var bar = [
		{
			title: 'File',
			context: [
				{
					title: 'Import...',
					callback: function() {
						//Do print
					}
				}, {
					title: 'Export...',
					callback: function() {
						//Do print
					}
				}, {}, {
					title: 'Print...',
					callback: function() {
						//Do print
					}
				}, {}, {
					title: 'Exit',
					callback: function() { win.close(); }
				}
			]
		}, {
			title: 'Edit',
			context: [
				{
					title: 'New Value',
					callback: function() {
						editKey(win.body.find('#TSide').val(), true);
					}
				}, {}, {
					title: 'Delete',
					callback: function() {
						system.registry.set(win.body.find('#LSide div.selected, #RSideBar div.selected').attr('path'));
						drawVal(win.body.find('#TSide').val());
					}
				}
			]
		}, {
			title: 'Help',
			context: [
				{
					title: 'About Registry Editor...',
					callback: function() { system.loader('webdows/webver.js'); }
				}
			]
		}
	];
	win.menuBar(bar);
	win.body.html(`

	<input id="TSide" type="text">

NameTypeData

	`);
	win.body.find('input').on('keyup', function (e) {
		if(e.keyCode == 13) {
			drawVal($(this).val());
		}
	});
	win.body.find('#LSide').on('dblclick', 'div', function() {
		editKey($(this).attr('path'), false);
	});
	win.body.find('#LSide, #RSideBar').on('click', 'div', function() {
		win.body.find('#LSide div, #RSideBar div').removeClass('selected');
		$(this).addClass('selected');
	});
	win.body.find('#RSideBar').on('click', 'div', function(e) {
		win.body.find('input').val($(this).attr('path'));
		drawReg($(this).attr('path'), $(this));
		drawVal($(this).attr('path'));
		e.stopPropagation();
	});
	drawReg('', win.body.find('#RSideBar'));
	win.body.find('#RSideBar div').click();
});<|endoftext|>


<commit_before>/**
 * Given a MagpieClient, get all models with a certain datatype
 * @param client [in|out] Connected MagpieServerClient
 * @param dataType [in] Desired data type
 * @return Map of name -> Model info for models that have the target type
 */
function getModelsOfType(client, dataType) {
    var allModels = client.getModelInformation();
    var output = new Array();
    for (var model in allModels) {
        if (allModels[model].dataType.indexOf(dataType) !== -1) {
            output[model] = allModels[model];
        }
    }
    return output;
}

/**
 * Create a table to store output data. Looks something like
 *
 *          oqmd_dH             oqmd_vol
 *          ΔH (eV/atom)  V (Ang^3/atom)
 * entry_1  model1_entry1       model2_entry1
 * entry_2  model1_entry2       model2_entry2
 * @param modelInfo [in] Information about all models
 * @param models    [in] Models to print
 * @param entries   [in] Entries to print
 * @return A table object, as specified
 */
function createTable(modelInfo, models, entries) {
    // Thanks to: http://stackoverflow.com/questions/14643617/create-table-using-javascript
    var tbl = document.createElement('table');
    var tbody = document.createElement('tbdy');
    tbl.appendChild(tbody)
    var tr = document.createElement('tr');

    // List out the model names
    var td = document.createElement('th');
    tr.appendChild(td);
    td.innerHTML = "Model:";
    for (model in models) {
        td = document.createElement('th');
        td.innerHTML = models[model]
        tr.appendChild(td)
    }
    tbody.appendChild(tr);

    // List out model properties
    var td = document.createElement('th');
    tr = document.createElement('tr');
    td.innerHTML = "Property:";
    tr.appendChild(td);
    for (model in models) {
        td = document.createElement('th');
        name = models[model];
        td.innerHTML = modelInfo[name].property + " (" + modelInfo[name].units + ")";
        tr.appendChild(td);
    }
    tbody.appendChild(tr);

    // Print out
    for (i in entries) {
        var entry = entries[i]
        tr = document.createElement('tr');
        td = document.createElement('td');
        td.innerHTML = entry.name
        tr.appendChild(td)
        for (model in models) {
        	mname = models[model]
            td = document.createElement('td');
            var num = entry.predictedProperties[mname];
            if (entry.classProbs[mname] == undefined) {
	            td.innerHTML = num.toPrecision(3)
            } else {
            	td.innerHTML = modelInfo[mname].units.split(";")[num]
            	td.innerHTML += " ("
            	x = entry.classProbs[mname][num] * 100
            	td.innerHTML += x.toFixed(1)
            	td.innerHTML += "%)"
            }
            tr.appendChild(td)
        }
        tbody.appendChild(tr)
    }

    return tbl;
}

/**
 * Write out model information
 *
 * @param modelInfo [in] Information about model
 * @param outputDiv [in] Part of page to output data
 */
function outputModelInfo(modelInfo, outputDiv) {

	if (modelInfo === undefined) {
		outputDiv.innerHTML += "
No such model.
";
	}
	outputDiv.innerHTML += "
" + modelInfo.notes + "
";

	// Make a table storing general information about model
	output = "
General Information
";
	output += "Property	" + modelInfo.property + "	";
	output += "
Units	" + modelInfo.units + "	";
	output += "
Training Set	" + modelInfo.training + "	";
	output += "
Author	" + modelInfo.author + "	";
	output += "
Citation	" + modelInfo.citation + "	";

	outputDiv.innerHTML += output;

	// Print out model performance
	output = "
Model Performance
"
	output += "
Validation Method" + modelInfo.valMethod + "
"
	if (modelInfo.classifier) {
		output += "Accuracy" + (modelInfo.valScore.Accuracy * 100).toFixed(2) + "%"
		output += "
Kappa" + modelInfo.valScore.Kappa.toFixed(4)
		output += "
Sensitivity" + (modelInfo.valScore.Sensitivity * 100).toFixed(2) + "%"
		output += "
False Positive Rate" + (modelInfo.valScore.FPR * 100).toFixed(2) + "%"
		output += "
Area Under ROC" + modelInfo.valScore.ROCAUC.toFixed(4)
	} else {
		output += "R" + modelInfo.valScore.R.toFixed(4)
		output += "
ρ" + modelInfo.valScore.Rho.toFixed(4)
		output += "
τ" + modelInfo.valScore.Tau.toFixed(4)
		output += "
MAE" + modelInfo.valScore.MAE.toPrecision(3) + " " + modelInfo.units
		output += "
RMSE" + modelInfo.valScore.RMSE.toPrecision(3) + " " + modelInfo.units
		if (modelInfo.valScore.MRE != "NaN") {
			output += "
MRE" + (modelInfo.valScore.MRE * 100).toFixed(2) + "%"
		}
	}

	outputDiv.innerHTML += output

	// Print out information about dataset
	output = "
Attribute Information
\n";
	dataInfo = modelInfo.dataType.split("\n");
	for (i in dataInfo) {
		line = dataInfo[i]

		words = line.split(" ")

		// Look for names of magpie classes
		for (j in words) {
			word = words[j]
			if (word.indexOf("magpie.") == 0) {
				words[j] = getDocReference(words[j])
			}
		}

		// Output line
		for (j in words) {
			output += " " + words[j]
		}
	}
	outputDiv.innerHTML += output;

	// Print out model structure
	output = "
Model Structure
"
	output += "
"
	typeLines = modelInfo.modelType.split("\n")
	for (i in typeLines) {
		line = typeLines[i]

		words = line.split(" ")

		// Look for names of magpie classes
		for (j in words) {
			word = words[j]
			if (word.indexOf("magpie.") == 0) {
				words[j] = getDocReference(words[j])
			}
		}

		// Output line
		for (j in words) {
			output += " " + words[j]
		}

	}
	output += "
"

	outputDiv.innerHTML += output
}

var javadocURL = "http://oqmd.org/static/analytics/magpie/javadoc/"
/**
 * Get weblink to a certain magpie class
 * @param className [in] Full name of magpie class (should start with "magpie.")
 * @return Hyperlink to its documentation page
 */
function getDocReference(className) {
	return "" + className.replace("magpie.","") + ""
}
<commit_msg>Added training time to model information page<commit_after>/**
 * Given a MagpieClient, get all models with a certain datatype
 * @param client [in|out] Connected MagpieServerClient
 * @param dataType [in] Desired data type
 * @return Map of name -> Model info for models that have the target type
 */
function getModelsOfType(client, dataType) {
    var allModels = client.getModelInformation();
    var output = new Array();
    for (var model in allModels) {
        if (allModels[model].dataType.indexOf(dataType) !== -1) {
            output[model] = allModels[model];
        }
    }
    return output;
}

/**
 * Create a table to store output data. Looks something like
 *
 *          oqmd_dH             oqmd_vol
 *          ΔH (eV/atom)  V (Ang^3/atom)
 * entry_1  model1_entry1       model2_entry1
 * entry_2  model1_entry2       model2_entry2
 * @param modelInfo [in] Information about all models
 * @param models    [in] Models to print
 * @param entries   [in] Entries to print
 * @return A table object, as specified
 */
function createTable(modelInfo, models, entries) {
    // Thanks to: http://stackoverflow.com/questions/14643617/create-table-using-javascript
    var tbl = document.createElement('table');
    var tbody = document.createElement('tbdy');
    tbl.appendChild(tbody)
    var tr = document.createElement('tr');

    // List out the model names
    var td = document.createElement('th');
    tr.appendChild(td);
    td.innerHTML = "Model:";
    for (model in models) {
        td = document.createElement('th');
        td.innerHTML = models[model]
        tr.appendChild(td)
    }
    tbody.appendChild(tr);

    // List out model properties
    var td = document.createElement('th');
    tr = document.createElement('tr');
    td.innerHTML = "Property:";
    tr.appendChild(td);
    for (model in models) {
        td = document.createElement('th');
        name = models[model];
        td.innerHTML = modelInfo[name].property + " (" + modelInfo[name].units + ")";
        tr.appendChild(td);
    }
    tbody.appendChild(tr);

    // Print out
    for (i in entries) {
        var entry = entries[i]
        tr = document.createElement('tr');
        td = document.createElement('td');
        td.innerHTML = entry.name
        tr.appendChild(td)
        for (model in models) {
        	mname = models[model]
            td = document.createElement('td');
            var num = entry.predictedProperties[mname];
            if (entry.classProbs[mname] == undefined) {
	            td.innerHTML = num.toPrecision(3)
            } else {
            	td.innerHTML = modelInfo[mname].units.split(";")[num]
            	td.innerHTML += " ("
            	x = entry.classProbs[mname][num] * 100
            	td.innerHTML += x.toFixed(1)
            	td.innerHTML += "%)"
            }
            tr.appendChild(td)
        }
        tbody.appendChild(tr)
    }

    return tbl;
}

/**
 * Write out model information
 *
 * @param modelInfo [in] Information about model
 * @param outputDiv [in] Part of page to output data
 */
function outputModelInfo(modelInfo, outputDiv) {

	if (modelInfo === undefined) {
		outputDiv.innerHTML += "
No such model.
";
	}
	outputDiv.innerHTML += "
" + modelInfo.notes + "
";

	// Make a table storing general information about model
	output = "
General Information
";
	output += "Property" + modelInfo.property;
	output += "
Units" + modelInfo.units;
	output += "
Training Set" + modelInfo.training;
	output += "
Training Time" + modelInfo.trainTime;
	output += "
Author" + modelInfo.author;
	output += "
Citation" + modelInfo.citation;

	outputDiv.innerHTML += output;

	// Print out model performance
	output = "
Model Performance
"
	output += "
Validation Method" + modelInfo.valMethod + "
"
	if (modelInfo.classifier) {
		output += "Accuracy" + (modelInfo.valScore.Accuracy * 100).toFixed(2) + "%"
		output += "
Kappa" + modelInfo.valScore.Kappa.toFixed(4)
		output += "
Sensitivity" + (modelInfo.valScore.Sensitivity * 100).toFixed(2) + "%"
		output += "
False Positive Rate" + (modelInfo.valScore.FPR * 100).toFixed(2) + "%"
		output += "
Area Under ROC" + modelInfo.valScore.ROCAUC.toFixed(4)
	} else {
		output += "R" + modelInfo.valScore.R.toFixed(4)
		output += "
ρ" + modelInfo.valScore.Rho.toFixed(4)
		output += "
τ" + modelInfo.valScore.Tau.toFixed(4)
		output += "
MAE" + modelInfo.valScore.MAE.toPrecision(3) + " " + modelInfo.units
		output += "
RMSE" + modelInfo.valScore.RMSE.toPrecision(3) + " " + modelInfo.units
		if (modelInfo.valScore.MRE != "NaN") {
			output += "
MRE" + (modelInfo.valScore.MRE * 100).toFixed(2) + "%"
		}
	}

	outputDiv.innerHTML += output

	// Print out information about dataset
	output = "
Attribute Information
\n";
	dataInfo = modelInfo.dataType.split("\n");
	for (i in dataInfo) {
		line = dataInfo[i]

		words = line.split(" ")

		// Look for names of magpie classes
		for (j in words) {
			word = words[j]
			if (word.indexOf("magpie.") == 0) {
				words[j] = getDocReference(words[j])
			}
		}

		// Output line
		for (j in words) {
			output += " " + words[j]
		}
	}
	outputDiv.innerHTML += output;

	// Print out model structure
	output = "
Model Structure
"
	output += "
"
	typeLines = modelInfo.modelType.split("\n")
	for (i in typeLines) {
		line = typeLines[i]

		words = line.split(" ")

		// Look for names of magpie classes
		for (j in words) {
			word = words[j]
			if (word.indexOf("magpie.") == 0) {
				words[j] = getDocReference(words[j])
			}
		}

		// Output line
		for (j in words) {
			output += " " + words[j]
		}

	}
	output += "
"

	outputDiv.innerHTML += output
}

var javadocURL = "http://oqmd.org/static/analytics/magpie/javadoc/"
/**
 * Get weblink to a certain magpie class
 * @param className [in] Full name of magpie class (should start with "magpie.")
 * @return Hyperlink to its documentation page
 */
function getDocReference(className) {
	return "" + className.replace("magpie.","") + ""
}
<|endoftext|>


<commit_before>/* @fileoverview
 * Provides full bootstrap based, multi-instance WYSIWYG editor.
 *
 * "Name"    = 'bootstrap-wysiwyg'
 * "Author"  = 'Various, see LICENCE'
 * "Version" = '1.0.1'
 * "About"   = 'Tiny Bootstrap and JQuery Based WISWYG rich text editor.'
 */
(function ($) {
	'use strict';
	/** underscoreThrottle()
	 * 	From underscore http://underscorejs.org/docs/underscore.html
	 */
	var underscoreThrottle = function(func, wait) {
		var context, args, timeout, result;
		var previous = 0;
		var later = function() {
			previous = new Date;
			timeout = null;
			result = func.apply(context, args);
		};
		return function() {
			var now = new Date;
			var remaining = wait - (now - previous);
			context = this;
			args = arguments;
			if (remaining <= 0) {
				clearTimeout(timeout);
				timeout = null;
				previous = now;
				result = func.apply(context, args);
			} else if (!timeout) {
				timeout = setTimeout(later, remaining);
			}
			return result;
		};
	}
	var readFileIntoDataUrl = function (fileInfo) {
		var loader = $.Deferred(),
			fReader = new FileReader();
		fReader.onload = function (e) {
			loader.resolve(e.target.result);
		};
		fReader.onerror = loader.reject;
		fReader.onprogress = loader.notify;
		fReader.readAsDataURL(fileInfo);
		return loader.promise();
	};
	$.fn.cleanHtml = function (o) {
		if ( $(this).data("wysiwyg-html-mode") === true ) {
			$(this).html($(this).text());
        	$(this).attr('contenteditable',true);
        	$(this).data('wysiwyg-html-mode',false);
		}

		// Strip the images with src="data:image/.." out;
		if ( o === true && $(this).parent().is("form") ) {
			var gGal = $(this).html;
			if ( $(gGal).has( "img" ).length ) {
				var gImages = $( "img", $(gGal));
				var gResults = [];
				var gEditor = $(this).parent();
				$.each(gImages, function(i,v) {
					if ( $(v).attr('src').match(/^data:image\/.*$/) ) {
						gResults.push(gImages[i]);
						$(gEditor).prepend("<input value='"+$(v).attr('src')+"' type='hidden' name='postedimage/"+i+"' />");
						$(v).attr('src', 'postedimage/'+i);
				}});
			}
		}
		var html = $(this).html();
		return html && html.replace(/(
|\s|

<\/div>| )*$/, '');
	};
	$.fn.wysiwyg = function (userOptions) {
		var editor = this,
			wrapper = $(editor).parent(),
			selectedRange,
			options,
			toolbarBtnSelector,
			updateToolbar = function () {
				if (options.activeToolbarClass) {
					$(options.toolbarSelector,wrapper).find(toolbarBtnSelector).each(underscoreThrottle(function () {
						var commandArr = $(this).data(options.commandRole).split(' '),
							command = commandArr[0];

						// If the command has an argument and its value matches this button. == used for string/number comparison
						if (commandArr.length > 1 && document.queryCommandEnabled(command) && document.queryCommandValue(command) == commandArr[1]) {
							$(this).addClass(options.activeToolbarClass);
						// Else if the command has no arguments and it is active
						} else if (commandArr.length === 1 && document.queryCommandEnabled(command) && document.queryCommandState(command)) {
							$(this).addClass(options.activeToolbarClass);
						// Else the command is not active
						} else {
							$(this).removeClass(options.activeToolbarClass);
						}
					}, options.keypressTimeout));
				}
			},
			execCommand = function (commandWithArgs, valueArg) {
				var commandArr = commandWithArgs.split(' '),
					command = commandArr.shift(),
					args = commandArr.join(' ') + (valueArg || '');

				if(command == 'createLink') {
					if (options.makeLinksValid || options.invalidLinkHandler) {
						var _match = valueArg.match(/^(http|https)+?:\/\//);
						if (!_match || _match.length == 0) {
							if (options.makeLinksValid) {
								valueArg = 'http://' + valueArg;
							}
							else {
								options.invalidLinkHandler(valueArg);
								return;
							}
						}
					}
				}

				var parts = commandWithArgs.split('-');

				if ( parts.length == 1 ) {
					document.execCommand(command, 0, args);
				}
				else if ( parts[0] == 'format' && parts.length == 2) {
					document.execCommand('formatBlock', false, parts[1] );
				}

				updateToolbar();
			},
			bindHotkeys = function (hotKeys) {
				$.each(hotKeys, function (hotkey, command) {
					editor.keydown(hotkey, function (e) {
						if (editor.attr('contenteditable') && editor.is(':visible')) {
							e.preventDefault();
							e.stopPropagation();
							execCommand(command);
						}
					}).keyup(hotkey, function (e) {
						if (editor.attr('contenteditable') && editor.is(':visible')) {
							e.preventDefault();
							e.stopPropagation();
						}
					});
				});
			},
			getCurrentRange = function () {
                var sel, range;
                if (window.getSelection) {
                    sel = window.getSelection();
                    if (sel.getRangeAt && sel.rangeCount) {
                        range = sel.getRangeAt(0);
				    }
                } else if (document.selection) {
                    range = document.selection.createRange();
                } return range;
			},
			saveSelection = function () {
				selectedRange = getCurrentRange();
			},
			restoreSelection = function () {
				var selection;
                if (window.getSelection || document.createRange) {
                    selection = window.getSelection();
                    if (selectedRange) {
                        try {
                            selection.removeAllRanges();
                        } catch (ex) {
                            document.body.createTextRange().select();
                            document.selection.empty();
                        }
                        selection.addRange(selectedRange);
                    }
                }
                else if (document.selection && selectedRange) {
                	selectedRange.select()
                }
			},

			// Adding Toggle HTML based on the work by @jd0000, but cleaned up a little to work in this context.
            toggleHtmlEdit = function(a) {
				if ( $(editor).data("wysiwyg-html-mode") !== true ) {
					var oContent = $(editor).html();
					var editorPre = $( "
" )
                	$(editorPre).append( document.createTextNode( oContent ) );
                	$(editorPre).attr('contenteditable',true);
                	$(editor).html(' ');
                	$(editor).append($(editorPre));
                    $(editor).attr('contenteditable', false);
                    $(editor).data("wysiwyg-html-mode", true);
                    $(editorPre).focus();
                }
                else {
                	$(editor).html($(editor).text());
                	$(editor).attr('contenteditable',true);
                	$(editor).data('wysiwyg-html-mode',false);
                    $(editor).focus();
                }
            },

			insertFiles = function (files) {
				editor.focus();
				$.each(files, function (idx, fileInfo) {
					if (/^image\//.test(fileInfo.type)) {
						$.when(readFileIntoDataUrl(fileInfo)).done(function (dataUrl) {
							execCommand('insertimage', dataUrl);
							editor.trigger('image-inserted');
						}).fail(function (e) {
							options.fileUploadError("file-reader", e);
						});
					} else {
						options.fileUploadError("unsupported-file-type", fileInfo.type);
					}
				});
			},
			markSelection = function (input, color) {
				restoreSelection();
				if (document.queryCommandSupported('hiliteColor')) {
					document.execCommand('hiliteColor', 0, color || 'transparent');
				}
				saveSelection();
				input.data(options.selectionMarker, color);
			},
			bindToolbar = function (toolbar, options) {
				toolbar.find(toolbarBtnSelector, wrapper).click(function () {
					restoreSelection();
					editor.focus();

                    if ($(this).data(options.commandRole) === 'html') {
                        toggleHtmlEdit();
                    }
                    else {
                    	execCommand($(this).data(options.commandRole));
                    }
					saveSelection();
				});
				toolbar.find('[data-toggle=dropdown]').click(restoreSelection);

				toolbar.find('input[type=text][data-' + options.commandRole + ']').on('webkitspeechchange change', function () {
					var newValue = this.value; /* ugly but prevents fake double-calls due to selection restoration */
					this.value = '';
					restoreSelection();
					if (newValue) {
						editor.focus();
						execCommand($(this).data(options.commandRole), newValue);
					}
					saveSelection();
				}).on('focus', function () {
					var input = $(this);
					if (!input.data(options.selectionMarker)) {
						markSelection(input, options.selectionColor);
						input.focus();
					}
				}).on('blur', function () {
					var input = $(this);
					if (input.data(options.selectionMarker)) {
						markSelection(input, false);
					}
				});
				toolbar.find('input[type=file][data-' + options.commandRole + ']').change(function () {
					restoreSelection();
					if (this.type === 'file' && this.files && this.files.length > 0) {
						insertFiles(this.files);
					}
					saveSelection();
					this.value = '';
				});
			},
			initFileDrops = function () {
				editor.on('dragenter dragover', false)
					.on('drop', function (e) {
						var dataTransfer = e.originalEvent.dataTransfer;
						e.stopPropagation();
						e.preventDefault();
						if (dataTransfer && dataTransfer.files && dataTransfer.files.length > 0) {
							insertFiles(dataTransfer.files);
						}
					});
			};
		options = $.extend(true, {}, $.fn.wysiwyg.defaults, userOptions);
		toolbarBtnSelector = 'a[data-' + options.commandRole + '],button[data-' + options.commandRole + '],input[type=button][data-' + options.commandRole + ']';
		bindHotkeys(options.hotKeys);

		// Support placeholder attribute on the DIV
		if ($(this).attr('placeholder') != '') {
			$(this).addClass('placeholderText');
			$(this).html($(this).attr('placeholder'));
			$(this).bind('focus',function(e) {
				if ( $(this).attr('placeholder') != '' && $(this).text() == $(this).attr('placeholder') ) {
					$(this).removeClass('placeholderText');
					$(this).html('');
				}
			});
			$(this).bind('blur',function(e) {
				if ( $(this).attr('placeholder') != '' && $(this).text() == '' ) {
					$(this).addClass('placeholderText');
					$(this).html($(this).attr('placeholder'));
				}
			})
		}

		if (options.dragAndDropImages) {
			initFileDrops();
		}
		bindToolbar($(options.toolbarSelector), options);
		editor.attr('contenteditable', true)
			.on('mouseup keyup mouseout', function () {
				saveSelection();
				updateToolbar();
			});
		$(window).bind('touchend', function (e) {
			var isInside = (editor.is(e.target) || editor.has(e.target).length > 0),
				currentRange = getCurrentRange(),
				clear = currentRange && (currentRange.startContainer === currentRange.endContainer && currentRange.startOffset === currentRange.endOffset);
			if (!clear || isInside) {
				saveSelection();
				updateToolbar();
			}
		});
		return this;
	};
	$.fn.wysiwyg.defaults = {
		hotKeys: {
			'Ctrl+b meta+b': 'bold',
			'Ctrl+i meta+i': 'italic',
			'Ctrl+u meta+u': 'underline',
			'Ctrl+z': 'undo',
			'Ctrl+y meta+y meta+shift+z': 'redo',
			'Ctrl+l meta+l': 'justifyleft',
			'Ctrl+r meta+r': 'justifyright',
			'Ctrl+e meta+e': 'justifycenter',
			'Ctrl+j meta+j': 'justifyfull',
			'Shift+tab': 'outdent',
			'tab': 'indent'
		},
		toolbarSelector: '[data-role=editor-toolbar]',
		commandRole: 'edit',
		activeToolbarClass: 'btn-info',
		selectionMarker: 'edit-focus-marker',
		selectionColor: 'darkgrey',
		dragAndDropImages: true,
		keypressTimeout: 200,
		makeLinksValid: true,
		invalidLinkHandler: function (link) { return; },
		fileUploadError: function (reason, detail) { console.log("File upload error", reason, detail); }
	};
}(window.jQuery));
<commit_msg>fix hotkey defaults<commit_after>/* @fileoverview
 * Provides full bootstrap based, multi-instance WYSIWYG editor.
 *
 * "Name"    = 'bootstrap-wysiwyg'
 * "Author"  = 'Various, see LICENCE'
 * "Version" = '1.0.1'
 * "About"   = 'Tiny Bootstrap and JQuery Based WISWYG rich text editor.'
 */
(function ($) {
	'use strict';
	/** underscoreThrottle()
	 * 	From underscore http://underscorejs.org/docs/underscore.html
	 */
	var underscoreThrottle = function(func, wait) {
		var context, args, timeout, result;
		var previous = 0;
		var later = function() {
			previous = new Date;
			timeout = null;
			result = func.apply(context, args);
		};
		return function() {
			var now = new Date;
			var remaining = wait - (now - previous);
			context = this;
			args = arguments;
			if (remaining <= 0) {
				clearTimeout(timeout);
				timeout = null;
				previous = now;
				result = func.apply(context, args);
			} else if (!timeout) {
				timeout = setTimeout(later, remaining);
			}
			return result;
		};
	}
	var readFileIntoDataUrl = function (fileInfo) {
		var loader = $.Deferred(),
			fReader = new FileReader();
		fReader.onload = function (e) {
			loader.resolve(e.target.result);
		};
		fReader.onerror = loader.reject;
		fReader.onprogress = loader.notify;
		fReader.readAsDataURL(fileInfo);
		return loader.promise();
	};
	$.fn.cleanHtml = function (o) {
		if ( $(this).data("wysiwyg-html-mode") === true ) {
			$(this).html($(this).text());
        	$(this).attr('contenteditable',true);
        	$(this).data('wysiwyg-html-mode',false);
		}

		// Strip the images with src="data:image/.." out;
		if ( o === true && $(this).parent().is("form") ) {
			var gGal = $(this).html;
			if ( $(gGal).has( "img" ).length ) {
				var gImages = $( "img", $(gGal));
				var gResults = [];
				var gEditor = $(this).parent();
				$.each(gImages, function(i,v) {
					if ( $(v).attr('src').match(/^data:image\/.*$/) ) {
						gResults.push(gImages[i]);
						$(gEditor).prepend("<input value='"+$(v).attr('src')+"' type='hidden' name='postedimage/"+i+"' />");
						$(v).attr('src', 'postedimage/'+i);
				}});
			}
		}
		var html = $(this).html();
		return html && html.replace(/(
|\s|

<\/div>| )*$/, '');
	};
	$.fn.wysiwyg = function (userOptions) {
		var editor = this,
			wrapper = $(editor).parent(),
			selectedRange,
			options,
			toolbarBtnSelector,
			updateToolbar = function () {
				if (options.activeToolbarClass) {
					$(options.toolbarSelector,wrapper).find(toolbarBtnSelector).each(underscoreThrottle(function () {
						var commandArr = $(this).data(options.commandRole).split(' '),
							command = commandArr[0];

						// If the command has an argument and its value matches this button. == used for string/number comparison
						if (commandArr.length > 1 && document.queryCommandEnabled(command) && document.queryCommandValue(command) == commandArr[1]) {
							$(this).addClass(options.activeToolbarClass);
						// Else if the command has no arguments and it is active
						} else if (commandArr.length === 1 && document.queryCommandEnabled(command) && document.queryCommandState(command)) {
							$(this).addClass(options.activeToolbarClass);
						// Else the command is not active
						} else {
							$(this).removeClass(options.activeToolbarClass);
						}
					}, options.keypressTimeout));
				}
			},
			execCommand = function (commandWithArgs, valueArg) {
				var commandArr = commandWithArgs.split(' '),
					command = commandArr.shift(),
					args = commandArr.join(' ') + (valueArg || '');

				if(command == 'createLink') {
					if (options.makeLinksValid || options.invalidLinkHandler) {
						var _match = valueArg.match(/^(http|https)+?:\/\//);
						if (!_match || _match.length == 0) {
							if (options.makeLinksValid) {
								valueArg = 'http://' + valueArg;
							}
							else {
								options.invalidLinkHandler(valueArg);
								return;
							}
						}
					}
				}

				var parts = commandWithArgs.split('-');

				if ( parts.length == 1 ) {
					document.execCommand(command, 0, args);
				}
				else if ( parts[0] == 'format' && parts.length == 2) {
					document.execCommand('formatBlock', false, parts[1] );
				}

				updateToolbar();
			},
			bindHotkeys = function (hotKeys) {
				$.each(hotKeys, function (hotkey, command) {
					editor.keydown(hotkey, function (e) {
						if (editor.attr('contenteditable') && editor.is(':visible')) {
							e.preventDefault();
							e.stopPropagation();
							execCommand(command);
						}
					}).keyup(hotkey, function (e) {
						if (editor.attr('contenteditable') && editor.is(':visible')) {
							e.preventDefault();
							e.stopPropagation();
						}
					});
				});
			},
			getCurrentRange = function () {
                var sel, range;
                if (window.getSelection) {
                    sel = window.getSelection();
                    if (sel.getRangeAt && sel.rangeCount) {
                        range = sel.getRangeAt(0);
				    }
                } else if (document.selection) {
                    range = document.selection.createRange();
                } return range;
			},
			saveSelection = function () {
				selectedRange = getCurrentRange();
			},
			restoreSelection = function () {
				var selection;
                if (window.getSelection || document.createRange) {
                    selection = window.getSelection();
                    if (selectedRange) {
                        try {
                            selection.removeAllRanges();
                        } catch (ex) {
                            document.body.createTextRange().select();
                            document.selection.empty();
                        }
                        selection.addRange(selectedRange);
                    }
                }
                else if (document.selection && selectedRange) {
                	selectedRange.select()
                }
			},

			// Adding Toggle HTML based on the work by @jd0000, but cleaned up a little to work in this context.
            toggleHtmlEdit = function(a) {
				if ( $(editor).data("wysiwyg-html-mode") !== true ) {
					var oContent = $(editor).html();
					var editorPre = $( "
" )
                	$(editorPre).append( document.createTextNode( oContent ) );
                	$(editorPre).attr('contenteditable',true);
                	$(editor).html(' ');
                	$(editor).append($(editorPre));
                    $(editor).attr('contenteditable', false);
                    $(editor).data("wysiwyg-html-mode", true);
                    $(editorPre).focus();
                }
                else {
                	$(editor).html($(editor).text());
                	$(editor).attr('contenteditable',true);
                	$(editor).data('wysiwyg-html-mode',false);
                    $(editor).focus();
                }
            },

			insertFiles = function (files) {
				editor.focus();
				$.each(files, function (idx, fileInfo) {
					if (/^image\//.test(fileInfo.type)) {
						$.when(readFileIntoDataUrl(fileInfo)).done(function (dataUrl) {
							execCommand('insertimage', dataUrl);
							editor.trigger('image-inserted');
						}).fail(function (e) {
							options.fileUploadError("file-reader", e);
						});
					} else {
						options.fileUploadError("unsupported-file-type", fileInfo.type);
					}
				});
			},
			markSelection = function (input, color) {
				restoreSelection();
				if (document.queryCommandSupported('hiliteColor')) {
					document.execCommand('hiliteColor', 0, color || 'transparent');
				}
				saveSelection();
				input.data(options.selectionMarker, color);
			},
			bindToolbar = function (toolbar, options) {
				toolbar.find(toolbarBtnSelector, wrapper).click(function () {
					restoreSelection();
					editor.focus();

                    if ($(this).data(options.commandRole) === 'html') {
                        toggleHtmlEdit();
                    }
                    else {
                    	execCommand($(this).data(options.commandRole));
                    }
					saveSelection();
				});
				toolbar.find('[data-toggle=dropdown]').click(restoreSelection);

				toolbar.find('input[type=text][data-' + options.commandRole + ']').on('webkitspeechchange change', function () {
					var newValue = this.value; /* ugly but prevents fake double-calls due to selection restoration */
					this.value = '';
					restoreSelection();
					if (newValue) {
						editor.focus();
						execCommand($(this).data(options.commandRole), newValue);
					}
					saveSelection();
				}).on('focus', function () {
					var input = $(this);
					if (!input.data(options.selectionMarker)) {
						markSelection(input, options.selectionColor);
						input.focus();
					}
				}).on('blur', function () {
					var input = $(this);
					if (input.data(options.selectionMarker)) {
						markSelection(input, false);
					}
				});
				toolbar.find('input[type=file][data-' + options.commandRole + ']').change(function () {
					restoreSelection();
					if (this.type === 'file' && this.files && this.files.length > 0) {
						insertFiles(this.files);
					}
					saveSelection();
					this.value = '';
				});
			},
			initFileDrops = function () {
				editor.on('dragenter dragover', false)
					.on('drop', function (e) {
						var dataTransfer = e.originalEvent.dataTransfer;
						e.stopPropagation();
						e.preventDefault();
						if (dataTransfer && dataTransfer.files && dataTransfer.files.length > 0) {
							insertFiles(dataTransfer.files);
						}
					});
			};
		options = $.extend(true, {}, $.fn.wysiwyg.defaults, userOptions);

		//do not extend hotkeys, either use the default of the passed in option
		if(userOptions.hotKeys) {
			options.hotKeys = userOptions.hotKeys;
		}

		toolbarBtnSelector = 'a[data-' + options.commandRole + '],button[data-' + options.commandRole + '],input[type=button][data-' + options.commandRole + ']';
		bindHotkeys(options.hotKeys);

		// Support placeholder attribute on the DIV
		if ($(this).attr('placeholder') != '') {
			$(this).addClass('placeholderText');
			$(this).html($(this).attr('placeholder'));
			$(this).bind('focus',function(e) {
				if ( $(this).attr('placeholder') != '' && $(this).text() == $(this).attr('placeholder') ) {
					$(this).removeClass('placeholderText');
					$(this).html('');
				}
			});
			$(this).bind('blur',function(e) {
				if ( $(this).attr('placeholder') != '' && $(this).text() == '' ) {
					$(this).addClass('placeholderText');
					$(this).html($(this).attr('placeholder'));
				}
			})
		}

		if (options.dragAndDropImages) {
			initFileDrops();
		}
		bindToolbar($(options.toolbarSelector), options);
		editor.attr('contenteditable', true)
			.on('mouseup keyup mouseout', function () {
				saveSelection();
				updateToolbar();
			});
		$(window).bind('touchend', function (e) {
			var isInside = (editor.is(e.target) || editor.has(e.target).length > 0),
				currentRange = getCurrentRange(),
				clear = currentRange && (currentRange.startContainer === currentRange.endContainer && currentRange.startOffset === currentRange.endOffset);
			if (!clear || isInside) {
				saveSelection();
				updateToolbar();
			}
		});
		return this;
	};
	$.fn.wysiwyg.defaults = {
		hotKeys: {
			'Ctrl+b meta+b': 'bold',
			'Ctrl+i meta+i': 'italic',
			'Ctrl+u meta+u': 'underline',
			'Ctrl+z': 'undo',
			'Ctrl+y meta+y meta+shift+z': 'redo',
			'Ctrl+l meta+l': 'justifyleft',
			'Ctrl+r meta+r': 'justifyright',
			'Ctrl+e meta+e': 'justifycenter',
			'Ctrl+j meta+j': 'justifyfull',
			'Shift+tab': 'outdent',
			'tab': 'indent'
		},
		toolbarSelector: '[data-role=editor-toolbar]',
		commandRole: 'edit',
		activeToolbarClass: 'btn-info',
		selectionMarker: 'edit-focus-marker',
		selectionColor: 'darkgrey',
		dragAndDropImages: true,
		keypressTimeout: 200,
		makeLinksValid: true,
		invalidLinkHandler: function (link) { return; },
		fileUploadError: function (reason, detail) { console.log("File upload error", reason, detail); }
	};
}(window.jQuery));
<|endoftext|>