<commit_before>/**
 * @author mikael emtinger / http://gomo.se/
 * @author alteredq / http://alteredqualia.com/
 * @author WestLangley / http://github.com/WestLangley
 * @author bhouston / http://exocortex.com
 */

THREE.Quaternion = function ( x, y, z, w ) {

	this._x = x || 0;
	this._y = y || 0;
	this._z = z || 0;
	this._w = ( w !== undefined ) ? w : 1;

};

THREE.Quaternion.prototype = {

	constructor: THREE.Quaternion,

	_x: 0,_y: 0, _z: 0, _w: 0,

	_euler: undefined,

	_updateEuler: function ( callback ) {

		if ( this._euler !== undefined ) {

			this._euler.setFromQuaternion( this, undefined, false );

		}

	},

	get x () {

		return this._x;

	},

	set x ( value ) {

		this._x = value;
		this._updateEuler();

	},

	get y () {

		return this._y;

	},

	set y ( value ) {

		this._y = value;
		this._updateEuler();

	},

	get z () {

		return this._z;

	},

	set z ( value ) {

		this._z = value;
		this._updateEuler();

	},

	get w () {

		return this._w;

	},

	set w ( value ) {

		this._w = value;
		this._updateEuler();

	},

	set: function ( x, y, z, w ) {

		this._x = x;
		this._y = y;
		this._z = z;
		this._w = w;

		this._updateEuler();

		return this;

	},

	copy: function ( quaternion ) {

		this._x = quaternion._x;
		this._y = quaternion._y;
		this._z = quaternion._z;
		this._w = quaternion._w;

		this._updateEuler();

		return this;

	},

	setFromEuler: function ( euler, update ) {

		if ( euler instanceof THREE.Euler === false ) {

			throw new Error( 'ERROR: Quaternion\'s .setFromEuler() now expects a Euler rotation rather than a Vector3 and order.  Please update your code.' );
		}

		// http://www.mathworks.com/matlabcentral/fileexchange/
		// 	20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/
		//	content/SpinCalc.m

		var c1 = Math.cos( euler._x / 2 );
		var c2 = Math.cos( euler._y / 2 );
		var c3 = Math.cos( euler._z / 2 );
		var s1 = Math.sin( euler._x / 2 );
		var s2 = Math.sin( euler._y / 2 );
		var s3 = Math.sin( euler._z / 2 );

		if ( euler.order === 'XYZ' ) {

			this._x = s1 * c2 * c3 + c1 * s2 * s3;
			this._y = c1 * s2 * c3 - s1 * c2 * s3;
			this._z = c1 * c2 * s3 + s1 * s2 * c3;
			this._w = c1 * c2 * c3 - s1 * s2 * s3;

		} else if ( euler.order === 'YXZ' ) {

			this._x = s1 * c2 * c3 + c1 * s2 * s3;
			this._y = c1 * s2 * c3 - s1 * c2 * s3;
			this._z = c1 * c2 * s3 - s1 * s2 * c3;
			this._w = c1 * c2 * c3 + s1 * s2 * s3;

		} else if ( euler.order === 'ZXY' ) {

			this._x = s1 * c2 * c3 - c1 * s2 * s3;
			this._y = c1 * s2 * c3 + s1 * c2 * s3;
			this._z = c1 * c2 * s3 + s1 * s2 * c3;
			this._w = c1 * c2 * c3 - s1 * s2 * s3;

		} else if ( euler.order === 'ZYX' ) {

			this._x = s1 * c2 * c3 - c1 * s2 * s3;
			this._y = c1 * s2 * c3 + s1 * c2 * s3;
			this._z = c1 * c2 * s3 - s1 * s2 * c3;
			this._w = c1 * c2 * c3 + s1 * s2 * s3;

		} else if ( euler.order === 'YZX' ) {

			this._x = s1 * c2 * c3 + c1 * s2 * s3;
			this._y = c1 * s2 * c3 + s1 * c2 * s3;
			this._z = c1 * c2 * s3 - s1 * s2 * c3;
			this._w = c1 * c2 * c3 - s1 * s2 * s3;

		} else if ( euler.order === 'XZY' ) {

			this._x = s1 * c2 * c3 - c1 * s2 * s3;
			this._y = c1 * s2 * c3 - s1 * c2 * s3;
			this._z = c1 * c2 * s3 + s1 * s2 * c3;
			this._w = c1 * c2 * c3 + s1 * s2 * s3;

		}

		if ( update !== false ) this._updateEuler();

		return this;

	},

	setFromAxisAngle: function ( axis, angle ) {

		// http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm

		// assumes axis is normalized

		var halfAngle = angle / 2, s = Math.sin( halfAngle );

		this._x = axis.x * s;
		this._y = axis.y * s;
		this._z = axis.z * s;
		this._w = Math.cos( halfAngle );

		this._updateEuler();

		return this;

	},

	setFromRotationMatrix: function ( m ) {

		// http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm

		// assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)

		var te = m.elements,

			m11 = te[0], m12 = te[4], m13 = te[8],
			m21 = te[1], m22 = te[5], m23 = te[9],
			m31 = te[2], m32 = te[6], m33 = te[10],

			trace = m11 + m22 + m33,
			s;

		if ( trace > 0 ) {

			s = 0.5 / Math.sqrt( trace + 1.0 );

			this._w = 0.25 / s;
			this._x = ( m32 - m23 ) * s;
			this._y = ( m13 - m31 ) * s;
			this._z = ( m21 - m12 ) * s;

		} else if ( m11 > m22 && m11 > m33 ) {

			s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 );

			this._w = (m32 - m23 ) / s;
			this._x = 0.25 * s;
			this._y = (m12 + m21 ) / s;
			this._z = (m13 + m31 ) / s;

		} else if ( m22 > m33 ) {

			s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 );

			this._w = (m13 - m31 ) / s;
			this._x = (m12 + m21 ) / s;
			this._y = 0.25 * s;
			this._z = (m23 + m32 ) / s;

		} else {

			s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 );

			this._w = ( m21 - m12 ) / s;
			this._x = ( m13 + m31 ) / s;
			this._y = ( m23 + m32 ) / s;
			this._z = 0.25 * s;

		}

		this._updateEuler();

		return this;

	},

	setFromUnitVectors: function () {

		// http://lolengine.net/blog/2013/09/18/beautiful-maths-quaternion-from-vectors

		// assumes direction vectors vFrom and vTo are normalized

		var v1;

		return function( vFrom, vTo ) {

			if ( v1 === undefined ) v1 = new THREE.Vector3();

			v1.crossVectors( vFrom, vTo );

			this.set( v1.x, v1.y, v1.z, vFrom.dot( vTo ) + 1 ).normalize();

			this._updateEuler();

			return this;

		}

	}(),

	inverse: function () {

		this.conjugate().normalize();

		return this;

	},

	conjugate: function () {

		this._x *= -1;
		this._y *= -1;
		this._z *= -1;

		this._updateEuler();

		return this;

	},

	lengthSq: function () {

		return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w;

	},

	length: function () {

		return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w );

	},

	normalize: function () {

		var l = this.length();

		if ( l === 0 ) {

			this._x = 0;
			this._y = 0;
			this._z = 0;
			this._w = 1;

		} else {

			l = 1 / l;

			this._x = this._x * l;
			this._y = this._y * l;
			this._z = this._z * l;
			this._w = this._w * l;

		}

		return this;

	},

	multiply: function ( q, p ) {

		if ( p !== undefined ) {

			console.warn( 'DEPRECATED: Quaternion\'s .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead.' );
			return this.multiplyQuaternions( q, p );

		}

		return this.multiplyQuaternions( this, q );

	},

	multiplyQuaternions: function ( a, b ) {

		// from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm

		var qax = a._x, qay = a._y, qaz = a._z, qaw = a._w;
		var qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w;

		this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby;
		this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz;
		this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx;
		this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz;

		this._updateEuler();

		return this;

	},

	multiplyVector3: function ( vector ) {

		console.warn( 'DEPRECATED: Quaternion\'s .multiplyVector3() has been removed. Use is now vector.applyQuaternion( quaternion ) instead.' );
		return vector.applyQuaternion( this );

	},

	slerp: function ( qb, t ) {

		var x = this._x, y = this._y, z = this._z, w = this._w;

		// http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/

		var cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z;

		if ( cosHalfTheta < 0 ) {

			this._w = -qb._w;
			this._x = -qb._x;
			this._y = -qb._y;
			this._z = -qb._z;

			cosHalfTheta = -cosHalfTheta;

		} else {

			this.copy( qb );

		}

		if ( cosHalfTheta >= 1.0 ) {

			this._w = w;
			this._x = x;
			this._y = y;
			this._z = z;

			return this;

		}

		var halfTheta = Math.acos( cosHalfTheta );
		var sinHalfTheta = Math.sqrt( 1.0 - cosHalfTheta * cosHalfTheta );

		if ( Math.abs( sinHalfTheta ) < 0.001 ) {

			this._w = 0.5 * ( w + this._w );
			this._x = 0.5 * ( x + this._x );
			this._y = 0.5 * ( y + this._y );
			this._z = 0.5 * ( z + this._z );

			return this;

		}

		var ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta,
		ratioB = Math.sin( t * halfTheta ) / sinHalfTheta;

		this._w = ( w * ratioA + this._w * ratioB );
		this._x = ( x * ratioA + this._x * ratioB );
		this._y = ( y * ratioA + this._y * ratioB );
		this._z = ( z * ratioA + this._z * ratioB );

		this._updateEuler();

		return this;

	},

	equals: function ( quaternion ) {

		return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w );

	},

	fromArray: function ( array ) {

		this._x = array[ 0 ];
		this._y = array[ 1 ];
		this._z = array[ 2 ];
		this._w = array[ 3 ];

		this._updateEuler();

		return this;

	},

	toArray: function () {

		return [ this._x, this._y, this._z, this._w ];

	},

	clone: function () {

		return new THREE.Quaternion( this._x, this._y, this._z, this._w );

	}

};

THREE.Quaternion.slerp = function ( qa, qb, qm, t ) {

	return qm.copy( qa ).slerp( qb, t );

}
<commit_msg>Quaternion.setFromUnitVectors(): Fixed edge case<commit_after>/**
 * @author mikael emtinger / http://gomo.se/
 * @author alteredq / http://alteredqualia.com/
 * @author WestLangley / http://github.com/WestLangley
 * @author bhouston / http://exocortex.com
 */

THREE.Quaternion = function ( x, y, z, w ) {

	this._x = x || 0;
	this._y = y || 0;
	this._z = z || 0;
	this._w = ( w !== undefined ) ? w : 1;

};

THREE.Quaternion.prototype = {

	constructor: THREE.Quaternion,

	_x: 0,_y: 0, _z: 0, _w: 0,

	_euler: undefined,

	_updateEuler: function ( callback ) {

		if ( this._euler !== undefined ) {

			this._euler.setFromQuaternion( this, undefined, false );

		}

	},

	get x () {

		return this._x;

	},

	set x ( value ) {

		this._x = value;
		this._updateEuler();

	},

	get y () {

		return this._y;

	},

	set y ( value ) {

		this._y = value;
		this._updateEuler();

	},

	get z () {

		return this._z;

	},

	set z ( value ) {

		this._z = value;
		this._updateEuler();

	},

	get w () {

		return this._w;

	},

	set w ( value ) {

		this._w = value;
		this._updateEuler();

	},

	set: function ( x, y, z, w ) {

		this._x = x;
		this._y = y;
		this._z = z;
		this._w = w;

		this._updateEuler();

		return this;

	},

	copy: function ( quaternion ) {

		this._x = quaternion._x;
		this._y = quaternion._y;
		this._z = quaternion._z;
		this._w = quaternion._w;

		this._updateEuler();

		return this;

	},

	setFromEuler: function ( euler, update ) {

		if ( euler instanceof THREE.Euler === false ) {

			throw new Error( 'ERROR: Quaternion\'s .setFromEuler() now expects a Euler rotation rather than a Vector3 and order.  Please update your code.' );
		}

		// http://www.mathworks.com/matlabcentral/fileexchange/
		// 	20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/
		//	content/SpinCalc.m

		var c1 = Math.cos( euler._x / 2 );
		var c2 = Math.cos( euler._y / 2 );
		var c3 = Math.cos( euler._z / 2 );
		var s1 = Math.sin( euler._x / 2 );
		var s2 = Math.sin( euler._y / 2 );
		var s3 = Math.sin( euler._z / 2 );

		if ( euler.order === 'XYZ' ) {

			this._x = s1 * c2 * c3 + c1 * s2 * s3;
			this._y = c1 * s2 * c3 - s1 * c2 * s3;
			this._z = c1 * c2 * s3 + s1 * s2 * c3;
			this._w = c1 * c2 * c3 - s1 * s2 * s3;

		} else if ( euler.order === 'YXZ' ) {

			this._x = s1 * c2 * c3 + c1 * s2 * s3;
			this._y = c1 * s2 * c3 - s1 * c2 * s3;
			this._z = c1 * c2 * s3 - s1 * s2 * c3;
			this._w = c1 * c2 * c3 + s1 * s2 * s3;

		} else if ( euler.order === 'ZXY' ) {

			this._x = s1 * c2 * c3 - c1 * s2 * s3;
			this._y = c1 * s2 * c3 + s1 * c2 * s3;
			this._z = c1 * c2 * s3 + s1 * s2 * c3;
			this._w = c1 * c2 * c3 - s1 * s2 * s3;

		} else if ( euler.order === 'ZYX' ) {

			this._x = s1 * c2 * c3 - c1 * s2 * s3;
			this._y = c1 * s2 * c3 + s1 * c2 * s3;
			this._z = c1 * c2 * s3 - s1 * s2 * c3;
			this._w = c1 * c2 * c3 + s1 * s2 * s3;

		} else if ( euler.order === 'YZX' ) {

			this._x = s1 * c2 * c3 + c1 * s2 * s3;
			this._y = c1 * s2 * c3 + s1 * c2 * s3;
			this._z = c1 * c2 * s3 - s1 * s2 * c3;
			this._w = c1 * c2 * c3 - s1 * s2 * s3;

		} else if ( euler.order === 'XZY' ) {

			this._x = s1 * c2 * c3 - c1 * s2 * s3;
			this._y = c1 * s2 * c3 - s1 * c2 * s3;
			this._z = c1 * c2 * s3 + s1 * s2 * c3;
			this._w = c1 * c2 * c3 + s1 * s2 * s3;

		}

		if ( update !== false ) this._updateEuler();

		return this;

	},

	setFromAxisAngle: function ( axis, angle ) {

		// http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm

		// assumes axis is normalized

		var halfAngle = angle / 2, s = Math.sin( halfAngle );

		this._x = axis.x * s;
		this._y = axis.y * s;
		this._z = axis.z * s;
		this._w = Math.cos( halfAngle );

		this._updateEuler();

		return this;

	},

	setFromRotationMatrix: function ( m ) {

		// http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm

		// assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)

		var te = m.elements,

			m11 = te[0], m12 = te[4], m13 = te[8],
			m21 = te[1], m22 = te[5], m23 = te[9],
			m31 = te[2], m32 = te[6], m33 = te[10],

			trace = m11 + m22 + m33,
			s;

		if ( trace > 0 ) {

			s = 0.5 / Math.sqrt( trace + 1.0 );

			this._w = 0.25 / s;
			this._x = ( m32 - m23 ) * s;
			this._y = ( m13 - m31 ) * s;
			this._z = ( m21 - m12 ) * s;

		} else if ( m11 > m22 && m11 > m33 ) {

			s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 );

			this._w = (m32 - m23 ) / s;
			this._x = 0.25 * s;
			this._y = (m12 + m21 ) / s;
			this._z = (m13 + m31 ) / s;

		} else if ( m22 > m33 ) {

			s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 );

			this._w = (m13 - m31 ) / s;
			this._x = (m12 + m21 ) / s;
			this._y = 0.25 * s;
			this._z = (m23 + m32 ) / s;

		} else {

			s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 );

			this._w = ( m21 - m12 ) / s;
			this._x = ( m13 + m31 ) / s;
			this._y = ( m23 + m32 ) / s;
			this._z = 0.25 * s;

		}

		this._updateEuler();

		return this;

	},

	setFromUnitVectors: function () {

		// http://lolengine.net/blog/2014/02/24/quaternion-from-two-vectors-final

		// assumes direction vectors vFrom and vTo are normalized

		var v1, r;

		var EPS = 0.000001;

		return function( vFrom, vTo ) {

			if ( v1 === undefined ) v1 = new THREE.Vector3();

			r = vFrom.dot( vTo ) + 1;

			if ( r < EPS ) {

				r = 0;

				if ( Math.abs( vFrom.x ) > Math.abs( vFrom.z ) ) {

					v1.set( - vFrom.y, vFrom.x, 0 );

				} else {

					v1.set( 0, - vFrom.z, vFrom.y );

				}

			} else {

				v1.crossVectors( vFrom, vTo );

			}

			this.set( v1.x, v1.y, v1.z, r ).normalize();

			this._updateEuler();

			return this;

		}

	}(),

	inverse: function () {

		this.conjugate().normalize();

		return this;

	},

	conjugate: function () {

		this._x *= -1;
		this._y *= -1;
		this._z *= -1;

		this._updateEuler();

		return this;

	},

	lengthSq: function () {

		return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w;

	},

	length: function () {

		return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w );

	},

	normalize: function () {

		var l = this.length();

		if ( l === 0 ) {

			this._x = 0;
			this._y = 0;
			this._z = 0;
			this._w = 1;

		} else {

			l = 1 / l;

			this._x = this._x * l;
			this._y = this._y * l;
			this._z = this._z * l;
			this._w = this._w * l;

		}

		return this;

	},

	multiply: function ( q, p ) {

		if ( p !== undefined ) {

			console.warn( 'DEPRECATED: Quaternion\'s .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead.' );
			return this.multiplyQuaternions( q, p );

		}

		return this.multiplyQuaternions( this, q );

	},

	multiplyQuaternions: function ( a, b ) {

		// from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm

		var qax = a._x, qay = a._y, qaz = a._z, qaw = a._w;
		var qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w;

		this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby;
		this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz;
		this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx;
		this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz;

		this._updateEuler();

		return this;

	},

	multiplyVector3: function ( vector ) {

		console.warn( 'DEPRECATED: Quaternion\'s .multiplyVector3() has been removed. Use is now vector.applyQuaternion( quaternion ) instead.' );
		return vector.applyQuaternion( this );

	},

	slerp: function ( qb, t ) {

		var x = this._x, y = this._y, z = this._z, w = this._w;

		// http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/

		var cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z;

		if ( cosHalfTheta < 0 ) {

			this._w = -qb._w;
			this._x = -qb._x;
			this._y = -qb._y;
			this._z = -qb._z;

			cosHalfTheta = -cosHalfTheta;

		} else {

			this.copy( qb );

		}

		if ( cosHalfTheta >= 1.0 ) {

			this._w = w;
			this._x = x;
			this._y = y;
			this._z = z;

			return this;

		}

		var halfTheta = Math.acos( cosHalfTheta );
		var sinHalfTheta = Math.sqrt( 1.0 - cosHalfTheta * cosHalfTheta );

		if ( Math.abs( sinHalfTheta ) < 0.001 ) {

			this._w = 0.5 * ( w + this._w );
			this._x = 0.5 * ( x + this._x );
			this._y = 0.5 * ( y + this._y );
			this._z = 0.5 * ( z + this._z );

			return this;

		}

		var ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta,
		ratioB = Math.sin( t * halfTheta ) / sinHalfTheta;

		this._w = ( w * ratioA + this._w * ratioB );
		this._x = ( x * ratioA + this._x * ratioB );
		this._y = ( y * ratioA + this._y * ratioB );
		this._z = ( z * ratioA + this._z * ratioB );

		this._updateEuler();

		return this;

	},

	equals: function ( quaternion ) {

		return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w );

	},

	fromArray: function ( array ) {

		this._x = array[ 0 ];
		this._y = array[ 1 ];
		this._z = array[ 2 ];
		this._w = array[ 3 ];

		this._updateEuler();

		return this;

	},

	toArray: function () {

		return [ this._x, this._y, this._z, this._w ];

	},

	clone: function () {

		return new THREE.Quaternion( this._x, this._y, this._z, this._w );

	}

};

THREE.Quaternion.slerp = function ( qa, qb, qm, t ) {

	return qm.copy( qa ).slerp( qb, t );

}
<|endoftext|>


<commit_before><commit_msg>Documentation fix<commit_after><|endoftext|>


<commit_before>import utils from './utils';

const { getParent } = utils;

export default function isAncestorFixed(element) {
  if (utils.isWindow(element) || element.nodeType === 9) {
    return false;
  }

  const doc = utils.getDocument(element);
  const body = doc.body;
  let parent = null;
  for (
    parent = getParent(element);
    parent && parent !== body;
    parent = getParent(parent)
  ) {
    const positionStyle = utils.css(parent, 'position');
    if (positionStyle === 'fixed') {
      return true;
    }
  }
  return false;
}
<commit_msg>fix(isAncestorFixed):fix isAncestorFixed crash caused by document.documentElement<commit_after>import utils from './utils';

const { getParent } = utils;

export default function isAncestorFixed(element) {
  if (utils.isWindow(element) || element.nodeType === 9) {
    return false;
  }

  const doc = utils.getDocument(element);
  const body = doc.body;
  let parent = null;
  for (
    parent = getParent(element);
    // 修复元素位于 document.documentElement 下导致崩溃问题
    parent && parent !== body && parent !== doc;
    parent = getParent(parent)
  ) {
    const positionStyle = utils.css(parent, 'position');
    if (positionStyle === 'fixed') {
      return true;
    }
  }
  return false;
}
<|endoftext|>


<commit_before>var autoSuggest = (function() {

  var _timer_autoSuggest = null;
  var _cuurentInput;

  function _delayRender(input) {
    render(input);
  };

  function bind() {
    var all_autoSuggest = helper.eA(".js-auto-suggest");
    for (var i = 0; i < all_autoSuggest.length; i++) {
      _bind_autoSuggest(all_autoSuggest[i]);
    };
  };

  function _bind_autoSuggest(autoSuggest) {
    var input = autoSuggest.querySelector(".js-auto-suggest-field");
    if (input) {
      input.addEventListener("input", function() {
        clearTimeout(_timer_autoSuggest);
        _timer_autoSuggest = setTimeout(_delayRender, 200, this);
      }, false);
      input.addEventListener("keydown", function(event) {
        if (event.keyCode == 13) {
          destroy(this);
        };
      }, false);
    };
  };

  function _navigateResults(event) {
    var elementToFocus;
    var currentFocus = null;
    var all_anchor = helper.eA(".js-auto-suggest-link");
    var _findInput = function() {
      if (event.target.classList.contains("js-auto-suggest-field")) {
        _cuurentInput = event.target;
      };
    };
    var _findFocus = function() {
      for (var i = 0; i < all_anchor.length; i++) {
        if (all_anchor[i] == document.activeElement) {
          currentFocus = i;
        };
      };
    };
    _findInput();
    _findFocus();
    // down key or tab key
    if (event.keyCode == 40 || event.keyCode == 9) {
      event.preventDefault();
      if (currentFocus == null) {
        elementToFocus = all_anchor[0];
      } else {
        if (currentFocus < all_anchor.length - 1) {
          elementToFocus = all_anchor[currentFocus + 1];
        } else {
          elementToFocus = all_anchor[currentFocus];
        };
      };
      elementToFocus.focus();
    };
    // up key or tab and shift key
    if (event.keyCode == 38 || event.keyCode == 9 && event.shiftKey) {
      event.preventDefault();
      if (currentFocus == null) {
        elementToFocus = _cuurentInput;
      } else {
        if (currentFocus == 0) {
          elementToFocus = _cuurentInput;
        } else if (currentFocus > 0) {
          elementToFocus = all_anchor[currentFocus - 1];
        } else {
          elementToFocus = all_anchor[0];
        };
      };
      elementToFocus.focus();
    };
  };

  function _addDocumentEvent() {
    document.addEventListener("click", _checkClick, false);
    document.addEventListener("keydown", _navigateResults, false);
  };

  function _removeDocumentEvent() {
    document.removeEventListener("click", _checkClick, false);
    document.removeEventListener("keydown", _navigateResults, false);
  };

  function _checkClick(event) {
    if (!(event.target.classList.contains("js-auto-suggest-field"))) {
      destroy();
    };
  };

  function destroy() {
    var autoSuggestList = helper.e(".js-auto-suggest-list");
    if (autoSuggestList) {
      autoSuggestList.remove();
    };
    _removeDocumentEvent();
  };

  function render(input) {
    _cuurentInput = input;
    var searchTerm = _cuurentInput.value.replace(/^\s+/, "").replace(/\s+$/, "");
    var body = helper.e("body");
    var autoSuggest = helper.getClosest(_cuurentInput, ".js-auto-suggest");
    var autoSuggestOptions = helper.makeObject(autoSuggest.dataset.autoSuggestOptions);
    var suggestItems;

    var _populateList = function(list) {
      var _populateSpells = function() {
        suggestItems.forEach(function(arrayItem) {
          var li = document.createElement("li");
          li.setAttribute("class", "m-auto-suggest-list-item");

          var anchor = document.createElement("a");
          anchor.setAttribute("href", "javascript:void(0)");
          anchor.setAttribute("tabindex", 1);
          anchor.setAttribute("class", "m-auto-suggest-link js-auto-suggest-link");
          anchor.setAttribute("data-spells-data", "index:#" + arrayItem.index);
          anchor.addEventListener("click", function() {
            if (autoSuggestOptions.type == "spells") {
              spells.add(_cuurentInput, arrayItem.index);
            };
            destroy();
            sheet.store();
          }, false);

          var string = arrayItem.name;

          var text = document.createElement("span");
          text.setAttribute("class", "m-auto-suggest-text");

          var result = document.createElement("span");
          result.setAttribute("class", "m-auto-suggest-result");

          var resultPartOneString = string.substr(0, (string.toLowerCase().indexOf(searchTerm.toLowerCase())));
          var resultPartHighlightString = string.substr((string.toLowerCase().indexOf(searchTerm.toLowerCase())), searchTerm.length);
          var resultPartTwoString = string.substr(((string.toLowerCase().indexOf(searchTerm.toLowerCase())) + searchTerm.length));

          if (resultPartOneString.length > 0) {
            var resultPartOne = document.createElement("strong");
            resultPartOne.setAttribute("class", "m-auto-suggest-part");
            resultPartOne.textContent = resultPartOneString;
            result.appendChild(resultPartOne);
          };

          if (resultPartHighlightString.length > 0) {
            var resultPartHighlight = document.createElement("strong");
            resultPartHighlight.setAttribute("class", "m-auto-suggest-highlight");
            resultPartHighlight.textContent = resultPartHighlightString;
            result.appendChild(resultPartHighlight);
          };

          if (resultPartTwoString.length > 0) {
            var resultPartTwo = document.createElement("strong");
            resultPartTwo.setAttribute("class", "m-auto-suggest-part");
            resultPartTwo.textContent = resultPartTwoString;
            result.appendChild(resultPartTwo);
          };

          if (resultPartOneString.length > 0 || resultPartHighlightString.length > 0 || resultPartTwoString.length > 0) {
            text.appendChild(result);
          };

          if (arrayItem.school) {
            var resultMeta = document.createElement("i");
            resultMeta.setAttribute("class", "m-auto-suggest-result-meta");
            resultMeta.textContent = helper.capFirstLetter(arrayItem.school);
            text.appendChild(resultMeta);
          };

          anchor.appendChild(text);

          if (arrayItem.description) {
            var textSub = document.createElement("span");
            textSub.setAttribute("class", "m-auto-suggest-text-sub");
            textSub.textContent = arrayItem.description;
            anchor.appendChild(textSub);
          };

          li.appendChild(anchor);
          list.appendChild(li);

        });
      };
      if (autoSuggestOptions.type == "spells") {
        _populateSpells();
      };
    };

    var _render_autoSuggestList = function() {
      var autoSuggestList = helper.e(".js-auto-suggest-list");
      if (autoSuggestList) {
        while (autoSuggestList.lastChild) {
          autoSuggestList.removeChild(autoSuggestList.lastChild);
        };
      } else {
        var style = {
          left: _cuurentInput.getBoundingClientRect().left,
          top: _cuurentInput.getBoundingClientRect().bottom + window.scrollY,
          width: _cuurentInput.getBoundingClientRect().width
        };
        var autoSuggestList = document.createElement("ul");
        autoSuggestList.setAttribute("class", "m-auto-suggest-list u-list-unstyled js-auto-suggest-list");
        body.appendChild(autoSuggestList);
        autoSuggestList.setAttribute("style", "width: " + style.width + "px; top: " + style.top + "px; left: " + style.left + "px;");
        _addDocumentEvent();
      };
      _populateList(autoSuggestList);
    };

    if (searchTerm != "") {
      if (autoSuggestOptions.type == "spells") {
        suggestItems = spellsData.get({
          name: searchTerm
        });
      };
      if (suggestItems) {
        _render_autoSuggestList();
      } else {
        destroy();
      };
    } else {
      destroy();
    };
  };

  // exposed methods
  return {
    bind: bind,
    destroy: destroy
  };

})();
<commit_msg>[refactor] improve auto suggest focus after making a selection<commit_after>var autoSuggest = (function() {

  var _timer_autoSuggest = null;
  var _cuurentInput;

  function _delayRender(input) {
    render(input);
  };

  function bind() {
    var all_autoSuggest = helper.eA(".js-auto-suggest");
    for (var i = 0; i < all_autoSuggest.length; i++) {
      _bind_autoSuggest(all_autoSuggest[i]);
    };
  };

  function _bind_autoSuggest(autoSuggest) {
    var input = autoSuggest.querySelector(".js-auto-suggest-field");
    if (input) {
      input.addEventListener("input", function() {
        clearTimeout(_timer_autoSuggest);
        _timer_autoSuggest = setTimeout(_delayRender, 200, this);
      }, false);
      input.addEventListener("keydown", function(event) {
        if (event.keyCode == 13) {
          destroy(this);
        };
      }, false);
    };
  };

  function _navigateResults(event) {
    var elementToFocus;
    var currentFocus = null;
    var all_anchor = helper.eA(".js-auto-suggest-link");
    var _findInput = function() {
      if (event.target.classList.contains("js-auto-suggest-field")) {
        _cuurentInput = event.target;
      };
    };
    var _findFocus = function() {
      for (var i = 0; i < all_anchor.length; i++) {
        if (all_anchor[i] == document.activeElement) {
          currentFocus = i;
        };
      };
    };
    _findInput();
    _findFocus();
    // down key or tab key
    if (event.keyCode == 40 || event.keyCode == 9) {
      event.preventDefault();
      if (currentFocus == null) {
        elementToFocus = all_anchor[0];
      } else {
        if (currentFocus < all_anchor.length - 1) {
          elementToFocus = all_anchor[currentFocus + 1];
        } else {
          elementToFocus = all_anchor[currentFocus];
        };
      };
      elementToFocus.focus();
    };
    // up key or tab and shift key
    if (event.keyCode == 38 || event.keyCode == 9 && event.shiftKey) {
      event.preventDefault();
      if (currentFocus == null) {
        elementToFocus = _cuurentInput;
      } else {
        if (currentFocus == 0) {
          elementToFocus = _cuurentInput;
        } else if (currentFocus > 0) {
          elementToFocus = all_anchor[currentFocus - 1];
        } else {
          elementToFocus = all_anchor[0];
        };
      };
      elementToFocus.focus();
    };
  };

  function _addDocumentEvent() {
    document.addEventListener("click", _checkClick, false);
    document.addEventListener("keydown", _navigateResults, false);
  };

  function _removeDocumentEvent() {
    document.removeEventListener("click", _checkClick, false);
    document.removeEventListener("keydown", _navigateResults, false);
  };

  function _checkClick(event) {
    if (!(event.target.classList.contains("js-auto-suggest-field"))) {
      destroy();
    };
  };

  function destroy() {
    var autoSuggestList = helper.e(".js-auto-suggest-list");
    if (autoSuggestList) {
      autoSuggestList.remove();
    };
    _removeDocumentEvent();
  };

  function render(input) {
    _cuurentInput = input;
    var searchTerm = _cuurentInput.value.replace(/^\s+/, "").replace(/\s+$/, "");
    var body = helper.e("body");
    var autoSuggest = helper.getClosest(_cuurentInput, ".js-auto-suggest");
    var autoSuggestOptions = helper.makeObject(autoSuggest.dataset.autoSuggestOptions);
    var suggestItems;

    var _populateList = function(list) {
      var _populateSpells = function() {
        suggestItems.forEach(function(arrayItem) {
          var li = document.createElement("li");
          li.setAttribute("class", "m-auto-suggest-list-item");

          var anchor = document.createElement("a");
          anchor.setAttribute("href", "javascript:void(0)");
          anchor.setAttribute("tabindex", 1);
          anchor.setAttribute("class", "m-auto-suggest-link js-auto-suggest-link");
          anchor.setAttribute("data-spells-data", "index:#" + arrayItem.index);
          anchor.addEventListener("click", function() {
            if (autoSuggestOptions.type == "spells") {
              spells.add(_cuurentInput, arrayItem.index);
            };
            destroy();
            sheet.store();
            _cuurentInput.focus();
          }, false);

          var string = arrayItem.name;

          var text = document.createElement("span");
          text.setAttribute("class", "m-auto-suggest-text");

          var result = document.createElement("span");
          result.setAttribute("class", "m-auto-suggest-result");

          var resultPartOneString = string.substr(0, (string.toLowerCase().indexOf(searchTerm.toLowerCase())));
          var resultPartHighlightString = string.substr((string.toLowerCase().indexOf(searchTerm.toLowerCase())), searchTerm.length);
          var resultPartTwoString = string.substr(((string.toLowerCase().indexOf(searchTerm.toLowerCase())) + searchTerm.length));

          if (resultPartOneString.length > 0) {
            var resultPartOne = document.createElement("strong");
            resultPartOne.setAttribute("class", "m-auto-suggest-part");
            resultPartOne.textContent = resultPartOneString;
            result.appendChild(resultPartOne);
          };

          if (resultPartHighlightString.length > 0) {
            var resultPartHighlight = document.createElement("strong");
            resultPartHighlight.setAttribute("class", "m-auto-suggest-highlight");
            resultPartHighlight.textContent = resultPartHighlightString;
            result.appendChild(resultPartHighlight);
          };

          if (resultPartTwoString.length > 0) {
            var resultPartTwo = document.createElement("strong");
            resultPartTwo.setAttribute("class", "m-auto-suggest-part");
            resultPartTwo.textContent = resultPartTwoString;
            result.appendChild(resultPartTwo);
          };

          if (resultPartOneString.length > 0 || resultPartHighlightString.length > 0 || resultPartTwoString.length > 0) {
            text.appendChild(result);
          };

          if (arrayItem.school) {
            var resultMeta = document.createElement("i");
            resultMeta.setAttribute("class", "m-auto-suggest-result-meta");
            resultMeta.textContent = helper.capFirstLetter(arrayItem.school);
            text.appendChild(resultMeta);
          };

          anchor.appendChild(text);

          if (arrayItem.description) {
            var textSub = document.createElement("span");
            textSub.setAttribute("class", "m-auto-suggest-text-sub");
            textSub.textContent = arrayItem.description;
            anchor.appendChild(textSub);
          };

          li.appendChild(anchor);
          list.appendChild(li);

        });
      };
      if (autoSuggestOptions.type == "spells") {
        _populateSpells();
      };
    };

    var _render_autoSuggestList = function() {
      var autoSuggestList = helper.e(".js-auto-suggest-list");
      if (autoSuggestList) {
        while (autoSuggestList.lastChild) {
          autoSuggestList.removeChild(autoSuggestList.lastChild);
        };
      } else {
        var style = {
          left: _cuurentInput.getBoundingClientRect().left,
          top: _cuurentInput.getBoundingClientRect().bottom + window.scrollY,
          width: _cuurentInput.getBoundingClientRect().width
        };
        var autoSuggestList = document.createElement("ul");
        autoSuggestList.setAttribute("class", "m-auto-suggest-list u-list-unstyled js-auto-suggest-list");
        body.appendChild(autoSuggestList);
        autoSuggestList.setAttribute("style", "width: " + style.width + "px; top: " + style.top + "px; left: " + style.left + "px;");
        _addDocumentEvent();
      };
      _populateList(autoSuggestList);
    };

    if (searchTerm != "") {
      if (autoSuggestOptions.type == "spells") {
        suggestItems = spellsData.get({
          name: searchTerm
        });
      };
      if (suggestItems) {
        _render_autoSuggestList();
      } else {
        destroy();
      };
    } else {
      destroy();
    };
  };

  // exposed methods
  return {
    bind: bind,
    destroy: destroy
  };

})();
<|endoftext|>


<commit_before><commit_msg>Handle case where no tools are below modules in left menu.<commit_after><|endoftext|>


<commit_before>Menu.prototype = Object.create(Phaser.Group.prototype);
Menu.prototype.constructor = Menu;

/**
 * The game's main menu.
 * @return {Object} Itself.
 */
function Menu () {
	Phaser.Group.call(this, game, null); // Parent constructor.
	var centerX = game.world.centerX;
	var centerY = game.world.centerY;

	/* Add menu button. */
	var button = game.add.button(5, 5, 'wood', function () { showMenu(true); }, this, 0, 0, 1, 0, this);
	var menuSplit = Math.ceil(LANG.TEXT.menu.length/2);
	var menuText = game.add.text(
		button.x + button.width/2,
		button.y + button.height/2 + 7,
		'\n' + LANG.TEXT.menu.substr(0, menuSplit) + '\n' + LANG.TEXT.menu.substr(menuSplit),
		{
			font: '20pt ' +  GLOBAL.FONT,
			stroke: '#000000',
			strokeThickness: 2,
			align: 'center'
		},
		this
	);
	menuText.lineSpacing = -30;
	menuText.anchor.set(0.5);

	/* For skipping timelines */
	var skipper = null;
	var skipButton = new TextButton('>>', {
		x: 75, y: 5, size: 56, fontSize: 30,
		doNotAdapt: true,
		onClick: function () {
			if (skipper) {
				skipper.totalProgress(1);
			}
		}
	});
	skipButton.visible = false;
	this.add(skipButton);

	EventSystem.subscribe(GLOBAL.EVENT.skippable, function (timeline) {
		skipper = timeline;
		skipButton.visible = !!timeline;
	});

	/* Create the menu group. It will be shown when the button is clicked. */
	var menuGroup = game.add.group(this);
	showMenu(false);

	/* Create a cover behind the menu. */
	menuGroup.add(new Cover('#056449', 0.7));

	/* Create the background of the menu. */
	var bmd = game.add.bitmapData(parseInt(game.world.width*0.4), parseInt(game.world.height*0.6));
	bmd.ctx.fillStyle = '#b9d384';
	bmd.ctx.roundRect(0, 0, bmd.width, bmd.height, 20).fill();
	menuGroup.create(game.world.width*0.3, centerY*0.6, bmd).alpha = 0.7;

	/* Create the texts. */
	var title = game.add.text(centerX, centerY*0.4, LANG.TEXT.title, {
		font: '50pt ' +  GLOBAL.FONT,
		fill: '#ffff00',
		stroke: '#000000',
		strokeThickness: 5
	}, menuGroup);
	title.anchor.set(0.5);

	var resume = new TextButton(LANG.TEXT.resume, {
		x: centerX,
		y: centerY*0.8,
		fontSize: 30,
		onClick: function () {
			showMenu(false);
		}
	});
	resume.x -= resume.width/2;
	menuGroup.add(resume);

	/* Add volume control. */
	var volumeSlider = new Slider(
		centerX - bmd.width*0.2,
		centerY/0.85,
		bmd.width*0.5,
		40,
		function (value) {
			game.sound.volume = value;
			localStorage.mainVolume = value;

			if (value > 0) {
				muteButton.sprite.frame = 0;
				muteButton.muteValue = value;
			} else {
				muteButton.sprite.frame = 1;
			}
		},
		game.sound.volume
	);
	menuGroup.add(volumeSlider);

	// TODO: Change graphics to volume object
	var muteButton = new SpriteButton('drop', game.sound.volume > 0 ? 0 : 1, {
		x: centerX - bmd.width*0.35,
		y: volumeSlider.y - volumeSlider.height*0.75,
		size: volumeSlider.height*1.5,
		onClick: function () {
			if (this.sprite.frame === 1) {
				volumeSlider.value = this.muteValue > 0.1 ? this.muteValue : 1;
			} else {
				volumeSlider.value = 0;
			}
		}
	});
	muteButton.muteValue = volumeSlider.value;
	menuGroup.add(muteButton);

	if (game.state.current !== GLOBAL.STATE.garden && player.agent) {
		var garden = game.add.text(centerX, centerY/0.7, LANG.TEXT.gotoGarden, {
			font: '30pt ' +  GLOBAL.FONT,
			fill: '#000000'
		}, menuGroup);
		garden.anchor.set(0.5);
		garden.inputEnabled = true;
		garden.events.onInputDown.add(function () {
			game.state.start(GLOBAL.STATE.garden);
		}, this);
	}

	var quit = game.add.text(centerX, centerY/0.6, LANG.TEXT.quit, {
		font: '30pt ' +  GLOBAL.FONT,
		fill: '#000000'
	}, menuGroup);
	quit.anchor.set(0.5);
	quit.inputEnabled = true;
	quit.events.onInputDown.add(function () {
		game.state.start(GLOBAL.STATE.entry);
	}, this);

	function showMenu (value) {
		menuGroup.visible = value;
	}

	return this;
}<commit_msg>Menu: Better garden menu item check<commit_after>Menu.prototype = Object.create(Phaser.Group.prototype);
Menu.prototype.constructor = Menu;

/**
 * The game's main menu.
 * @return {Object} Itself.
 */
function Menu () {
	Phaser.Group.call(this, game, null); // Parent constructor.
	var centerX = game.world.centerX;
	var centerY = game.world.centerY;

	/* Add menu button. */
	var button = game.add.button(5, 5, 'wood', function () { showMenu(true); }, this, 0, 0, 1, 0, this);
	var menuSplit = Math.ceil(LANG.TEXT.menu.length/2);
	var menuText = game.add.text(
		button.x + button.width/2,
		button.y + button.height/2 + 7,
		'\n' + LANG.TEXT.menu.substr(0, menuSplit) + '\n' + LANG.TEXT.menu.substr(menuSplit),
		{
			font: '20pt ' +  GLOBAL.FONT,
			stroke: '#000000',
			strokeThickness: 2,
			align: 'center'
		},
		this
	);
	menuText.lineSpacing = -30;
	menuText.anchor.set(0.5);

	/* For skipping timelines */
	var skipper = null;
	var skipButton = new TextButton('>>', {
		x: 75, y: 5, size: 56, fontSize: 30,
		doNotAdapt: true,
		onClick: function () {
			if (skipper) {
				skipper.totalProgress(1);
			}
		}
	});
	skipButton.visible = false;
	this.add(skipButton);

	EventSystem.subscribe(GLOBAL.EVENT.skippable, function (timeline) {
		skipper = timeline;
		skipButton.visible = !!timeline;
	});

	/* Create the menu group. It will be shown when the button is clicked. */
	var menuGroup = game.add.group(this);
	showMenu(false);

	/* Create a cover behind the menu. */
	menuGroup.add(new Cover('#056449', 0.7));

	/* Create the background of the menu. */
	var bmd = game.add.bitmapData(parseInt(game.world.width*0.4), parseInt(game.world.height*0.6));
	bmd.ctx.fillStyle = '#b9d384';
	bmd.ctx.roundRect(0, 0, bmd.width, bmd.height, 20).fill();
	menuGroup.create(game.world.width*0.3, centerY*0.6, bmd).alpha = 0.7;

	/* Create the texts. */
	var title = game.add.text(centerX, centerY*0.4, LANG.TEXT.title, {
		font: '50pt ' +  GLOBAL.FONT,
		fill: '#ffff00',
		stroke: '#000000',
		strokeThickness: 5
	}, menuGroup);
	title.anchor.set(0.5);

	var resume = new TextButton(LANG.TEXT.resume, {
		x: centerX,
		y: centerY*0.8,
		fontSize: 30,
		onClick: function () {
			showMenu(false);
		}
	});
	resume.x -= resume.width/2;
	menuGroup.add(resume);

	/* Add volume control. */
	var volumeSlider = new Slider(
		centerX - bmd.width*0.2,
		centerY/0.85,
		bmd.width*0.5,
		40,
		function (value) {
			game.sound.volume = value;
			localStorage.mainVolume = value;

			if (value > 0) {
				muteButton.sprite.frame = 0;
				muteButton.muteValue = value;
			} else {
				muteButton.sprite.frame = 1;
			}
		},
		game.sound.volume
	);
	menuGroup.add(volumeSlider);

	// TODO: Change graphics to volume object
	var muteButton = new SpriteButton('drop', game.sound.volume > 0 ? 0 : 1, {
		x: centerX - bmd.width*0.35,
		y: volumeSlider.y - volumeSlider.height*0.75,
		size: volumeSlider.height*1.5,
		onClick: function () {
			if (this.sprite.frame === 1) {
				volumeSlider.value = this.muteValue > 0.1 ? this.muteValue : 1;
			} else {
				volumeSlider.value = 0;
			}
		}
	});
	muteButton.muteValue = volumeSlider.value;
	menuGroup.add(muteButton);

	if (game.state.states[game.state.current] instanceof Subgame) {
		var garden = game.add.text(centerX, centerY/0.7, LANG.TEXT.gotoGarden, {
			font: '30pt ' +  GLOBAL.FONT,
			fill: '#000000'
		}, menuGroup);
		garden.anchor.set(0.5);
		garden.inputEnabled = true;
		garden.events.onInputDown.add(function () {
			game.state.start(GLOBAL.STATE.garden);
		}, this);
	}

	var quit = game.add.text(centerX, centerY/0.6, LANG.TEXT.quit, {
		font: '30pt ' +  GLOBAL.FONT,
		fill: '#000000'
	}, menuGroup);
	quit.anchor.set(0.5);
	quit.inputEnabled = true;
	quit.events.onInputDown.add(function () {
		game.state.start(GLOBAL.STATE.entry);
	}, this);

	function showMenu (value) {
		menuGroup.visible = value;
	}

	return this;
}<|endoftext|>


<commit_before>define([
  'jquery',
  './options',
  './utils',
  './keys'
], function ($, Options, Utils, KEYS) {
  var Select2 = function ($element, options) {
    if ($element.data('select2') != null) {
      $element.data('select2').destroy();
    }

    this.$element = $element;

    this.id = this._generateId($element);

    options = options || {};

    this.options = new Options(options, $element);

    Select2.__super__.constructor.call(this);

    // Set up the tabindex

    var tabindex = $element.attr('tabindex') || 0;
    $element.data('old-tabindex', tabindex);
    $element.attr('tabindex', '-1');

    // Set up containers and adapters

    var DataAdapter = this.options.get('dataAdapter');
    this.dataAdapter = new DataAdapter($element, this.options);

    var $container = this.render();

    this._placeContainer($container);

    var SelectionAdapter = this.options.get('selectionAdapter');
    this.selection = new SelectionAdapter($element, this.options);
    this.$selection = this.selection.render();

    this.selection.position(this.$selection, $container);

    var DropdownAdapter = this.options.get('dropdownAdapter');
    this.dropdown = new DropdownAdapter($element, this.options);
    this.$dropdown = this.dropdown.render();

    this.dropdown.position(this.$dropdown, $container);

    var ResultsAdapter = this.options.get('resultsAdapter');
    this.results = new ResultsAdapter($element, this.options, this.dataAdapter);
    this.$results = this.results.render();

    this.results.position(this.$results, this.$dropdown);

    // Bind events

    var self = this;

    // Bind the container to all of the adapters
    this._bindAdapters();

    // Register any DOM event handlers
    this._registerDomEvents();

    // Register any internal event handlers
    this._registerDataEvents();
    this._registerSelectionEvents();
    this._registerDropdownEvents();
    this._registerResultsEvents();
    this._registerEvents();

    // Set the initial state
    this.dataAdapter.current(function (initialData) {
      self.trigger('selection:update', {
        data: initialData
      });
    });

    // Hide the original select
    $element.addClass('select2-hidden-accessible');
    $element.attr('aria-hidden', 'true');

    // Synchronize any monitored attributes
    this._syncAttributes();

    $element.data('select2', this);
  };

  Utils.Extend(Select2, Utils.Observable);

  Select2.prototype._generateId = function ($element) {
    var id = '';

    if ($element.attr('id') != null) {
      id = $element.attr('id');
    } else if ($element.attr('name') != null) {
      id = $element.attr('name') + '-' + Utils.generateChars(2);
    } else {
      id = Utils.generateChars(4);
    }

    id = 'select2-' + id;

    return id;
  };

  Select2.prototype._placeContainer = function ($container) {
    $container.insertAfter(this.$element);

    var width = this._resolveWidth(this.$element, this.options.get('width'));

    if (width != null) {
      $container.css('width', width);
    }
  };

  Select2.prototype._resolveWidth = function ($element, method) {
    var WIDTH = /^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i;

    if (method == 'resolve') {
      var styleWidth = this._resolveWidth($element, 'style');

      if (styleWidth != null) {
        return styleWidth;
      }

      return this._resolveWidth($element, 'element');
    }

    if (method == 'element') {
      var elementWidth = $element.outerWidth(false);

      if (elementWidth <= 0) {
        return 'auto';
      }

      return elementWidth + 'px';
    }

    if (method == 'style') {
      var style = $element.attr('style');

      if (typeof(style) !== 'string') {
        return null;
      }

      var attrs = style.split(';');

      for (var i = 0, l = attrs.length; i < l; i = i + 1) {
        var attr = attrs[i].replace(/\s/g, '');
        var matches = attr.match(WIDTH);

        if (matches !== null && matches.length >= 1) {
          return matches[1];
        }
      }

      return null;
    }

    return method;
  };

  Select2.prototype._bindAdapters = function () {
    this.dataAdapter.bind(this, this.$container);
    this.selection.bind(this, this.$container);

    this.dropdown.bind(this, this.$container);
    this.results.bind(this, this.$container);
  };

  Select2.prototype._registerDomEvents = function () {
    var self = this;

    this.$element.on('change.select2', function () {
      self.dataAdapter.current(function (data) {
        self.trigger('selection:update', {
          data: data
        });
      });
    });

    this._sync = Utils.bind(this._syncAttributes, this);

    if (this.$element[0].attachEvent) {
      this.$element[0].attachEvent('onpropertychange', this._sync);
    }

    var observer = window.MutationObserver ||
      window.WebKitMutationObserver ||
      window.MozMutationObserver
    ;

    if (observer != null) {
      this._observer = new observer(function (mutations) {
        $.each(mutations, self._sync);
      });
      this._observer.observe(this.$element[0], {
        attributes: true,
        subtree: false
      });
    } else if (this.$element[0].addEventListener) {
      this.$element[0].addEventListener('DOMAttrModified', self._sync, false);
    }
  };

  Select2.prototype._registerDataEvents = function () {
    var self = this;

    this.dataAdapter.on('*', function (name, params) {
      self.trigger(name, params);
    });
  };

  Select2.prototype._registerSelectionEvents = function () {
    var self = this;
    var nonRelayEvents = ['toggle', 'focus'];

    this.selection.on('toggle', function () {
      self.toggleDropdown();
    });

    this.selection.on('focus', function (params) {
      self.focus(params);
    });

    this.selection.on('*', function (name, params) {
      if ($.inArray(name, nonRelayEvents) !== -1) {
        return;
      }

      self.trigger(name, params);
    });
  };

  Select2.prototype._registerDropdownEvents = function () {
    var self = this;

    this.dropdown.on('*', function (name, params) {
      self.trigger(name, params);
    });
  };

  Select2.prototype._registerResultsEvents = function () {
    var self = this;

    this.results.on('*', function (name, params) {
      self.trigger(name, params);
    });
  };

  Select2.prototype._registerEvents = function () {
    var self = this;

    this.on('open', function () {
      self.$container.addClass('select2-container--open');
    });

    this.on('close', function () {
      self.$container.removeClass('select2-container--open');
    });

    this.on('enable', function () {
      self.$container.removeClass('select2-container--disabled');
    });

    this.on('disable', function () {
      self.$container.addClass('select2-container--disabled');
    });

    this.on('blur', function () {
      self.$container.removeClass('select2-container--focus');
    });

    this.on('query', function (params) {
      if (!self.isOpen()) {
        self.trigger('open', {});
      }

      this.dataAdapter.query(params, function (data) {
        self.trigger('results:all', {
          data: data,
          query: params
        });
      });
    });

    this.on('query:append', function (params) {
      this.dataAdapter.query(params, function (data) {
        self.trigger('results:append', {
          data: data,
          query: params
        });
      });
    });

    this.on('keypress', function (evt) {
      var key = evt.which;

      if (self.isOpen()) {
        if (key === KEYS.ESC || key === KEYS.TAB ||
            (key === KEYS.UP && evt.altKey)) {
          self.close();

          evt.preventDefault();
        } else if (key === KEYS.ENTER) {
          self.trigger('results:select', {});

          evt.preventDefault();
        } else if ((key === KEYS.SPACE && evt.ctrlKey)) {
          self.trigger('results:toggle', {});

          evt.preventDefault();
        } else if (key === KEYS.UP) {
          self.trigger('results:previous', {});

          evt.preventDefault();
        } else if (key === KEYS.DOWN) {
          self.trigger('results:next', {});

          evt.preventDefault();
        }
      } else {
        if (key === KEYS.ENTER || key === KEYS.SPACE ||
            (key === KEYS.DOWN && evt.altKey)) {
          self.open();

          evt.preventDefault();
        }
      }
    });
  };

  Select2.prototype._syncAttributes = function () {
    this.options.set('disabled', this.$element.prop('disabled'));

    if (this.options.get('disabled')) {
      if (this.isOpen()) {
        this.close();
      }

      this.trigger('disable', {});
    } else {
      this.trigger('enable', {});
    }
  };

  /**
   * Override the trigger method to automatically trigger pre-events when
   * there are events that can be prevented.
   */
  Select2.prototype.trigger = function (name, args) {
    var actualTrigger = Select2.__super__.trigger;
    var preTriggerMap = {
      'open': 'opening',
      'close': 'closing',
      'select': 'selecting',
      'unselect': 'unselecting'
    };

    if (name in preTriggerMap) {
      var preTriggerName = preTriggerMap[name];
      var preTriggerArgs = {
        prevented: false,
        name: name,
        args: args
      };

      actualTrigger.call(this, preTriggerName, preTriggerArgs);

      if (preTriggerArgs.prevented) {
        args.prevented = true;

        return;
      }
    }

    actualTrigger.call(this, name, args);
  };

  Select2.prototype.toggleDropdown = function () {
    if (this.options.get('disabled')) {
      return;
    }

    if (this.isOpen()) {
      this.close();
    } else {
      this.open();
    }
  };

  Select2.prototype.open = function () {
    if (this.isOpen()) {
      return;
    }

    this.trigger('query', {});
  };

  Select2.prototype.close = function () {
    if (!this.isOpen()) {
      return;
    }

    this.trigger('close', {});
  };

  Select2.prototype.isOpen = function () {
    return this.$container.hasClass('select2-container--open');
  };

  Select2.prototype.hasFocus = function () {
    return this.$container.hasClass('select2-container--focus');
  };

  Select2.prototype.focus = function (data) {
    // No need to re-trigger focus events if we are already focused
    if (this.hasFocus()) {
      return;
    }

    this.$container.addClass('select2-container--focus');
    this.trigger('focus', {});
  };

  Select2.prototype.enable = function (args) {
    if (this.options.get('debug') && window.console && console.warn) {
      console.warn(
        'Select2: The `select2("enable")` method has been deprecated and will' +
        ' be removed in later Select2 versions. Use $element.prop("disabled")' +
        ' instead.'
      );
    }

    if (args == null || args.length === 0) {
      args = [true];
    }

    var disabled = !args[0];

    this.$element.prop('disabled', disabled);
  };

  Select2.prototype.data = function () {
    if (this.options.get('debug') &&
        arguments.length > 0 && window.console && console.warn) {
      console.warn(
        'Select2: Data can no longer be set using `select2("data")`. You ' +
        'should consider setting the value instead using `$element.val()`.'
      );
    }

    var data = [];

    this.dataAdapter.current(function (currentData) {
      data = currentData;
    });

    return data;
  };

  Select2.prototype.val = function (args) {
    if (this.options.get('debug') && window.console && console.warn) {
      console.warn(
        'Select2: The `select2("val")` method has been deprecated and will be' +
        ' removed in later Select2 versions. Use $element.val() instead.'
      );
    }

    if (args == null || args.length === 0) {
      return this.$element.val();
    }

    var newVal = args[0];

    if ($.isArray(newVal)) {
      newVal = $.map(newVal, function (obj) {
        return obj.toString();
      });
    }

    this.$element.val(newVal).trigger('change');
  };

  Select2.prototype.destroy = function () {
    this.$container.remove();

    if (this.$element[0].detachEvent) {
      this.$element[0].detachEvent('onpropertychange', this._sync);
    }

    if (this._observer != null) {
      this._observer.disconnect();
      this._observer = null;
    } else if (this.$element[0].removeEventListener) {
      this.$element[0]
        .removeEventListener('DOMAttrModified', this._sync, false);
    }

    this._sync = null;

    this.$element.off('.select2');
    this.$element.attr('tabindex', this.$element.data('old-tabindex'));

    this.$element.removeClass('select2-hidden-accessible');
    this.$element.attr('aria-hidden', 'false');
    this.$element.removeData('select2');

    this.dataAdapter.destroy();
    this.selection.destroy();
    this.dropdown.destroy();
    this.results.destroy();

    this.dataAdapter = null;
    this.selection = null;
    this.dropdown = null;
    this.results = null;
  };

  Select2.prototype.render = function () {
    var $container = $(
      '' +
        '' +
        '' +
      ''
    );

    $container.attr('dir', this.options.get('dir'));

    this.$container = $container;

    this.$container.addClass('select2-container--' + this.options.get('theme'));

    $container.data('element', this.$element);

    return $container;
  };

  return Select2;
});
<commit_msg>Fixes and issue where If args are not present script terminates<commit_after>define([
  'jquery',
  './options',
  './utils',
  './keys'
], function ($, Options, Utils, KEYS) {
  var Select2 = function ($element, options) {
    if ($element.data('select2') != null) {
      $element.data('select2').destroy();
    }

    this.$element = $element;

    this.id = this._generateId($element);

    options = options || {};

    this.options = new Options(options, $element);

    Select2.__super__.constructor.call(this);

    // Set up the tabindex

    var tabindex = $element.attr('tabindex') || 0;
    $element.data('old-tabindex', tabindex);
    $element.attr('tabindex', '-1');

    // Set up containers and adapters

    var DataAdapter = this.options.get('dataAdapter');
    this.dataAdapter = new DataAdapter($element, this.options);

    var $container = this.render();

    this._placeContainer($container);

    var SelectionAdapter = this.options.get('selectionAdapter');
    this.selection = new SelectionAdapter($element, this.options);
    this.$selection = this.selection.render();

    this.selection.position(this.$selection, $container);

    var DropdownAdapter = this.options.get('dropdownAdapter');
    this.dropdown = new DropdownAdapter($element, this.options);
    this.$dropdown = this.dropdown.render();

    this.dropdown.position(this.$dropdown, $container);

    var ResultsAdapter = this.options.get('resultsAdapter');
    this.results = new ResultsAdapter($element, this.options, this.dataAdapter);
    this.$results = this.results.render();

    this.results.position(this.$results, this.$dropdown);

    // Bind events

    var self = this;

    // Bind the container to all of the adapters
    this._bindAdapters();

    // Register any DOM event handlers
    this._registerDomEvents();

    // Register any internal event handlers
    this._registerDataEvents();
    this._registerSelectionEvents();
    this._registerDropdownEvents();
    this._registerResultsEvents();
    this._registerEvents();

    // Set the initial state
    this.dataAdapter.current(function (initialData) {
      self.trigger('selection:update', {
        data: initialData
      });
    });

    // Hide the original select
    $element.addClass('select2-hidden-accessible');
    $element.attr('aria-hidden', 'true');

    // Synchronize any monitored attributes
    this._syncAttributes();

    $element.data('select2', this);
  };

  Utils.Extend(Select2, Utils.Observable);

  Select2.prototype._generateId = function ($element) {
    var id = '';

    if ($element.attr('id') != null) {
      id = $element.attr('id');
    } else if ($element.attr('name') != null) {
      id = $element.attr('name') + '-' + Utils.generateChars(2);
    } else {
      id = Utils.generateChars(4);
    }

    id = 'select2-' + id;

    return id;
  };

  Select2.prototype._placeContainer = function ($container) {
    $container.insertAfter(this.$element);

    var width = this._resolveWidth(this.$element, this.options.get('width'));

    if (width != null) {
      $container.css('width', width);
    }
  };

  Select2.prototype._resolveWidth = function ($element, method) {
    var WIDTH = /^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i;

    if (method == 'resolve') {
      var styleWidth = this._resolveWidth($element, 'style');

      if (styleWidth != null) {
        return styleWidth;
      }

      return this._resolveWidth($element, 'element');
    }

    if (method == 'element') {
      var elementWidth = $element.outerWidth(false);

      if (elementWidth <= 0) {
        return 'auto';
      }

      return elementWidth + 'px';
    }

    if (method == 'style') {
      var style = $element.attr('style');

      if (typeof(style) !== 'string') {
        return null;
      }

      var attrs = style.split(';');

      for (var i = 0, l = attrs.length; i < l; i = i + 1) {
        var attr = attrs[i].replace(/\s/g, '');
        var matches = attr.match(WIDTH);

        if (matches !== null && matches.length >= 1) {
          return matches[1];
        }
      }

      return null;
    }

    return method;
  };

  Select2.prototype._bindAdapters = function () {
    this.dataAdapter.bind(this, this.$container);
    this.selection.bind(this, this.$container);

    this.dropdown.bind(this, this.$container);
    this.results.bind(this, this.$container);
  };

  Select2.prototype._registerDomEvents = function () {
    var self = this;

    this.$element.on('change.select2', function () {
      self.dataAdapter.current(function (data) {
        self.trigger('selection:update', {
          data: data
        });
      });
    });

    this._sync = Utils.bind(this._syncAttributes, this);

    if (this.$element[0].attachEvent) {
      this.$element[0].attachEvent('onpropertychange', this._sync);
    }

    var observer = window.MutationObserver ||
      window.WebKitMutationObserver ||
      window.MozMutationObserver
    ;

    if (observer != null) {
      this._observer = new observer(function (mutations) {
        $.each(mutations, self._sync);
      });
      this._observer.observe(this.$element[0], {
        attributes: true,
        subtree: false
      });
    } else if (this.$element[0].addEventListener) {
      this.$element[0].addEventListener('DOMAttrModified', self._sync, false);
    }
  };

  Select2.prototype._registerDataEvents = function () {
    var self = this;

    this.dataAdapter.on('*', function (name, params) {
      self.trigger(name, params);
    });
  };

  Select2.prototype._registerSelectionEvents = function () {
    var self = this;
    var nonRelayEvents = ['toggle', 'focus'];

    this.selection.on('toggle', function () {
      self.toggleDropdown();
    });

    this.selection.on('focus', function (params) {
      self.focus(params);
    });

    this.selection.on('*', function (name, params) {
      if ($.inArray(name, nonRelayEvents) !== -1) {
        return;
      }

      self.trigger(name, params);
    });
  };

  Select2.prototype._registerDropdownEvents = function () {
    var self = this;

    this.dropdown.on('*', function (name, params) {
      self.trigger(name, params);
    });
  };

  Select2.prototype._registerResultsEvents = function () {
    var self = this;

    this.results.on('*', function (name, params) {
      self.trigger(name, params);
    });
  };

  Select2.prototype._registerEvents = function () {
    var self = this;

    this.on('open', function () {
      self.$container.addClass('select2-container--open');
    });

    this.on('close', function () {
      self.$container.removeClass('select2-container--open');
    });

    this.on('enable', function () {
      self.$container.removeClass('select2-container--disabled');
    });

    this.on('disable', function () {
      self.$container.addClass('select2-container--disabled');
    });

    this.on('blur', function () {
      self.$container.removeClass('select2-container--focus');
    });

    this.on('query', function (params) {
      if (!self.isOpen()) {
        self.trigger('open', {});
      }

      this.dataAdapter.query(params, function (data) {
        self.trigger('results:all', {
          data: data,
          query: params
        });
      });
    });

    this.on('query:append', function (params) {
      this.dataAdapter.query(params, function (data) {
        self.trigger('results:append', {
          data: data,
          query: params
        });
      });
    });

    this.on('keypress', function (evt) {
      var key = evt.which;

      if (self.isOpen()) {
        if (key === KEYS.ESC || key === KEYS.TAB ||
            (key === KEYS.UP && evt.altKey)) {
          self.close();

          evt.preventDefault();
        } else if (key === KEYS.ENTER) {
          self.trigger('results:select', {});

          evt.preventDefault();
        } else if ((key === KEYS.SPACE && evt.ctrlKey)) {
          self.trigger('results:toggle', {});

          evt.preventDefault();
        } else if (key === KEYS.UP) {
          self.trigger('results:previous', {});

          evt.preventDefault();
        } else if (key === KEYS.DOWN) {
          self.trigger('results:next', {});

          evt.preventDefault();
        }
      } else {
        if (key === KEYS.ENTER || key === KEYS.SPACE ||
            (key === KEYS.DOWN && evt.altKey)) {
          self.open();

          evt.preventDefault();
        }
      }
    });
  };

  Select2.prototype._syncAttributes = function () {
    this.options.set('disabled', this.$element.prop('disabled'));

    if (this.options.get('disabled')) {
      if (this.isOpen()) {
        this.close();
      }

      this.trigger('disable', {});
    } else {
      this.trigger('enable', {});
    }
  };

  /**
   * Override the trigger method to automatically trigger pre-events when
   * there are events that can be prevented.
   */
  Select2.prototype.trigger = function (name, args) {
    var actualTrigger = Select2.__super__.trigger;
    var preTriggerMap = {
      'open': 'opening',
      'close': 'closing',
      'select': 'selecting',
      'unselect': 'unselecting'
    };

    if (args === undefined) {
      args = {};
    }

    if (name in preTriggerMap) {
      var preTriggerName = preTriggerMap[name];
      var preTriggerArgs = {
        prevented: false,
        name: name,
        args: args
      };

      actualTrigger.call(this, preTriggerName, preTriggerArgs);

      if (preTriggerArgs.prevented) {
        args.prevented = true;

        return;
      }
    }

    actualTrigger.call(this, name, args);
  };

  Select2.prototype.toggleDropdown = function () {
    if (this.options.get('disabled')) {
      return;
    }

    if (this.isOpen()) {
      this.close();
    } else {
      this.open();
    }
  };

  Select2.prototype.open = function () {
    if (this.isOpen()) {
      return;
    }

    this.trigger('query', {});
  };

  Select2.prototype.close = function () {
    if (!this.isOpen()) {
      return;
    }

    this.trigger('close', {});
  };

  Select2.prototype.isOpen = function () {
    return this.$container.hasClass('select2-container--open');
  };

  Select2.prototype.hasFocus = function () {
    return this.$container.hasClass('select2-container--focus');
  };

  Select2.prototype.focus = function (data) {
    // No need to re-trigger focus events if we are already focused
    if (this.hasFocus()) {
      return;
    }

    this.$container.addClass('select2-container--focus');
    this.trigger('focus', {});
  };

  Select2.prototype.enable = function (args) {
    if (this.options.get('debug') && window.console && console.warn) {
      console.warn(
        'Select2: The `select2("enable")` method has been deprecated and will' +
        ' be removed in later Select2 versions. Use $element.prop("disabled")' +
        ' instead.'
      );
    }

    if (args == null || args.length === 0) {
      args = [true];
    }

    var disabled = !args[0];

    this.$element.prop('disabled', disabled);
  };

  Select2.prototype.data = function () {
    if (this.options.get('debug') &&
        arguments.length > 0 && window.console && console.warn) {
      console.warn(
        'Select2: Data can no longer be set using `select2("data")`. You ' +
        'should consider setting the value instead using `$element.val()`.'
      );
    }

    var data = [];

    this.dataAdapter.current(function (currentData) {
      data = currentData;
    });

    return data;
  };

  Select2.prototype.val = function (args) {
    if (this.options.get('debug') && window.console && console.warn) {
      console.warn(
        'Select2: The `select2("val")` method has been deprecated and will be' +
        ' removed in later Select2 versions. Use $element.val() instead.'
      );
    }

    if (args == null || args.length === 0) {
      return this.$element.val();
    }

    var newVal = args[0];

    if ($.isArray(newVal)) {
      newVal = $.map(newVal, function (obj) {
        return obj.toString();
      });
    }

    this.$element.val(newVal).trigger('change');
  };

  Select2.prototype.destroy = function () {
    this.$container.remove();

    if (this.$element[0].detachEvent) {
      this.$element[0].detachEvent('onpropertychange', this._sync);
    }

    if (this._observer != null) {
      this._observer.disconnect();
      this._observer = null;
    } else if (this.$element[0].removeEventListener) {
      this.$element[0]
        .removeEventListener('DOMAttrModified', this._sync, false);
    }

    this._sync = null;

    this.$element.off('.select2');
    this.$element.attr('tabindex', this.$element.data('old-tabindex'));

    this.$element.removeClass('select2-hidden-accessible');
    this.$element.attr('aria-hidden', 'false');
    this.$element.removeData('select2');

    this.dataAdapter.destroy();
    this.selection.destroy();
    this.dropdown.destroy();
    this.results.destroy();

    this.dataAdapter = null;
    this.selection = null;
    this.dropdown = null;
    this.results = null;
  };

  Select2.prototype.render = function () {
    var $container = $(
      '' +
        '' +
        '' +
      ''
    );

    $container.attr('dir', this.options.get('dir'));

    this.$container = $container;

    this.$container.addClass('select2-container--' + this.options.get('theme'));

    $container.data('element', this.$element);

    return $container;
  };

  return Select2;
});
<|endoftext|>


<commit_before>/// FIXME replace while(hasnext) with do-while(hasnext) ?

(function($) {

$.mediawiki = {};
$.mediawiki.tokenize = function tokenize(str, callback) {
	var g_curr_pos = 0; // current possition in stream
	var g_last_pos = 0; // last position were token was found

	function peek() {
		return str.charAt(g_curr_pos);
	}

	function next() {
		g_curr_pos++;
	}

	function hasnext() {
		return g_curr_pos < str.length;
	}

	function add_token(token, s_curr_pos) {
		callback(token);
		g_last_pos = s_curr_pos;
	}

	function handle_newline() {
		var newline = 0;

		var was_r = false;
		while(hasnext()) {
			var ch = peek();
			if (ch === "\n") {
				next();
				newline++;
				was_r = false;
			} else if (ch === "\r") {
				next(); //always go to the next char to avoid second call of handle_p()
				if (!was_r) {
					was_r = true;
				} else {
					break;
				}
			} else {
				break;
			}
		}

		return newline;
	}

	function handle_text(s_curr_pos) {
		if (g_last_pos !== s_curr_pos) {
			add_token(["t", str.substring(g_last_pos, s_curr_pos)], s_curr_pos);
		}
	}

	function handle_heading(s_curr_pos, newline) {
		var open_tag = 0;
		var close_tag = 0;

		// calculate tag level, always eat control symbols to avoid second call of handler
		while(hasnext()) {
			if (peek() === "=") {
				open_tag++;
				next();
			} else {
				break;
			}
		}

		// invalid tag length or not at start of the line
		if (!newline || open_tag < 1 || open_tag > 6) {
			return 0;
		}

		// header body and ending tag
		while(hasnext()) {
			var ch = peek();
			if (ch === "=") {
				close_tag++;
				next();
			} else if (ch === "\r" || ch === "\n") {
				break;
			} else {
				close_tag = 0;
				next();
			}
		}

		// no close tag
		if (!close_tag) {
			return 0;
		}

		var e_curr_pos = g_curr_pos; // end position of the text

		newline = handle_newline(); // skip empty lines
		if (newline) {
			var level = Math.min(open_tag, close_tag);
			handle_text(s_curr_pos);
			add_token(["h", level, str.substring(g_last_pos + level, e_curr_pos - level) ], g_curr_pos);
		}

		return newline;
	}

	function handle_paragraph(s_curr_pos, newline) {
		// calculate newlines only if it was called after non-newline character
		newline = newline || handle_newline();

		if (newline > 1) {
			handle_text(s_curr_pos);
			add_token(["p"], g_curr_pos);
			if (newline > 2) {
				add_token(["b", newline - 2], g_curr_pos);
			}
		}
		return newline;
	}

	function handle_list(s_curr_pos, newline) {
		var ch;
		// always eat control symbols to avoid second call of handler
		while(hasnext()) {
			ch = peek();
			if (ch === '#' || ch === '*') {
				next();
			} else {
				break;
			}
		}
		var l_curr_pos = g_curr_pos; // end position of the markers

		// no at start of the line
		if (!newline) {
			return 0;
		}

		while(hasnext()) {
			ch = peek();
			if (ch === "\n" || ch === "\r") {
				break;
			} else {
				next();
			}
		}
		var e_curr_pos = g_curr_pos; // end position of the text

		newline = handle_newline(); //skip empty lines
		if (newline) {
			handle_text(s_curr_pos);
			add_token([str.substring(g_last_pos, l_curr_pos), str.substring(l_curr_pos, e_curr_pos) ], g_curr_pos);
			handle_paragraph(g_curr_pos, newline);
		}

		return newline;
	}

	function handle_emphasize(s_curr_pos, newline) {
		var e_level = 0;
		while(hasnext()) {
			if (peek() === "'") {
				e_level++;
				next();
			} else {
				break;
			}
		}

		switch(e_level) {
			case 1:
				break;
			case 2:
			case 3:
			case 5:
				handle_text(s_curr_pos);
				add_token(["'", e_level], g_curr_pos);
				break;
			case 4:
				handle_text(s_curr_pos);
				add_token(["'", 3], g_curr_pos-1);
				break;
			default: // > 5
				handle_text(s_curr_pos);
				add_token(["'", 5], g_curr_pos - e_level + 5);
				break;
		}
		return 0;
	}

	function handle_default(s_curr_pos, newline) {
		next();
		return 0;
	}

	var newline = 1;
	while(hasnext()) {
		switch(peek()) {
			case "=":
				newline = handle_heading(g_curr_pos, newline);
				break;
			case "\r":
			case "\n":
				newline = handle_paragraph(g_curr_pos, 0);
				break;
			case "#":
			case "*":
				newline = handle_list(g_curr_pos, newline);
				break;
			case "'":
				newline = handle_emphasize(g_curr_pos, newline);
				break;
			default:
				newline = handle_default(g_curr_pos, newline);
				break;
		}
	}
	handle_text(g_curr_pos);
};

$.mediawiki.autocorrect = function(callback) {
	var g_stack = []; // stack of disbalanced items
	var g_head = null; // head of list of unprocessing items
	var g_tail = null; // tail of list of unprocessing items
	var g_context = null; // current global context

	function append_token(token) {
		var item = { token: token, prev: g_tail, next: null };
		if (g_head == null) {
			g_head = item;
		}
		if (g_tail != null) {
			g_tail.next = item;
		}
		g_tail = item;
		return item;
	}

	function dump_tokens() {
		// automatically drop last unclosed tokens
		// automatically close other unclosed tokens
		while(g_stack.length) {
			var last = g_stack.pop();
			if (!last.next) {
				g_tail = last.prev;
				if (g_tail) {
					g_tail.next = null;
				} else {
					g_head = null;
				}
			} else {
				var l_tag = last.token[1];
				if (l_tag === 5) {
					modify_token(last, 3, 2);
					append_token(["'", 2]);
					append_token(["'", 3]);
				} else {
					append_token(["'", l_tag]);
				}
			}
		}

		var item = g_head;
		while(item) {
			callback(item.token);
			item = item.next;
		}
		g_head = g_tail = null;
	}

	function modify_token(item, tag1, tag2) {
		item.token[1] = tag1;
		var item2 = { token: ["'", tag2], prev: item, next: item.next };
		if (item.next) {
			item.next.prev = item2;
		}
		item.next = item2;
		if (item === g_tail) {
			g_tail = item2;
		}
	}

	function handle_emphasize(token) {
		if (!g_stack.length) { // no unbalanced items
			dump_tokens();
			g_stack.push(append_token(token));
			return;
		}

		var curr_tag = token[1];
		var prev = g_stack[g_stack.length - 1]; // last unbalanced item
		var prev_tag = prev.token[1];

		if (prev_tag === curr_tag) {
			if (prev_tag === 5) {
				modify_token(prev, 3, 2);
				append_token(["'", 2]);
				append_token(["'", 3]);
			} else {
				append_token(token);
			}
			g_stack.pop();
		} else {
			if (prev_tag === 5) {
				modify_token(prev, 5 - curr_tag, curr_tag);
				append_token(["'", curr_tag]);
			} else if (curr_tag === 5) {
				append_token(["'", prev_tag]);
				g_stack.pop();
				var new_tag = 5 - prev_tag;
				var new_curr = append_token(["'", new_tag]);
				if (!g_stack.length || g_stack[g_stack.length-1].token[1] !== new_tag) {
					g_stack.push(new_curr);
				} else {
					g_stack.pop();
				}
			} else if (g_stack.length === 1) {
				g_stack.push(append_token(token));
			} else {
				append_token(["'", prev_tag]);
				g_stack.pop();
				append_token(token);
				g_stack.pop();
				g_stack.push(append_token(["'", prev_tag]));
			}
		}
	}

	return function(token) {
		if (token == null) {
			dump_tokens();
			return;
		}
		var token_type = token[0][0];
		switch(token_type) {
			// block tokens
			case "h":
			case "p":
			case "*":
			case "#":
				dump_tokens();
				if (token_type === "p") {
					g_context = null;
				} else {
					g_context = token;
					callback(g_context);
				}
				break;
			// inline tokens
			case "b":
			case "t":
			case "'":
				if (!g_context || g_context[0] !== "p") {
					g_context = ["p"];
					callback(g_context);
				}
				if (token_type === "'") {
					handle_emphasize(token);
				} else if (g_head != null) {
					append_token(token);
				} else {
					callback(token);
				}
				break;
			default:
				throw new Error("unsupported token");
		}
	};
};

$.mediawiki.format = function (text) {
	var g_context = []; // current global context, paragraph, ordered or unordered list,
	var g_result = []; // output

	function close_ctx(depth) {
		while(g_context.length && (depth == null || depth--)) {
			var ctx = g_context.pop();
			g_result.push("</" + ctx[0] + ">");
		}
	}

	function open_ctx(ctx) {
		g_context.push(ctx);
		g_result.push("<" + ctx[0] + ">");
	}

	function curr_ctx() {
		return g_context.length?g_context[g_context.length - 1]:[null];
	}

	function handle_paragraph(s_token) {
		close_ctx(null);
		open_ctx(["p"]);
	}

	function handle_text(s_token) {
		g_result.push(s_token[1]);
	}

	function handle_br(s_token) {
		g_result.push(new Array(s_token[1] + 1).join("
"));
	}

	function handle_heading(s_token) {
		close_ctx(null);
		g_result.push(["<h",s_token[1],">",$.trim(s_token[2]),"</h",s_token[1],">"].join(""));
	}

	function handle_list(s_token) {
		var l_type = s_token[0];
		var l_text = s_token[1];
		var level;

		var ctx = curr_ctx();
		if (ctx[0] === "li") {
			var c_type = ctx[1];
			var common_prefix = 0;
			var common_length = Math.min(c_type.length, l_type.length);

			for(common_prefix = 0; common_prefix < common_length; ++common_prefix) {
				if (c_type[common_prefix] !== l_type[common_prefix]) {
					break;
				}
			}
			for(level=c_type.length; level > common_prefix; --level) {
				close_ctx(2);
			}
			if (l_type.length <= common_prefix) {
				close_ctx(1);
			}
			for(level=common_prefix; level < l_type.length; ++level) {
				open_ctx([(l_type[level] === "*")?"ul":"ol"]);
				open_ctx(["li", l_type]);
			}
			if (l_type.length === common_prefix) {
				open_ctx(["li", l_type]);
			}
		} else {
			close_ctx(null);
			for(level=0; level < l_type.length; ++level) {
				open_ctx([(l_type[level] === "*")?"ul":"ol"]);
				open_ctx(["li", l_type]);
			}
		}

		g_result.push($.trim(l_text));
	}

	function handle_emphasize(s_token) {
		var e_tag;
		switch(s_token[1]) {
			case 2:
				e_tag = "em";
				break;
			case 3:
				e_tag = "strong";
				break;
			default:
				return; // ignore invalid emphasize tags
		}

		if (e_tag === curr_ctx()[0]) {
			close_ctx(1);
		} else {
			open_ctx([e_tag]);
		}
	}

	var corrector = $.mediawiki.autocorrect(function(token) {
		switch (token[0][0]) {
			case "p":
				handle_paragraph(token);
				break;
			case "t":
				handle_text(token);
				break;
			case "h":
				handle_heading(token);
				break;
			case "b":
				handle_br(token);
				break;
			case "*":
			case "#":
				handle_list(token);
				break;
			case "'":
				handle_emphasize(token);
				break;
			default:
				throw new Error("invalid token");
		}
	});
	$.mediawiki.tokenize(text, corrector);
	corrector(null); // flush buffers
	close_ctx(null); // finally close all active contexts

	return g_result.join("");
};

}(jQuery));
<commit_msg>use short references to library<commit_after>/// FIXME replace while(hasnext) with do-while(hasnext) ?

(function($) {

var lib = $.mediawiki = $.mediawiki || {};

lib.tokenize = function tokenize(str, callback) {
	var g_curr_pos = 0; // current possition in stream
	var g_last_pos = 0; // last position were token was found

	function peek() {
		return str.charAt(g_curr_pos);
	}

	function next() {
		g_curr_pos++;
	}

	function hasnext() {
		return g_curr_pos < str.length;
	}

	function add_token(token, s_curr_pos) {
		callback(token);
		g_last_pos = s_curr_pos;
	}

	function handle_newline() {
		var newline = 0;

		var was_r = false;
		while(hasnext()) {
			var ch = peek();
			if (ch === "\n") {
				next();
				newline++;
				was_r = false;
			} else if (ch === "\r") {
				next(); //always go to the next char to avoid second call of handle_p()
				if (!was_r) {
					was_r = true;
				} else {
					break;
				}
			} else {
				break;
			}
		}

		return newline;
	}

	function handle_text(s_curr_pos) {
		if (g_last_pos !== s_curr_pos) {
			add_token(["t", str.substring(g_last_pos, s_curr_pos)], s_curr_pos);
		}
	}

	function handle_heading(s_curr_pos, newline) {
		var open_tag = 0;
		var close_tag = 0;

		// calculate tag level, always eat control symbols to avoid second call of handler
		while(hasnext()) {
			if (peek() === "=") {
				open_tag++;
				next();
			} else {
				break;
			}
		}

		// invalid tag length or not at start of the line
		if (!newline || open_tag < 1 || open_tag > 6) {
			return 0;
		}

		// header body and ending tag
		while(hasnext()) {
			var ch = peek();
			if (ch === "=") {
				close_tag++;
				next();
			} else if (ch === "\r" || ch === "\n") {
				break;
			} else {
				close_tag = 0;
				next();
			}
		}

		// no close tag
		if (!close_tag) {
			return 0;
		}

		var e_curr_pos = g_curr_pos; // end position of the text

		newline = handle_newline(); // skip empty lines
		if (newline) {
			var level = Math.min(open_tag, close_tag);
			handle_text(s_curr_pos);
			add_token(["h", level, str.substring(g_last_pos + level, e_curr_pos - level) ], g_curr_pos);
		}

		return newline;
	}

	function handle_paragraph(s_curr_pos, newline) {
		// calculate newlines only if it was called after non-newline character
		newline = newline || handle_newline();

		if (newline > 1) {
			handle_text(s_curr_pos);
			add_token(["p"], g_curr_pos);
			if (newline > 2) {
				add_token(["b", newline - 2], g_curr_pos);
			}
		}
		return newline;
	}

	function handle_list(s_curr_pos, newline) {
		var ch;
		// always eat control symbols to avoid second call of handler
		while(hasnext()) {
			ch = peek();
			if (ch === '#' || ch === '*') {
				next();
			} else {
				break;
			}
		}
		var l_curr_pos = g_curr_pos; // end position of the markers

		// no at start of the line
		if (!newline) {
			return 0;
		}

		while(hasnext()) {
			ch = peek();
			if (ch === "\n" || ch === "\r") {
				break;
			} else {
				next();
			}
		}
		var e_curr_pos = g_curr_pos; // end position of the text

		newline = handle_newline(); //skip empty lines
		if (newline) {
			handle_text(s_curr_pos);
			add_token([str.substring(g_last_pos, l_curr_pos), str.substring(l_curr_pos, e_curr_pos) ], g_curr_pos);
			handle_paragraph(g_curr_pos, newline);
		}

		return newline;
	}

	function handle_emphasize(s_curr_pos, newline) {
		var e_level = 0;
		while(hasnext()) {
			if (peek() === "'") {
				e_level++;
				next();
			} else {
				break;
			}
		}

		switch(e_level) {
			case 1:
				break;
			case 2:
			case 3:
			case 5:
				handle_text(s_curr_pos);
				add_token(["'", e_level], g_curr_pos);
				break;
			case 4:
				handle_text(s_curr_pos);
				add_token(["'", 3], g_curr_pos-1);
				break;
			default: // > 5
				handle_text(s_curr_pos);
				add_token(["'", 5], g_curr_pos - e_level + 5);
				break;
		}
		return 0;
	}

	function handle_default(s_curr_pos, newline) {
		next();
		return 0;
	}

	var newline = 1;
	while(hasnext()) {
		switch(peek()) {
			case "=":
				newline = handle_heading(g_curr_pos, newline);
				break;
			case "\r":
			case "\n":
				newline = handle_paragraph(g_curr_pos, 0);
				break;
			case "#":
			case "*":
				newline = handle_list(g_curr_pos, newline);
				break;
			case "'":
				newline = handle_emphasize(g_curr_pos, newline);
				break;
			default:
				newline = handle_default(g_curr_pos, newline);
				break;
		}
	}
	handle_text(g_curr_pos);
};

lib.autocorrect = function(callback) {
	var g_stack = []; // stack of disbalanced items
	var g_head = null; // head of list of unprocessing items
	var g_tail = null; // tail of list of unprocessing items
	var g_context = null; // current global context

	function append_token(token) {
		var item = { token: token, prev: g_tail, next: null };
		if (g_head == null) {
			g_head = item;
		}
		if (g_tail != null) {
			g_tail.next = item;
		}
		g_tail = item;
		return item;
	}

	function dump_tokens() {
		// automatically drop last unclosed tokens
		// automatically close other unclosed tokens
		while(g_stack.length) {
			var last = g_stack.pop();
			if (!last.next) {
				g_tail = last.prev;
				if (g_tail) {
					g_tail.next = null;
				} else {
					g_head = null;
				}
			} else {
				var l_tag = last.token[1];
				if (l_tag === 5) {
					modify_token(last, 3, 2);
					append_token(["'", 2]);
					append_token(["'", 3]);
				} else {
					append_token(["'", l_tag]);
				}
			}
		}

		var item = g_head;
		while(item) {
			callback(item.token);
			item = item.next;
		}
		g_head = g_tail = null;
	}

	function modify_token(item, tag1, tag2) {
		item.token[1] = tag1;
		var item2 = { token: ["'", tag2], prev: item, next: item.next };
		if (item.next) {
			item.next.prev = item2;
		}
		item.next = item2;
		if (item === g_tail) {
			g_tail = item2;
		}
	}

	function handle_emphasize(token) {
		if (!g_stack.length) { // no unbalanced items
			dump_tokens();
			g_stack.push(append_token(token));
			return;
		}

		var curr_tag = token[1];
		var prev = g_stack[g_stack.length - 1]; // last unbalanced item
		var prev_tag = prev.token[1];

		if (prev_tag === curr_tag) {
			if (prev_tag === 5) {
				modify_token(prev, 3, 2);
				append_token(["'", 2]);
				append_token(["'", 3]);
			} else {
				append_token(token);
			}
			g_stack.pop();
		} else {
			if (prev_tag === 5) {
				modify_token(prev, 5 - curr_tag, curr_tag);
				append_token(["'", curr_tag]);
			} else if (curr_tag === 5) {
				append_token(["'", prev_tag]);
				g_stack.pop();
				var new_tag = 5 - prev_tag;
				var new_curr = append_token(["'", new_tag]);
				if (!g_stack.length || g_stack[g_stack.length-1].token[1] !== new_tag) {
					g_stack.push(new_curr);
				} else {
					g_stack.pop();
				}
			} else if (g_stack.length === 1) {
				g_stack.push(append_token(token));
			} else {
				append_token(["'", prev_tag]);
				g_stack.pop();
				append_token(token);
				g_stack.pop();
				g_stack.push(append_token(["'", prev_tag]));
			}
		}
	}

	return function(token) {
		if (token == null) {
			dump_tokens();
			return;
		}
		var token_type = token[0][0];
		switch(token_type) {
			// block tokens
			case "h":
			case "p":
			case "*":
			case "#":
				dump_tokens();
				if (token_type === "p") {
					g_context = null;
				} else {
					g_context = token;
					callback(g_context);
				}
				break;
			// inline tokens
			case "b":
			case "t":
			case "'":
				if (!g_context || g_context[0] !== "p") {
					g_context = ["p"];
					callback(g_context);
				}
				if (token_type === "'") {
					handle_emphasize(token);
				} else if (g_head != null) {
					append_token(token);
				} else {
					callback(token);
				}
				break;
			default:
				throw new Error("unsupported token");
		}
	};
};

lib.format = function (text) {
	var g_context = []; // current global context, paragraph, ordered or unordered list,
	var g_result = []; // output

	function close_ctx(depth) {
		while(g_context.length && (depth == null || depth--)) {
			var ctx = g_context.pop();
			g_result.push("</" + ctx[0] + ">");
		}
	}

	function open_ctx(ctx) {
		g_context.push(ctx);
		g_result.push("<" + ctx[0] + ">");
	}

	function curr_ctx() {
		return g_context.length?g_context[g_context.length - 1]:[null];
	}

	function handle_paragraph(s_token) {
		close_ctx(null);
		open_ctx(["p"]);
	}

	function handle_text(s_token) {
		g_result.push(s_token[1]);
	}

	function handle_br(s_token) {
		g_result.push(new Array(s_token[1] + 1).join("
"));
	}

	function handle_heading(s_token) {
		close_ctx(null);
		g_result.push(["<h",s_token[1],">",$.trim(s_token[2]),"</h",s_token[1],">"].join(""));
	}

	function handle_list(s_token) {
		var l_type = s_token[0];
		var l_text = s_token[1];
		var level;

		var ctx = curr_ctx();
		if (ctx[0] === "li") {
			var c_type = ctx[1];
			var common_prefix = 0;
			var common_length = Math.min(c_type.length, l_type.length);

			for(common_prefix = 0; common_prefix < common_length; ++common_prefix) {
				if (c_type[common_prefix] !== l_type[common_prefix]) {
					break;
				}
			}
			for(level=c_type.length; level > common_prefix; --level) {
				close_ctx(2);
			}
			if (l_type.length <= common_prefix) {
				close_ctx(1);
			}
			for(level=common_prefix; level < l_type.length; ++level) {
				open_ctx([(l_type[level] === "*")?"ul":"ol"]);
				open_ctx(["li", l_type]);
			}
			if (l_type.length === common_prefix) {
				open_ctx(["li", l_type]);
			}
		} else {
			close_ctx(null);
			for(level=0; level < l_type.length; ++level) {
				open_ctx([(l_type[level] === "*")?"ul":"ol"]);
				open_ctx(["li", l_type]);
			}
		}

		g_result.push($.trim(l_text));
	}

	function handle_emphasize(s_token) {
		var e_tag;
		switch(s_token[1]) {
			case 2:
				e_tag = "em";
				break;
			case 3:
				e_tag = "strong";
				break;
			default:
				return; // ignore invalid emphasize tags
		}

		if (e_tag === curr_ctx()[0]) {
			close_ctx(1);
		} else {
			open_ctx([e_tag]);
		}
	}

	var corrector = lib.autocorrect(function(token) {
		switch (token[0][0]) {
			case "p":
				handle_paragraph(token);
				break;
			case "t":
				handle_text(token);
				break;
			case "h":
				handle_heading(token);
				break;
			case "b":
				handle_br(token);
				break;
			case "*":
			case "#":
				handle_list(token);
				break;
			case "'":
				handle_emphasize(token);
				break;
			default:
				throw new Error("invalid token");
		}
	});
	lib.tokenize(text, corrector);
	corrector(null); // flush buffers
	close_ctx(null); // finally close all active contexts

	return g_result.join("");
};

}(jQuery));
<|endoftext|>