<commit_before>var utils = require('./utils')
var Chunk = require('./chunk')

function File (uploader, file, parent) {
	this.uploader = uploader
	this.isRoot = this.isFolder = uploader === this
	this.parent = parent || null
	this.files = []
	this.fileList = []
	this.chunks = []

	if (this.isRoot || !file) {
		this.file = null
	} else {
		if (utils.isString(file)) {
			// folder
			this.isFolder = true
			this.file = null
			this.path = file
			if (this.parent.path) {
				file = file.substr(this.parent.path.length)
			}
			this.name = file.charAt(file.length - 1) === '/' ? file.substr(0, file.length - 1) : file
		} else {
			this.file = file
			this.fileType = this.file.type
			this.name = file.fileName || file.name
			this.size = file.size
			this.relativePath = file.relativePath || file.webkitRelativePath || this.name
			this._parseFile()
		}
	}

	this.paused = false
	this.errored = false
	this.aborted = false
	this.averageSpeed = 0
	this.currentSpeed = 0
	this._lastProgressCallback = Date.now()
	this._prevUploadedSize = 0
	this._prevProgress = 0

	this.bootstrap()
}

utils.extend(File.prototype, {

	_parseFile: function () {
		var ppaths = parsePaths(this.relativePath)
		if (ppaths.length) {
			var filePaths = this.uploader.filePaths
			utils.each(ppaths, function (path, i) {
				var folderFile = filePaths[path]
				if (!folderFile) {
					folderFile = new File(this.uploader, path, this.parent)
					filePaths[path] = folderFile
					this._updateParentFileList(folderFile)
				}
				this.parent = folderFile
				folderFile.files.push(this)
				if (!ppaths[i + 1]) {
					folderFile.fileList.push(this)
				}
			}, this)
		} else {
			this._updateParentFileList()
		}
	},

	_updateParentFileList: function (file) {
		if (!file) {
			file = this
		}
		var p = this.parent
		if (p) {
			p.fileList.push(file)
			// while (p && !p.isRoot) {
			// 	p.files.push(this)
			// 	p = p.parent
			// }
		}
	},

	_eachAccess: function (eachFn, fileFn) {
		if (this.isFolder) {
			utils.each(this.files, function (f, i) {
				return eachFn.call(this, f, i)
			}, this)
			return
		}
		if (!fileFn) {
			fileFn = eachFn
		}
		fileFn.call(this, this)
	},

	bootstrap: function () {
		if (this.isFolder) return
		var opts = this.uploader.opts
		if (utils.isFunction(opts.initFileFn)) {
			opts.initFileFn.call(this, this)
		}

		this.abort(true)
		this.errored = false
		// Rebuild stack of chunks from file
		this._prevProgress = 0
		var round = opts.forceChunkSize ? Math.ceil : Math.floor
		var chunks = Math.max(round(this.size / opts.chunkSize), 1)
		for (var offset = 0; offset < chunks; offset++) {
			this.chunks.push(new Chunk(this.uploader, this, offset))
		}
	},

	_measureSpeed: function () {
		var averageSpeeds = 0
		var currentSpeeds = 0
		var num = 0
		this._eachAccess(function (file) {
			if (!file.paused && !file.errored) {
				num += 1
				averageSpeeds += file.averageSpeed || 0
				currentSpeeds += file.currentSpeed || 0
			}
		}, function () {
			var timeSpan = Date.now() - this._lastProgressCallback
			if (!timeSpan) {
				return
			}
			var smoothingFactor = this.uploader.opts.speedSmoothingFactor
			var uploaded = this.sizeUploaded()
			// Prevent negative upload speed after file upload resume
			this.currentSpeed = Math.max((uploaded - this._prevUploadedSize) / timeSpan * 1000, 0)
			this.averageSpeed = smoothingFactor * this.currentSpeed + (1 - smoothingFactor) * this.averageSpeed
			this._prevUploadedSize = uploaded
		})
		if (this.isFolder) {
			if (num) {
				this.currentSpeed = currentSpeeds / num
				this.averageSpeed = averageSpeeds / num
			} else {
				this.currentSpeed = 0
				this.averageSpeed = 0
			}
		}
		if (this.parent) {
			this.parent._measureSpeed()
		}
	},

	_chunkEvent: function (chunk, evt, message) {
		var uploader = this.uploader
		var STATUS = Chunk.STATUS
		switch (evt) {
			case STATUS.PROGRESS:
				if (Date.now() - this._lastProgressCallback < uploader.opts.progressCallbacksInterval) {
					break
				}
				this._measureSpeed()
				uploader._trigger('fileProgress', this, chunk)
				uploader._trigger('progress')
				this._lastProgressCallback = Date.now()
				break
			case STATUS.ERROR:
				this.errored = true
				this.abort(true)
				uploader._trigger('fileError', this, message, chunk)
				uploader._trigger('error', message, this, chunk)
				break
			case STATUS.SUCCESS:
				if (this.errored) {
					return
				}
				this._measureSpeed()
				uploader._trigger('fileProgress', this, chunk)
				uploader._trigger('progress')
				this._lastProgressCallback = Date.now()
				if (this.isComplete()) {
					this.currentSpeed = 0
					this.averageSpeed = 0
					uploader._trigger('fileSuccess', this, message, chunk)
				}
				break
			case STATUS.RETRY:
				uploader._trigger('fileRetry', this, chunk)
				break
		}
	},

	isComplete: function () {
		var outstanding = false
		this._eachAccess(function (file) {
			if (!file.isComplete()) {
				outstanding = true
				return false
			}
		}, function () {
			var STATUS = Chunk.STATUS
			utils.each(this.chunks, function (chunk) {
				var status = chunk.status()
				if (status === STATUS.PENDING || status === STATUS.UPLOADING || status === STATUS.READING || chunk.preprocessState === 1 || chunk.readState === 1) {
					outstanding = true
					return false
				}
			})
		})
		return !outstanding
	},

	isUploading: function () {
		var uploading = false
		this._eachAccess(function (file) {
			if (file.isUploading()) {
				uploading = true
				return false
			}
		}, function () {
			var uploadingStatus = Chunk.STATUS.UPLOADING
			utils.each(this.chunks, function (chunk) {
				if (chunk.status() === uploadingStatus) {
					uploading = true
					return false
				}
			})
		})
		return uploading
	},

	resume: function () {
		this._eachAccess(function (f) {
			f.resume()
		}, function () {
			this.paused = false
			this.aborted = false
			this.uploader.upload()
		})
		this.paused = false
		this.aborted = false
	},

	error: function (errored) {
		this.errored = errored

		if (this.parent) {
			this.parent.error(errored)
		}
	},

	pause: function () {
		this._eachAccess(function (f) {
			f.pause()
		}, function () {
			this.paused = true
			this.abort()
		})
		this.paused = true
	},

	cancel: function () {
		if (this.isFolder) {
			for (var i = this.files.length - 1; i >= 0; i--) {
				this.files[i].cancel()
			}
			return
		}
		this.uploader.removeFile(this)
	},

	retry: function (file) {
		if (file) {
			file.bootstrap()
		} else {
			this._eachAccess(function (f) {
				f.bootstrap()
			}, function () {
				this.bootstrap()
			})
		}
		this.uploader.upload()
	},

	abort: function (reset) {
		if (this.aborted) {
			return
		}
		this.currentSpeed = 0
		this.averageSpeed = 0
		this.aborted = !reset
		var chunks = this.chunks
		if (reset) {
			this.chunks = []
		}
		var uploadingStatus = Chunk.STATUS.UPLOADING
		utils.each(chunks, function (c) {
			if (c.status() === uploadingStatus) {
				c.abort()
				this.uploader.uploadNextChunk()
			}
		}, this)
	},

	progress: function () {
		var totalDone = 0
		var totalSize = 0
		var ret = 0
		this._eachAccess(function (file, index) {
			totalDone += file.progress() * file.size
			totalSize += file.size
			if (index === this.files.length - 1) {
				ret = totalSize > 0 ? totalDone / totalSize : this.isComplete() ? 1 : 0
			}
		}, function () {
			if (this.errored) {
				ret = 1
				return
			}
			if (this.chunks.length === 1) {
				this._prevProgress = Math.max(this._prevProgress, this.chunks[0].progress())
				ret = this._prevProgress
				return
			}
			// Sum up progress across everything
			var bytesLoaded = 0
			utils.each(this.chunks, function (c) {
				// get chunk progress relative to entire file
				bytesLoaded += c.progress() * (c.endByte - c.startByte)
			})
			var percent = bytesLoaded / this.size
			// We don't want to lose percentages when an upload is paused
			this._prevProgress = Math.max(this._prevProgress, percent > 0.9999 ? 1 : percent)
			ret = this._prevProgress
		})
		return ret
	},

	getSize: function () {
		var size = 0
		this._eachAccess(function (file) {
			size += file.size
		}, function () {
			size += this.size
		})
		return size
	},

	getFormatSize: function () {
		var size = this.getSize()
		return utils.formatSize(size)
	},

	getRoot: function () {
		if (this.isRoot) {
			return this
		}
		var parent = this.parent
		while (parent) {
			if (parent.parent === this.uploader) {
				// find it
				return parent
			}
			parent = parent.parent
		}
		return this
	},

	sizeUploaded: function () {
		var size = 0
		this._eachAccess(function (file) {
			size += file.sizeUploaded()
		}, function () {
			utils.each(this.chunks, function (chunk) {
				size += chunk.sizeUploaded()
			})
		})
		return size
	},

	timeRemaining: function () {
		var ret = 0
		var sizeDelta = 0
		var averageSpeed = 0
		this._eachAccess(function (file, i) {
			if (!file.paused && !file.errored) {
				sizeDelta += file.size - file.sizeUploaded()
				averageSpeed += file.averageSpeed
			}
			if (i === this.files.length - 1) {
				ret = calRet(sizeDelta, averageSpeed)
			}
		}, function () {
			if (this.paused || this.errored) {
				ret = 0
				return
			}
			var delta = this.size - this.sizeUploaded()
			ret = calRet(delta, this.averageSpeed)
		})
		return ret
		function calRet (delta, averageSpeed) {
			if (delta && !averageSpeed) {
				return Number.POSITIVE_INFINITY
			}
			if (!delta && !averageSpeed) {
				return 0
			}
			return Math.floor(delta / averageSpeed)
		}
	},

	removeFile: function (file) {
		if (file.isFolder) {
			while (file.files.length) {
				this._removeFile(file.files[file.files.length - 1])
			}
		}
		this._removeFile(file)
		this._delFilePath(file)
	},

	_delFilePath: function (file) {
		if (file.path && this.filePaths) {
			delete this.filePaths[file.path]
		}
		utils.each(file.fileList, function (file) {
			this._delFilePath(file)
		}, this)
	},

	_removeFile: function (file) {
		!file.isFolder && utils.each(this.files, function (f, i) {
			if (f === file) {
				this.files.splice(i, 1)
				file.abort()
				var parent = file.parent
				while (parent && parent !== this) {
					parent._removeFile(file)
					parent = parent.parent
				}
				return false
			}
		}, this)
		file.parent === this && utils.each(this.fileList, function (f, i) {
			if (f === file) {
				this.fileList.splice(i, 1)
				return false
			}
		}, this)
		file.parent = null
	},

	getType: function () {
		if (this.isFolder) {
			return 'folder'
		}
		return this.fileType && this.fileType.split('/')[1]
	},

	getExtension: function () {
		if (this.isFolder) {
			return ''
		}
		return this.name.substr((~-this.name.lastIndexOf('.') >>> 0) + 2).toLowerCase()
	}

})

module.exports = File

function parsePaths (path) {
	var ret = []
	var paths = path.split('/')
	var len = paths.length
	var i = 1
	paths.splice(len - 1, 1)
	len--
	if (paths.length) {
		while (i <= len) {
			ret.push(paths.slice(0, i++).join('/') + '/')
		}
	}
	return ret
}
<commit_msg>fix filetype<commit_after>var utils = require('./utils')
var Chunk = require('./chunk')

function File (uploader, file, parent) {
	this.uploader = uploader
	this.isRoot = this.isFolder = uploader === this
	this.parent = parent || null
	this.files = []
	this.fileList = []
	this.chunks = []

	if (this.isRoot || !file) {
		this.file = null
	} else {
		if (utils.isString(file)) {
			// folder
			this.isFolder = true
			this.file = null
			this.path = file
			if (this.parent.path) {
				file = file.substr(this.parent.path.length)
			}
			this.name = file.charAt(file.length - 1) === '/' ? file.substr(0, file.length - 1) : file
		} else {
			this.file = file
			this.fileType = this.file.type
			this.name = file.fileName || file.name
			this.size = file.size
			this.relativePath = file.relativePath || file.webkitRelativePath || this.name
			this._parseFile()
		}
	}

	this.paused = false
	this.errored = false
	this.aborted = false
	this.averageSpeed = 0
	this.currentSpeed = 0
	this._lastProgressCallback = Date.now()
	this._prevUploadedSize = 0
	this._prevProgress = 0

	this.bootstrap()
}

utils.extend(File.prototype, {

	_parseFile: function () {
		var ppaths = parsePaths(this.relativePath)
		if (ppaths.length) {
			var filePaths = this.uploader.filePaths
			utils.each(ppaths, function (path, i) {
				var folderFile = filePaths[path]
				if (!folderFile) {
					folderFile = new File(this.uploader, path, this.parent)
					filePaths[path] = folderFile
					this._updateParentFileList(folderFile)
				}
				this.parent = folderFile
				folderFile.files.push(this)
				if (!ppaths[i + 1]) {
					folderFile.fileList.push(this)
				}
			}, this)
		} else {
			this._updateParentFileList()
		}
	},

	_updateParentFileList: function (file) {
		if (!file) {
			file = this
		}
		var p = this.parent
		if (p) {
			p.fileList.push(file)
			// while (p && !p.isRoot) {
			// 	p.files.push(this)
			// 	p = p.parent
			// }
		}
	},

	_eachAccess: function (eachFn, fileFn) {
		if (this.isFolder) {
			utils.each(this.files, function (f, i) {
				return eachFn.call(this, f, i)
			}, this)
			return
		}
		if (!fileFn) {
			fileFn = eachFn
		}
		fileFn.call(this, this)
	},

	bootstrap: function () {
		if (this.isFolder) return
		var opts = this.uploader.opts
		if (utils.isFunction(opts.initFileFn)) {
			opts.initFileFn.call(this, this)
		}

		this.abort(true)
		this.errored = false
		// Rebuild stack of chunks from file
		this._prevProgress = 0
		var round = opts.forceChunkSize ? Math.ceil : Math.floor
		var chunks = Math.max(round(this.size / opts.chunkSize), 1)
		for (var offset = 0; offset < chunks; offset++) {
			this.chunks.push(new Chunk(this.uploader, this, offset))
		}
	},

	_measureSpeed: function () {
		var averageSpeeds = 0
		var currentSpeeds = 0
		var num = 0
		this._eachAccess(function (file) {
			if (!file.paused && !file.errored) {
				num += 1
				averageSpeeds += file.averageSpeed || 0
				currentSpeeds += file.currentSpeed || 0
			}
		}, function () {
			var timeSpan = Date.now() - this._lastProgressCallback
			if (!timeSpan) {
				return
			}
			var smoothingFactor = this.uploader.opts.speedSmoothingFactor
			var uploaded = this.sizeUploaded()
			// Prevent negative upload speed after file upload resume
			this.currentSpeed = Math.max((uploaded - this._prevUploadedSize) / timeSpan * 1000, 0)
			this.averageSpeed = smoothingFactor * this.currentSpeed + (1 - smoothingFactor) * this.averageSpeed
			this._prevUploadedSize = uploaded
		})
		if (this.isFolder) {
			if (num) {
				this.currentSpeed = currentSpeeds / num
				this.averageSpeed = averageSpeeds / num
			} else {
				this.currentSpeed = 0
				this.averageSpeed = 0
			}
		}
		if (this.parent) {
			this.parent._measureSpeed()
		}
	},

	_chunkEvent: function (chunk, evt, message) {
		var uploader = this.uploader
		var STATUS = Chunk.STATUS
		switch (evt) {
			case STATUS.PROGRESS:
				if (Date.now() - this._lastProgressCallback < uploader.opts.progressCallbacksInterval) {
					break
				}
				this._measureSpeed()
				uploader._trigger('fileProgress', this, chunk)
				uploader._trigger('progress')
				this._lastProgressCallback = Date.now()
				break
			case STATUS.ERROR:
				this.errored = true
				this.abort(true)
				uploader._trigger('fileError', this, message, chunk)
				uploader._trigger('error', message, this, chunk)
				break
			case STATUS.SUCCESS:
				if (this.errored) {
					return
				}
				this._measureSpeed()
				uploader._trigger('fileProgress', this, chunk)
				uploader._trigger('progress')
				this._lastProgressCallback = Date.now()
				if (this.isComplete()) {
					this.currentSpeed = 0
					this.averageSpeed = 0
					uploader._trigger('fileSuccess', this, message, chunk)
				}
				break
			case STATUS.RETRY:
				uploader._trigger('fileRetry', this, chunk)
				break
		}
	},

	isComplete: function () {
		var outstanding = false
		this._eachAccess(function (file) {
			if (!file.isComplete()) {
				outstanding = true
				return false
			}
		}, function () {
			var STATUS = Chunk.STATUS
			utils.each(this.chunks, function (chunk) {
				var status = chunk.status()
				if (status === STATUS.PENDING || status === STATUS.UPLOADING || status === STATUS.READING || chunk.preprocessState === 1 || chunk.readState === 1) {
					outstanding = true
					return false
				}
			})
		})
		return !outstanding
	},

	isUploading: function () {
		var uploading = false
		this._eachAccess(function (file) {
			if (file.isUploading()) {
				uploading = true
				return false
			}
		}, function () {
			var uploadingStatus = Chunk.STATUS.UPLOADING
			utils.each(this.chunks, function (chunk) {
				if (chunk.status() === uploadingStatus) {
					uploading = true
					return false
				}
			})
		})
		return uploading
	},

	resume: function () {
		this._eachAccess(function (f) {
			f.resume()
		}, function () {
			this.paused = false
			this.aborted = false
			this.uploader.upload()
		})
		this.paused = false
		this.aborted = false
	},

	error: function (errored) {
		this.errored = errored

		if (this.parent) {
			this.parent.error(errored)
		}
	},

	pause: function () {
		this._eachAccess(function (f) {
			f.pause()
		}, function () {
			this.paused = true
			this.abort()
		})
		this.paused = true
	},

	cancel: function () {
		if (this.isFolder) {
			for (var i = this.files.length - 1; i >= 0; i--) {
				this.files[i].cancel()
			}
			return
		}
		this.uploader.removeFile(this)
	},

	retry: function (file) {
		if (file) {
			file.bootstrap()
		} else {
			this._eachAccess(function (f) {
				f.bootstrap()
			}, function () {
				this.bootstrap()
			})
		}
		this.uploader.upload()
	},

	abort: function (reset) {
		if (this.aborted) {
			return
		}
		this.currentSpeed = 0
		this.averageSpeed = 0
		this.aborted = !reset
		var chunks = this.chunks
		if (reset) {
			this.chunks = []
		}
		var uploadingStatus = Chunk.STATUS.UPLOADING
		utils.each(chunks, function (c) {
			if (c.status() === uploadingStatus) {
				c.abort()
				this.uploader.uploadNextChunk()
			}
		}, this)
	},

	progress: function () {
		var totalDone = 0
		var totalSize = 0
		var ret = 0
		this._eachAccess(function (file, index) {
			totalDone += file.progress() * file.size
			totalSize += file.size
			if (index === this.files.length - 1) {
				ret = totalSize > 0 ? totalDone / totalSize : this.isComplete() ? 1 : 0
			}
		}, function () {
			if (this.errored) {
				ret = 1
				return
			}
			if (this.chunks.length === 1) {
				this._prevProgress = Math.max(this._prevProgress, this.chunks[0].progress())
				ret = this._prevProgress
				return
			}
			// Sum up progress across everything
			var bytesLoaded = 0
			utils.each(this.chunks, function (c) {
				// get chunk progress relative to entire file
				bytesLoaded += c.progress() * (c.endByte - c.startByte)
			})
			var percent = bytesLoaded / this.size
			// We don't want to lose percentages when an upload is paused
			this._prevProgress = Math.max(this._prevProgress, percent > 0.9999 ? 1 : percent)
			ret = this._prevProgress
		})
		return ret
	},

	getSize: function () {
		var size = 0
		this._eachAccess(function (file) {
			size += file.size
		}, function () {
			size += this.size
		})
		return size
	},

	getFormatSize: function () {
		var size = this.getSize()
		return utils.formatSize(size)
	},

	getRoot: function () {
		if (this.isRoot) {
			return this
		}
		var parent = this.parent
		while (parent) {
			if (parent.parent === this.uploader) {
				// find it
				return parent
			}
			parent = parent.parent
		}
		return this
	},

	sizeUploaded: function () {
		var size = 0
		this._eachAccess(function (file) {
			size += file.sizeUploaded()
		}, function () {
			utils.each(this.chunks, function (chunk) {
				size += chunk.sizeUploaded()
			})
		})
		return size
	},

	timeRemaining: function () {
		var ret = 0
		var sizeDelta = 0
		var averageSpeed = 0
		this._eachAccess(function (file, i) {
			if (!file.paused && !file.errored) {
				sizeDelta += file.size - file.sizeUploaded()
				averageSpeed += file.averageSpeed
			}
			if (i === this.files.length - 1) {
				ret = calRet(sizeDelta, averageSpeed)
			}
		}, function () {
			if (this.paused || this.errored) {
				ret = 0
				return
			}
			var delta = this.size - this.sizeUploaded()
			ret = calRet(delta, this.averageSpeed)
		})
		return ret
		function calRet (delta, averageSpeed) {
			if (delta && !averageSpeed) {
				return Number.POSITIVE_INFINITY
			}
			if (!delta && !averageSpeed) {
				return 0
			}
			return Math.floor(delta / averageSpeed)
		}
	},

	removeFile: function (file) {
		if (file.isFolder) {
			while (file.files.length) {
				this._removeFile(file.files[file.files.length - 1])
			}
		}
		this._removeFile(file)
		this._delFilePath(file)
	},

	_delFilePath: function (file) {
		if (file.path && this.filePaths) {
			delete this.filePaths[file.path]
		}
		utils.each(file.fileList, function (file) {
			this._delFilePath(file)
		}, this)
	},

	_removeFile: function (file) {
		!file.isFolder && utils.each(this.files, function (f, i) {
			if (f === file) {
				this.files.splice(i, 1)
				file.abort()
				var parent = file.parent
				while (parent && parent !== this) {
					parent._removeFile(file)
					parent = parent.parent
				}
				return false
			}
		}, this)
		file.parent === this && utils.each(this.fileList, function (f, i) {
			if (f === file) {
				this.fileList.splice(i, 1)
				return false
			}
		}, this)
		file.parent = null
	},

	getType: function () {
		if (this.isFolder) {
			return 'folder'
		}
		return this.file.type && this.file.type.split('/')[1]
	},

	getExtension: function () {
		if (this.isFolder) {
			return ''
		}
		return this.name.substr((~-this.name.lastIndexOf('.') >>> 0) + 2).toLowerCase()
	}

})

module.exports = File

function parsePaths (path) {
	var ret = []
	var paths = path.split('/')
	var len = paths.length
	var i = 1
	paths.splice(len - 1, 1)
	len--
	if (paths.length) {
		while (i <= len) {
			ret.push(paths.slice(0, i++).join('/') + '/')
		}
	}
	return ret
}
<|endoftext|>


<commit_before>/*!
 * Star Rating
 * @version: 2.1.1
 * @author: Paul Ryley (http://geminilabs.io)
 * @url: https://github.com/geminilabs/star-rating.js
 * @license: MIT
 */

/** global: define */

;(function( window, document, undefined ) {

	"use strict";

	/** @return object */
	var Plugin = function( selector, options ) { // string|object, object
		this.selects = {}.toString.call( selector ) === '[object String]' ? document.querySelectorAll( selector ) : [selector];
		this.destroy = function() {
			this.widgets.forEach( function( widget ) {
				widget.destroy_();
			});
		};
		this.rebuild = function() {
			this.widgets.forEach( function( widget ) {
				widget.rebuild_();
			});
		};
		this.widgets = [];
		for( var i = 0; i < this.selects.length; i++ ) {
			if( this.selects[i].tagName !== 'SELECT' )continue;
			var widget = new Widget( this.selects[i], options );
			if( widget.direction === undefined )continue;
			this.widgets.push( widget );
		}
	};

	/** @return void */
	var Widget = function( el, options ) { // HTMLElement, object|null
		this.el = el;
		this.options_ = this.extend_( {}, this.defaults, options || {}, JSON.parse( el.getAttribute( 'data-options' )));
		this.setStarCount_();
		if( this.stars < 1 || this.stars > this.options_.maxStars )return;
		this.init_();
	};

	Widget.prototype = {

		defaults: {
			clearable: true,
			initialText: 'Select a Rating',
			maxStars: 10,
			onClick: null,
			showText: true,
		},

		/** @return void */
		init_: function() {
			this.initEvents_();
			this.current = this.selected = this.getSelectedValue_();
			this.wrapEl_();
			this.buildWidgetEl_();
			this.setDirection_();
			this.setValue_( this.current );
			this.handleEvents_( 'add' );
		},

		/** @return void */
				class: 'gl-star-rating-text',
		buildLabelEl_: function() {
			if( !this.options_.showText )return;
			this.textEl = this.insertSpanEl_( this.widgetEl, {
			}, true );
		},

		/** @return void */
				class: 'gl-star-rating-stars',
		buildWidgetEl_: function() {
			var values = this.getOptionValues_();
			var widgetEl = this.insertSpanEl_( this.el, {
			}, true );
			for( var key in values ) {
				if( !values.hasOwnProperty( key ))continue;
				var newEl = this.createSpanEl_({
					'data-value': key,
					'data-text': values[key],
				});
				widgetEl.innerHTML += newEl.outerHTML;
			}
			this.widgetEl = widgetEl;
			this.buildLabelEl_();
		},

		/** @return void */
		changeTo_: function( index ) { // int
			if( index < 0 || index === '' ) {
				index = 0;
			}
			if( index > this.stars ) {
				index = this.stars;
			}
			this.widgetEl.classList.remove( 's' + ( 10 * this.current ));
			this.widgetEl.classList.add( 's' + ( 10 * index ));
			if( this.options_.showText ) {
				this.textEl.textContent = index < 1 ? this.options_.initialText : this.widgetEl.childNodes[index - 1].dataset.text;
			}
			this.current = index;
		},

		/** @return HTMLElement */
		createSpanEl_: function( attributes ) { // object
			var el = document.createElement( 'span' );
			attributes = attributes || {};
			for( var key in attributes ) {
				if( !attributes.hasOwnProperty( key ))continue;
				el.setAttribute( key, attributes[key] );
			}
			return el;
		},

		/** @return void */
		destroy_: function() {
			this.handleEvents_( 'remove' );
			var wrapEl = this.el.parentNode;
			wrapEl.parentNode.replaceChild( this.el, wrapEl );
		},

		/** @return void */
		eventListener_: function( el, action, events ) { // HTMLElement, string, array
			events.forEach( function( event ) {
				el[action + 'EventListener']( event, this.events[event] );
			}.bind( this ));
		},

		/** @return object */
		extend_: function() { // ...object
			var args = [].slice.call( arguments );
			var result = args[0];
			var extenders = args.slice(1);
			Object.keys( extenders ).forEach( function( i ) {
				for( var key in extenders[i] ) {
					if( !extenders[i].hasOwnProperty( key ))continue;
					result[key] = extenders[i][key];
				}
			});
			return result;
		},

		/** @return int */
		getIndexFromPosition_: function( pageX ) { // int
			var direction = {};
			var widgetWidth = this.widgetEl.offsetWidth;
			direction.ltr = Math.max( pageX - this.offsetLeft, 1 );
			direction.rtl = widgetWidth - direction.ltr;
			return Math.min(
				Math.ceil( direction[this.direction] / Math.round( widgetWidth / this.stars )),
				this.stars
			);
		},

		/** @return object */
		getOptionValues_: function() {
			var el = this.el;
			var unorderedValues = {};
			var orderedValues = {};
			for( var i = 0; i < el.length; i++ ) {
				if( el[i].value === '' )continue;
				unorderedValues[el[i].value] = el[i].text;
			}
			Object.keys( unorderedValues ).sort().forEach( function( key ) {
				orderedValues[key] = unorderedValues[key];
			});
			return orderedValues;
		},

		/** @return int */
		getSelectedValue_: function() {
			return parseInt( this.el.options[Math.max( this.el.selectedIndex, 0 )].value ) || 0;
		},

		/** @return void */
		handleEvents_: function( action ) { // string
			var formEl = this.el.closest( 'form' );
			this.eventListener_( this.el, action, ['change', 'keydown'] );
			this.eventListener_( this.widgetEl, action, ['click', 'mouseenter', 'mouseleave'] );
			if( formEl ) {
				this.eventListener_( formEl, action, ['reset'] );
			}
		},

		/** @return void */
		initEvents_: function() {
			this.events = {
				change: this.onChange_.bind( this ),
				click: this.onClick_.bind( this ),
				keydown: this.onKeydown_.bind( this ),
				mouseenter: this.onMouseenter_.bind( this ),
				mouseleave: this.onMouseleave_.bind( this ),
				mousemove: this.onMousemove_.bind( this ),
				reset: this.onReset_.bind( this ),
			};
		},

		/** @return void */
		insertSpanEl_: function( el, attributes, after ) { // HTMLElement, object, bool
			var newEl = this.createSpanEl_( attributes );
			el.parentNode.insertBefore( newEl, after === true ? el.nextSibling : el );
			return newEl;
		},

		/** @return bool */
		isCloneable: function( obj ) { // mixed
			return Array.isArray( obj ) || {}.toString.call( obj ) == '[object Object]';
		},

		/** @return void */
		onChange_: function() {
			this.changeTo_( this.getSelectedValue_() );
		},

		/** @return void */
		onClick_: function( ev ) { // MouseEvent
			var index = this.getIndexFromPosition_( ev.pageX );
			if( this.current !== 0 && parseFloat( this.selected ) === index && this.options_.clearable ) {
				return this.onReset_();
			}
			this.setValue_( index );
			if( typeof this.options_.onClick === 'function' ) {
				this.options_.onClick.call( this, this.el );
			}
		},

		/** @return void */
		onKeydown_: function( ev ) { // KeyboardEvent
			if( ['ArrowLeft', 'ArrowRight'].indexOf( ev.key ) === -1 )return;
			var increment = ev.key === 'ArrowLeft' ? -1 : 1;
			if( this.direction === 'rtl' ) {
				increment *= -1;
			}
			this.setValue_( Math.min( Math.max( this.getSelectedValue_() + increment, 0 ), this.stars ));
		},

		/** @return void */
		onMouseenter_: function() {
			var rect = this.widgetEl.getBoundingClientRect();
			this.offsetLeft = rect.left + document.body.scrollLeft;
			this.widgetEl.addEventListener( 'mousemove', this.events.mousemove );
		},

		/** @return void */
		onMouseleave_: function() {
			this.widgetEl.removeEventListener( 'mousemove', this.events.mousemove );
			this.changeTo_( this.selected );
		},

		/** @return void */
		onMousemove_: function( ev ) { // MouseEvent
			this.changeTo_( this.getIndexFromPosition_( ev.pageX ));
		},

		/** @return void */
		onReset_: function() {
			var originallySelected = this.el.querySelector( '[selected]' );
			var value = originallySelected ? originallySelected.value : '';
			this.el.value = value;
			this.selected = parseInt( value ) || 0;
			this.changeTo_( value );
		},

		/** @return void */
			if( this.el.parentNode.classList.contains( 'gl-star-rating' )) {
		rebuild_: function() {
				this.destroy_();
			}
			this.init_();
		},

		/** @return void */
		setDirection_: function() {
			var wrapEl = this.el.parentNode;
			this.direction = window.getComputedStyle( wrapEl, null ).getPropertyValue( 'direction' );
			wrapEl.classList.add( 'gl-star-rating-' + this.direction );
		},

		/** @return void */
		setValue_: function( index ) {
			this.el.value = index;
			this.selected = index;
			this.changeTo_( index );
		},

		/** @return void */
		setStarCount_: function() {
			var el = this.el;
			this.stars = 0;
			for( var i = 0; i < el.length; i++ ) {
				if( el[i].value === '' )continue;
				if( isNaN( parseFloat( el[i].value )) || !isFinite( el[i].value )) {
					this.stars = 0;
					return;
				}
				this.stars++;
			}
		},

		/** @return void */
				class: 'gl-star-rating',
		wrapEl_: function() {
			var wrapEl = this.insertSpanEl_( this.el, {
				'data-star-rating': '',
			});
			wrapEl.appendChild( this.el );
		},
	};

	if( typeof define === 'function' && define.amd ) {
		define( [], function() { return Plugin; });
	}
	else if( typeof module === 'object' && module.exports ) {
		module.exports = Plugin;
	}
	else {
		window.StarRating = Plugin;
	}

})( window, document );
<commit_msg>Add a classname option<commit_after>/*!
 * Star Rating
 * @version: 2.1.1
 * @author: Paul Ryley (http://geminilabs.io)
 * @url: https://github.com/geminilabs/star-rating.js
 * @license: MIT
 */

/** global: define */

;(function( window, document, undefined ) {

	"use strict";

	/** @return object */
	var Plugin = function( selector, options ) { // string|object, object
		this.selects = {}.toString.call( selector ) === '[object String]' ? document.querySelectorAll( selector ) : [selector];
		this.destroy = function() {
			this.widgets.forEach( function( widget ) {
				widget.destroy_();
			});
		};
		this.rebuild = function() {
			this.widgets.forEach( function( widget ) {
				widget.rebuild_();
			});
		};
		this.widgets = [];
		for( var i = 0; i < this.selects.length; i++ ) {
			if( this.selects[i].tagName !== 'SELECT' )continue;
			var widget = new Widget( this.selects[i], options );
			if( widget.direction === undefined )continue;
			this.widgets.push( widget );
		}
	};

	/** @return void */
	var Widget = function( el, options ) { // HTMLElement, object|null
		this.el = el;
		this.options_ = this.extend_( {}, this.defaults, options || {}, JSON.parse( el.getAttribute( 'data-options' )));
		this.setStarCount_();
		if( this.stars < 1 || this.stars > this.options_.maxStars )return;
		this.init_();
	};

	Widget.prototype = {

		defaults: {
			classname: 'gl-star-rating',
			clearable: true,
			initialText: 'Select a Rating',
			maxStars: 10,
			onClick: null,
			showText: true,
		},

		/** @return void */
		init_: function() {
			this.initEvents_();
			this.current = this.selected = this.getSelectedValue_();
			this.wrapEl_();
			this.buildWidgetEl_();
			this.setDirection_();
			this.setValue_( this.current );
			this.handleEvents_( 'add' );
		},

		/** @return void */
		buildLabelEl_: function() {
			if( !this.options_.showText )return;
			this.textEl = this.insertSpanEl_( this.widgetEl, {
				class: this.options_.classname + '-text',
			}, true );
		},

		/** @return void */
		buildWidgetEl_: function() {
			var values = this.getOptionValues_();
			var widgetEl = this.insertSpanEl_( this.el, {
				class: this.options_.classname + '-stars',
			}, true );
			for( var key in values ) {
				if( !values.hasOwnProperty( key ))continue;
				var newEl = this.createSpanEl_({
					'data-value': key,
					'data-text': values[key],
				});
				widgetEl.innerHTML += newEl.outerHTML;
			}
			this.widgetEl = widgetEl;
			this.buildLabelEl_();
		},

		/** @return void */
		changeTo_: function( index ) { // int
			if( index < 0 || index === '' ) {
				index = 0;
			}
			if( index > this.stars ) {
				index = this.stars;
			}
			this.widgetEl.classList.remove( 's' + ( 10 * this.current ));
			this.widgetEl.classList.add( 's' + ( 10 * index ));
			if( this.options_.showText ) {
				this.textEl.textContent = index < 1 ? this.options_.initialText : this.widgetEl.childNodes[index - 1].dataset.text;
			}
			this.current = index;
		},

		/** @return HTMLElement */
		createSpanEl_: function( attributes ) { // object
			var el = document.createElement( 'span' );
			attributes = attributes || {};
			for( var key in attributes ) {
				if( !attributes.hasOwnProperty( key ))continue;
				el.setAttribute( key, attributes[key] );
			}
			return el;
		},

		/** @return void */
		destroy_: function() {
			this.handleEvents_( 'remove' );
			var wrapEl = this.el.parentNode;
			wrapEl.parentNode.replaceChild( this.el, wrapEl );
		},

		/** @return void */
		eventListener_: function( el, action, events ) { // HTMLElement, string, array
			events.forEach( function( event ) {
				el[action + 'EventListener']( event, this.events[event] );
			}.bind( this ));
		},

		/** @return object */
		extend_: function() { // ...object
			var args = [].slice.call( arguments );
			var result = args[0];
			var extenders = args.slice(1);
			Object.keys( extenders ).forEach( function( i ) {
				for( var key in extenders[i] ) {
					if( !extenders[i].hasOwnProperty( key ))continue;
					result[key] = extenders[i][key];
				}
			});
			return result;
		},

		/** @return int */
		getIndexFromPosition_: function( pageX ) { // int
			var direction = {};
			var widgetWidth = this.widgetEl.offsetWidth;
			direction.ltr = Math.max( pageX - this.offsetLeft, 1 );
			direction.rtl = widgetWidth - direction.ltr;
			return Math.min(
				Math.ceil( direction[this.direction] / Math.round( widgetWidth / this.stars )),
				this.stars
			);
		},

		/** @return object */
		getOptionValues_: function() {
			var el = this.el;
			var unorderedValues = {};
			var orderedValues = {};
			for( var i = 0; i < el.length; i++ ) {
				if( el[i].value === '' )continue;
				unorderedValues[el[i].value] = el[i].text;
			}
			Object.keys( unorderedValues ).sort().forEach( function( key ) {
				orderedValues[key] = unorderedValues[key];
			});
			return orderedValues;
		},

		/** @return int */
		getSelectedValue_: function() {
			return parseInt( this.el.options[Math.max( this.el.selectedIndex, 0 )].value ) || 0;
		},

		/** @return void */
		handleEvents_: function( action ) { // string
			var formEl = this.el.closest( 'form' );
			this.eventListener_( this.el, action, ['change', 'keydown'] );
			this.eventListener_( this.widgetEl, action, ['click', 'mouseenter', 'mouseleave'] );
			if( formEl ) {
				this.eventListener_( formEl, action, ['reset'] );
			}
		},

		/** @return void */
		initEvents_: function() {
			this.events = {
				change: this.onChange_.bind( this ),
				click: this.onClick_.bind( this ),
				keydown: this.onKeydown_.bind( this ),
				mouseenter: this.onMouseenter_.bind( this ),
				mouseleave: this.onMouseleave_.bind( this ),
				mousemove: this.onMousemove_.bind( this ),
				reset: this.onReset_.bind( this ),
			};
		},

		/** @return void */
		insertSpanEl_: function( el, attributes, after ) { // HTMLElement, object, bool
			var newEl = this.createSpanEl_( attributes );
			el.parentNode.insertBefore( newEl, after === true ? el.nextSibling : el );
			return newEl;
		},

		/** @return bool */
		isCloneable: function( obj ) { // mixed
			return Array.isArray( obj ) || {}.toString.call( obj ) == '[object Object]';
		},

		/** @return void */
		onChange_: function() {
			this.changeTo_( this.getSelectedValue_() );
		},

		/** @return void */
		onClick_: function( ev ) { // MouseEvent
			var index = this.getIndexFromPosition_( ev.pageX );
			if( this.current !== 0 && parseFloat( this.selected ) === index && this.options_.clearable ) {
				return this.onReset_();
			}
			this.setValue_( index );
			if( typeof this.options_.onClick === 'function' ) {
				this.options_.onClick.call( this, this.el );
			}
		},

		/** @return void */
		onKeydown_: function( ev ) { // KeyboardEvent
			if( ['ArrowLeft', 'ArrowRight'].indexOf( ev.key ) === -1 )return;
			var increment = ev.key === 'ArrowLeft' ? -1 : 1;
			if( this.direction === 'rtl' ) {
				increment *= -1;
			}
			this.setValue_( Math.min( Math.max( this.getSelectedValue_() + increment, 0 ), this.stars ));
		},

		/** @return void */
		onMouseenter_: function() {
			var rect = this.widgetEl.getBoundingClientRect();
			this.offsetLeft = rect.left + document.body.scrollLeft;
			this.widgetEl.addEventListener( 'mousemove', this.events.mousemove );
		},

		/** @return void */
		onMouseleave_: function() {
			this.widgetEl.removeEventListener( 'mousemove', this.events.mousemove );
			this.changeTo_( this.selected );
		},

		/** @return void */
		onMousemove_: function( ev ) { // MouseEvent
			this.changeTo_( this.getIndexFromPosition_( ev.pageX ));
		},

		/** @return void */
		onReset_: function() {
			var originallySelected = this.el.querySelector( '[selected]' );
			var value = originallySelected ? originallySelected.value : '';
			this.el.value = value;
			this.selected = parseInt( value ) || 0;
			this.changeTo_( value );
		},

		/** @return void */
		rebuild_: function() {
			if( this.el.parentNode.classList.contains( this.options_.classname )) {
				this.destroy_();
			}
			this.init_();
		},

		/** @return void */
		setDirection_: function() {
			var wrapEl = this.el.parentNode;
			this.direction = window.getComputedStyle( wrapEl, null ).getPropertyValue( 'direction' );
			wrapEl.classList.add( this.options_.classname + '-' + this.direction );
		},

		/** @return void */
		setValue_: function( index ) {
			this.el.value = index;
			this.selected = index;
			this.changeTo_( index );
		},

		/** @return void */
		setStarCount_: function() {
			var el = this.el;
			this.stars = 0;
			for( var i = 0; i < el.length; i++ ) {
				if( el[i].value === '' )continue;
				if( isNaN( parseFloat( el[i].value )) || !isFinite( el[i].value )) {
					this.stars = 0;
					return;
				}
				this.stars++;
			}
		},

		/** @return void */
		wrapEl_: function() {
			var wrapEl = this.insertSpanEl_( this.el, {
				class: this.options_.classname,
				'data-star-rating': '',
			});
			wrapEl.appendChild( this.el );
		},
	};

	if( typeof define === 'function' && define.amd ) {
		define( [], function() { return Plugin; });
	}
	else if( typeof module === 'object' && module.exports ) {
		module.exports = Plugin;
	}
	else {
		window.StarRating = Plugin;
	}

})( window, document );
<|endoftext|>


<commit_before>/**
 * @file Generic and secured class constructor
 */
/*#ifndef(UMD)*/
"use strict";
/*global _GpfFunctionBuilder*/ // Function builder
/*global _gpfErrorDeclare*/ // Declare new gpf.Error names
/*exported _getNewClassConstructor*/ // Allocate a named class constructor
/*#endif*/

_gpfErrorDeclare("define/builder/constructor", {
    "constructorFunction":
        "This is a constructor function, use with new"
});

/**
 * Template for new class constructor
 * - Function can't be used without new
 * - Uses closure to keep track of the class definition
 * - Class name will be injected at the right place
 *
 * @param {Function} innerConstructor Constructor as defined by the class
 * @return {Function} Constructor
 * @closure
 */
function _gpfNewClassConstructorTpl (innerConstructor) {
    var constructor = function () {
        var me = this; //eslint-disable-line no-invalid-this
        if (!(me instanceof constructor)) {
            throw gpf.Error.constructorFunction();
        }
        innerConstructor.apply(me, arguments);
    };
    return constructor;
}

/**
 * Allocate a named class constructor for a class definition
 *
 * @param {_GpfOldClassDefinition} classDef Class definition
 * @param {Function} innerConstructor Constructor as defined by the class
 * @return {Function} Constructor
 */
// Build a new constructor
function _getNewClassConstructor (classDef, innerConstructor) {
    var builder = new _GpfFunctionBuilder(_gpfNewClassConstructorTpl);
    builder.replaceInBody({
        "function": "function " + classDef.getName()
    });
    return builder.generate()(innerConstructor);
}

/*#endif*/
<commit_msg>Prefix jsdoc extensions with gpf<commit_after>/**
 * @file Generic and secured class constructor
 */
/*#ifndef(UMD)*/
"use strict";
/*global _GpfFunctionBuilder*/ // Function builder
/*global _gpfErrorDeclare*/ // Declare new gpf.Error names
/*exported _getNewClassConstructor*/ // Allocate a named class constructor
/*#endif*/

_gpfErrorDeclare("define/builder/constructor", {
    "constructorFunction":
        "This is a constructor function, use with new"
});

/**
 * Template for new class constructor
 * - Function can't be used without new
 * - Uses closure to keep track of the class definition
 * - Class name will be injected at the right place
 *
 * @param {Function} innerConstructor Constructor as defined by the class
 * @return {Function} Constructor
 * @gpf:closure
 */
function _gpfNewClassConstructorTpl (innerConstructor) {
    var constructor = function () {
        var me = this; //eslint-disable-line no-invalid-this
        if (!(me instanceof constructor)) {
            throw gpf.Error.constructorFunction();
        }
        innerConstructor.apply(me, arguments);
    };
    return constructor;
}

/**
 * Allocate a named class constructor for a class definition
 *
 * @param {_GpfOldClassDefinition} classDef Class definition
 * @param {Function} innerConstructor Constructor as defined by the class
 * @return {Function} Constructor
 */
// Build a new constructor
function _getNewClassConstructor (classDef, innerConstructor) {
    var builder = new _GpfFunctionBuilder(_gpfNewClassConstructorTpl);
    builder.replaceInBody({
        "function": "function " + classDef.getName()
    });
    return builder.generate()(innerConstructor);
}

/*#endif*/
<|endoftext|>


<commit_before>module.exports = LocalCallStore;

LocalCallStore._storage = window.localStorage;

/**
 * CallPersistence: save the details of function calls to localStorage,
 * and let us fish them out, too.
 * @param storageKey String: key our calls will be stored against
 */
function LocalCallStore(storageKey) {
    var storage = LocalCallStore._storage;
    var callData = getCallDataFromStore();

    this.record = function record(callArguments, thisBinding, onExecuted) {
        // Recording a call might fail if storage fills up.
        // So we clone our list before we mutate and commit it,
        // And only mutate the real list once we know the commit worked

        var newDatum = new CallDatum(callArguments, onExecuted);
        var newRecords = callData.concat([newDatum]);

        // This might throw an error if storage is full
        putCallDataInStore(newRecords);

        // It's important to keep the original list in place at all times,
        // as this allows our deletion logic to match an element for deletion
        // by reference equality.
        callData.push(newDatum);
    };

    this.getCalls = function getCalls() {
        return callData.map(function(callDatum) {
            // Augment the onExecuted callback with an additional step to delete the data
            var originalOnExecuted = callDatum.onExecuted;

            callDatum.onExecuted = function onExecuted(result) {
                if (originalOnExecuted) {
                    originalOnExecuted(result);
                }
                remove(callDatum);
            };
            return callDatum;
        });
    };

    function remove(callParams) {
        var elementIndex = callData.indexOf(callParams);
        if (elementIndex !== -1) {
            callData.splice(elementIndex, 1);
            putCallDataInStore(callData);
        }
    }

    function CallDatum(args, onExecuted) {
        this.callArguments = args;
        this.thisBinding = null;
        this.onExecuted = onExecuted;
    }

    function getCallDataFromStore() {
        var rawData = storage.getItem(storageKey);
        var paramsList = rawData ? JSON.parse(rawData) : [];
        return paramsList.map(function(params) {
            return new CallDatum(params);
        });
    }

    function putCallDataInStore(newData) {
        var paramsList = newData.map(function(datum) {
            return datum.callArguments;
        });
        if (paramsList.length) {
            storage.setItem(storageKey, JSON.stringify(paramsList));
        } else {
            // Stop empty records littering localStorage
            storage.removeItem(storageKey);
        }
    }
}

function clone(obj) {
    return JSON.parse(JSON.stringify(obj));
}
<commit_msg>Remnant from previous rename<commit_after>module.exports = LocalCallStore;

LocalCallStore._storage = window.localStorage;

/**
 * LocalCallStore: save the details of function calls to localStorage,
 * and let us fish them out, too.
 * @param storageKey String: key our calls will be stored against
 */
function LocalCallStore(storageKey) {
    var storage = LocalCallStore._storage;
    var callData = getCallDataFromStore();

    this.record = function record(callArguments, thisBinding, onExecuted) {
        // Recording a call might fail if storage fills up.
        // So we clone our list before we mutate and commit it,
        // And only mutate the real list once we know the commit worked

        var newDatum = new CallDatum(callArguments, onExecuted);
        var newRecords = callData.concat([newDatum]);

        // This might throw an error if storage is full
        putCallDataInStore(newRecords);

        // It's important to keep the original list in place at all times,
        // as this allows our deletion logic to match an element for deletion
        // by reference equality.
        callData.push(newDatum);
    };

    this.getCalls = function getCalls() {
        return callData.map(function(callDatum) {
            // Augment the onExecuted callback with an additional step to delete the data
            var originalOnExecuted = callDatum.onExecuted;

            callDatum.onExecuted = function onExecuted(result) {
                if (originalOnExecuted) {
                    originalOnExecuted(result);
                }
                remove(callDatum);
            };
            return callDatum;
        });
    };

    function remove(callParams) {
        var elementIndex = callData.indexOf(callParams);
        if (elementIndex !== -1) {
            callData.splice(elementIndex, 1);
            putCallDataInStore(callData);
        }
    }

    function CallDatum(args, onExecuted) {
        this.callArguments = args;
        this.thisBinding = null;
        this.onExecuted = onExecuted;
    }

    function getCallDataFromStore() {
        var rawData = storage.getItem(storageKey);
        var paramsList = rawData ? JSON.parse(rawData) : [];
        return paramsList.map(function(params) {
            return new CallDatum(params);
        });
    }

    function putCallDataInStore(newData) {
        var paramsList = newData.map(function(datum) {
            return datum.callArguments;
        });
        if (paramsList.length) {
            storage.setItem(storageKey, JSON.stringify(paramsList));
        } else {
            // Stop empty records littering localStorage
            storage.removeItem(storageKey);
        }
    }
}

function clone(obj) {
    return JSON.parse(JSON.stringify(obj));
}
<|endoftext|>


<commit_before>/**
 * New Relic agent configuration.
 *
 * See lib/config.defaults.js in the agent distribution for a more complete
 * description of configuration variables and their potential values.
 */
exports.config = {
  /**
   * Array of application names.
   */
  app_name : ['tandem.io'],
  /**
   * Your New Relic license key.
   */
  license_key : process.env.NEW_RELIC_LICENSE_KEY,
  logging : {
    /**
     * Level at which to log. 'trace' is most useful to New Relic when diagnosing
     * issues with the agent, 'info' and higher will impose the least overhead on
     * production applications.
     */
    level : 'info'
  }
};
<commit_msg>configure newrelic without newrelic.js<commit_after><|endoftext|>


<commit_before>/**
 * @file rule: indent-char
 * @author nighca<nighca@live.cn>
 */

module.exports = {

    name: 'indent-char',

    desc: 'Line should indent with space(2/4) or tab.',

    target: 'parser',

    lint: function (getCfg, parser, reporter) {
        var cfgMap = {
            'tab': {
                name: 'tab',
                pattern: /^\t*(?=\S|$)/
            },
            'space-2': {
                name: '2 spaces',
                pattern: /^(  )*(?=\S|$)/
            },
            'space-4': {
                name: '4 spaces',
                pattern: /^(    )*(?=\S|$)/
            }
        };

        // exclude-tags
        var excludeTags = ['script', 'style'];

        parser.on('text', function (data) {
            var cfg = getCfg();
            if (!cfg) {
                return;
            }

            // if should be excluded
            if (excludeTags.indexOf(this._stack[this._stack.length - 1]) >= 0) {
                return;
            }

            // get pattern ( use space-4 as default )
            cfg = cfgMap[cfg] || cfgMap['space-4'];

            var pos = this.startIndex;
            data.split('\n').forEach(function (line, i) {
                // discard the first line cause it does not start from \n,
                if (i && !cfg.pattern.test(line)) {
                    reporter.warn(
                        pos,
                        '032',
                        'Line should indent with ' + cfg.name + '.'
                    );
                }

                pos += line.length + 1;
            });
        });
    }

};
<commit_msg>format for rule indent-chat<commit_after>/**
 * @file rule: indent-char
 * @author nighca<nighca@live.cn>
 */

module.exports = {

    name: 'indent-char',

    desc: 'Line should indent with space(2/4) or tab.',

    target: 'parser',

    lint: function (getCfg, parser, reporter) {
        var cfgMap = {
            'tab': {
                name: 'tab',
                pattern: /^\t*(?=\S|$)/
            },
            'space-2': {
                name: '2 spaces',
                pattern: /^(  )*(?=\S|$)/
            },
            'space-4': {
                name: '4 spaces',
                pattern: /^(    )*(?=\S|$)/
            }
        };

        // exclude-tags
        var excludeTags = ['script', 'style'];

        parser.on('text', function (data) {
            var cfg = getCfg();
            if (!cfg) {
                return;
            }

            // if should be excluded
            if (excludeTags.indexOf(this._stack[this._stack.length - 1]) >= 0) {
                return;
            }

            // get pattern ( use space-4 as default )
            cfg = cfgMap[cfg] || cfgMap['space-4'];

            var pos = this.startIndex;
            data.split('\n').forEach(function (line, i) {
                // discard the first line cause it does not start from \n,
                if (i && !cfg.pattern.test(line)) {
                    reporter.warn(
                        pos,
                        '032',
                        'Line should indent with ' + cfg.name + '.'
                    );
                }

                pos += line.length + 1;
            });
        });
    },

    format: function (getCfg, document, options) {
        switch (getCfg()) {
            case 'tab':
                options['indent-char'] = 'tab';
                break;
            case 'space-2':
                options['indent-char'] = 'space';
                options['indent-size'] = 2;
                break;
            case 'space-4':
                options['indent-char'] = 'space';
                options['indent-size'] = 4;
                break;
        }
    }

};
<|endoftext|>


<commit_before>var gen={};

/* A forgetful generator, as simple as it gets!
  initialize it with:
    a function you'd like to increment
    an optional index to count from
    an optional environment passed as a second argument to the function
      (for more complex behaviour, if needed)
  This is the function to use if you just want a closure with minimal overhead.
  */

/* Example usage found in example/forgetful.js */

gen.forget=function(f,i,c){
  // make sure the cache is initialized, 'i' has a default value of 0
  c=c||{i:i||0};
  // return the generator, passing the value of the index
  // and a reference to the cache
  return function(){
    return f(c.i++,c);
  };
};

/* gen.stateful is only slightly more complicated than gen.forget:
  it returns a generator which optionally takes a cache
  meaning you can reinitialize at any time.
  By its very nature, though, it exposes its innards
  and as such it can possibly exhibit dangerous behaviour.
 */

/* Example usage found in example/markov.js */

gen.stateful=function(f,c){
  // no default values for this generator
  // you're expected to do it yourself..
  return function(cache){
    // when a new cache is passed, it overrides the original
    c=cache||c;
    return f(c);
  };
};

/* At cjd's request, we want an async loop
  we can reduce boilerplate by passing a generator as a callback */

/*
gen.async=function(fz,d){
  // fz is a generator which wraps an async function which takes a callback
  // d is what happens when you're done

  return function(){
    // pass the generator a reference to itself for the next iteration
    return fz(fz,d);
  };
};
 */

/* gen.memo is significantly fancier than the other generators so far
  It's meant to be used in situations where the computation is expensive
  particularly if it relies on recursion.

  At each successive call, the function's return value is pushed to an array
  passing an index to the generator will retrieve that index from the array
  saving any extraneous effort involved in the computation.

  If that index has not yet been generated, it will automatically increment
  until it has been generated, then return that value
*/

/* Example usage found in example/memoedPrimes.js and example/memoedFibs.js */

gen.memo=function(f,c){
  // initialize the cache with an empty object if it was not passed as an argument
  c=c||{};
  // if the cache does not contain 'i' (its index) initialize it as 'zero'
  c.i=c.i||0;
  // the memoization process depends on an array which can be filled and referenced
  c.memo=c.memo||[];
  // c.last stores the last value which was modified
  c.last=c.last||null;
  // using c.f, you can reference the initial function which was passed (and recurse)
  c.f=f;

  // c.fn is the new function which will be returned (which increments c.f)
  c.fn=function(n){
    if(isNaN(n)){
      c.memo[c.i]=c.last=f(c.i,c);
      c.i++;
      return c.last;
    }else if(c.memo[n]){
      return c.memo[n];
    }else{
      n=Math.floor(n);
      while(typeof c.memo[n] === "undefined"){
        c.fn();
      }
      return c.memo[n];
    }
  };
  // return the memoizing generator
  return c.fn;
};

/* gen.either captures an array in a closure and increments through its members
  and returns undefined when it exceeds its bounds
  It saves you having to create a closure manually in order to lazily index an array
  Everything is done internally, meaning that unless the returned function is stored
  the array will be garbage collected after you are done with it.
*/

/* Example usage found in example/either.js */

gen.either=function(a){
  var i=0;
  return function(){
    return a[i++];
  };
};

/* gen.cycle will also lazily iterate over an array that it has captured in its scope
  however, rather than returned undefined at the end of its array
  it loops back to the beginning when you exceed its bounds
*/

/* Example usage found in example/cycleThrough.js */

gen.cycle=function(a){
  var i=0,l=a.length;
  return function(){
    return a[i++ % l];
  };
};

/* like gen.cycle, but each element is a function which which is called
    its result is returned, and we move onto the next function
    pass it a bunch of lazy generators and you basically have breadth-first search */

/* Example usage found in example/breadth.js */

gen.fcycle=function(a){
  var i=0,l=a.length;
  return function(){
    return a[i++ % l]();
  };
};

/* A general condition under which you probably want your generator to fail */

gen.fail=function(x){
  return typeof x==="undefined";
};

/* The default callback used at the termination of a lazy sequence
    feel free to override with `gen.done=function(){/* .... */

gen.done=function(){}; // dead function

/* gen.first generates the first 'n' results of a lazy function.
  'n' defaults to a 1.
  results are returned in an array.
  optionally takes a third argument 'cond', which specifies terms under which
  gen.first should terminate and return its accumulator.
  if no condition is specified, it is assumed that it should fail on 'undefined'
*/

/* Examples can be found in example/
  breadth.js
  chaining.js
  memoedPrimes.js */

gen.first=function(lz,n,cond){
  var i=0; // what element are we currently working on?
  n=n||1; // default number of elements to return
  var acc=[]; // the accumulator you will return
  cond=cond||gen.fail; // default to checking for undefined
  while(n>i++){
    var res=lz();
    if(cond(res))break;
    acc.push(res);
  }
  return acc;
};

/* increment a lazy function, and throw away the results.
    only useful if you're after the side effects and not the results.
    accepts a condition under which it should terminate
    since your lazy generator is quite likely an infinite one.
  the condition defaults to checking for an undefined result.
 */

gen.all=function(lz,cond){
  cond=cond||gen.fail;
  while(1){
    if(!cond(lz()))break;
  }
};

/* just like gen.all, except it returns the results in a list */

gen.listall=function(lz,cond){
  cond=cond||gen.fail;
  var acc=[];
  while(1){
    var res=lz();
    if(!cond(res))break;
    acc.push(res);
  }
  return acc;
};

/* returns a function which increments a generator
    and returns only the results which satisfy some predicate */

gen.filter=function(lz,sat){
  sat=sat||function(x){return true;};
  return function(){
    while(1){
      var res=lz();
      if(sat(res))return res;
    }
  };
};

/* gen.cons returns what is basically the concatenation of two lazy lists
  when the first list fails to return a result, it falls through to the second.

*/

gen.cons=function(lz,next,cond){
  next=next||gen.done;
  cond=cond||gen.fail;
  var f=function(){return lz();};

  var fn=function(){
    var res=f();
    if(cond(res)){
      f=function(){return next();}
      return fn();
    }else
      return res;
  };
  return fn;
};

/* take an array of generators and return a function which sequences them
    at some terminal condition, it increments the array and uses the next
    basically depth first search... */

gen.chain=function(F,cond){ // the end of this chain is dangerous!
  var l=F.length;
  var i=0;
  cond=cond||gen.fail;

  return function(){
    var res=F[i]();
    if(cond(res)){
      if(i==(l-1))return;
      res=F[++i]();
    }
    return res;
  };
};

// if you are not using these functions within the browser
if(typeof module!=="undefined"&&module.exports)
  module.exports=gen; // then export it as a module
<commit_msg>safer behaviour in gen.cons<commit_after>var gen={};

/* A forgetful generator, as simple as it gets!
  initialize it with:
    a function you'd like to increment
    an optional index to count from
    an optional environment passed as a second argument to the function
      (for more complex behaviour, if needed)
  This is the function to use if you just want a closure with minimal overhead.
  */

/* Example usage found in example/forgetful.js */

gen.forget=function(f,i,c){
  // make sure the cache is initialized, 'i' has a default value of 0
  c=c||{i:i||0};
  // return the generator, passing the value of the index
  // and a reference to the cache
  return function(){
    return f(c.i++,c);
  };
};

/* gen.stateful is only slightly more complicated than gen.forget:
  it returns a generator which optionally takes a cache
  meaning you can reinitialize at any time.
  By its very nature, though, it exposes its innards
  and as such it can possibly exhibit dangerous behaviour.
 */

/* Example usage found in example/markov.js */

gen.stateful=function(f,c){
  // no default values for this generator
  // you're expected to do it yourself..
  return function(cache){
    // when a new cache is passed, it overrides the original
    c=cache||c;
    return f(c);
  };
};

/* At cjd's request, we want an async loop
  we can reduce boilerplate by passing a generator as a callback */

/*
gen.async=function(fz,d){
  // fz is a generator which wraps an async function which takes a callback
  // d is what happens when you're done

  return function(){
    // pass the generator a reference to itself for the next iteration
    return fz(fz,d);
  };
};
 */

/* gen.memo is significantly fancier than the other generators so far
  It's meant to be used in situations where the computation is expensive
  particularly if it relies on recursion.

  At each successive call, the function's return value is pushed to an array
  passing an index to the generator will retrieve that index from the array
  saving any extraneous effort involved in the computation.

  If that index has not yet been generated, it will automatically increment
  until it has been generated, then return that value
*/

/* Example usage found in example/memoedPrimes.js and example/memoedFibs.js */

gen.memo=function(f,c){
  // initialize the cache with an empty object if it was not passed as an argument
  c=c||{};
  // if the cache does not contain 'i' (its index) initialize it as 'zero'
  c.i=c.i||0;
  // the memoization process depends on an array which can be filled and referenced
  c.memo=c.memo||[];
  // c.last stores the last value which was modified
  c.last=c.last||null;
  // using c.f, you can reference the initial function which was passed (and recurse)
  c.f=f;

  // c.fn is the new function which will be returned (which increments c.f)
  c.fn=function(n){
    if(isNaN(n)){
      c.memo[c.i]=c.last=f(c.i,c);
      c.i++;
      return c.last;
    }else if(c.memo[n]){
      return c.memo[n];
    }else{
      n=Math.floor(n);
      while(typeof c.memo[n] === "undefined"){
        c.fn();
      }
      return c.memo[n];
    }
  };
  // return the memoizing generator
  return c.fn;
};

/* gen.either captures an array in a closure and increments through its members
  and returns undefined when it exceeds its bounds
  It saves you having to create a closure manually in order to lazily index an array
  Everything is done internally, meaning that unless the returned function is stored
  the array will be garbage collected after you are done with it.
*/

/* Example usage found in example/either.js */

gen.either=function(a){
  var i=0;
  return function(){
    return a[i++];
  };
};

/* gen.cycle will also lazily iterate over an array that it has captured in its scope
  however, rather than returned undefined at the end of its array
  it loops back to the beginning when you exceed its bounds
*/

/* Example usage found in example/cycleThrough.js */

gen.cycle=function(a){
  var i=0,l=a.length;
  return function(){
    return a[i++ % l];
  };
};

/* like gen.cycle, but each element is a function which which is called
    its result is returned, and we move onto the next function
    pass it a bunch of lazy generators and you basically have breadth-first search */

/* Example usage found in example/breadth.js */

gen.fcycle=function(a){
  var i=0,l=a.length;
  return function(){
    return a[i++ % l]();
  };
};

/* A general condition under which you probably want your generator to fail */

gen.fail=function(x){
  return typeof x==="undefined";
};

/* The default callback used at the termination of a lazy sequence
    feel free to override with `gen.done=function(){/* .... */

gen.done=function(){}; // dead function

/* gen.first generates the first 'n' results of a lazy function.
  'n' defaults to a 1.
  results are returned in an array.
  optionally takes a third argument 'cond', which specifies terms under which
  gen.first should terminate and return its accumulator.
  if no condition is specified, it is assumed that it should fail on 'undefined'
*/

/* Examples can be found in example/
  breadth.js
  chaining.js
  memoedPrimes.js */

gen.first=function(lz,n,cond){
  var i=0; // what element are we currently working on?
  n=n||1; // default number of elements to return
  var acc=[]; // the accumulator you will return
  cond=cond||gen.fail; // default to checking for undefined
  while(n>i++){
    var res=lz();
    if(cond(res))break;
    acc.push(res);
  }
  return acc;
};

/* increment a lazy function, and throw away the results.
    only useful if you're after the side effects and not the results.
    accepts a condition under which it should terminate
    since your lazy generator is quite likely an infinite one.
  the condition defaults to checking for an undefined result.
 */

gen.all=function(lz,cond){
  cond=cond||gen.fail;
  while(1){
    if(!cond(lz()))break;
  }
};

/* just like gen.all, except it returns the results in a list */

gen.listall=function(lz,cond){
  cond=cond||gen.fail;
  var acc=[];
  while(1){
    var res=lz();
    if(!cond(res))break;
    acc.push(res);
  }
  return acc;
};

/* returns a function which increments a generator
    and returns only the results which satisfy some predicate */

gen.filter=function(lz,sat){
  sat=sat||function(x){return true;};
  return function(){
    while(1){
      var res=lz();
      if(sat(res))return res;
    }
  };
};

/* gen.cons returns what is basically the concatenation of two lazy lists
  when the first list fails to return a result, it falls through to the second.
*/

/* Example usage found in example/fallThrough.js */

gen.cons=function(lz,next,cond,done){
  next=next||gen.done; //
  cond=cond||gen.fail;
  done=done||gen.done;
  var f=function(){return lz();};
  var failed=false;

  var fn=function(){
    var res=f();
    if(cond(res)&&failed){
      return;
    }else if(cond(res)){
      f=function(){return next();}
      failed=true;
      return fn();
    }else
      return res;
  };
  return fn;
};

/* take an array of generators and return a function which sequences them
    at some terminal condition, it increments the array and uses the next
    basically depth first search... */

gen.chain=function(F,cond){ // the end of this chain is dangerous!
  var l=F.length;
  var i=0;
  cond=cond||gen.fail;

  return function(){
    var res=F[i]();
    if(cond(res)){
      if(i==(l-1))return;
      res=F[++i]();
    }
    return res;
  };
};

// if you are not using these functions within the browser
if(typeof module!=="undefined"&&module.exports)
  module.exports=gen; // then export it as a module
<|endoftext|>


<commit_before>// Detect language and automatically fill in channel

window.onload = function()
        {
        var languageStr = window.navigator.userLanguage || window.navigator.language;
        var langArray = languageStr.split("-"); //simplify "en-us" down to "en"
        var lang = langArray[0];

        switch (lang) {
        case "fr":
                channel = "#francais";
                break;
        case "da":
        case "no":
        case "se":
                channel = "#scandinavia";
                break;
        case "de":
                channel = "#deutsch";
                break;
        case "nl":
                channel = "#dutch";
                break;
        case "nz":
                channel = "#kiwi";
                break;
        case "pl":
                channel = "#polish";
                break;
        case "it":
                channel = "#italiano";
                break;
        case "hr":
                channel = "#croatian";
                break;
        case "pt":
                channel = "#portugues";
                break;
        case "zh":
                channel = "#chinese";
                break;
        case "es":
                channel = "#espanol";
                break;
        case "fi":
                channel = "#suomi";
                break;
        case "ru":
                channel = "#russian";
                break;
        case "id":
                channel = "#indonesian";
                break;
        case "is":
                channel = "#icelandic";
                break;
        case "ar":
                channel = "#arabics";
                break;
        case "ro":
                channel = "#romanian";
                break;
        case "en":
                channel = "#english";
                break;
        default:
                channel = "#international";
        }

document.getElementById("connect:channels").value = channel;

        }
<commit_msg>Generate a random Chat_Guest nick on page load<commit_after>// Automatically select default channel by determining user's browser language
// Additionally generate a random Chat_Guest nickname
// ScoutLink TechTeam - techteam@scoutlink.net

window.onload = function()
        {
        var languageStr = window.navigator.userLanguage || window.navigator.language;
        var langArray = languageStr.split("-"); //simplify "en-us" down to "en"
        var lang = langArray[0];

        switch (lang) {
        case "fr":
                channel = "#francais";
                break;
        case "da":
        case "no":
        case "se":
                channel = "#scandinavia";
                break;
        case "de":
                channel = "#deutsch";
                break;
        case "nl":
                channel = "#dutch";
                break;
        case "nz":
                channel = "#kiwi";
                break;
        case "pl":
                channel = "#polish";
                break;
        case "it":
                channel = "#italiano";
                break;
        case "hr":
                channel = "#croatian";
                break;
        case "pt":
                channel = "#portugues";
                break;
        case "zh":
                channel = "#chinese";
                break;
        case "es":
                channel = "#espanol";
                break;
        case "fi":
                channel = "#suomi";
                break;
        case "ru":
                channel = "#russian";
                break;
        case "id":
                channel = "#indonesian";
                break;
        case "is":
                channel = "#icelandic";
                break;
        case "ar":
                channel = "#arabics";
                break;
        case "ro":
                channel = "#romanian";
                break;
        case "en":
                channel = "#english";
                break;
        default:
                channel = "#international";
        }

// Choose a random nick
document.getElementById('connect:nick').value = 'Chat_Guest_' + Math.floor((Math.random() * 99999) + 9999);

// Output channel to dropdown
document.getElementById("connect:channels").value = channel;

        }
<|endoftext|>