<commit_before>module.exports = Chainable

var $chainables = []

/**
 * An object with chainable methods
 *
 * @param {string} apiName - api name
 */
function Chainable (apiName) {
  var chainable = this
  var parentId = indexChainable(chainable)
  chainable[apiName] = new Chains(parentId, apiName)
}

// CHAINS API
// =======================================================================

function Chains (parentId, apiName) {
  this.private = {
    parentId: parentId,
    name: apiName,
    methods: {}, // private collection of method functions
    tasks: [], // queue of tasks created from chainable method calls;
                   // tasks will call real functions in methods {}
    results: [], // results from chained methods call
    executing: false, // chain execution status
    onError: 'fn',
    onFinished: 'fn'
  }
}

/**
 * Results operations
 */
Chains.prototype.results = function () {
  return this.private.results
}

Chains.prototype.addResult = function (r) {
  var results = this.private.results
  results[results.length] = r
}

Chains.prototype.lastResult = function () {
  var results = this.private.results
  return results[results.length - 1]
}

Chains.prototype.clearResults = function () {
  this.private.results = []
}

/**
 * Run the first queued task with a done() callback.
 * Splice the [0] task if splice === true
 */
Chains.prototype.runTask = function (splice, done) {
  var tasks = this.private.tasks

  if (splice) tasks.splice(0, 1)
  if (tasks.length === 0) return false

  tasks[0](done)
  return true
}

/**
 * Queue an async function or a method call with arguments.
 * By calling internally, fnOrMethod is guaranteed
 * to be a valid methodName or an async function
 * TODO: make this a private function
 */
Chains.prototype.queueTask = function (fnOrMethod, args) {
  var chainable = this.parent()
  var methods = this.private.methods
  var tasks = this.private.tasks

  var fn = fnOrMethod.constructor === String
    ? methods[fnOrMethod]
    : fnOrMethod

  tasks[tasks.length] = function (done) {
    args[args.length] = done
    fn.apply(chainable, args) // fn(...params, done)
  }

  this.exec()
}

Chains.prototype.clearTasks = function () {
  this.private.tasks = []
}

/**
 * Api to register a chainable method,
 * fn is an async function with done(err, result) callback
 */
Chains.prototype.chainable = function (methodName, fn) {
  var chainable = this.parent()
  var methods = this.private.methods

  // check if method name is a string and not a reserved keyword
  if (!methodName || methodName.constructor !== String) throw new Error('Method name must be a string')
  if (methodName === this.name || methodName.match(/^then$/)) throw new Error('Reserved keyword: ' + methodName)

  // add fn method to private collection methods{}
  checkAsync(fn, 'Method', true)
  methods[methodName] = fn

  // create a public chainable method
  chainableMethod.call(chainable, methodName)

  // enable usage of .chainable().chainable()
  return this
}

/**
 * Execute the chain of queued methods
 */
Chains.prototype.exec = function () {
  var chains = this
  if (chains.private.executing) return
  chains.private.executing = true
  chains.private.results = []
  exec.call(this)
}

/**
 * Handle chains execution error
 * Usage: chainable.chains.catch(function (err, results) {})
 */
Chains.prototype.catch = function (fn) {
  checkAsync(fn, 'Error handler')
  this.private.onError = fn
  return this
}

/**
 * Handle when chains execution finished successfully
 * Usage: chainable.chains.done(function (results) {})
 */
Chains.prototype.done = function (fn) {
  checkAsync(fn, 'Done handler')
  this.private.onFinished = fn
  return this
}

/**
 * Returns access to parent chainable object when working with chains api
 */
Chains.prototype.parent = function () {
  var parentId = this.private.parentId
  return $chainables[parentId]
}

// CHAINABLE PROTOTYPE
// =======================================================================

/**
 * Api to chain a custom function fn
 * params will be passed to fn by this structure:
 * .then(
 *   function fn (p1, p2, p3, done) {
 *     done(err, result) // callback is optional for .then
 *   },
 *   [p1, p2, p3]
 *       -OR-
 *    p1, p2, p3
 * )
 *
 * Note: if fn(hasOnlyP1, done) and P1 takes an array V1[] as value,
 * then the calling structure must be: .then(fn, [ V1[] ])
 */
Chainable.prototype.then = function (fn, params) {
  var chains = this[api]

  // check if fn is an async function
  checkAsync(fn, 'Then handler')

  // prepare args[] to apply to real methods[methodName]
  var args = []
  for (var arg in arguments) {
    if (arg > 0) args[args.length] = arguments[arg]
  }

  // if params are passed by a single array
  if (args.length === 1 && args[0] && args[0].constructor === Array) args = args[0]

  // queue the custom function call
  chains.queueTask(fn, args)

  return this
}

// HELPERS
// =======================================================================

/**
 * Set chainable api name
 *
 * @param {string} apiName
 */
function setApiName (apiName) {
  if (apiName === undefined) {
    // default
  } else if (!apiName || apiName.constructor !== String) {
    throw new Error('apiName should be a string')
  } else if (apiName !== api && initialized) {
    throw new Error('Chainable api name can only be customized once for consistency')
  } else {
    api = apiName
  }
  initialized = true
}

/**
 * Check if fn is an async function
 * - should has at least one parameter which is a callback function
 * - should call the callback function
 *
 * @param {Function} fn - an async function (...params, done)
 * @param {string} desc - description of fn, e.g. Error handler
 * @param {boolean} checkCallback - set to true to check fn source for callback
 */
function checkAsync (fn, desc, checkCallback) {
  if (!fn || fn.constructor !== Function) throw new Error(desc + ' is not a function: ' + fn)
  if (!checkCallback) return

  var src = fn.toString().replace(/\/\/.*/g, '')
  var params
  try {
    params = src.match(/^function[^(]*\(([^)]*)\)/)[1].replace(/\s+/g, '').split(',')
  } catch (error) {
    throw error
  }
  if (params[0] === '') throw new Error('There is no callback in the function below\n\n' + fn.toString())

  var lastparam = params[params.length - 1]
  var cb = new RegExp(lastparam + '\\s*\\(')
  if (!cb.test(src)) throw new Error(lastparam + ' is not a callback, or is not called anywhere inside the function below\n\n' + fn.toString())
}

/**
 * Create a chainable method
 *
 * @param {string} methodName - name of new chainable method
 */
function chainableMethod (methodName) {
  var chainable = this
  var chains = chainable[api]
  chainable[methodName] = function (...params) {
    var args = [] // prepare args[] to apply to real methods[methodName]
    for (var arg in arguments) {
      args[args.length] = arguments[arg]
    }
    // queue the method call
    chains.queueTask(methodName, args)
    // return the chain object to make method chaining works
    return chainable
  }
}

/**
 * Execute queued tasks[0]
 * - then slice item 0
 * - loop until tasks[] empty or an err returned
 */
function exec () {
  var chains = this

  // tasks' done callback
  var done = function (err, result) {
    if (result !== undefined) chains.addResult(result)
    if (err) {
      chains.clearTasks() // nothing more to do
      if (chains.onError.constructor !== Function) throw new Error(err) // expect error handler
      chains.onError(err, chains.results())
      chains.clearResults()
    } else if (!_exec(true) && chains.onFinished.constructor === Function) {
      // if all tasks executed && there's onFinished()
      chains.onFinished(chains.results())
      chains.clearResults()
    }
    // otherwise: _exec() runs a task, or tasks finished but there's no onFinished() handler
  }

  // tasks runner
  var _exec = function (splice) {
    return chains.runTask(splice, done)
  }

  _exec()
}

function indexChainable (chainable) {
  var id = $chainables.length
  $chainables[id] = chainable
  return id
}
<commit_msg>Refactor to a single .chainable() call, no need to manage parent()<commit_after>module.exports = Chainable

/**
 * An object with chainable methods
 *
 * @param {string} api - api name
 */
function Chainable (api) {
  if (api && api.constructor !== String) throw new Error('Chainable api should be a string')
  api = api || 'chainable'
  this[api] = Chainable.prototype.__chainable__
  this.__chains__ = new Chains(api)
  if (!(this instanceof Chainable)) extend(this)
}

function extend (chainable) {
  // or use Object.assign
  chainable.then = Chainable.prototype.then
}

Chainable.prototype.then = function () {
  $then(this, arguments)
  return this
}

Chainable.prototype.__chainable__ = function (method, fn) {
  var chainable = this
  if (method !== undefined || fn !== undefined) {
    // check if method name is a string and not a reserved keyword
    if (!method || method.constructor !== String) throw new Error('Method name must be a string')
    if (method === chainable.__chains__._api || method.match(/^then$/)) throw new Error('Reserved keyword: ' + method)

    // add fn method to private collection methods{}
    checkAsync(fn, 'Method', true)
    chainable.__chains__._methods[method] = fn

    // create a public chainable method
    chainable[method] = function (...params) {
      // queue the method call
      queueTask(chainable, method, arguments)
      // return the chain object to make method chaining works
      return this
    }
    // enable usage of .chainable().chainable()
    return chainable
  } else {
    // api: chainable().results()
    return chainable.__chains__
  }
}

/**
 * Api to chain a custom function fn
 * params will be passed to fn by this structure:
 * .then(
 *   function fn (p1, p2, p3, done) {
 *     done(err, result) // callback is optional for .then
 *   },
 *   [p1, p2, p3]
 *       -OR-
 *    p1, p2, p3
 * )
 *
 * Note: if fn(hasOnlyP1, done) and P1 takes an array V1[] as value,
 * then the calling structure must be: .then(fn, [ V1[] ])
 */
function $then (chainable, $arguments) {
  // check if fn is an async function
  var fn = $arguments[0]
  checkAsync(fn, 'Then handler')

  // prepare args[] to apply to real methods[methodName]
  var args, i

  if ($arguments.length === 2 && $arguments[1] && $arguments[1].constructor === Array) {
    // calling .then( fn(p1, p2, p3, done), [v1, v2, v3] )
    // if fn has only one param and it takes an array
    //   call .then( fn(p1, done), [ [v1.1, v1.2, v1.3] ])
    args = $arguments[1]
  } else {
    // calling .then( fn(p1, p2, p3, done), v1, v2, v3)
    args = []
    for (i = 1; i < $arguments.length; i++) {
      args[i - 1] = $arguments[i]
    }
  }

  // queue the custom function call
  queueTask(chainable, fn, args)

  return chainable
}

// CHAINS API
// =======================================================================

function Chains (api) {
  this._api = api
  this._methods = {} // private collection of method functions
  this._tasks = [] // queue of tasks created from chainable method calls
                  // tasks will call real functions in methods {}
  this._results = [] // results from chained methods call
  this._executing = false // chain execution status
  this._onError = 'fn' // error handler
  this._onFinished = 'fn' // done handler
}

/**
 * Results operations
 */
Chains.prototype.results = function () {
  return this._results
}

Chains.prototype.lastResult = function () {
  return this._results[this._results.length - 1]
}

/**
 * Handle chains execution error
 * Usage: chainable.chains.catch(function (err, results) {})
 */
Chains.prototype.catch = function (fn) {
  checkAsync(fn, 'Error handler')
  this._onError = fn
  return this
}

/**
 * Handle when chains execution finished successfully
 * Usage: chainable.chains.done(function (results) {})
 */
Chains.prototype.done = function (fn) {
  checkAsync(fn, 'Done handler')
  this._onFinished = fn
  return this
}

// HELPERS
// =======================================================================

/**
 * Check if fn is an async function
 * - should has at least one parameter which is a callback function
 * - should call the callback function
 *
 * @param {Function} fn - an async function (...params, done)
 * @param {string} desc - description of fn, e.g. Error handler
 * @param {boolean} checkCallback - set to true to check fn source for callback
 */
function checkAsync (fn, desc, checkCallback) {
  if (!fn || fn.constructor !== Function) throw new Error(desc + ' is not a function: ' + fn)
  if (!checkCallback) return

  var src = fn.toString().replace(/\/\/.*/g, '')
  var params
  try {
    params = src.match(/^function[^(]*\(([^)]*)\)/)[1].replace(/\s+/g, '').split(',')
  } catch (error) {
    throw error
  }
  if (params[0] === '') throw new Error('There is no callback in the function below\n\n' + fn.toString())

  var lastparam = params[params.length - 1]
  var cb = new RegExp(lastparam + '\\s*\\(')
  if (!cb.test(src)) throw new Error(lastparam + ' is not a callback, or is not called anywhere inside the function below\n\n' + fn.toString())
}

/**
 * Queue an async function or a method call with arguments.
 * By calling internally, fnOrMethod is guaranteed
 * to be a valid methodName or an async function
 * TODO: make this a private function
 */
function queueTask (chainable, fnOrMethod, args) {
  var fn = fnOrMethod.constructor === String
    ? chainable.__chains__.methods[fnOrMethod]
    : fnOrMethod

  var tasks = chainable.__chains__._tasks
  tasks[tasks.length] = function (done) {
    args[args.length] = done
    fn.apply(chainable, args) // fn(...params, done) // what scope is THIS ?
  }

  exec(chainable.__chains__)
}

/**
 * Execute the chain of queued methods
 */
function exec (chains) {
  if (chains._executing) return
  chains._results = []

  // tasks' done callback
  function _done (err, result) {
    if (result !== undefined) chains._results[chains._results.length] = result
    if (err) {
      chains._tasks = [] // nothing more to do
      if (chains._onError.constructor !== Function) throw new Error(err) // expect error handler
      chains._onError(err, chains._results)
      chains._results = []
      chains._executing = false
    } else if (!_exec() && chains._onFinished.constructor === Function) {
      // if all tasks executed && there's onFinished()
      chains._onFinished(chains._results)
      chains._results = []
    }
    // otherwise: _exec() runs a task, or tasks finished but there's no onFinished() handler
  }

  // tasks runner
  function _exec () {
    chains._executing
      ? chains._tasks.shift()
      : chains._executing = true

    if (chains._tasks.length === 0) {
      chains._executing = false
      return false
    }

    chains._tasks[0](_done)
    return true
  }

  _exec()
}
<|endoftext|>


<commit_before>/**
The MIT License (MIT)

Copyright (c) 2014 Michiel Visser

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
**/

if (typeof Object.create !== 'function') {
    Object.create = function (o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

function ChainList(parent,compareFunc) {
	this._next=this._prev=this;
	this._parent=parent||this;
	this._list=this;
	this._length=0;
	this._compareFunc = compareFunc
}

ChainList.Link = function(prev, elem/*optional*/) { // A class captured in the ChainList namespace, which is also the base class of a Linked List
	var t=prev._next;
	prev._next=this;
	this._prev=prev;
	this._next=t;
	t._prev = this;
	this._elem=elem||this;
	this._list = prev._list;
	this._list._length++;
}

ChainList.Link.prototype={
	constructor: ChainList.Link,

	//----- list functions -----

	list: function() {
		return this._list;
	},
	isList: function() { return this._isList; },
	reverse: function() {
		var r=new ChainList(); // no need for _initClone
		while (!this._next._isList)
			r._next.takeSome(this,1);
		this.take(r);
		return this;
	},

	//----- parent functions -----

	parent: function() {
		return this._list._parent;
	},
	setParent: function(val) {
		_list._parent = val;
	},
	takeRange: function(start,end) {
		start=start.first();
		if (start==end)
			return this;
		var last=end._prev;

		var myList=this._list;
		start.forEachTill(end, function(elem,index,node) {
			node._list._length--;
			node._list=myList;
			node._list._length++;
		});

		end._prev=start._prev;
		start._prev._next=end;

		var tn=this;
		tn._prev._next=start;
		start._prev = tn._prev;

		tn._prev = last;
		last._next = tn;
		return last;
	},
	take: function(list) {
		return this.takeRange(list,list._list);
	},
	takeSome: function(list,count) {
		return this.takeRange(list,list.indexPos(count));
	},
	cloneRange: function(end) {
		var ll=this.list()._initClone();
		for(var n=this.first(); n!=end;n=n._next)
			ll.insert(n._elem);
		return ll;
	},
	cloneSome: function(count) {
		return this.cloneRange(this.indexPos(count));
	},
	shiftRange: function(end) {
		var ll=this.list()._initClone();
		ll.takeRange(this,end);
		return ll;
	},
	shiftSome: function(count) {
		return this.shiftRange(this.indexPos(count));
	},
	truncate: function() {
		return this.shiftRange(this._list);
	},
	shift: function() { return this.shiftSome(1).index(0); },
	slice: function(begin,end) {
		return this.indexPos(begin).cloneRange(end!=null?this.indexPos(end):this._list);
	},
	remove: function() {
		return this.shiftSome(1).index(0);
	},
	pop: function() {
		return this.last().remove();
	},

	//----- node navigation functions -----

	node: function() { return this; },
	first: function() { return this; },
	last: function() {
		return this._list._prev;
	},
	index: function(index) { return this.indexPos(index).node(); },
	indexOf: function(node) {
		var r = this.forEach(function(i,index,n) {
			if (n==node)
				return index;
		});
		return r==null? -1 : r;
	},
	next: function() { return this._next.node(); },
	prev: function() { return this._prev.node(); },
	selfIndex: function() {
		return this._list.indexOf(this);
	},
	indexPos: function(index) {
		var s;
		if (index<0) {
			s=this._prev;
			while(++index<0 && !s._isList)
				s=s._prev;
		} else {
			s=this.first();
			while(--index>=0 && !s._isList)
				s=s._next;
		}
		return s;
	},

	//----- elem functions -----

	elem: function() { return this._elem; },
	setElem: function(val) { this._elem = val; },
	indexOfElem: function(elem) {
		var r= this.forEach(function(i,index,n) {
			if (i==elem)
				return index;
		});
		return r==null? -1 : r;
	},
	indexElem: function(index) { return this.indexPos(index).elem(); },
	lastIndexOfElem: function(elem) {
		this.forEachBack(function(i,index,n) {
			if (i==elem)
				return index;
		});
		return -1;
	},
	toArray: function() {
		return this.map(function(elem) { return elem; });
	},

    //------ elem insertion -----

    insert: function(elem) {
		return new ChainList.Link(this._prev,elem);
	},
	push: function(elem) {
		this._list.insertArray(arguments);
		return this._list._count;
	},
	unshift: function() {
		this.first().insertArray(arguments);
		return this._list._count;
	},
	insertArray: function(elems) {
		var n=this;
		for(var i=0; i<elems.length; i++) {
			n=n.insert(elems[i])._next;
		}
	},
	splice: function(index,cutCount,elemToInsert1) {
		var n=this.indexPos(index);
		var nn=n._next;
		var r=n.shiftSome(cutCount);
		nn.insertArray( Array.prototype.slice.call(arguments,2));
		return r;
	},

	//------ link functions -----

	indexLink: function(index) {
		var s = this;
		if (index<0) {
			while(++index<=0) s=s._prev;
		} else
			while(--index>=0) s=s._next;
		return s;
	},
	nextLink: function() { return this._next; },
	prevLink: function() { return this._prev; },
	countRange: function(end) { // counts the nunmber of successors
		var i = 0;
		for(var s=this._next;s!=end;s=s._next)
			i++;
		return i;
	},

	//----- iterators -----

	forEachTill: function (end, func/*(elem,index,Node): retVal*/) {
		var index=0;
		for(var s=this.first();s!=end;s=s._next) {
			var value=func.call(this,s._elem,index,s);
			if (value!==undefined)
				return value;
			index++;
		}
	},
	forEach: function (func/*(elem,index,Node): retVal*/) {
		return this.forEachTill(this.list(), func);
	},
	forEachBackRange: function (end, func/*(elem,index,Node): retVal*/) {
		var index=0; //this is faster than starting with calculating count
		for(var s=this._prev;s!=end;s=s._prev) {
			index--;
			var value=func.call(this,s._elem,index,s);
			if (value!==undefined)
				return value;
		}
	},
	forEachBack: function (func/*(elem,index,Node): retVal*/) {
		return this.forEachBackRange(this.list(), func);
	},
	map: function (func/*(elem,index,Node): retVal*/) {
		var index=0;
		var res=[];
		for(var s=this._next;!s._isList;s=s._next) {
			res.push(func(s._elem,index,s));
			index++;
		}
		return res;
	},
};

ChainList.Link.prototype.get = ChainList.Link.prototype.indexElem;

ChainList.prototype = Object.create (ChainList.Link.prototype);
ChainList.prototype.constructor = ChainList;
ChainList.prototype._isList = true;
ChainList.prototype.list = function() { return this; };
ChainList.prototype.empty = ChainList.Link.prototype.truncate;
ChainList.prototype.truncate = function() {}
ChainList.prototype.remove = null;
ChainList.prototype.count = function() { return this._length; }
ChainList.prototype.clone = function() { return this.cloneRange(this);};
ChainList.prototype.node = function() { return null; };
ChainList.prototype.first = function() { return this._next; };
ChainList.prototype.elem = function() { return null; };
ChainList.prototype.insertAt = function(index,elem) { return this.indexLink(index+1).insert(elem); } // 0 = begin, length or -1=end
ChainList.prototype.removeAt = function(index) { return this.indexPos(index).remove(); } // 0 = first, -1=last
ChainList.prototype._testInvariants = function() {
	var count=0;
	this.forEach(function(elem,index,node) {
		count++;
		assert(node._prev._next==node);
		assert(node._next._prev==node);
		assert(node._list==this);
		assert(node._isList==null);
	});
	assert_equal(count,this.count());
};

ChainList.prototype._initClone = function() { return new ChainList(); }

ChainList.fromArray = function(array,parent) {
	var ll = new ChainList(parent);
	ll.insertArray(array);
	return ll;
}

function ChainListSorted(parent,compareFunc) {
	ChainList.call(this,parent);
	this._compareFunc = compareFunc || (parent && parent.compareFunc) || ChainListSorted.defaultCompareFunc;
}

ChainListSorted.defaultCompareFunc = function(a,b) {
	return a-b;
}

ChainListSorted.prototype = Object.create (ChainList.prototype);
ChainListSorted.prototype.constructor = ChainListSorted;

ChainListSorted.prototype.insert = function(elem) {
	var node = this.forEach(function(e,index,node) {
		if (this._compareFunc(elem,e)<=0)
			return node;
	}) || this;
	return new ChainListSorted.Node(node._prev,elem);
};

ChainListSorted.prototype.insertAt = null; //disable

ChainListSorted.prototype.unshift = null; //disable

ChainListSorted.prototype.push = null; //disable

ChainListSorted.prototype._initClone = function() { return new ChainListSorted(null,this._compareFunc); }

ChainListSorted.fromArray = function(array,parent,compareFunc) {
	var ll = new ChainListSorted(parent,compareFunc);
	ll.insertArray(array);
	return ll;
};

ChainListSorted.Node = function(prev, elem) {
	ChainList.Link.call(this,prev,elem);
};

ChainListSorted.Node.prototype = Object.create (ChainList.Link.prototype);

ChainListSorted.Node.prototype.constructor = ChainListSorted.Node;

ChainListSorted.Node.prototype.insert = function(elem) {
	return this.list().insert(elem);
};<commit_msg>make special node class and add extend function<commit_after>/**
The MIT License (MIT)

Copyright (c) 2014 Michiel Visser

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
**/

if (typeof Object.create !== 'function') {
    Object.create = function (o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

function ChainList(parent,compareFunc) {
	this._next=this._prev=this;
	this._parent=parent||this;
	this._list=this;
	this._length=0;
	this._compareFunc = compareFunc
}

ChainList.Link = function(prev, elem/*optional*/) { // A class captured in the ChainList namespace, which is also the base class of a Linked List
	var t=prev._next;
	prev._next=this;
	this._prev=prev;
	this._next=t;
	t._prev = this;
	this._elem=elem||this;
	this._list = prev._list;
	this._list._length++;
}

ChainList.Link.prototype={
	constructor: ChainList.Link,

	//----- parent functions -----

	parent: function() {
		return this._list._parent;
	},
	setParent: function(val) {
		_list._parent = val;
	},

	//----- list functions -----

	isList: function() { return this._isList; },
	reverse: function() {
		var r=new ChainList(); // no need for _initClone
		while (!this._next._isList)
			r._next.takeSome(this,1);
		this.take(r);
		return this;
	},
	takeRange: function(start,end) {
		start=start.firstPos();
		if (start==end)
			return this;
		var last=end._prev;

		var myList=this._list;
		start.forEachTill(end, function(elem,index,node) {
			node._list._length--;
			node._list=myList;
			node._list._length++;
		});

		end._prev=start._prev;
		start._prev._next=end;

		var tn=this;
		tn._prev._next=start;
		start._prev = tn._prev;

		tn._prev = last;
		last._next = tn;
		return last;
	},
	take: function(list) {
		return this.takeRange(list,list._list);
	},
	takeSome: function(list,count) {
		return this.takeRange(list,list.indexPos(count));
	},
	cloneRange: function(end) {
		var ll=this.list()._initClone();
		for(var n=this.firstPos(); n!=end;n=n._next)
			ll.insert(n._elem);
		return ll;
	},
	cloneSome: function(count) {
		return this.cloneRange(this.indexPos(count));
	},
	shiftRange: function(end) {
		var ll=this.list()._initClone();
		ll.takeRange(this,end);
		return ll;
	},
	shiftSome: function(count) {
		return this.shiftRange(this.indexPos(count));
	},
	truncate: function() {
		return this.shiftRange(this._list);
	},
	shift: function() { return this.shiftSome(1).index(0); },
	slice: function(begin,end) {
		return this.indexPos(begin).cloneRange(end!=null?this.indexPos(end):this._list);
	},
	pop: function() {
		return this.lastPos().remove();
	},

	//----- node navigation functions -----
	last: function() {
		return this.lastPos().node();
	},
	first: function() {
		return this.firstPos().node();
	},
	lastPos: function() {
		return this._list._prev;
	},
	index: function(index) { return this.indexPos(index).node(); },
	indexOf: function(node) {
		var r = this.forEach(function(i,index,n) {
			if (n==node)
				return index;
		});
		return r==null? -1 : r;
	},
	next: function() { return this._next.node(); },
	prev: function() { return this._prev.node(); },
	selfIndex: function() {
		return this._list.indexOf(this);
	},
	indexPos: function(index) {
		var s;
		if (index<0) {
			s=this._prev;
			while(++index<0 && !s._isList)
				s=s._prev;
		} else {
			s=this.firstPos();
			while(--index>=0 && !s._isList)
				s=s._next;
		}
		return s;
	},

	//----- elem functions -----

	setElem: function(val) { this._elem = val; },
	indexOfElem: function(elem) {
		var r= this.forEach(function(i,index,n) {
			if (i==elem)
				return index;
		});
		return r==null? -1 : r;
	},
	indexElem: function(index) { return this.indexPos(index).elem(); },
	lastIndexOfElem: function(elem) {
		this.forEachBack(function(i,index,n) {
			if (i==elem)
				return index;
		});
		return -1;
	},
	toArray: function() {
		return this.map(function(elem) { return elem; });
	},

    //------ elem insertion -----

    insert: function(elem) {
		return new ChainList.Node(this._prev,elem);
	},
	push: function(elem) {
		this._list.insertArray(arguments);
		return this._list._count;
	},
	unshift: function() {
		this.firstPos().insertArray(arguments);
		return this._list._count;
	},
	insertArray: function(elems) {
		var n=this;
		for(var i=0; i<elems.length; i++) {
			n=n.insert(elems[i])._next;
		}
	},
	splice: function(index,cutCount,elemToInsert1) {
		var n=this.indexPos(index);
		var nn=n._next;
		var r=n.shiftSome(cutCount);
		nn.insertArray( Array.prototype.slice.call(arguments,2));
		return r;
	},

	//------ link functions -----

	indexLink: function(index) {
		var s = this;
		if (index<0) {
			while(++index<=0) s=s._prev;
		} else
			while(--index>=0) s=s._next;
		return s;
	},
	nextLink: function() { return this._next; },
	prevLink: function() { return this._prev; },
	countRange: function(end) { // counts the nunmber of successors
		var i = 0;
		for(var s=this._next;s!=end;s=s._next)
			i++;
		return i;
	},

	//----- iterators -----

	forEachTill: function (end, func/*(elem,index,Node): retVal*/) {
		var index=0;
		for(var s=this.firstPos();s!=end;s=s._next) {
			var value=func.call(this,s._elem,index,s);
			if (value!==undefined)
				return value;
			index++;
		}
	},
	forEach: function (func/*(elem,index,Node): retVal*/) {
		return this.forEachTill(this.list(), func);
	},
	forEachBackRange: function (end, func/*(elem,index,Node): retVal*/) {
		var index=0; //this is faster than starting with calculating count
		for(var s=this._prev;s!=end;s=s._prev) {
			index--;
			var value=func.call(this,s._elem,index,s);
			if (value!==undefined)
				return value;
		}
	},
	forEachBack: function (func/*(elem,index,Node): retVal*/) {
		return this.forEachBackRange(this.list(), func);
	},
	map: function (func/*(elem,index,Node): retVal*/) {
		var index=0;
		var res=[];
		for(var s=this._next;!s._isList;s=s._next) {
			res.push(func(s._elem,index,s));
			index++;
		}
		return res;
	},
	extend: function(props) {
		for (var prop in props) {
            if (!this.hasOwnProperty(prop)) {
                this[prop] = props[prop];
            }
		}
	}
};
ChainList.Link.prototype.get = ChainList.Link.prototype.indexElem;

ChainList.Node = function(prev, elem) {
	ChainList.Link.call(this, prev, elem);
};

ChainList.Node.prototype = Object.create (ChainList.Link.prototype);

ChainList.Node.prototype.extend({
	constructor: ChainList.Node,
	// ----- list functions -----
	remove: function() {
		return this.shiftSome(1).index(0);
	},
	list: function() {
		return this._list;
	},
	//----- node navigation functions -----
	node: function() { return this; },
	firstPos: function() { return this; },
	//----- elem functions -----
	elem: function() { return this._elem; },
});

ChainList.prototype = Object.create (ChainList.Link.prototype);
ChainList.prototype.extend({
	constructor: ChainList,
	_isList: true,
	list: function() { return this; },
	empty: ChainList.Link.prototype.truncate,
	truncate: function() {},
	count: function() { return this._length; },
	clone: function() { return this.cloneRange(this);},
	node: function() { return null; },
	firstPos: function() { return this._next; },
	elem: function() { return null; },
	insertAt: function(index,elem) { return this.indexLink(index+1).insert(elem); }, // 0:begin, length or -1=end
	removeAt: function(index) { return this.indexPos(index).remove(); }, // 0:first, -1=last
	_testInvariants:function() {
		var count=0;
		this.forEach(function(elem,index,node) {
			count++;
			assert(node._prev._next==node);
			assert(node._next._prev==node);
			assert(node._list==this);
			assert(node._isList==null);
		});
		assert_equal(count,this.count());
	},
	_initClone: function() { return new ChainList(); }
});
ChainList.fromArray = function(array,parent) {
	var ll = new ChainList(parent);
	ll.insertArray(array);
	return ll;
}

function ChainListSorted(parent,compareFunc) {
	ChainList.call(this,parent);
	this._compareFunc = compareFunc || (parent && parent.compareFunc) || ChainListSorted.defaultCompareFunc;
}

ChainListSorted.defaultCompareFunc = function(a,b) {
	return a-b;
};

ChainListSorted.prototype = Object.create (ChainList.prototype);
ChainListSorted.prototype.extend({
	constructor: ChainListSorted,
	insert: function(elem) {
		var node = this.forEach(function(e,index,node) {
			if (this._compareFunc(elem,e)<=0)
				return node;
		}) || this;
		return new ChainListSorted.Node(node._prev,elem);
	},
	insertAt: null, //disable
	unshift: null, //disable
	push: null, //disable
	_initClone: function() { return new ChainListSorted(null,this._compareFunc); }
});

ChainListSorted.fromArray = function(array,parent,compareFunc) {
	var ll = new ChainListSorted(parent,compareFunc);
	ll.insertArray(array);
	return ll;
};

ChainListSorted.Node = function(prev, elem) {
	ChainList.Node.call(this,prev,elem);
};

ChainListSorted.Node.prototype = Object.create (ChainList.Node.prototype);

ChainListSorted.Node.prototype.extend({
	constructor: ChainListSorted.Node,
	insert: function(elem) {
		return this.list().insert(elem);
	}
});<|endoftext|>


<commit_before>import jQuery from 'jquery';

import plugins from './plugins';

const csrfCookieName = window.csrfCookieName || 'sc';

// setup jquery for CSRF tokens
function getCookie(name) {
  let cookieValue = null;
  if (document.cookie && document.cookie !== '') {
    let cookies = document.cookie.split(';');
    for (let i = 0; i < cookies.length; i++) {
      let cookie = jQuery.trim(cookies[i]);
      // Does this cookie string begin with the name we want?
      if (cookie.substring(0, name.length + 1) == (name + '=')) {
        cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
        break;
      }
    }
  }
  return cookieValue;
}

function csrfSafeMethod(method) {
  // these HTTP methods do not require CSRF protection
  return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}

jQuery.ajaxSetup({
  beforeSend: function(xhr, settings) {
    if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
      xhr.setRequestHeader('X-CSRFToken', getCookie(csrfCookieName));
    }
  }
});

// these get exported to a global variable, which is important as its the only
// way we can call into scoped objects

export default {
  jQuery: jQuery,
  moment: require('moment'),
  Raven: require('raven-js'),
  React: require('react'),
  ReactDOM: require('react-dom'),
  Reflux: require('reflux'),
  Router: require('react-router'),
  underscore: require('underscore'),

  Sentry: {
    api: require('./api'),
    routes: require('./routes'),
    plugins: {
      add: plugins.add,
      BasePlugin: plugins.BasePlugin,
      DefaultIssuePlugin: plugins.DefaultIssuePlugin
    },

    createHistory: require('history/lib/createBrowserHistory'),
    Alerts: require('./components/alerts'),
    AlertActions: require('./actions/alertActions'),
    AvatarSettings: require('./components/avatarSettings'),
    mixins: {
      ApiMixin: require('./mixins/apiMixin'),
    },
    BarChart: require('./components/barChart'),
    i18n: require('./locale'),
    ConfigStore: require('./stores/configStore'),
    Count: require('./components/count'),
    DateTime: require('./components/dateTime'),
    DropdownLink: require('./components/dropdownLink'),
    FlotChart: require('./components/flotChart'),
    Form: require('./components/forms/form'),
    FormState: require('./components/forms/index').FormState,
    HookStore: require('./stores/hookStore'),
    Indicators: require('./components/indicators'),
    IndicatorStore: require('./stores/indicatorStore'),
    LoadingError: require('./components/loadingError'),
    LoadingIndicator: require('./components/loadingIndicator'),
    ListLink: require('./components/listLink'),
    MenuItem: require('./components/menuItem'),
    OrganizationHomeContainer: require('./components/organizations/homeContainer'),
    OrganizationsLoader: require('./components/organizations/organizationsLoader'),
    Pagination: require('./components/pagination'),
    PluginConfig: require('./components/pluginConfig'),
    ProjectIssueTracking: require('./views/projectIssueTracking'),
    ProjectSelector: require('./components/projectHeader/projectSelector'),
    RuleEditor: require('./views/ruleEditor'),
    Sidebar: require('./components/sidebar'),
    StackedBarChart: require('./components/stackedBarChart'),
    TimeSince: require('./components/timeSince'),
    TodoList: require('./components/todos'),
    U2fEnrollment: require('./components/u2fenrollment'),
    U2fSign: require('./components/u2fsign'),
    utils: {
      errorHandler: require('./utils/errorHandler')
    }
  }
};
<commit_msg>expose tooltip mixin for new billing page (#4397)<commit_after>import jQuery from 'jquery';

import plugins from './plugins';

const csrfCookieName = window.csrfCookieName || 'sc';

// setup jquery for CSRF tokens
function getCookie(name) {
  let cookieValue = null;
  if (document.cookie && document.cookie !== '') {
    let cookies = document.cookie.split(';');
    for (let i = 0; i < cookies.length; i++) {
      let cookie = jQuery.trim(cookies[i]);
      // Does this cookie string begin with the name we want?
      if (cookie.substring(0, name.length + 1) == (name + '=')) {
        cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
        break;
      }
    }
  }
  return cookieValue;
}

function csrfSafeMethod(method) {
  // these HTTP methods do not require CSRF protection
  return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}

jQuery.ajaxSetup({
  beforeSend: function(xhr, settings) {
    if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
      xhr.setRequestHeader('X-CSRFToken', getCookie(csrfCookieName));
    }
  }
});

// these get exported to a global variable, which is important as its the only
// way we can call into scoped objects

export default {
  jQuery: jQuery,
  moment: require('moment'),
  Raven: require('raven-js'),
  React: require('react'),
  ReactDOM: require('react-dom'),
  Reflux: require('reflux'),
  Router: require('react-router'),
  underscore: require('underscore'),

  Sentry: {
    api: require('./api'),
    routes: require('./routes'),
    plugins: {
      add: plugins.add,
      BasePlugin: plugins.BasePlugin,
      DefaultIssuePlugin: plugins.DefaultIssuePlugin
    },

    createHistory: require('history/lib/createBrowserHistory'),
    Alerts: require('./components/alerts'),
    AlertActions: require('./actions/alertActions'),
    AvatarSettings: require('./components/avatarSettings'),
    mixins: {
      ApiMixin: require('./mixins/apiMixin'),
      TooltipMixin: require('./mixins/tooltip')
    },
    BarChart: require('./components/barChart'),
    i18n: require('./locale'),
    ConfigStore: require('./stores/configStore'),
    Count: require('./components/count'),
    DateTime: require('./components/dateTime'),
    DropdownLink: require('./components/dropdownLink'),
    FlotChart: require('./components/flotChart'),
    Form: require('./components/forms/form'),
    FormState: require('./components/forms/index').FormState,
    HookStore: require('./stores/hookStore'),
    Indicators: require('./components/indicators'),
    IndicatorStore: require('./stores/indicatorStore'),
    LoadingError: require('./components/loadingError'),
    LoadingIndicator: require('./components/loadingIndicator'),
    ListLink: require('./components/listLink'),
    MenuItem: require('./components/menuItem'),
    OrganizationHomeContainer: require('./components/organizations/homeContainer'),
    OrganizationsLoader: require('./components/organizations/organizationsLoader'),
    Pagination: require('./components/pagination'),
    PluginConfig: require('./components/pluginConfig'),
    ProjectIssueTracking: require('./views/projectIssueTracking'),
    ProjectSelector: require('./components/projectHeader/projectSelector'),
    RuleEditor: require('./views/ruleEditor'),
    Sidebar: require('./components/sidebar'),
    StackedBarChart: require('./components/stackedBarChart'),
    TimeSince: require('./components/timeSince'),
    TodoList: require('./components/todos'),
    U2fEnrollment: require('./components/u2fenrollment'),
    U2fSign: require('./components/u2fsign'),
    utils: {
      errorHandler: require('./utils/errorHandler')
    }
  }
};
<|endoftext|>


<commit_before>/*
Copyright 2012 - $Date $ by PeopleWare n.v.

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

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

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

define([],
  function() {

    function typeOf(obj) {
      // summary:
      //   A better type then Object.toString() or typeof.
      // description:
      //      toType(undefined); //"undefined"
      //      toType(new); //"null"
      //      toType({a: 4}); //"object"
      //      toType([1, 2, 3]); //"array"
      //      (function() {console.log(toType(arguments))})(); //arguments
      //      toType(new ReferenceError); //"error"
      //      toType(new Date); //"date"
      //      toType(/a-z/); //"regexp"
      //      toType(Math); //"math"
      //      toType(JSON); //"json"
      //      toType(new Number(4)); //"number"
      //      toType(new String("abc")); //"string"
      //      toType(new Boolean(true)); //"boolean"

      /* based on
       http://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/
       */

      if (obj === null) {
        // workaround for eopdf; it seems that js.typeOf(null) is returned by eopdf as "domwindow"
        return "null";
      }
      var result = Object.prototype.toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase();
      // on some browsers, the main window returns as "global" (WebKit) or "window" (FF), but this is an object too
      if (result === "global" || result == "window") {
        result = "object";
      }
      return result; // return String
    }

    function getPrototypeChain(/*Object*/ obj) {
      // summary:
      //   Returns an array where result[i] is the prototype
      //   of result[i-1], with obj === result[0]. result[length]
      //   is thus Object.prototype.

      function recursive(acc, o) {
        if (!o) {
          return acc;
        }
        else {
          acc.push(o);
          return recursive(acc, Object.getPrototypeOf(o));
        }
      }

      return recursive([], obj);
    }

    function getAllKeys(/*Object*/ obj) {
      // summary:
      //   Returns an array containing the names of all given objects enumerable properties.
      //   Like keys, but for the entire prototype chain. The array starts with the properties
      //   of Object.prototype, and works down the chain.

      return getPrototypeChain(obj).reduceRight(
        function(acc, proto) {
          return nub(acc.concat(Object.keys(proto)));
        },
        []
      );
    }

    function isInt(/*Number*/ n) {
      // summary:
      //   True if n is an integer.
      //   n must be a number.

      return n % 1 === 0;
    }

    function nub(/*Array*/ array, /*Boolean?*/ removeNull) {
      // summary:
      //   Returns an array that has all the elements of `array`, in order, but with later duplicates removed.
      //   If `removeNull` is given and thruthy, nulls and undefineds are removed too.

      if (!array) {
        return array;
      }
      var result = array.reduce(
        function(acc, el) {
          if ((!removeNull || el) && acc.indexOf(el) < 0) {
            acc.push(el);
          }
          return acc;
        },
        []
      );
      return result;
    }

    function substitute(/*String*/ str, /*Object*/ context) {
      // summary:
      //  All occurrences of "${...}$" in `str` are replaced by the result of the
      //  execution of what is between the curly brackets as JavaScript, with `context`
      //  as `this`.

      //   Like dojo/string.substitute, but evals full code.
      //   No formatting (yet).
      return str.replace( // return String
        /\$\{(.+?)\}\$/gm,
        function(match, pattern) {
          var value;
          try {
            var executor = new Function("return (" + pattern + ");");
            value = executor.call(context);
          }
          catch (err) {
            logger.error("Executing pattern substitution of ${" + pattern + "}$ in '" + str + "' with context " + context, err);
            return "?? ${" + pattern  + "}$ -- " + (err.message || err) + " ??";
          }
          return value || value === "" ? value.toString() : "?" + pattern  + "?";
        }
      );
    }

    function sortComparable(/*Object*/ c1, /*Object*/ c2) {
      // summary:
      //   A sort function for objects that have a compare function.
      //   undefined < null < the compare function of the objects

      return c1 === undefined ? (
               c2 === undefined ? 0 : -1
             )
             : (
               c1 === null ? (
                 c2 === undefined ?
                   +1 :
                   c2 === null ? 0 : -1
               )
               : (
                 !c2 ? +1 : c1.compare(c2)
               )
             );
    }

    function sortReversed(/*Function*/ sort) {
      // summary:
      //   Produces a compare function that does the reverse sort defined by sort.

      return function(o1, o2) {
        return -sort(o1, o2);
      }
    }

    function sortVersionNumbers(/*Array*/ arrayToSort) {
      /**
       * Compares two software version numbers (e.g. "1.7.1" or "1.2b").
       *
       * This function was born in http://stackoverflow.com/a/6832721.
       *
       * @param {string} v1 The first version to be compared.
       * @param {string} v2 The second version to be compared.
       * @param {object} [options] Optional flags that affect comparison behavior:
       *

       *

       *         <tt>lexicographical: true</tt> compares each part of the version strings lexicographically instead of
       *         naturally; this allows suffixes such as "b" or "dev" but will cause "1.10" to be considered smaller than
       *         "1.2".
       *

       *

       *         <tt>zeroExtend: true</tt> changes the result if one version string has less parts than the other. In
       *         this case the shorter string will be padded with "zero" parts instead of being considered smaller.
       *

       *

       * @returns {number|NaN}
       *

       *
0 if the versions are equal

       *
a negative integer iff v1 < v2

       *
a positive integer iff v1 > v2

       *
NaN if either version string is in the wrong format

       *

       *
       * @copyright by Jon Papaioannou (["john", "papaioannou"].join(".") + "@gmail.com")
       * @license This function is in the public domain. Do what you want with it, no strings attached.
       */
      arrayToSort.sort(function(/*String*/ v1, /*String*/ v2, /*Object*/ options) {
        var lexicographical = options && options.lexicographical,
          zeroExtend = options && options.zeroExtend,
          v1parts = v1.split('.'),
          v2parts = v2.split('.');

        function isValidPart(x) {
          return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x);
        }

        if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) {
          return NaN;
        }

        if (zeroExtend) {
          while (v1parts.length < v2parts.length) v1parts.push("0");
          while (v2parts.length < v1parts.length) v2parts.push("0");
        }

        if (!lexicographical) {
          v1parts = v1parts.map(Number);
          v2parts = v2parts.map(Number);
        }

        for (var i = 0; i < v1parts.length; ++i) {
          if (v2parts.length == i) {
            return 1;
          }

          if (v1parts[i] == v2parts[i]) {
            // continue; go to next loop cycle
          }
          else if (v1parts[i] > v2parts[i]) {
            return 1;
          }
          else {
            return -1;
          }
        }

        if (v1parts.length != v2parts.length) {
          return -1;
        }

        return 0;
      });
    }

    function string2CharCode(str) {
      // summary:
      //   Transform a string into an array of CharCodes.

      return str.split("").map(function(c) {return c.charCodeAt(0);});
    }

    function charCode2String(charCodes) {
      // summary:
      //   Transform an array of CharCodes into a String.

      return charCodes.map(function(charCode) {return String.fromCharCode(charCode);}).join("");
    }

    var MAX_INT = 9007199254740992;
    var MIN_INT = -9007199254740992;

    // From https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/random
    // Returns a random integer between min and max
    // Using Math.round() will give you a non-uniform distribution!
    function randomInt(min, max) {
      if (!max && max !== 0) {
        max = MAX_INT;
      }
      if (!min && min !== 0) {
        min = MIN_INT;
      }
      return Math.floor(Math.random() * (max - min + 1)) + min;
    }

    function randomInts(min, max, nr) {
      var result = [];
      var r = undefined;
      for (var i = 0; i < nr; i++) {
        while (!r || result.some(function(earlierR) {//noinspection JSReferencingMutableVariableFromClosure
          return r === earlierR;})) {
          r = randomInt(min, max);
        }
        result[i] = r;
        r = undefined;
      }
      return result;
    }

    function haveSameElements(a1, a2) {
      // summary:
      //   Returns true if array a1 and array a2 have the samen elements, irrespective of their order.
      //   Duplicates are allowed.

      return a1.every(function(a1i) {return a2.indexOf(a1i) >= 0;}) &&
        a2.every(function(a2i) {return a1.indexOf(a2i) >= 0;});
    }

    function flatten(/*Array*/ arr) {
      return arr.reduce(
        function(acc, el) {
          if (typeOf(el) === "array") {
            return acc.concat(flatten(el));
          }
          else {
            acc.push(el);
            return acc;
          }
        },
        []
      );
    }

    function compactPath(path){
      // summary:
      //   Function copied from require.compactPath. It is not available there after a build.
      // Description:
      //   Normalizes a path.
      //   A '.' is removed from the path.
      //   '..' is optimized by removing the preceding part from the path.

      var result = [],
        segment, lastSegment;
      path = path.replace(/\\/g, '/').split('/');
      while(path.length){
        segment = path.shift();
        if(segment==".." && result.length && lastSegment!=".."){
          result.pop();
          lastSegment = result[result.length - 1];
        }else if(segment!="."){
          result.push(lastSegment= segment);
        } // else ignore "."
      }
      return result.join("/");
    }

    var js = {
      // summary:
      //   Methods to aid with the JavaScript language.
      typeOf: typeOf,
      getPrototypeChain: getPrototypeChain,
      getAllKeys: getAllKeys,
      isInt: isInt,
      nub: nub,
      flatten: flatten,
      substitute: substitute,
      sortComparable: sortComparable,
      sortReversed: sortReversed,
      sortVersionNumbers: sortVersionNumbers,
      string2CharCode: string2CharCode,
      charCode2String: charCode2String,
      MAX_INT: MAX_INT,
      MIN_INT: MIN_INT,
      randomInt: randomInt,
      randomInts: randomInts,
      haveSameElements: haveSameElements,
      compactPath: compactPath
    };

    return js;
  }
);
<commit_msg>PICTOPERFECT-731 js.compactPath() now ignores .. once it reached the beginning of the URL and there is nothing to remove.<commit_after>/*
Copyright 2012 - $Date $ by PeopleWare n.v.

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

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

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

define([],
  function() {

    function typeOf(obj) {
      // summary:
      //   A better type then Object.toString() or typeof.
      // description:
      //      toType(undefined); //"undefined"
      //      toType(new); //"null"
      //      toType({a: 4}); //"object"
      //      toType([1, 2, 3]); //"array"
      //      (function() {console.log(toType(arguments))})(); //arguments
      //      toType(new ReferenceError); //"error"
      //      toType(new Date); //"date"
      //      toType(/a-z/); //"regexp"
      //      toType(Math); //"math"
      //      toType(JSON); //"json"
      //      toType(new Number(4)); //"number"
      //      toType(new String("abc")); //"string"
      //      toType(new Boolean(true)); //"boolean"

      /* based on
       http://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/
       */

      if (obj === null) {
        // workaround for eopdf; it seems that js.typeOf(null) is returned by eopdf as "domwindow"
        return "null";
      }
      var result = Object.prototype.toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase();
      // on some browsers, the main window returns as "global" (WebKit) or "window" (FF), but this is an object too
      if (result === "global" || result == "window") {
        result = "object";
      }
      return result; // return String
    }

    function getPrototypeChain(/*Object*/ obj) {
      // summary:
      //   Returns an array where result[i] is the prototype
      //   of result[i-1], with obj === result[0]. result[length]
      //   is thus Object.prototype.

      function recursive(acc, o) {
        if (!o) {
          return acc;
        }
        else {
          acc.push(o);
          return recursive(acc, Object.getPrototypeOf(o));
        }
      }

      return recursive([], obj);
    }

    function getAllKeys(/*Object*/ obj) {
      // summary:
      //   Returns an array containing the names of all given objects enumerable properties.
      //   Like keys, but for the entire prototype chain. The array starts with the properties
      //   of Object.prototype, and works down the chain.

      return getPrototypeChain(obj).reduceRight(
        function(acc, proto) {
          return nub(acc.concat(Object.keys(proto)));
        },
        []
      );
    }

    function isInt(/*Number*/ n) {
      // summary:
      //   True if n is an integer.
      //   n must be a number.

      return n % 1 === 0;
    }

    function nub(/*Array*/ array, /*Boolean?*/ removeNull) {
      // summary:
      //   Returns an array that has all the elements of `array`, in order, but with later duplicates removed.
      //   If `removeNull` is given and thruthy, nulls and undefineds are removed too.

      if (!array) {
        return array;
      }
      var result = array.reduce(
        function(acc, el) {
          if ((!removeNull || el) && acc.indexOf(el) < 0) {
            acc.push(el);
          }
          return acc;
        },
        []
      );
      return result;
    }

    function substitute(/*String*/ str, /*Object*/ context) {
      // summary:
      //  All occurrences of "${...}$" in `str` are replaced by the result of the
      //  execution of what is between the curly brackets as JavaScript, with `context`
      //  as `this`.

      //   Like dojo/string.substitute, but evals full code.
      //   No formatting (yet).
      return str.replace( // return String
        /\$\{(.+?)\}\$/gm,
        function(match, pattern) {
          var value;
          try {
            var executor = new Function("return (" + pattern + ");");
            value = executor.call(context);
          }
          catch (err) {
            logger.error("Executing pattern substitution of ${" + pattern + "}$ in '" + str + "' with context " + context, err);
            return "?? ${" + pattern  + "}$ -- " + (err.message || err) + " ??";
          }
          return value || value === "" ? value.toString() : "?" + pattern  + "?";
        }
      );
    }

    function sortComparable(/*Object*/ c1, /*Object*/ c2) {
      // summary:
      //   A sort function for objects that have a compare function.
      //   undefined < null < the compare function of the objects

      return c1 === undefined ? (
               c2 === undefined ? 0 : -1
             )
             : (
               c1 === null ? (
                 c2 === undefined ?
                   +1 :
                   c2 === null ? 0 : -1
               )
               : (
                 !c2 ? +1 : c1.compare(c2)
               )
             );
    }

    function sortReversed(/*Function*/ sort) {
      // summary:
      //   Produces a compare function that does the reverse sort defined by sort.

      return function(o1, o2) {
        return -sort(o1, o2);
      }
    }

    function sortVersionNumbers(/*Array*/ arrayToSort) {
      /**
       * Compares two software version numbers (e.g. "1.7.1" or "1.2b").
       *
       * This function was born in http://stackoverflow.com/a/6832721.
       *
       * @param {string} v1 The first version to be compared.
       * @param {string} v2 The second version to be compared.
       * @param {object} [options] Optional flags that affect comparison behavior:
       *

       *

       *         <tt>lexicographical: true</tt> compares each part of the version strings lexicographically instead of
       *         naturally; this allows suffixes such as "b" or "dev" but will cause "1.10" to be considered smaller than
       *         "1.2".
       *

       *

       *         <tt>zeroExtend: true</tt> changes the result if one version string has less parts than the other. In
       *         this case the shorter string will be padded with "zero" parts instead of being considered smaller.
       *

       *

       * @returns {number|NaN}
       *

       *
0 if the versions are equal

       *
a negative integer iff v1 < v2

       *
a positive integer iff v1 > v2

       *
NaN if either version string is in the wrong format

       *

       *
       * @copyright by Jon Papaioannou (["john", "papaioannou"].join(".") + "@gmail.com")
       * @license This function is in the public domain. Do what you want with it, no strings attached.
       */
      arrayToSort.sort(function(/*String*/ v1, /*String*/ v2, /*Object*/ options) {
        var lexicographical = options && options.lexicographical,
          zeroExtend = options && options.zeroExtend,
          v1parts = v1.split('.'),
          v2parts = v2.split('.');

        function isValidPart(x) {
          return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x);
        }

        if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) {
          return NaN;
        }

        if (zeroExtend) {
          while (v1parts.length < v2parts.length) v1parts.push("0");
          while (v2parts.length < v1parts.length) v2parts.push("0");
        }

        if (!lexicographical) {
          v1parts = v1parts.map(Number);
          v2parts = v2parts.map(Number);
        }

        for (var i = 0; i < v1parts.length; ++i) {
          if (v2parts.length == i) {
            return 1;
          }

          if (v1parts[i] == v2parts[i]) {
            // continue; go to next loop cycle
          }
          else if (v1parts[i] > v2parts[i]) {
            return 1;
          }
          else {
            return -1;
          }
        }

        if (v1parts.length != v2parts.length) {
          return -1;
        }

        return 0;
      });
    }

    function string2CharCode(str) {
      // summary:
      //   Transform a string into an array of CharCodes.

      return str.split("").map(function(c) {return c.charCodeAt(0);});
    }

    function charCode2String(charCodes) {
      // summary:
      //   Transform an array of CharCodes into a String.

      return charCodes.map(function(charCode) {return String.fromCharCode(charCode);}).join("");
    }

    var MAX_INT = 9007199254740992;
    var MIN_INT = -9007199254740992;

    // From https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/random
    // Returns a random integer between min and max
    // Using Math.round() will give you a non-uniform distribution!
    function randomInt(min, max) {
      if (!max && max !== 0) {
        max = MAX_INT;
      }
      if (!min && min !== 0) {
        min = MIN_INT;
      }
      return Math.floor(Math.random() * (max - min + 1)) + min;
    }

    function randomInts(min, max, nr) {
      var result = [];
      var r = undefined;
      for (var i = 0; i < nr; i++) {
        while (!r || result.some(function(earlierR) {//noinspection JSReferencingMutableVariableFromClosure
          return r === earlierR;})) {
          r = randomInt(min, max);
        }
        result[i] = r;
        r = undefined;
      }
      return result;
    }

    function haveSameElements(a1, a2) {
      // summary:
      //   Returns true if array a1 and array a2 have the samen elements, irrespective of their order.
      //   Duplicates are allowed.

      return a1.every(function(a1i) {return a2.indexOf(a1i) >= 0;}) &&
        a2.every(function(a2i) {return a1.indexOf(a2i) >= 0;});
    }

    function flatten(/*Array*/ arr) {
      return arr.reduce(
        function(acc, el) {
          if (typeOf(el) === "array") {
            return acc.concat(flatten(el));
          }
          else {
            acc.push(el);
            return acc;
          }
        },
        []
      );
    }

    function compactPath(path) {
      // summary:
      //   Function copied from require.compactPath. It is not available there after a build.
      // Description:
      //   Normalizes a path.
      //   A '.' is removed from the path.
      //   '..' is optimized by removing the preceding part from the path.

      var result = [],
        segment, lastSegment;
      path = path.replace(/\\/g, '/').split('/');
      while(path.length) {
        segment = path.shift();
        if(segment === ".." && result.length && lastSegment !== "..") {
          result.pop();
          lastSegment = result[result.length - 1];
        } else if(segment!=="." && segment !== "..") {
          result.push(lastSegment = segment);
        } // else ignore "."
      }
      return result.join("/");
    }

    var js = {
      // summary:
      //   Methods to aid with the JavaScript language.
      typeOf: typeOf,
      getPrototypeChain: getPrototypeChain,
      getAllKeys: getAllKeys,
      isInt: isInt,
      nub: nub,
      flatten: flatten,
      substitute: substitute,
      sortComparable: sortComparable,
      sortReversed: sortReversed,
      sortVersionNumbers: sortVersionNumbers,
      string2CharCode: string2CharCode,
      charCode2String: charCode2String,
      MAX_INT: MAX_INT,
      MIN_INT: MIN_INT,
      randomInt: randomInt,
      randomInts: randomInts,
      haveSameElements: haveSameElements,
      compactPath: compactPath
    };

    return js;
  }
);
<|endoftext|>


<commit_before>import Vue from 'vue';
import VueResource from 'vue-resource';

import bp from '~/breakpoints';
import ProjectsService from '~/projects_dropdown/service/projects_service';
import { FREQUENT_PROJECTS } from '~/projects_dropdown/constants';
import { currentSession, unsortedFrequents, sortedFrequents } from '../mock_data';

Vue.use(VueResource);

FREQUENT_PROJECTS.MAX_COUNT = 3;

describe('ProjectsService', () => {
  let service;

  beforeEach(() => {
    gon.api_version = currentSession.apiVersion;
    service = new ProjectsService(currentSession.username);
  });

  describe('contructor', () => {
    it('should initialize default properties of class', () => {
      expect(service.isLocalStorageAvailable).toBeTruthy();
      expect(service.currentUserName).toBe(currentSession.username);
      expect(service.storageKey).toBe(currentSession.storageKey);
      expect(service.projectsPath).toBeDefined();
    });
  });

  describe('getSearchedProjects', () => {
    it('should return promise from VueResource HTTP GET', () => {
      spyOn(service.projectsPath, 'get').and.stub();

      const searchQuery = 'lab';
      const queryParams = {
        simple: false,
        per_page: 20,
        membership: false,
        order_by: 'last_activity_at',
        search: searchQuery,
      };

      service.getSearchedProjects(searchQuery);
      expect(service.projectsPath.get).toHaveBeenCalledWith(queryParams);
    });
  });

  describe('logProjectAccess', () => {
    let storage;

    beforeEach(() => {
      storage = {};

      spyOn(window.localStorage, 'setItem').and.callFake((storageKey, value) => {
        storage[storageKey] = value;
      });

      spyOn(window.localStorage, 'getItem').and.callFake((storageKey) => {
        if (storage[storageKey]) {
          return storage[storageKey];
        }

        return null;
      });
    });

    it('should create a project store if it does not exist and adds a project', () => {
      service.logProjectAccess(currentSession.project);

      const projects = JSON.parse(storage[currentSession.storageKey]);
      expect(projects.length).toBe(1);
      expect(projects[0].frequency).toBe(1);
      expect(projects[0].lastAccessedOn).toBeDefined();
    });

    it('should prevent inserting same report multiple times into store', () => {
      service.logProjectAccess(currentSession.project);
      service.logProjectAccess(currentSession.project);

      const projects = JSON.parse(storage[currentSession.storageKey]);
      expect(projects.length).toBe(1);
    });

    it('should increase frequency of report if it was logged multiple times over the course of an hour', () => {
      let projects;
      spyOn(Math, 'abs').and.returnValue(3600001); // this will lead to `diff` > 1;
      service.logProjectAccess(currentSession.project);

      projects = JSON.parse(storage[currentSession.storageKey]);
      expect(projects[0].frequency).toBe(1);

      service.logProjectAccess(currentSession.project);
      projects = JSON.parse(storage[currentSession.storageKey]);
      expect(projects[0].frequency).toBe(2);
      expect(projects[0].lastAccessedOn).not.toBe(currentSession.project.lastAccessedOn);
    });

    it('should always update project metadata', () => {
      let projects;
      const oldProject = {
        ...currentSession.project,
      };

      const newProject = {
        ...currentSession.project,
        name: 'New Name',
        avatarUrl: 'new/avatar.png',
        namespace: 'New / Namespace',
        webUrl: 'http://localhost/new/web/url',
      };

      service.logProjectAccess(oldProject);
      projects = JSON.parse(storage[currentSession.storageKey]);
      expect(projects[0].name).toBe(oldProject.name);
      expect(projects[0].avatarUrl).toBe(oldProject.avatarUrl);
      expect(projects[0].namespace).toBe(oldProject.namespace);
      expect(projects[0].webUrl).toBe(oldProject.webUrl);

      service.logProjectAccess(newProject);
      projects = JSON.parse(storage[currentSession.storageKey]);
      expect(projects[0].name).toBe(newProject.name);
      expect(projects[0].avatarUrl).toBe(newProject.avatarUrl);
      expect(projects[0].namespace).toBe(newProject.namespace);
      expect(projects[0].webUrl).toBe(newProject.webUrl);
    });

    it('should not add more than 20 projects in store', () => {
      for (let i = 1; i <= 5; i += 1) {
        const project = Object.assign(currentSession.project, { id: i });
        service.logProjectAccess(project);
      }

      const projects = JSON.parse(storage[currentSession.storageKey]);
      expect(projects.length).toBe(3);
    });
  });

  describe('getTopFrequentProjects', () => {
    let storage = {};

    beforeEach(() => {
      storage[currentSession.storageKey] = JSON.stringify(unsortedFrequents);

      spyOn(window.localStorage, 'getItem').and.callFake((storageKey) => {
        if (storage[storageKey]) {
          return storage[storageKey];
        }

        return null;
      });
    });

    it('should return top 5 frequently accessed projects for desktop screens', () => {
      spyOn(bp, 'getBreakpointSize').and.returnValue('md');
      const frequentProjects = service.getTopFrequentProjects();

      expect(frequentProjects.length).toBe(5);
      frequentProjects.forEach((project, index) => {
        expect(project.id).toBe(sortedFrequents[index].id);
      });
    });

    it('should return top 3 frequently accessed projects for mobile screens', () => {
      spyOn(bp, 'getBreakpointSize').and.returnValue('sm');
      const frequentProjects = service.getTopFrequentProjects();

      expect(frequentProjects.length).toBe(3);
      frequentProjects.forEach((project, index) => {
        expect(project.id).toBe(sortedFrequents[index].id);
      });
    });

    it('should return empty array if there are no projects available in store', () => {
      storage = {};
      expect(service.getTopFrequentProjects().length).toBe(0);
    });
  });
});
<commit_msg>Fix flaky behaviour of `getSearchedProjects` due to missing `gon.current_user_id`<commit_after>import Vue from 'vue';
import VueResource from 'vue-resource';

import bp from '~/breakpoints';
import ProjectsService from '~/projects_dropdown/service/projects_service';
import { FREQUENT_PROJECTS } from '~/projects_dropdown/constants';
import { currentSession, unsortedFrequents, sortedFrequents } from '../mock_data';

Vue.use(VueResource);

FREQUENT_PROJECTS.MAX_COUNT = 3;

describe('ProjectsService', () => {
  let service;

  beforeEach(() => {
    gon.api_version = currentSession.apiVersion;
    gon.current_user_id = 1;
    service = new ProjectsService(currentSession.username);
  });

  describe('contructor', () => {
    it('should initialize default properties of class', () => {
      expect(service.isLocalStorageAvailable).toBeTruthy();
      expect(service.currentUserName).toBe(currentSession.username);
      expect(service.storageKey).toBe(currentSession.storageKey);
      expect(service.projectsPath).toBeDefined();
    });
  });

  describe('getSearchedProjects', () => {
    it('should return promise from VueResource HTTP GET', () => {
      spyOn(service.projectsPath, 'get').and.stub();

      const searchQuery = 'lab';
      const queryParams = {
        simple: false,
        per_page: 20,
        membership: true,
        order_by: 'last_activity_at',
        search: searchQuery,
      };

      service.getSearchedProjects(searchQuery);
      expect(service.projectsPath.get).toHaveBeenCalledWith(queryParams);
    });
  });

  describe('logProjectAccess', () => {
    let storage;

    beforeEach(() => {
      storage = {};

      spyOn(window.localStorage, 'setItem').and.callFake((storageKey, value) => {
        storage[storageKey] = value;
      });

      spyOn(window.localStorage, 'getItem').and.callFake((storageKey) => {
        if (storage[storageKey]) {
          return storage[storageKey];
        }

        return null;
      });
    });

    it('should create a project store if it does not exist and adds a project', () => {
      service.logProjectAccess(currentSession.project);

      const projects = JSON.parse(storage[currentSession.storageKey]);
      expect(projects.length).toBe(1);
      expect(projects[0].frequency).toBe(1);
      expect(projects[0].lastAccessedOn).toBeDefined();
    });

    it('should prevent inserting same report multiple times into store', () => {
      service.logProjectAccess(currentSession.project);
      service.logProjectAccess(currentSession.project);

      const projects = JSON.parse(storage[currentSession.storageKey]);
      expect(projects.length).toBe(1);
    });

    it('should increase frequency of report if it was logged multiple times over the course of an hour', () => {
      let projects;
      spyOn(Math, 'abs').and.returnValue(3600001); // this will lead to `diff` > 1;
      service.logProjectAccess(currentSession.project);

      projects = JSON.parse(storage[currentSession.storageKey]);
      expect(projects[0].frequency).toBe(1);

      service.logProjectAccess(currentSession.project);
      projects = JSON.parse(storage[currentSession.storageKey]);
      expect(projects[0].frequency).toBe(2);
      expect(projects[0].lastAccessedOn).not.toBe(currentSession.project.lastAccessedOn);
    });

    it('should always update project metadata', () => {
      let projects;
      const oldProject = {
        ...currentSession.project,
      };

      const newProject = {
        ...currentSession.project,
        name: 'New Name',
        avatarUrl: 'new/avatar.png',
        namespace: 'New / Namespace',
        webUrl: 'http://localhost/new/web/url',
      };

      service.logProjectAccess(oldProject);
      projects = JSON.parse(storage[currentSession.storageKey]);
      expect(projects[0].name).toBe(oldProject.name);
      expect(projects[0].avatarUrl).toBe(oldProject.avatarUrl);
      expect(projects[0].namespace).toBe(oldProject.namespace);
      expect(projects[0].webUrl).toBe(oldProject.webUrl);

      service.logProjectAccess(newProject);
      projects = JSON.parse(storage[currentSession.storageKey]);
      expect(projects[0].name).toBe(newProject.name);
      expect(projects[0].avatarUrl).toBe(newProject.avatarUrl);
      expect(projects[0].namespace).toBe(newProject.namespace);
      expect(projects[0].webUrl).toBe(newProject.webUrl);
    });

    it('should not add more than 20 projects in store', () => {
      for (let i = 1; i <= 5; i += 1) {
        const project = Object.assign(currentSession.project, { id: i });
        service.logProjectAccess(project);
      }

      const projects = JSON.parse(storage[currentSession.storageKey]);
      expect(projects.length).toBe(3);
    });
  });

  describe('getTopFrequentProjects', () => {
    let storage = {};

    beforeEach(() => {
      storage[currentSession.storageKey] = JSON.stringify(unsortedFrequents);

      spyOn(window.localStorage, 'getItem').and.callFake((storageKey) => {
        if (storage[storageKey]) {
          return storage[storageKey];
        }

        return null;
      });
    });

    it('should return top 5 frequently accessed projects for desktop screens', () => {
      spyOn(bp, 'getBreakpointSize').and.returnValue('md');
      const frequentProjects = service.getTopFrequentProjects();

      expect(frequentProjects.length).toBe(5);
      frequentProjects.forEach((project, index) => {
        expect(project.id).toBe(sortedFrequents[index].id);
      });
    });

    it('should return top 3 frequently accessed projects for mobile screens', () => {
      spyOn(bp, 'getBreakpointSize').and.returnValue('sm');
      const frequentProjects = service.getTopFrequentProjects();

      expect(frequentProjects.length).toBe(3);
      frequentProjects.forEach((project, index) => {
        expect(project.id).toBe(sortedFrequents[index].id);
      });
    });

    it('should return empty array if there are no projects available in store', () => {
      storage = {};
      expect(service.getTopFrequentProjects().length).toBe(0);
    });
  });
});
<|endoftext|>


<commit_before>function KobocatFormPublisherDirective($restApi, $miscUtils, $routeTo) {
    return {
        scope: {
            item: '='
        },
        templateUrl: staticFilesUri + 'templates/KobocatFormPublisher.Template.html',
        link: function (scope, element, attributes) {
            var dialog = element.find('.forms__kobocat__publisher');
            scope.publish = function () {
                function success (results, headers) {
                    scope.close();
                    $miscUtils.alert('Survey Publishing succeeded');
                    $routeTo.external(results.published_form_url);
                }
                function fail (response) {
                    scope.show_form_name_exists_message = true;
                    scope.error_message = 'Survey Publishing failed: ' + (response.data.text || response.data.error);
                }

                var id = scope.form_name ? dkobo_xlform.model.utils.sluggifyLabel(scope.form_name) : '';
                $restApi.createSurveyDraftApi(scope.item.id)
                    .publish({title:scope.form_label, id_string: id}, success, fail);
            };

            scope.form_label = scope.item.name;

            scope.open = function () {
                scope.show_publisher = true;
                dialog.dialog('open');
            };
            scope.close = function () {
                scope.show_publisher = false;
                dialog.dialog('close');
            };
            scope.show_publisher = false;

            scope.show_form_name_exists_message = false;
            scope.get_form_id = function (item) {
                if (item.formid) {
                    return item.formid;
                }
                if (item.body) {
                    var s = dkobo_xlform.model.Survey.create(item.body)
                    return s.settings.get('form_id');
                }
            };
            scope.form_name = scope.get_form_id(scope.item);

            dialog.dialog({
                modal: true,
                height: 400,
                width: 600,
                autoOpen: false,
                title: 'Publish Survey',
                draggable: false,
                resizable: false,
                buttons: {
                    'Done': scope.publish,
                    'Cancel': scope.close
                }
            });
        }
    }
}<commit_msg>Improved deployment modal title<commit_after>function KobocatFormPublisherDirective($restApi, $miscUtils, $routeTo) {
    return {
        scope: {
            item: '='
        },
        templateUrl: staticFilesUri + 'templates/KobocatFormPublisher.Template.html',
        link: function (scope, element, attributes) {
            var dialog = element.find('.forms__kobocat__publisher');
            scope.publish = function () {
                function success (results, headers) {
                    scope.close();
                    $miscUtils.alert('Survey Publishing succeeded');
                    $routeTo.external(results.published_form_url);
                }
                function fail (response) {
                    scope.show_form_name_exists_message = true;
                    scope.error_message = 'Survey Publishing failed: ' + (response.data.text || response.data.error);
                }

                var id = scope.form_name ? dkobo_xlform.model.utils.sluggifyLabel(scope.form_name) : '';
                $restApi.createSurveyDraftApi(scope.item.id)
                    .publish({title:scope.form_label, id_string: id}, success, fail);
            };

            scope.form_label = scope.item.name;

            scope.open = function () {
                scope.show_publisher = true;
                dialog.dialog('open');
            };
            scope.close = function () {
                scope.show_publisher = false;
                dialog.dialog('close');
            };
            scope.show_publisher = false;

            scope.show_form_name_exists_message = false;
            scope.get_form_id = function (item) {
                if (item.formid) {
                    return item.formid;
                }
                if (item.body) {
                    var s = dkobo_xlform.model.Survey.create(item.body)
                    return s.settings.get('form_id');
                }
            };
            scope.form_name = scope.get_form_id(scope.item);

            dialog.dialog({
                modal: true,
                height: 400,
                width: 600,
                autoOpen: false,
                title: 'Deploy as New Survey Project',
                draggable: false,
                resizable: false,
                buttons: {
                    'Done': scope.publish,
                    'Cancel': scope.close
                }
            });
        }
    }
}
<|endoftext|>


<commit_before>import React, { PureComponent } from 'react';
import moment from 'moment';
import validate from 'validate.js';
import head from 'lodash.head';
import { Button, InputGroup, ServerErrorMsg, ErrorMsg } from 'binary-components';
import { getConstraints } from './SettingsSelfExclusion.validation.config';
import UpdateNotice from '../containers/UpdateNotice';
import { api } from '../_data/LiveData';

export default class SettingsSelfExclusion extends PureComponent {

	props: {
		max_balance: number,
		max_turnover: number,
		max_losses: number,
		max_7day_turnover: number,
		max_7day_losses: number,
		max_30day_turnover: number,
		max_30day_losses: number,
		max_open_bets: number,
		session_duration_limit: number,
		timeout_until: number,
		exclude_until: string,
	};

	constructor(props) {
    super(props);
    this.state = {
      max_balance: props.max_balance,
      max_turnover: props.max_turnover,
      max_losses: props.max_losses,
      max_7day_turnover: props.max_7day_turnover,
      max_7day_losses: props.max_7day_losses,
      max_30day_turnover: props.max_30day_turnover,
      max_30day_losses: props.max_30day_losses,
      max_open_bets: props.max_open_bets,
      session_duration_limit: props.session_duration_limit,
      timeout_until_time: props.timeout_until,
      timeout_until_date: props.timeout_until,
      exclude_until: props.exclude_until,
      touched: {
        max_balance: false,
        max_turnover: false,
        max_losses: false,
        max_7day_turnover: false,
        max_7day_losses: false,
        max_30day_turnover: false,
        max_30day_losses: false,
        max_open_bets: false,
        session_duration_limit: false,
        timeout_until_time: false,
        timeout_until_date: false,
        exclude_until: false,
      },
    };
  }

  onEntryChange = (e: SyntheticEvent) => {
    this.setState({
			[e.target.id]: e.target.value,
      touched: { ...this.state.touched, [e.target.id]: true },
      hasError: false,
		}, () => {
      this.validateTitle();
		});
  }

  validateTitle = () => {
    this.constraints = getConstraints(this.props, this.state);
    this.setState({
      errors: validate(this.state, this.constraints, { format: 'grouped', fullMessages: false, cleanAttributes: false }) || {},
    });
  }

    onFormSubmit = (e: SyntheticEvent) => {
        e.preventDefault();
				if (Object.keys(this.state.errors).length > 0) {
					this.setState({ hasError: true });
				} else {
					this.updateSelfExclusion();
				}
    }

	updateSelfExclusion = async () => {
		const { max_balance, max_turnover, max_losses, max_7day_turnover, max_7day_losses,
			max_30day_turnover, max_30day_losses, max_open_bets, session_duration_limit,
			timeout_until_time, timeout_until_date, exclude_until } = this.state;
		const timeout_until = moment(timeout_until_date + ' ' + timeout_until_time).valueOf() / 1000;
		const newSelfExclusionSettings = {
			max_balance,
			max_turnover,
			max_losses,
			max_7day_turnover,
			max_7day_losses,
			max_30day_turnover,
			max_30day_losses,
			max_open_bets,
			session_duration_limit,
			exclude_until,
			timeout_until,
		};
		try {
			await api.setSelfExclusion(newSelfExclusionSettings);
			this.setState({
				success: true,
				serverError: false,
			});
			setTimeout(() => {
				this.setState({ success: false });
				api.getSelfExclusion();
				api.getAccountLimits();
				if (exclude_until) {
					window.location.reload();
				}
			}, 3000);
		} catch (e) {
			this.setState({ serverError: e.error.error.message });
		}
	}

	render() {
		const { max_balance, max_turnover, max_losses, max_7day_turnover, max_7day_losses,
			max_30day_turnover, max_30day_losses, max_open_bets, session_duration_limit,
			exclude_until, timeout_until_date, timeout_until_time, success, serverError, hasError, touched, errors } = this.state;
    // const wrongExcludeUntillTime = isValidTime(timeout_until_time);
    return (
			<form className="settings-self-exclusion" onSubmit={this.onFormSubmit}>
			{serverError && <ServerErrorMsg text={serverError} />}
			{hasError && <ErrorMsg text="Please fill the form with valid values" />}
				<UpdateNotice text="Settings updated" show={success} />
				<InputGroup
					id="max_balance"
					name="max_balance"
					label="Maximum account cash balance"
					type="text"
					maxLength="20"
					// hint="Once this limit is reached, you may no longer deposit."
					value={max_balance}
					onChange={this.onEntryChange}
				/>
				{touched.max_balance && <ErrorMsg text={head((errors || {}).max_balance)} />}
				<InputGroup
					id="max_turnover"
					name="max_turnover"
					label="Daily turnover limit"
					type="text"
					maxLength="20"
					// hint="Maximum aggregate contract purchases per day."
					value={max_turnover}
					onChange={this.onEntryChange}
				/>
        {touched.max_turnover && <ErrorMsg text={head((errors || {}).max_turnover)} />}
				<InputGroup
					id="max_losses"
					name="max_losses"
					label="Daily limit on losses"
					type="text"
					maxLength="20"
					// hint="Maximum aggregate loss per day."
					value={max_losses}
					onChange={this.onEntryChange}
				/>
        {touched.max_losses && <ErrorMsg text={head((errors || {}).max_losses)} />}
				<InputGroup
					id="max_7day_turnover"
					name="max_7day_turnover"
					label="7-day turnover limit"
					type="text"
					maxLength="20"
					// hint="Maximum aggregate contract purchases over a 7-day period."
					value={max_7day_turnover}
					onChange={this.onEntryChange}
				/>
        {touched.max_7day_turnover && <ErrorMsg text={head((errors || {}).max_7day_turnover)} />}
				<InputGroup
					id="max_7day_losses"
					name="max_7day_losses"
					label="7-day limit on losses"
					type="text"
					maxLength="20"
					// hint="Maximum aggregate loss over a 7-day period."
					value={max_7day_losses}
					onChange={this.onEntryChange}
				/>
        {touched.max_7day_losses && <ErrorMsg text={head((errors || {}).max_7day_losses)} />}
				<InputGroup
					id="max_30day_turnover"
					name="max_30day_turnover"
					label="30-day turnover limit"
					type="text"
					maxLength="20"
					// hint="Maximum aggregate contract purchases over a 30-day period."
					value={max_30day_turnover}
					onChange={this.onEntryChange}
				/>
        {touched.max_30day_turnover && <ErrorMsg text={head((errors || {}).max_30day_turnover)} />}
				<InputGroup
					id="max_30day_losses"
					name="max_30day_losses"
					label="30-day limit on losses"
					type="text"
					maxLength="20"
					// hint="Maximum aggregate loss over a 30-day period."
					value={max_30day_losses}
					onChange={this.onEntryChange}
				/>
        {touched.max_30day_losses && <ErrorMsg text={head((errors || {}).max_30day_losses)} />}
				<InputGroup
					id="max_open_bets"
					name="max_open_bets"
					label="Maximum number of open positions"
					type="number"
					maxLength="4"
					value={max_open_bets}
					onChange={this.onEntryChange}
				/>
        {touched.max_open_bets && <ErrorMsg text={head((errors || {}).max_open_bets)} />}
				<InputGroup
					id="session_duration_limit"
					name="session_duration_limit"
					label="Session duration limit, in minutes"
					type="number"
					maxLength="5"
					// hint="You will be automatically logged out after such time."
					value={session_duration_limit}
					onChange={this.onEntryChange}
				/>
        {touched.session_duration_limit && <ErrorMsg text={head((errors || {}).session_duration_limit)} />}
				<InputGroup
					id="timeout_until_date"
					name="timeout_until_date"
					label="Time out until date"
					type="date"
					maxLength="10"
					value={timeout_until_date || 'yyyy-mm-dd'}
					onChange={this.onEntryChange}
				/>
        {touched.timeout_until_date && <ErrorMsg text={head((errors || {}).timeout_until_date)} />}
				<InputGroup
					id="timeout_until_time"
					name="timeout_until_time"
					label="Time out until time"
					type="time"
					maxLength="8"
					value={timeout_until_time || '--:--:--'}
					onChange={this.onEntryChange}
				/>
        {touched.timeout_until_time && <ErrorMsg text={head((errors || {}).timeout_until_time)} />}
				<InputGroup
					id="exclude_until"
					name="exclude_until"
					label="Exclude me from the website until"
					type="date"
					maxLength="10"
					value={exclude_until || 'yyyy-mm-dd'}
					onChange={this.onEntryChange}
				/>
        {touched.exclude_until && <ErrorMsg text={head((errors || {}).exclude_until)} />}
				<Button text="Update" />
			</form>
		);
	}
}
<commit_msg>fix function naming<commit_after>import React, { PureComponent } from 'react';
import moment from 'moment';
import validate from 'validate.js';
import head from 'lodash.head';
import { Button, InputGroup, ServerErrorMsg, ErrorMsg } from 'binary-components';
import { getConstraints } from './SettingsSelfExclusion.validation.config';
import UpdateNotice from '../containers/UpdateNotice';
import { api } from '../_data/LiveData';

export default class SettingsSelfExclusion extends PureComponent {

	props: {
		max_balance: number,
		max_turnover: number,
		max_losses: number,
		max_7day_turnover: number,
		max_7day_losses: number,
		max_30day_turnover: number,
		max_30day_losses: number,
		max_open_bets: number,
		session_duration_limit: number,
		timeout_until: number,
		exclude_until: string,
	};

	constructor(props) {
    super(props);
    this.state = {
      max_balance: props.max_balance,
      max_turnover: props.max_turnover,
      max_losses: props.max_losses,
      max_7day_turnover: props.max_7day_turnover,
      max_7day_losses: props.max_7day_losses,
      max_30day_turnover: props.max_30day_turnover,
      max_30day_losses: props.max_30day_losses,
      max_open_bets: props.max_open_bets,
      session_duration_limit: props.session_duration_limit,
      timeout_until_time: props.timeout_until,
      timeout_until_date: props.timeout_until,
      exclude_until: props.exclude_until,
      touched: {
        max_balance: false,
        max_turnover: false,
        max_losses: false,
        max_7day_turnover: false,
        max_7day_losses: false,
        max_30day_turnover: false,
        max_30day_losses: false,
        max_open_bets: false,
        session_duration_limit: false,
        timeout_until_time: false,
        timeout_until_date: false,
        exclude_until: false,
      },
    };
  }

  onEntryChange = (e: SyntheticEvent) => {
    this.setState({
			[e.target.id]: e.target.value,
      touched: { ...this.state.touched, [e.target.id]: true },
      hasError: false,
		}, () => {
      this.validateForm();
		});
  }

  validateForm = () => {
    this.constraints = getConstraints(this.props, this.state);
    this.setState({
      errors: validate(this.state, this.constraints, { format: 'grouped', fullMessages: false, cleanAttributes: false }) || {},
    });
  }

    onFormSubmit = (e: SyntheticEvent) => {
        e.preventDefault();
				if (Object.keys(this.state.errors).length > 0) {
					this.setState({ hasError: true });
				} else {
					this.updateSelfExclusion();
				}
    }

	updateSelfExclusion = async () => {
		const { max_balance, max_turnover, max_losses, max_7day_turnover, max_7day_losses,
			max_30day_turnover, max_30day_losses, max_open_bets, session_duration_limit,
			timeout_until_time, timeout_until_date, exclude_until } = this.state;
		const timeout_until = moment(timeout_until_date + ' ' + timeout_until_time).valueOf() / 1000;
		const newSelfExclusionSettings = {
			max_balance,
			max_turnover,
			max_losses,
			max_7day_turnover,
			max_7day_losses,
			max_30day_turnover,
			max_30day_losses,
			max_open_bets,
			session_duration_limit,
			exclude_until,
			timeout_until,
		};
		try {
			await api.setSelfExclusion(newSelfExclusionSettings);
			this.setState({
				success: true,
				serverError: false,
			});
			setTimeout(() => {
				this.setState({ success: false });
				api.getSelfExclusion();
				api.getAccountLimits();
				if (exclude_until) {
					window.location.reload();
				}
			}, 3000);
		} catch (e) {
			this.setState({ serverError: e.error.error.message });
		}
	}

	render() {
		const { max_balance, max_turnover, max_losses, max_7day_turnover, max_7day_losses,
			max_30day_turnover, max_30day_losses, max_open_bets, session_duration_limit,
			exclude_until, timeout_until_date, timeout_until_time, success, serverError, hasError, touched, errors } = this.state;
    // const wrongExcludeUntillTime = isValidTime(timeout_until_time);
    return (
			<form className="settings-self-exclusion" onSubmit={this.onFormSubmit}>
			{serverError && <ServerErrorMsg text={serverError} />}
			{hasError && <ErrorMsg text="Please fill the form with valid values" />}
				<UpdateNotice text="Settings updated" show={success} />
				<InputGroup
					id="max_balance"
					name="max_balance"
					label="Maximum account cash balance"
					type="text"
					maxLength="20"
					// hint="Once this limit is reached, you may no longer deposit."
					value={max_balance}
					onChange={this.onEntryChange}
				/>
				{touched.max_balance && <ErrorMsg text={head((errors || {}).max_balance)} />}
				<InputGroup
					id="max_turnover"
					name="max_turnover"
					label="Daily turnover limit"
					type="text"
					maxLength="20"
					// hint="Maximum aggregate contract purchases per day."
					value={max_turnover}
					onChange={this.onEntryChange}
				/>
        {touched.max_turnover && <ErrorMsg text={head((errors || {}).max_turnover)} />}
				<InputGroup
					id="max_losses"
					name="max_losses"
					label="Daily limit on losses"
					type="text"
					maxLength="20"
					// hint="Maximum aggregate loss per day."
					value={max_losses}
					onChange={this.onEntryChange}
				/>
        {touched.max_losses && <ErrorMsg text={head((errors || {}).max_losses)} />}
				<InputGroup
					id="max_7day_turnover"
					name="max_7day_turnover"
					label="7-day turnover limit"
					type="text"
					maxLength="20"
					// hint="Maximum aggregate contract purchases over a 7-day period."
					value={max_7day_turnover}
					onChange={this.onEntryChange}
				/>
        {touched.max_7day_turnover && <ErrorMsg text={head((errors || {}).max_7day_turnover)} />}
				<InputGroup
					id="max_7day_losses"
					name="max_7day_losses"
					label="7-day limit on losses"
					type="text"
					maxLength="20"
					// hint="Maximum aggregate loss over a 7-day period."
					value={max_7day_losses}
					onChange={this.onEntryChange}
				/>
        {touched.max_7day_losses && <ErrorMsg text={head((errors || {}).max_7day_losses)} />}
				<InputGroup
					id="max_30day_turnover"
					name="max_30day_turnover"
					label="30-day turnover limit"
					type="text"
					maxLength="20"
					// hint="Maximum aggregate contract purchases over a 30-day period."
					value={max_30day_turnover}
					onChange={this.onEntryChange}
				/>
        {touched.max_30day_turnover && <ErrorMsg text={head((errors || {}).max_30day_turnover)} />}
				<InputGroup
					id="max_30day_losses"
					name="max_30day_losses"
					label="30-day limit on losses"
					type="text"
					maxLength="20"
					// hint="Maximum aggregate loss over a 30-day period."
					value={max_30day_losses}
					onChange={this.onEntryChange}
				/>
        {touched.max_30day_losses && <ErrorMsg text={head((errors || {}).max_30day_losses)} />}
				<InputGroup
					id="max_open_bets"
					name="max_open_bets"
					label="Maximum number of open positions"
					type="number"
					maxLength="4"
					value={max_open_bets}
					onChange={this.onEntryChange}
				/>
        {touched.max_open_bets && <ErrorMsg text={head((errors || {}).max_open_bets)} />}
				<InputGroup
					id="session_duration_limit"
					name="session_duration_limit"
					label="Session duration limit, in minutes"
					type="number"
					maxLength="5"
					// hint="You will be automatically logged out after such time."
					value={session_duration_limit}
					onChange={this.onEntryChange}
				/>
        {touched.session_duration_limit && <ErrorMsg text={head((errors || {}).session_duration_limit)} />}
				<InputGroup
					id="timeout_until_date"
					name="timeout_until_date"
					label="Time out until date"
					type="date"
					maxLength="10"
					value={timeout_until_date || 'yyyy-mm-dd'}
					onChange={this.onEntryChange}
				/>
        {touched.timeout_until_date && <ErrorMsg text={head((errors || {}).timeout_until_date)} />}
				<InputGroup
					id="timeout_until_time"
					name="timeout_until_time"
					label="Time out until time"
					type="time"
					maxLength="8"
					value={timeout_until_time || '--:--:--'}
					onChange={this.onEntryChange}
				/>
        {touched.timeout_until_time && <ErrorMsg text={head((errors || {}).timeout_until_time)} />}
				<InputGroup
					id="exclude_until"
					name="exclude_until"
					label="Exclude me from the website until"
					type="date"
					maxLength="10"
					value={exclude_until || 'yyyy-mm-dd'}
					onChange={this.onEntryChange}
				/>
        {touched.exclude_until && <ErrorMsg text={head((errors || {}).exclude_until)} />}
				<Button text="Update" />
			</form>
		);
	}
}
<|endoftext|>