<commit_before>// ========================================================================
// SC.SparseArray Tests
// ========================================================================

Test.context("SC.SparseArray calls delegate", {

  "new SparseArray has expected length": function() {
    var ary = SC.SparseArray.create(10000) ;
    assertEqual(10000, ary.get('length'), "length") ;
  }

});

<commit_msg>port SC.SparseArray tests to qUnit<commit_after>// ========================================================================
// SC.SparseArray Tests
// ========================================================================
/*globals module test ok isObj equals expects */

module("SC.SparseArray") ;

test("new SparseArray has expected length", function() {
  var ary = SC.SparseArray.create(10000) ;
  equals(10000, ary.get('length'), "length") ;
});

<|endoftext|>


<commit_before>/**
 * @file   common/js/xml_js_filter.js
 * @author NHN (developers@xpressengine.com)
 * @brief  xml filter (validator) plugin
 *
 * A rule is a method validate one field.
 * A filter is made up of one or more rules.
 **/
(function($){

var messages  = [];
var rules     = [];
var filters   = {};
var callbacks = [];
var extras    = {};

var Validator = xe.createApp('Validator', {
	init : function() {
		// {{{ add filters
		// email
		var regEmail = /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/;
		this.cast('ADD_RULE', ['email', regEmail]);
		this.cast('ADD_RULE', ['email_address', regEmail]);

		// userid
		var regUserid = /^[a-z]+[\w-]*[a-z0-9_]+$/i;
		this.cast('ADD_RULE', ['userid', regUserid]);
		this.cast('ADD_RULE', ['user_id', regUserid]);

		// url
		var regUrl = /^(https?|ftp|mms):\/\/[0-9a-z-]+(\.[_0-9a-z-]+)+(:\d+)?/;
		this.cast('ADD_RULE', ['url', regUrl]);
		this.cast('ADD_RULE', ['homepage', regUrl]);

		// korean
		var regKor = /^[가-힣]*$/;
		this.cast('ADD_RULE', ['korean', regKor]);

		// korean_number
		var regKorNum = /^[가-힣0-9]*$/;
		this.cast('ADD_RULE', ['korean_number', regKorNum]);

		// alpha
		var regAlpha = /^[a-z]*$/i;
		this.cast('ADD_RULE', ['alpha', regAlpha]);

		// alpha_number
		var regAlphaNum = /^[a-z][a-z0-9_]*$/i;
		this.cast('ADD_RULE', ['alpha_number', regAlphaNum]);

		// number
		var regNum = /^[0-9]*$/;
		this.cast('ADD_RULE', ['number', regNum]);
		// }}} add filters
	},
	// run validator
	run : function(oForm) {
		var filter = '';

		if (oForm._filter) filter = oForm._filter.value;

		var params = [oForm, filter];
		var result = this.cast('VALIDATE', params);
		if (typeof result == 'undefined') result = false;

		return result;
	},
	API_ONREADY : function() {
		var self = this;

		// hook form submit event
		$('form')
			.each(function(){
				if (this.onsubmit) {
					this['xe:onsubmit'] = this.onsubmit;
					this.onsubmit = null;
				}
			})
			.submit(function(){
				var legacyFn = this['xe:onsubmit'];
				var hasLegacyFn = $.isFunction(legacyFn);
				var bResult = hasLegacyFn?legacyFn.apply(this):self.run(this);

				return bResult;
			});
	},
	API_VALIDATE : function(sender, params) {
		var result = true, form = params[0], elems = form.elements, filter, filter_to_add, ruleset, callback;
		var fields, names, name, el, val, mod, len, lenb, max, min, maxb, minb, rules, e_el, e_val, i, c, r, if_, fn;

		if(elems['ruleset']) filter = form.elements['ruleset'].value;
		else if(elems['_filter']) filter = form.elements['_filter'].value;
		if(!filter) return true;

		if($.isFunction(callbacks[filter])) callback = callbacks[filter];
		filter = $.extend({}, filters[filter.toLowerCase()] || {}, extras);

		function regex_quote(str){
			return str.replace(/([\.\+\-\[\]\{\}\(\)\\])/g, '\\$1');
		};

		// get form names
		fields = [];
		for(i=0,c=form.elements.length; i < c; i++) {
			el   = elems[i];
			name = el.name;

			if(!elems[name].length || elems[name][0] === el) fields.push(name);
		};
		fields = fields.join('\n');

		// get field names matching patterns
		filter_to_add = {};
		for(name in filter) {
			if(!filter.hasOwnProperty(name)) continue;

			names = [];
			if(name.substr(0,1) == '^') {
				names = fields.match( (new RegExp('^'+regex_quote(name.substr(1))+'.*$','gm')) );
			} else {
				continue;
			}
			if(!names) names = [];

			for(i=0,c=names.length; i < c; i++) {
				filter_to_add[names[i]]= filter[name];
			}

			filter[name] = null;
			delete filter[name];
		};

		filter = $.extend(filter, filter_to_add);

		for(name in filter) {
			if(!filter.hasOwnProperty(name)) continue;

			f   = filter[name];
			el  = elems[name];
			val = el?$.trim(get_value($(el))):'';
			mod = (f.modifier||'')+',';

			if(!el || el.disabled) continue;

			if(f['if']) {
				if(!$.isArray(f['if'])) f['if'] = [f['if']];
				for(i in f['if']) {
					if_ = f['if'][i];
					fn  = new Function('el', 'return !!(' + (if_.test.replace(/$(\w+)/g, 'el["$1"]')) +')');
					if(fn(elems)) f[if_.attr] = if_.value;
				}
			}

			if(!val) {
				if(f['default']) val = f['default'];
				if(f.required) return this.cast('ALERT', [form, name, 'isnull']) && false;
				else continue;
			}

			min  = parseInt(f.minlength) || 0;
			max  = parseInt(f.maxlength) || 0;
			minb = /b$/.test(f.minlength||'');
			maxb = /b$/.test(f.maxlength||'');
			len  = val.length;
			if(minb || maxb) lenb = get_bytes(val);
			if((min && min > (minb?lenb:len)) || (max && max < (maxb?lenb:len))) {
				return this.cast('ALERT', [form, name, 'outofrange', min, max]) && false;
			}

			if(f.equalto) {
				e_el  = elems[f.equalto];
				e_val = e_el?$.trim(get_value($(e_el))):'';
				if(e_el && e_val !== val) {
					return this.cast('ALERT', [form, name, 'equalto']) && false;
				}
			}

			rules = (f.rule || '').split(',');
			for(i=0,c=rules.length; i < c; i++) {
				if(!(r = rules[i])) continue;

				result = this.cast('APPLY_RULE', [r, val]);
				if(mod.indexOf('not,') > -1) result = !result;
				if(!result) {
					return this.cast('ALERT', [form, name, 'invalid_'+r]) && false;
				}
			}
		};

		if($.isFunction(callback)) return callback(form);

		return true;
	},
	API_ADD_RULE : function(sender, params) {
		var name = params[0].toLowerCase();
		rules[name] = params[1];
	},
	API_DEL_RULE : function(sender, params) {
		var name = params[0].toLowerCase();
		delete rules[name];
	},
	API_GET_RULE : function(sender, params) {
		var name = params[0].toLowerCase();

		if (rules[name]) {
			return rules[name];
		} else {
			return null;
		}
	},
	API_ADD_FILTER : function(sender, params) {
		var name   = params[0].toLowerCase();
		var filter = params[1];

		filters[name] = filter;
	},
	API_DEL_FILTER : function(sender, params) {
		var name = params[0].toLowerCase();
		delete filters[name];
	},
	API_GET_FILTER : function(sender, params) {
		var name = params[0].toLowerCase();

		if (filters[name]) {
			return filters[name];
		} else {
			return null;
		}
	},
	API_ADD_EXTRA_FIELD : function(sender, params) {
		var name = params[0].toLowerCase();
		var prop = params[1];

		extras[name] = prop;
	},
	API_GET_EXTRA_FIELD : function(sender, params) {
		var name = params[0].toLowerCase();
		return extras[name];
	},
	API_DEL_EXTRA_FIELD : function(sender, params) {
		var name = params[0].toLowerCase();
		delete extras[name];
	},
	API_APPLY_RULE : function(sender, params) {
		var name  = params[0];
		var value = params[1];

		if(typeof(rules[name]) == 'undefined') return true; // no filter
		if($.isFunction(rules[name])) return rules[name](value);
		if(rules[name] instanceof RegExp) return rules[name].test(value);
		if($.isArray(rules[name])) return ($.inArray(value, rules[name]) > -1);

		return true;
	},
	API_ALERT : function(sender, params) {
		var form = params[0];
		var field_name = params[1];
		var msg_code = params[2];
		var minlen   = params[3];
		var maxlen   = params[4];

		var field_msg = this.cast('GET_MESSAGE', [field_name]);
		var msg = this.cast('GET_MESSAGE', [msg_code]);

		if (msg != msg_code) msg = (msg.indexOf('%s')<0)?(field_msg+msg):(msg.replace('%s',field_msg));
		if (minlen||maxlen) msg +=  '('+(minlen||'')+'~'+(maxlen||'')+')';

		this.cast('SHOW_ALERT', [msg]);

		// set focus
		$(form.elements[field_name]).focus();
	},
	API_SHOW_ALERT : function(sender, params) {
		alert(params[0]);
	},
	API_ADD_MESSAGE : function(sender, params) {
		var msg_code = params[0];
		var msg_str  = params[1];

		messages[msg_code] = msg_str;
	},
	API_GET_MESSAGE : function(sender, params) {
		var msg_code = params[0];

		return messages[msg_code] || msg_code;
	},
	API_ADD_CALLBACK : function(sender, params) {
		var name = params[0];
		var func = params[1];

		callbacks[name] = func;
	},
	API_REMOVE_CALLBACK : function(sender, params) {
		var name = params[0];

		delete callbacks[name];
	}
});

var oValidator = new Validator;

// register validator
xe.registerApp(oValidator);

// 호환성을 위해 추가한 플러그인 - 에디터에서 컨텐트를 가져와서 설정한다.
var EditorStub = xe.createPlugin('editor_stub', {
	API_BEFORE_VALIDATE : function(sender, params) {
		var form = params[0];
		var seq  = form.getAttribute('editor_sequence');

		// bug fix for IE6,7
		if (seq && typeof seq == 'object') seq = seq.value;

		if (seq) {
			try {
				editorRelKeys[seq].content.value = editorRelKeys[seq].func(seq) || '';
			} catch(e) { }
		}
	}
});
oValidator.registerPlugin(new EditorStub);

// functions
function get_value($elem) {
	var vals = [];
	if ($elem.is(':radio')){
		return $elem.filter(':checked').val();
	} else if ($elem.is(':checkbox')) {
		$elem.filter(':checked').each(function(){
			vals.push(this.value);
		});
		return vals.join('|@|');
	} else {
		return $elem.val();
	}
}

function get_bytes(str) {
	str += '';
	if(!str.length) return 0;

	str = encodeURI(str);
	var c = str.split('%').length - 1;

	return str.length - c*2;
}

})(jQuery);

/**
 * @function filterAlertMessage
 * @brief ajax로 서버에 요청후 결과를 처리할 callback_function을 지정하지 않았을 시 호출되는 기본 함수
 **/
function filterAlertMessage(ret_obj) {
	var error = ret_obj["error"];
	var message = ret_obj["message"];
	var act = ret_obj["act"];
	var redirect_url = ret_obj["redirect_url"];
	var url = location.href;

	if(typeof(message)!="undefined"&&message&&message!="success") alert(message);

	if(typeof(act)!="undefined" && act) url = current_url.setQuery("act", act);
	else if(typeof(redirect_url)!="undefined" && redirect_url) url = redirect_url;

	if(url == location.href) url = url.replace(/#(.*)$/,'');

	location.href = url;
}

/**
 * @brief Function to process filters
 * @deprecated
 */
function procFilter(form, filter_func) {
	filter_func(form);
	return false;
}

function legacy_filter(filter_name, form, module, act, callback, responses, confirm_msg, rename_params) {
	var v = xe.getApp('Validator')[0], $ = jQuery, args = [];

	if (!v) return false;

	if (!form.elements['_filter']) $(form).prepend('<input type="hidden" name="_filter" />');
	form.elements['_filter'].value = filter_name;

	args[0] = filter_name;
	args[1] = function(f) {
		var params = {}, res = [], elms = f.elements, data = $(f).serializeArray();
		$.each(data, function(i, field) {
			var v = $.trim(field.value), n = field.name;
			if(!v || !n) return true;
			if(rename_params[n]) n = rename_params[n];

			if(/\[\]$/.test(n)) n = n.replace(/\[\]$/, '');
			if(params[n]) params[n] += '|@|'+v;
			else params[n] = field.value;
		});

		if (confirm_msg && !confirm(confirm_msg)) return false;
		exec_xml(module, act, params, callback, responses, params, form);
	};

	v.cast('ADD_CALLBACK', args);
	v.cast('VALIDATE', [form, filter_name]);

	return false;
}
<commit_msg>Fixed unicode problem<commit_after>/**
 * @file   common/js/xml_js_filter.js
 * @author NHN (developers@xpressengine.com)
 * @brief  xml filter (validator) plugin
 *
 * A rule is a method validate one field.
 * A filter is made up of one or more rules.
 **/
(function($){

var messages  = [];
var rules     = [];
var filters   = {};
var callbacks = [];
var extras    = {};

var Validator = xe.createApp('Validator', {
	init : function() {
		// {{{ add filters
		// email
		var regEmail = /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/;
		this.cast('ADD_RULE', ['email', regEmail]);
		this.cast('ADD_RULE', ['email_address', regEmail]);

		// userid
		var regUserid = /^[a-z]+[\w-]*[a-z0-9_]+$/i;
		this.cast('ADD_RULE', ['userid', regUserid]);
		this.cast('ADD_RULE', ['user_id', regUserid]);

		// url
		var regUrl = /^(https?|ftp|mms):\/\/[0-9a-z-]+(\.[_0-9a-z-]+)+(:\d+)?/;
		this.cast('ADD_RULE', ['url', regUrl]);
		this.cast('ADD_RULE', ['homepage', regUrl]);

		// korean
		var regKor = new RegExp('^[\uAC00-\uD7A3]*$');
		this.cast('ADD_RULE', ['korean', regKor]);

		// korean_number
		var regKorNum = new RegExp('^[\uAC00-\uD7A30-9]*$');
		this.cast('ADD_RULE', ['korean_number', regKorNum]);

		// alpha
		var regAlpha = /^[a-z]*$/i;
		this.cast('ADD_RULE', ['alpha', regAlpha]);

		// alpha_number
		var regAlphaNum = /^[a-z][a-z0-9_]*$/i;
		this.cast('ADD_RULE', ['alpha_number', regAlphaNum]);

		// number
		var regNum = /^[0-9]*$/;
		this.cast('ADD_RULE', ['number', regNum]);
		// }}} add filters
	},
	// run validator
	run : function(oForm) {
		var filter = '';

		if (oForm._filter) filter = oForm._filter.value;

		var params = [oForm, filter];
		var result = this.cast('VALIDATE', params);
		if (typeof result == 'undefined') result = false;

		return result;
	},
	API_ONREADY : function() {
		var self = this;

		// hook form submit event
		$('form')
			.each(function(){
				if (this.onsubmit) {
					this['xe:onsubmit'] = this.onsubmit;
					this.onsubmit = null;
				}
			})
			.submit(function(){
				var legacyFn = this['xe:onsubmit'];
				var hasLegacyFn = $.isFunction(legacyFn);
				var bResult = hasLegacyFn?legacyFn.apply(this):self.run(this);

				return bResult;
			});
	},
	API_VALIDATE : function(sender, params) {
		var result = true, form = params[0], elems = form.elements, filter, filter_to_add, ruleset, callback;
		var fields, names, name, el, val, mod, len, lenb, max, min, maxb, minb, rules, e_el, e_val, i, c, r, if_, fn;

		if(elems['ruleset']) filter = form.elements['ruleset'].value;
		else if(elems['_filter']) filter = form.elements['_filter'].value;
		if(!filter) return true;

		if($.isFunction(callbacks[filter])) callback = callbacks[filter];
		filter = $.extend({}, filters[filter.toLowerCase()] || {}, extras);

		function regex_quote(str){
			return str.replace(/([\.\+\-\[\]\{\}\(\)\\])/g, '\\$1');
		};

		// get form names
		fields = [];
		for(i=0,c=form.elements.length; i < c; i++) {
			el   = elems[i];
			name = el.name;

			if(!elems[name].length || elems[name][0] === el) fields.push(name);
		};
		fields = fields.join('\n');

		// get field names matching patterns
		filter_to_add = {};
		for(name in filter) {
			if(!filter.hasOwnProperty(name)) continue;

			names = [];
			if(name.substr(0,1) == '^') {
				names = fields.match( (new RegExp('^'+regex_quote(name.substr(1))+'.*$','gm')) );
			} else {
				continue;
			}
			if(!names) names = [];

			for(i=0,c=names.length; i < c; i++) {
				filter_to_add[names[i]]= filter[name];
			}

			filter[name] = null;
			delete filter[name];
		};

		filter = $.extend(filter, filter_to_add);

		for(name in filter) {
			if(!filter.hasOwnProperty(name)) continue;

			f   = filter[name];
			el  = elems[name];
			val = el?$.trim(get_value($(el))):'';
			mod = (f.modifier||'')+',';

			if(!el || el.disabled) continue;

			if(f['if']) {
				if(!$.isArray(f['if'])) f['if'] = [f['if']];
				for(i in f['if']) {
					if_ = f['if'][i];
					fn  = new Function('el', 'return !!(' + (if_.test.replace(/$(\w+)/g, 'el["$1"]')) +')');
					if(fn(elems)) f[if_.attr] = if_.value;
				}
			}

			if(!val) {
				if(f['default']) val = f['default'];
				if(f.required) return this.cast('ALERT', [form, name, 'isnull']) && false;
				else continue;
			}

			min  = parseInt(f.minlength) || 0;
			max  = parseInt(f.maxlength) || 0;
			minb = /b$/.test(f.minlength||'');
			maxb = /b$/.test(f.maxlength||'');
			len  = val.length;
			if(minb || maxb) lenb = get_bytes(val);
			if((min && min > (minb?lenb:len)) || (max && max < (maxb?lenb:len))) {
				return this.cast('ALERT', [form, name, 'outofrange', min, max]) && false;
			}

			if(f.equalto) {
				e_el  = elems[f.equalto];
				e_val = e_el?$.trim(get_value($(e_el))):'';
				if(e_el && e_val !== val) {
					return this.cast('ALERT', [form, name, 'equalto']) && false;
				}
			}

			rules = (f.rule || '').split(',');
			for(i=0,c=rules.length; i < c; i++) {
				if(!(r = rules[i])) continue;

				result = this.cast('APPLY_RULE', [r, val]);
				if(mod.indexOf('not,') > -1) result = !result;
				if(!result) {
					return this.cast('ALERT', [form, name, 'invalid_'+r]) && false;
				}
			}
		};

		if($.isFunction(callback)) return callback(form);

		return true;
	},
	API_ADD_RULE : function(sender, params) {
		var name = params[0].toLowerCase();
		rules[name] = params[1];
	},
	API_DEL_RULE : function(sender, params) {
		var name = params[0].toLowerCase();
		delete rules[name];
	},
	API_GET_RULE : function(sender, params) {
		var name = params[0].toLowerCase();

		if (rules[name]) {
			return rules[name];
		} else {
			return null;
		}
	},
	API_ADD_FILTER : function(sender, params) {
		var name   = params[0].toLowerCase();
		var filter = params[1];

		filters[name] = filter;
	},
	API_DEL_FILTER : function(sender, params) {
		var name = params[0].toLowerCase();
		delete filters[name];
	},
	API_GET_FILTER : function(sender, params) {
		var name = params[0].toLowerCase();

		if (filters[name]) {
			return filters[name];
		} else {
			return null;
		}
	},
	API_ADD_EXTRA_FIELD : function(sender, params) {
		var name = params[0].toLowerCase();
		var prop = params[1];

		extras[name] = prop;
	},
	API_GET_EXTRA_FIELD : function(sender, params) {
		var name = params[0].toLowerCase();
		return extras[name];
	},
	API_DEL_EXTRA_FIELD : function(sender, params) {
		var name = params[0].toLowerCase();
		delete extras[name];
	},
	API_APPLY_RULE : function(sender, params) {
		var name  = params[0];
		var value = params[1];

		if(typeof(rules[name]) == 'undefined') return true; // no filter
		if($.isFunction(rules[name])) return rules[name](value);
		if(rules[name] instanceof RegExp) return rules[name].test(value);
		if($.isArray(rules[name])) return ($.inArray(value, rules[name]) > -1);

		return true;
	},
	API_ALERT : function(sender, params) {
		var form = params[0];
		var field_name = params[1];
		var msg_code = params[2];
		var minlen   = params[3];
		var maxlen   = params[4];

		var field_msg = this.cast('GET_MESSAGE', [field_name]);
		var msg = this.cast('GET_MESSAGE', [msg_code]);

		if (msg != msg_code) msg = (msg.indexOf('%s')<0)?(field_msg+msg):(msg.replace('%s',field_msg));
		if (minlen||maxlen) msg +=  '('+(minlen||'')+'~'+(maxlen||'')+')';

		this.cast('SHOW_ALERT', [msg]);

		// set focus
		$(form.elements[field_name]).focus();
	},
	API_SHOW_ALERT : function(sender, params) {
		alert(params[0]);
	},
	API_ADD_MESSAGE : function(sender, params) {
		var msg_code = params[0];
		var msg_str  = params[1];

		messages[msg_code] = msg_str;
	},
	API_GET_MESSAGE : function(sender, params) {
		var msg_code = params[0];

		return messages[msg_code] || msg_code;
	},
	API_ADD_CALLBACK : function(sender, params) {
		var name = params[0];
		var func = params[1];

		callbacks[name] = func;
	},
	API_REMOVE_CALLBACK : function(sender, params) {
		var name = params[0];

		delete callbacks[name];
	}
});

var oValidator = new Validator;

// register validator
xe.registerApp(oValidator);

// 호환성을 위해 추가한 플러그인 - 에디터에서 컨텐트를 가져와서 설정한다.
var EditorStub = xe.createPlugin('editor_stub', {
	API_BEFORE_VALIDATE : function(sender, params) {
		var form = params[0];
		var seq  = form.getAttribute('editor_sequence');

		// bug fix for IE6,7
		if (seq && typeof seq == 'object') seq = seq.value;

		if (seq) {
			try {
				editorRelKeys[seq].content.value = editorRelKeys[seq].func(seq) || '';
			} catch(e) { }
		}
	}
});
oValidator.registerPlugin(new EditorStub);

// functions
function get_value($elem) {
	var vals = [];
	if ($elem.is(':radio')){
		return $elem.filter(':checked').val();
	} else if ($elem.is(':checkbox')) {
		$elem.filter(':checked').each(function(){
			vals.push(this.value);
		});
		return vals.join('|@|');
	} else {
		return $elem.val();
	}
}

function get_bytes(str) {
	str += '';
	if(!str.length) return 0;

	str = encodeURI(str);
	var c = str.split('%').length - 1;

	return str.length - c*2;
}

})(jQuery);

/**
 * @function filterAlertMessage
 * @brief ajax로 서버에 요청후 결과를 처리할 callback_function을 지정하지 않았을 시 호출되는 기본 함수
 **/
function filterAlertMessage(ret_obj) {
	var error = ret_obj["error"];
	var message = ret_obj["message"];
	var act = ret_obj["act"];
	var redirect_url = ret_obj["redirect_url"];
	var url = location.href;

	if(typeof(message)!="undefined"&&message&&message!="success") alert(message);

	if(typeof(act)!="undefined" && act) url = current_url.setQuery("act", act);
	else if(typeof(redirect_url)!="undefined" && redirect_url) url = redirect_url;

	if(url == location.href) url = url.replace(/#(.*)$/,'');

	location.href = url;
}

/**
 * @brief Function to process filters
 * @deprecated
 */
function procFilter(form, filter_func) {
	filter_func(form);
	return false;
}

function legacy_filter(filter_name, form, module, act, callback, responses, confirm_msg, rename_params) {
	var v = xe.getApp('Validator')[0], $ = jQuery, args = [];

	if (!v) return false;

	if (!form.elements['_filter']) $(form).prepend('<input type="hidden" name="_filter" />');
	form.elements['_filter'].value = filter_name;

	args[0] = filter_name;
	args[1] = function(f) {
		var params = {}, res = [], elms = f.elements, data = $(f).serializeArray();
		$.each(data, function(i, field) {
			var v = $.trim(field.value), n = field.name;
			if(!v || !n) return true;
			if(rename_params[n]) n = rename_params[n];

			if(/\[\]$/.test(n)) n = n.replace(/\[\]$/, '');
			if(params[n]) params[n] += '|@|'+v;
			else params[n] = field.value;
		});

		if (confirm_msg && !confirm(confirm_msg)) return false;
		exec_xml(module, act, params, callback, responses, params, form);
	};

	v.cast('ADD_CALLBACK', args);
	v.cast('VALIDATE', [form, filter_name]);

	return false;
}
<|endoftext|>


<commit_before>/* jshint node: true */
'use strict';

var http = require('http');
var crypto = require('crypto');
var request = require('request');

global.isInIdentityExchange = false;

module.exports = function identityExchange (app, replicationManager) {

  var
    pkMineBuffer,         // My private key hash
    pkOtherBuffer,        // Other private key hash
    rnMine,               // My random bytes
    exchangeCb,           // Callback for if my hash is larger
    originalDeviceHash,   // Original device hash before changing it for identity exchange
    newDeviceHash,        // New device hash after the change for identity exchange
    localPeerIdentifier,  // Peer identifier for remote device
    cbValue;              // Cb value used for identity exchange

  replicationManager._emitter.on('peerAvailabilityChanged', function (peers) {
    peers.forEach(function (peer) {
      if (peer.peerName.indexOf(';') !== -1 && peer.peerAvailable) {
        var split = peer.peerName.split(';');
        peer.peerFriendlyName = split[1];
        peer.peerName = split[0];

        replicationManager.emit('peerIdentityExchange', peer);
      }
    });
  });

  function startIdentityExchange(myFriendlyName, cb) {
    if (global.isInIdentityExchange) {
      return cb(new Error('Already in identity exchange'));
    }

    global.isInIdentityExchange = true;

    replicationManager.getDeviceIdentity(function (err, deviceName) {
      if (err) { return cb(err); }

      originalDeviceHash = deviceName;
      newDeviceHash = deviceName + ';' + myFriendlyName;

      replicationManager.once('stopped', function () {

        replicationManager.once('started', function () {
          cb(null, newDeviceHash);
        });

        replicationManager.once('startError', cb);
        replicationManager.start(
          replicationManager._port,
          replicationManager._dbName,
          newDeviceHash);
      });

      replicationManager.once('stopError', cb);
      replicationManager.stop();
    });
  }

  function stopIdentityExchange(cb) {
    global.isInIdentityExchange = false;

    replicationManager.once('stopped', function () {

      replicationManager.once('started', function () {
        cb(null, originalDeviceHash);
      });

      replicationManager.once('startError', cb);
      replicationManager.start(
        replicationManager._port,
        replicationManager._dbName,
        originalDeviceHash);
    });

    replicationManager.once('stopError', cb);
    replicationManager.stop();
  }

  function executeIdentityExchange(peerIdentifier, pkOther, pkMine, cb) {
    if (!global.isInIdentityExchange) {
      return cb(new Error('Identity Exchange not started'));
    }

    localPeerIdentifier = peerIdentifier;
    pkMineBuffer = new Buffer(pkMine);
    pkOtherBuffer = new Buffer(pkOther);

    rnMine = crypto.randomBytes(32);

    replicationManager._emitter.connect(peerIdentifier, function (err, port) {
      if (err) {
        return cleanupStopIdentityExchange(function () {
          cb(err);
        });
      }

      var compared = pkMineBuffer.compare(pkOtherBuffer);

      if (compared < 0) {
        // My hash is smaller
        var concatHash = Buffer.concat([pkMineBuffer, pkOtherBuffer]);
        var cbHash = crypto.createHmac('sha256', rnMine);
        var cbBuffer = cbHash.update(concatHash);
        var cbValue = cbBuffer.toString('base64');

        var rnOtherReq = request.post({
          url: 'http://localhost:' + port + '/identity/cb',
          body: {
            cbValue: cbValue,
            pkMine: pkMine
          }
        });

        rnOtherReq.once('response', function (response) {
          if (response.status !== 200) {
            return cleanupIdentityExchange(function () {
              cb(new Error('Invalid exchange'));
            });
          }

          var rnOther = new Buffer(response.body.rnOther);
          var pkO = new Buffer(response.body.pkOther);

          if (!pkO.equals(pkOtherBuffer)) {
            return cleanupIdentityExchange(function () {
              cb(new Error('Invalid exchange'));
            });
          }

          var rnMineReq = request.post({
            url: 'http://localhost:' + port + '/identity/rnmine',
            body: {
              rnMine: rnMine.toString('base64'),
              pkMine: pkMine
            }
          });

          rnMineReq.once('response', function (response) {
            if (response.status !== 200) {
              return cleanupIdentityExchange(function () {
                cb(new Error('Invalid exchange'));
              });
            }

            var value = createIdentityExchangeValue(rnOther, Buffer.concat([pkOtherBuffer, pkMineBuffer, rnMine]));
            disconnectExchange(function () {
              cb(null, value);
            });
          });
        });
      } else if (compared > 0) {
        // My hash is bigger and rest is done in cb express endpoint
        exchangeCb = cb;
      } else {
        cb(new Error('Hashes cannot be equal'));
      }
    });
  }

  app.post('/identity/cb', function (req, res) {
    if (!global.isInIdentityExchange) { return res.sendStatus(400); }

    if (!req.body) { return res.sendStatus(400); }
    if (typeof req.body.cbValue !== 'string' || req.body.cbValue.length === 0) {
      return res.sendStatus(400);
    }
    if (typeof req.body.pkMine !== 'string' || req.body.pkMine.length === 0) {
      return res.sendStatus(400);
    }

    cbValue = new Buffer(req.body.cbValue);
    var buf = new Buffer(req.body.pkMine);
    if (!buf.equals(pkOtherBuffer)) { return res.sendStatus(400); }

    res.sendStatus(200).json({
      pkOther: pkMineBuffer.toString('base64'),
      rnOther: rnMine.toString('base64')
    });
  });

  app.post('/identity/rnmine', function (req, res) {
    if (!global.isInIdentityExchange) { return res.sendStatus(400); }

    if (!req.body) { return res.sendStatus(400); }
    if (typeof req.body.pkMine !== 'string' || req.body.pkMine.length === 0) {
      return res.sendStatus(400);
    }
    if (typeof req.body.rnMine !== 'string' || req.body.rnMine.length !== 44) {
      return res.sendStatus(400);
    }

    // Test if we're under attack
    var testBuff = new Buffer(req.body.pkMine);
    if (!testBuff.equals(pkOtherBuffer)) { return res.sendStatus(400); }

    var rnOther = new Buffer(req.body.rnMine);
    var newBuff = Buffer.concat([pkOtherBuffer, pkMineBuffer]);
    var hmac = crypto.createHmac('sha256', rnOther);
    hmac.update(newBuff);
    var ret = hmac.digest();

    // No match
    if (!ret.equals(cbValue)) { return res.sendStatus(400); }

    res.sendStatus(200);

    var value = createIdentityExchangeValue(rnMine, Buffer.concat([pkMineBuffer, pkOtherBuffer, rnOther]));
    cleanupIdentityExchange(function () {
      exchangeCb(null, value);
    });
  });

  /* Create identity exchange value */
  function createIdentityExchangeValue(key, buffer) {
    var hmac = crypto.createHmac('sha256', key);
    hmac.update(buffer);
    return parseInt(hmac.digest().toString('hex'), 16) % Math.pow(10, 6);
  }

  /* Cleanup functions */
  function cleanupIdentityExchange(cb) {
    disconnectExchange(function () {
      cleanupStopIdentityExchange(function (cb) {
        cb();
      });
    });
  }

  function cleanupStopIdentityExchange(cb) {
    stopIdentityExchange(function (innerError) {
      console.log('Stop Identity Exchange Error %s', innerError);
      cb();
    });
  }

  function disconnectExchange(cb) {
    replicationManager._emitter.disconnect(localPeerIdentifier, function (err) {
      console.log('Disconnect error %s', err);
      cb();
    });
  }

  return {
    startIdentityExchange: startIdentityExchange,
    executeIdentityExchange: executeIdentityExchange,
    stopIdentityExchange: stopIdentityExchange
  };
};
<commit_msg>Adding reset state<commit_after>/* jshint node: true */
'use strict';

var http = require('http');
var crypto = require('crypto');
var request = require('request');

global.isInIdentityExchange = false;

module.exports = function identityExchange (app, replicationManager) {

  var
    pkMineBuffer,         // My private key hash
    pkOtherBuffer,        // Other private key hash
    rnMine,               // My random bytes
    exchangeCb,           // Callback for if my hash is larger
    originalDeviceHash,   // Original device hash before changing it for identity exchange
    newDeviceHash,        // New device hash after the change for identity exchange
    localPeerIdentifier,  // Peer identifier for remote device
    cbValue;              // Cb value used for identity exchange

  function resetState() {
    pkMineBuffer = null;
    pkOtherBuffer = null;
    rnMine = null;
    exchangeCb = null;
    localPeerIdentifier = null;
    cbValue = null;
  }

  replicationManager._emitter.on('peerAvailabilityChanged', function (peers) {
    peers.forEach(function (peer) {
      if (peer.peerName.indexOf(';') !== -1 && peer.peerAvailable) {
        var split = peer.peerName.split(';');
        peer.peerFriendlyName = split[1];
        peer.peerName = split[0];

        replicationManager.emit('peerIdentityExchange', peer);
      }
    });
  });

  function startIdentityExchange(myFriendlyName, cb) {
    if (global.isInIdentityExchange) {
      return cb(new Error('Already in identity exchange'));
    }

    global.isInIdentityExchange = true;

    replicationManager.getDeviceIdentity(function (err, deviceName) {
      if (err) { return cb(err); }

      originalDeviceHash = deviceName;
      newDeviceHash = deviceName + ';' + myFriendlyName;

      replicationManager.once('stopped', function () {

        replicationManager.once('started', function () {
          cb(null, newDeviceHash);
        });

        replicationManager.once('startError', cb);
        replicationManager.start(
          replicationManager._port,
          replicationManager._dbName,
          newDeviceHash);
      });

      replicationManager.once('stopError', cb);
      replicationManager.stop();
    });
  }

  function stopIdentityExchange(cb) {
    global.isInIdentityExchange = false;

    replicationManager.once('stopped', function () {

      replicationManager.once('started', function () {
        cb(null, originalDeviceHash);
      });

      replicationManager.once('startError', cb);
      replicationManager.start(
        replicationManager._port,
        replicationManager._dbName,
        originalDeviceHash);
    });

    replicationManager.once('stopError', cb);
    replicationManager.stop();
  }

  function executeIdentityExchange(peerIdentifier, pkOther, pkMine, cb) {
    if (!global.isInIdentityExchange) {
      return cb(new Error('Identity Exchange not started'));
    }

    localPeerIdentifier = peerIdentifier;
    pkMineBuffer = new Buffer(pkMine);
    pkOtherBuffer = new Buffer(pkOther);

    rnMine = crypto.randomBytes(32);

    replicationManager._emitter.connect(peerIdentifier, function (err, port) {
      if (err) {
        return cleanupStopIdentityExchange(function () {
          cb(err);
          resetState();
        });
      }

      var compared = pkMineBuffer.compare(pkOtherBuffer);

      if (compared < 0) {
        // My hash is smaller
        var concatHash = Buffer.concat([pkMineBuffer, pkOtherBuffer]);
        var cbHash = crypto.createHmac('sha256', rnMine);
        var cbBuffer = cbHash.update(concatHash);
        var cbValue = cbBuffer.toString('base64');

        var rnOtherReq = request.post({
          url: 'http://localhost:' + port + '/identity/cb',
          body: {
            cbValue: cbValue,
            pkMine: pkMine
          }
        });

        rnOtherReq.once('response', function (response) {
          if (response.status !== 200) {
            return cleanupIdentityExchange(function () {
              cb(new Error('Invalid exchange'));
              resetState();
            });
          }

          var rnOther = new Buffer(response.body.rnOther);
          var pkO = new Buffer(response.body.pkOther);

          if (!pkO.equals(pkOtherBuffer)) {
            return cleanupIdentityExchange(function () {
              cb(new Error('Invalid exchange'));
              resetState();
            });
          }

          var rnMineReq = request.post({
            url: 'http://localhost:' + port + '/identity/rnmine',
            body: {
              rnMine: rnMine.toString('base64'),
              pkMine: pkMine
            }
          });

          rnMineReq.once('response', function (response) {
            if (response.status !== 200) {
              return cleanupIdentityExchange(function () {
                cb(new Error('Invalid exchange'));
                resetState();
              });
            }

            var value = createIdentityExchangeValue(rnOther, Buffer.concat([pkOtherBuffer, pkMineBuffer, rnMine]));
            disconnectExchange(function () {
              cb(null, value);
              resetState();
            });
          });
        });
      } else if (compared > 0) {
        // My hash is bigger and rest is done in cb express endpoint
        exchangeCb = cb;
      } else {
        cb(new Error('Hashes cannot be equal'));
      }
    });
  }

  app.post('/identity/cb', function (req, res) {
    if (!global.isInIdentityExchange) { return res.sendStatus(400); }

    if (!req.body) { return res.sendStatus(400); }
    if (typeof req.body.cbValue !== 'string' || req.body.cbValue.length === 0) {
      return res.sendStatus(400);
    }
    if (typeof req.body.pkMine !== 'string' || req.body.pkMine.length === 0) {
      return res.sendStatus(400);
    }

    cbValue = new Buffer(req.body.cbValue);
    var buf = new Buffer(req.body.pkMine);
    if (!buf.equals(pkOtherBuffer)) { return res.sendStatus(400); }

    res.sendStatus(200).json({
      pkOther: pkMineBuffer.toString('base64'),
      rnOther: rnMine.toString('base64')
    });
  });

  app.post('/identity/rnmine', function (req, res) {
    if (!global.isInIdentityExchange) { return res.sendStatus(400); }

    if (!req.body) { return res.sendStatus(400); }
    if (typeof req.body.pkMine !== 'string' || req.body.pkMine.length === 0) {
      return res.sendStatus(400);
    }
    if (typeof req.body.rnMine !== 'string' || req.body.rnMine.length !== 44) {
      return res.sendStatus(400);
    }

    // Test if we're under attack
    var testBuff = new Buffer(req.body.pkMine);
    if (!testBuff.equals(pkOtherBuffer)) { return res.sendStatus(400); }

    var rnOther = new Buffer(req.body.rnMine);
    var newBuff = Buffer.concat([pkOtherBuffer, pkMineBuffer]);
    var hmac = crypto.createHmac('sha256', rnOther);
    hmac.update(newBuff);
    var ret = hmac.digest();

    // No match
    if (!ret.equals(cbValue)) { return res.sendStatus(400); }

    res.sendStatus(200);

    var value = createIdentityExchangeValue(rnMine, Buffer.concat([pkMineBuffer, pkOtherBuffer, rnOther]));
    cleanupIdentityExchange(function () {
      exchangeCb(null, value);
      resetState();
    });
  });

  /* Create identity exchange value */
  function createIdentityExchangeValue(key, buffer) {
    var hmac = crypto.createHmac('sha256', key);
    hmac.update(buffer);
    return parseInt(hmac.digest().toString('hex'), 16) % Math.pow(10, 6);
  }

  /* Cleanup functions */
  function cleanupIdentityExchange(cb) {
    disconnectExchange(function () {
      cleanupStopIdentityExchange(function (cb) {
        cb();
      });
    });
  }

  function cleanupStopIdentityExchange(cb) {
    stopIdentityExchange(function (innerError) {
      console.log('Stop Identity Exchange Error %s', innerError);
      cb();
    });
  }

  function disconnectExchange(cb) {
    replicationManager._emitter.disconnect(localPeerIdentifier, function (err) {
      console.log('Disconnect error %s', err);
      cb();
    });
  }

  return {
    startIdentityExchange: startIdentityExchange,
    executeIdentityExchange: executeIdentityExchange,
    stopIdentityExchange: stopIdentityExchange
  };
};
<|endoftext|>


<commit_before>/* eslint-disable no-console, no-param-reassign */

import * as pg from '../src/lib/pg';
import BulkEmailChecker from '../src/lib/BulkEmailChecker';
import winston from 'winston';
import promisify from '../src/lib/promisify';
import contactsFilterConfig from './tools-config/contactsFilterConfig';
import { config } from '../src/core-server';

const LIMIT_CONTACT_TO = contactsFilterConfig.limitContactTo;
const JOB_INVOCATION_INTERVAL = contactsFilterConfig.jobInvocationInterval;
const performReadQuery = promisify(pg.read.bind(pg, config.connStr));
const performWriteQuery = promisify(pg.write.bind(pg, config.connStr));

const verifyMails = async () => {
	const emailContactMap = {};
	const verifiedMails = {};
	const bulkUpdateQuery = [];
	const bec = new BulkEmailChecker();

	const contacts = await performReadQuery({
		$: `SELECT contact::text AS contacttext, contact->>'email' AS email FROM contacts
			WHERE contact ? 'email' AND lastmailverifytime IS NULL LIMIT &{LIMIT_CONTACT_TO}`,
		LIMIT_CONTACT_TO
	});

	bec.on('data', async data => {
		if (!verifiedMails[data.email]) {
			verifiedMails[data.email] = data.isValid;
		}
	});

	bec.on('error', error => winston.info(error));

	bec.on('end', async () => {
		let now = Date.now();
		for (const email in verifiedMails) {
			for (const contactText of emailContactMap[email]) {
				const timeStamp = now++;
				bulkUpdateQuery.push({
					$: `UPDATE contacts SET valid = &{valid}::text, lastmailverifytime = &{timeStamp}
						WHERE contact::text = &{contactText}`,
					valid: verifiedMails[email],
					timeStamp,
					contactText
				});
			}
		}
		await performWriteQuery(bulkUpdateQuery);
		winston.info(`updated ${bulkUpdateQuery.length} rows in contacts table`);
	});

	for (const contact of contacts) {
		if (!emailContactMap[contact.email]) {
			emailContactMap[contact.email] = [];
		}
		emailContactMap[contact.email].push(contact.contacttext);
		bec.add(contact.email);
	}
	bec.done();
};

setInterval(verifyMails, JOB_INVOCATION_INTERVAL);
<commit_msg>removed unused async keyword<commit_after>/* eslint-disable no-console, no-param-reassign */

import * as pg from '../src/lib/pg';
import BulkEmailChecker from '../src/lib/BulkEmailChecker';
import winston from 'winston';
import promisify from '../src/lib/promisify';
import contactsFilterConfig from './tools-config/contactsFilterConfig';
import { config } from '../src/core-server';

const LIMIT_CONTACT_TO = contactsFilterConfig.limitContactTo;
const JOB_INVOCATION_INTERVAL = contactsFilterConfig.jobInvocationInterval;
const performReadQuery = promisify(pg.read.bind(pg, config.connStr));
const performWriteQuery = promisify(pg.write.bind(pg, config.connStr));

const verifyMails = async () => {
	const emailContactMap = {};
	const verifiedMails = {};
	const bulkUpdateQuery = [];
	const bec = new BulkEmailChecker();

	const contacts = await performReadQuery({
		$: `SELECT contact::text AS contacttext, contact->>'email' AS email FROM contacts
			WHERE contact ? 'email' AND lastmailverifytime IS NULL LIMIT &{LIMIT_CONTACT_TO}`,
		LIMIT_CONTACT_TO
	});

	bec.on('data', data => {
		if (!verifiedMails[data.email]) {
			verifiedMails[data.email] = data.isValid;
		}
	});

	bec.on('error', error => winston.info(error));

	bec.on('end', async () => {
		let now = Date.now();
		for (const email in verifiedMails) {
			for (const contactText of emailContactMap[email]) {
				const timeStamp = now++;
				bulkUpdateQuery.push({
					$: `UPDATE contacts SET valid = &{valid}::text, lastmailverifytime = &{timeStamp}
						WHERE contact::text = &{contactText}`,
					valid: verifiedMails[email],
					timeStamp,
					contactText
				});
			}
		}
		await performWriteQuery(bulkUpdateQuery);
		winston.info(`updated ${bulkUpdateQuery.length} rows in contacts table`);
	});

	for (const contact of contacts) {
		if (!emailContactMap[contact.email]) {
			emailContactMap[contact.email] = [];
		}
		emailContactMap[contact.email].push(contact.contacttext);
		bec.add(contact.email);
	}
	bec.done();
};

setInterval(verifyMails, JOB_INVOCATION_INTERVAL);
<|endoftext|>


<commit_before>// -------------------------------------------------------------------------- \\
// File: EventSource.js                                                       \\
// Module: IO                                                                 \\
// Requires: Core, Foundation, UA, XHR.js                                     \\
// Author: Neil Jenkins                                                       \\
// License: © 2010–2012 Opera Software ASA. All rights reserved.              \\
// -------------------------------------------------------------------------- \\

"use strict";

( function ( NS, global ) {

var CONNECTING = 0;
var OPEN = 1;
var CLOSED = 2;

/**
    Class: O.EventSource

    Extends: O.Object

    Subscribe to push events on the server using this wrapper around the W3C
    EventSource object: <http://dev.w3.org/html5/eventsource/>

    Events are sent using a text/event-stream content type; see the linked spec
    for details. The event source object will fire events as they arrive.
*/
var EventSource = global.EventSource ? NS.Class({

    Extends: NS.Object,

    /**
        Property: O.EventSource#readyState
        Type: Number

        A number describing the ready state of the event source, corresponding
        to those in the W3C spec:

        0 - CONNECTING
        1 - OPEN
        2 - CLOSED
    */
    readyState: CLOSED,

    /**
        Property: O.EventSource#url
        Type: String

        The url to connect to in order to receive events
    */
    url: '',

    /**
        Constructor: O.EventSource

        Parameters:
            options - {Object} (optional) Any properties in this object will be
                      added to the new O.EventSource instance before
                      initialisation (so you can pass it getter/setter functions
                      or observing methods).
    */
    init: function () {
        EventSource.parent.init.apply( this, arguments );

        var eventTypes = [ 'open', 'message', 'error' ],
            observers = NS.meta( this, true ).observers,
            type;
        for ( type in observers ) {
            if ( /^__event__/.test( type ) ) {
                eventTypes.include( type.slice( 9 ) );
            }
        }
        this._eventTypes = eventTypes;

        this.open();
    },

    on: function ( type ) {
        var types = this._eventTypes,
            eventSource = this._eventSource;
        if ( types.indexOf( type ) === -1 ) {
            types.push( type );
            if ( eventSource ) {
                eventSource.addEventListener( type, this, false );
            }
        }
        EventSource.parent.on.apply( this, arguments );
    },

    handleEvent: function ( event ) {
        this.set( 'readyState', this._eventSource.readyState );
        this.fire( event.type, event );
    }.invokeInRunLoop(),

    /**
        Method (private): O.EventSource#_check

        Checks the computer hasn't been asleep. If it has, it restarts the
        connection.
    */
    _check: function () {
        var now = Date.now();
        if ( now - this._then > 67500 ) {
            this.fire( 'restart' );
            this.close().open();
        } else {
            this._then = now;
            this._tick =
                NS.RunLoop.invokeAfterDelay( this._check, 60000, this );
        }
    },
    /**
        Method (private): O.EventSource#_startStopCheck

        Sets up the timer to check if the computer has been asleep.
    */
    _startStopCheck: function () {
        var tick = this._tick;
        if ( this.get( 'readyState' ) !== CLOSED ) {
            if ( !tick ) {
                this._then = Date.now();
                this._check();
            }
        } else {
            if ( tick ) {
                NS.RunLoop.cancel( tick );
                this._tick = null;
            }
        }
    }.observes( 'readyState' ),

    /**
        Method: O.EventSource#open

        If there is no current connection to the event source server,
        establishes a new connection.

        Returns:
            {O.EventSource} Returns self.
    */
    open: function () {
        if ( this.get( 'readyState' ) === CLOSED ) {
            var eventSource = this._eventSource =
                new global.EventSource( this.get( 'url' ) );

            this._eventTypes.forEach( function ( type ) {
                eventSource.addEventListener( type, this, false );
            }, this );

            this.set( 'readyState', eventSource.readyState );
        }
        return this;
    },

    /**
        Method: O.EventSource#close

        Close the connection to the event source server, if not already closed.

        Returns:
            {O.EventSource} Returns self.
    */
    close: function () {
        if ( this.get( 'readyState' ) !== CLOSED ) {
            this._eventSource.close();
            this._eventSource = null;
            this.set( 'readyState', CLOSED );
        }
        return this;
    }
}) : NS.Class({

    Extends: NS.Object,

    readyState: CONNECTING,

    init: function () {
        EventSource.parent.init.apply( this, arguments );
        this._xhr = new NS.XHR( this );
        this.open();
    },
    open: function () {
        var headers = {
            'Accept': 'text/event-stream',
            'Cache-Control': 'no-cache'
        };
        if ( this._lastEventId ) {
            headers[ 'Last-Event-ID' ] = this._lastEventId;
        }
        if ( this._poll ) {
            headers[ 'X-Nginx-PushStream-Mode' ] = 'long-polling';
        }

        this.set( 'readyState', CONNECTING );
        this._data = '';
        this._eventName = '';
        this._processedIndex = 0;
        this._lastNewLineIndex = 0;
        this._xhr.send( 'GET', this.get( 'url' ), null, headers );
        return this;
    },
    close: function () {
        if ( this.get( 'readyState' ) !== CLOSED ) {
            this._xhr.abort();
            this.set( 'readyState', CLOSED );
        }
        return this;
    },

    _reconnectAfter: 30000,
    _lastEventId: '',
    _poll: !!NS.UA.msie,

    // --- io ---
    loading: function ( xhr ) {
        // Must start with text/event-stream (i.e. indexOf must === 0)
        // If it doesn't, fail the connection.
        if ( xhr.getResponseType().indexOf( 'text/event-stream' ) ) {
            this._failConnection();
        } else {
            this._openConnection();
            this._processData( xhr.getResponse() );
        }
    }.invokeInRunLoop(),
    success: function ( xhr ) {
        this._openConnection();
        this._processData( xhr.getResponse() + '\n\n' );
        this._reconnect();
    }.invokeInRunLoop(),
    failure: function ( xhr ) {
        this._failConnection();
    }.invokeInRunLoop(),
    // -- end io ---

    _openConnection: function () {
        if ( this.get( 'readyState' ) === CONNECTING ) {
            this.set( 'readyState', OPEN );
            this.fire( 'open' );
        }
    },
    _failConnection: function () {
        this.close();
        this.fire( 'error' );
    },
    _reconnect: function () {
        if ( this._poll ) {
            this.open();
        } else {
            NS.RunLoop.invokeAfterDelay(
                this.open, this._reconnectAfter, this );
        }
    },
    _processData: function ( text ) {
        // Look for a new line character since the last processed
        var lastIndex = this._lastNewLineIndex,
            newLine = /\u000d\u000a?|\u000a/g,
            match;

        // One leading U+FEFF BYTE ORDER MARK character must be ignored if any
        // are present.
        if ( !lastIndex && text.charAt( 0 ) === '\ufeff' ) {
            lastIndex = 1;
        }
        newLine.lastIndex = this._processedIndex;
        while ( match = newLine.exec( text ) ) {
            this._processLine( text.slice( lastIndex, match.index ) );
            lastIndex = newLine.lastIndex;
        }
        this._lastNewLineIndex = lastIndex;
        this._processedIndex = text.length;
    },
    _processLine: function ( line ) {
        // Blank line, dispatch event
        if ( /^\s*$/.test( line ) ) {
            this._dispatchEvent();
        } else {
            var colon = line.indexOf( ':' ),
                field = line,
                value = '';
            // Line starts with colon -> ignore.
            if ( !colon ) {
                return;
            }
            // Line contains colon:
            if ( colon > 0 ) {
                field = line.slice( 0, colon );
                value = line.slice( line.charAt( colon + 1 ) === ' ' ?
                    colon + 2 : colon + 1 );
            }
            switch ( field ) {
                case 'event':
                    this._eventName = value;
                    break;
                case 'data':
                    this._data += value + '\u000a';
                    break;
                case 'id':
                    this._lastEventId = value;
                    break;
                case 'retry':
                    if ( /^\d+$/.test( value ) ) {
                        this._reconnectAfter = parseInt( value, 10 );
                    }
                    break;
            }
        }
    },
    _dispatchEvent: function () {
        var data = this._data,
            type = this._eventName;
        if ( data ) {
            if ( data.slice( -1 ) === '\u000a' ) {
                data = data.slice( 0, -1 );
            }
            this.fire( type || 'message', {
                data: data,
                // origin: '',
                lastEventId: this._lastEventId
            });
        }
        this._data = '';
        this._eventName = '';
    }
});

/**
    Constant: O.EventSource.CONNECTING
    Type: Number

    <O.EventSource#readyState> when establishing a connection to the server.
*/
/**
    Constant: O.EventSource.OPEN
    Type: Number

    <O.EventSource#readyState> when a connection is open and receiving events.
*/
/**
    Constant: O.EventSource.CLOSED
    Type: Number

    <O.EventSource#readyState> when there is no connection and it is not being
    reestablished.
*/
EventSource.extend({
    CONNECTING: CONNECTING,
    OPEN: OPEN,
    CLOSED: CLOSED
});

NS.EventSource = EventSource;

}( this.O, this ) );
<commit_msg>Don't open EventSource on init.<commit_after>// -------------------------------------------------------------------------- \\
// File: EventSource.js                                                       \\
// Module: IO                                                                 \\
// Requires: Core, Foundation, UA, XHR.js                                     \\
// Author: Neil Jenkins                                                       \\
// License: © 2010–2012 Opera Software ASA. All rights reserved.              \\
// -------------------------------------------------------------------------- \\

/*global EventSource */

"use strict";

( function ( NS, NativeEventSource ) {

var CONNECTING = 0;
var OPEN = 1;
var CLOSED = 2;

/**
    Class: O.EventSource

    Extends: O.Object

    Subscribe to push events on the server using this wrapper around the W3C
    EventSource object: <http://dev.w3.org/html5/eventsource/>

    Events are sent using a text/event-stream content type; see the linked spec
    for details. The event source object will fire events as they arrive.
*/
var EventSource = NativeEventSource ? NS.Class({

    Extends: NS.Object,

    /**
        Property: O.EventSource#readyState
        Type: Number

        A number describing the ready state of the event source, corresponding
        to those in the W3C spec:

        0 - CONNECTING
        1 - OPEN
        2 - CLOSED
    */
    readyState: CLOSED,

    /**
        Property: O.EventSource#url
        Type: String

        The url to connect to in order to receive events
    */
    url: '',

    /**
        Constructor: O.EventSource

        Parameters:
            options - {Object} (optional) Any properties in this object will be
                      added to the new O.EventSource instance before
                      initialisation (so you can pass it getter/setter functions
                      or observing methods).
    */
    init: function () {
        EventSource.parent.init.apply( this, arguments );

        var eventTypes = [ 'open', 'message', 'error' ],
            observers = NS.meta( this, true ).observers,
            type;
        for ( type in observers ) {
            if ( /^__event__/.test( type ) ) {
                eventTypes.include( type.slice( 9 ) );
            }
        }
        this._eventTypes = eventTypes;
    },

    on: function ( type ) {
        var types = this._eventTypes,
            eventSource = this._eventSource;
        if ( types.indexOf( type ) === -1 ) {
            types.push( type );
            if ( eventSource ) {
                eventSource.addEventListener( type, this, false );
            }
        }
        EventSource.parent.on.apply( this, arguments );
    },

    handleEvent: function ( event ) {
        this.set( 'readyState', this._eventSource.readyState );
        this.fire( event.type, event );
    }.invokeInRunLoop(),

    /**
        Method (private): O.EventSource#_check

        Checks the computer hasn't been asleep. If it has, it restarts the
        connection.
    */
    _check: function () {
        var now = Date.now();
        if ( now - this._then > 67500 ) {
            this.fire( 'restart' );
            this.close().open();
        } else {
            this._then = now;
            this._tick =
                NS.RunLoop.invokeAfterDelay( this._check, 60000, this );
        }
    },
    /**
        Method (private): O.EventSource#_startStopCheck

        Sets up the timer to check if the computer has been asleep.
    */
    _startStopCheck: function () {
        var tick = this._tick;
        if ( this.get( 'readyState' ) !== CLOSED ) {
            if ( !tick ) {
                this._then = Date.now();
                this._check();
            }
        } else {
            if ( tick ) {
                NS.RunLoop.cancel( tick );
                this._tick = null;
            }
        }
    }.observes( 'readyState' ),

    /**
        Method: O.EventSource#open

        If there is no current connection to the event source server,
        establishes a new connection.

        Returns:
            {O.EventSource} Returns self.
    */
    open: function () {
        if ( this.get( 'readyState' ) === CLOSED ) {
            var eventSource = this._eventSource =
                new NativeEventSource( this.get( 'url' ) );

            this._eventTypes.forEach( function ( type ) {
                eventSource.addEventListener( type, this, false );
            }, this );

            this.set( 'readyState', eventSource.readyState );
        }
        return this;
    },

    /**
        Method: O.EventSource#close

        Close the connection to the event source server, if not already closed.

        Returns:
            {O.EventSource} Returns self.
    */
    close: function () {
        if ( this.get( 'readyState' ) !== CLOSED ) {
            this._eventSource.close();
            this._eventSource = null;
            this.set( 'readyState', CLOSED );
        }
        return this;
    }
}) : NS.Class({

    Extends: NS.Object,

    readyState: CONNECTING,

    init: function () {
        EventSource.parent.init.apply( this, arguments );
        this._xhr = new NS.XHR( this );
    },
    open: function () {
        var headers = {
            'Accept': 'text/event-stream',
            'Cache-Control': 'no-cache'
        };
        if ( this._lastEventId ) {
            headers[ 'Last-Event-ID' ] = this._lastEventId;
        }
        if ( this._poll ) {
            headers[ 'X-Nginx-PushStream-Mode' ] = 'long-polling';
        }

        this.set( 'readyState', CONNECTING );
        this._data = '';
        this._eventName = '';
        this._processedIndex = 0;
        this._lastNewLineIndex = 0;
        this._xhr.send( 'GET', this.get( 'url' ), null, headers );
        return this;
    },
    close: function () {
        if ( this.get( 'readyState' ) !== CLOSED ) {
            this._xhr.abort();
            this.set( 'readyState', CLOSED );
        }
        return this;
    },

    _reconnectAfter: 30000,
    _lastEventId: '',
    _poll: !!NS.UA.msie,

    // --- io ---
    loading: function ( xhr ) {
        // Must start with text/event-stream (i.e. indexOf must === 0)
        // If it doesn't, fail the connection.
        if ( xhr.getResponseType().indexOf( 'text/event-stream' ) ) {
            this._failConnection();
        } else {
            this._openConnection();
            this._processData( xhr.getResponse() );
        }
    }.invokeInRunLoop(),
    success: function ( xhr ) {
        this._openConnection();
        this._processData( xhr.getResponse() + '\n\n' );
        this._reconnect();
    }.invokeInRunLoop(),
    failure: function ( xhr ) {
        this._failConnection();
    }.invokeInRunLoop(),
    // -- end io ---

    _openConnection: function () {
        if ( this.get( 'readyState' ) === CONNECTING ) {
            this.set( 'readyState', OPEN );
            this.fire( 'open' );
        }
    },
    _failConnection: function () {
        this.close();
        this.fire( 'error' );
    },
    _reconnect: function () {
        if ( this._poll ) {
            this.open();
        } else {
            NS.RunLoop.invokeAfterDelay(
                this.open, this._reconnectAfter, this );
        }
    },
    _processData: function ( text ) {
        // Look for a new line character since the last processed
        var lastIndex = this._lastNewLineIndex,
            newLine = /\u000d\u000a?|\u000a/g,
            match;

        // One leading U+FEFF BYTE ORDER MARK character must be ignored if any
        // are present.
        if ( !lastIndex && text.charAt( 0 ) === '\ufeff' ) {
            lastIndex = 1;
        }
        newLine.lastIndex = this._processedIndex;
        while ( match = newLine.exec( text ) ) {
            this._processLine( text.slice( lastIndex, match.index ) );
            lastIndex = newLine.lastIndex;
        }
        this._lastNewLineIndex = lastIndex;
        this._processedIndex = text.length;
    },
    _processLine: function ( line ) {
        // Blank line, dispatch event
        if ( /^\s*$/.test( line ) ) {
            this._dispatchEvent();
        } else {
            var colon = line.indexOf( ':' ),
                field = line,
                value = '';
            // Line starts with colon -> ignore.
            if ( !colon ) {
                return;
            }
            // Line contains colon:
            if ( colon > 0 ) {
                field = line.slice( 0, colon );
                value = line.slice( line.charAt( colon + 1 ) === ' ' ?
                    colon + 2 : colon + 1 );
            }
            switch ( field ) {
                case 'event':
                    this._eventName = value;
                    break;
                case 'data':
                    this._data += value + '\u000a';
                    break;
                case 'id':
                    this._lastEventId = value;
                    break;
                case 'retry':
                    if ( /^\d+$/.test( value ) ) {
                        this._reconnectAfter = parseInt( value, 10 );
                    }
                    break;
            }
        }
    },
    _dispatchEvent: function () {
        var data = this._data,
            type = this._eventName;
        if ( data ) {
            if ( data.slice( -1 ) === '\u000a' ) {
                data = data.slice( 0, -1 );
            }
            this.fire( type || 'message', {
                data: data,
                // origin: '',
                lastEventId: this._lastEventId
            });
        }
        this._data = '';
        this._eventName = '';
    }
});

/**
    Constant: O.EventSource.CONNECTING
    Type: Number

    <O.EventSource#readyState> when establishing a connection to the server.
*/
/**
    Constant: O.EventSource.OPEN
    Type: Number

    <O.EventSource#readyState> when a connection is open and receiving events.
*/
/**
    Constant: O.EventSource.CLOSED
    Type: Number

    <O.EventSource#readyState> when there is no connection and it is not being
    reestablished.
*/
EventSource.extend({
    CONNECTING: CONNECTING,
    OPEN: OPEN,
    CLOSED: CLOSED
});

NS.EventSource = EventSource;

}( this.O, typeof EventSource !== 'undefined' ? EventSource : null ) );
<|endoftext|>


<commit_before>/* global $, React*/

var d = React.DOM;

var timeSort = function (l, r) {
    return r.created_at.localeCompare(l.created_at);
};

var repoDefaults = [
    { repo: "highfive", language: "L-python" },
    { repo: "servo-starters", language: "L-javascript" },
    { repo: "crowbot", language: "L-javascript" },
    { repo: "saltfs", language: "L-saltstack" },
];

var langLabels = [{
        name: "L-rust",
        color: "bfd4f2",
        url:  "https://api.github.com/servo/servo/labels/L-rust",
        selected: true
    },
    {
        name: "L-python",
        color: "bfd4f2",
        url:  "https://api.github.com/servo/servo/labels/L-python",
        selected: true
    },
    {
        name: "L-javascript",
        color: "bfd4f2",
        url:  "https://api.github.com/servo/servo/labels/L-javascript",
        selected: true
    },
    {
        name: "L-saltstack",
        color: "bfd4f2",
        url:  "https://api.github.com/servo/servo/labels/L-saltstack",
        selected: true
    },
];

var getIssueLanguageLabel = function (issue) {
    for (var i = 0; i < issue.labels.length; i++) {
        var label = issue.labels[i];
        if (/^L-(.*)/.test(label.name)) {
            return label;
        }
    }
    return null;
};

var addDefaultLanguageLabel = function (issue) {
    if (getIssueLanguageLabel(issue) !== null) {
        return issue;
    }

    var defaultLanguageLabelName = "L-rust";
    for (var i = 0; i < repoDefaults.length; i++) {
        var repoUrlSubStr = "servo/" + repoDefaults[i].repo + "/issues";
        if (issue.url && issue.url.match(repoUrlSubStr)) {
            defaultLanguageLabelName = repoDefaults[i].language;
            break;
        }
    }

    var defaultLanguageLabelIndex = langLabels.map(function (l) {
        return l.name;
    }).indexOf(defaultLanguageLabelName);

    if (langLabels[defaultLanguageLabelIndex]) {
        issue.labels.push(langLabels[defaultLanguageLabelIndex]);
    }
    return issue;
};

var extractFunction = function (callback) {
  return function (r1, r2) {
    var easies = r1[0].items,
        lessEasies = r2[0].items,
        all = easies.concat(lessEasies);

    all.sort(timeSort);
    all.map(addDefaultLanguageLabel);
    callback(all);
  };
};

var extractLabel = function (label, regex) {
  return label.match(regex)[1];
};

var issuesUrl = "https://api.github.com/search/issues";

var getPotentiallyOpenIssues = function (callback) {

    var today = new Date(),
        twoWeeksAgo = new Date(today - 86400000 * 14),
        olderThanTwoWeeks = "<" + twoWeeksAgo.toISOString().slice(0, 10);

    var easy = $.ajax({
        dataType: "json",
        url: issuesUrl,
        data: "q=updated:" + olderThanTwoWeeks + "+state:open+label:C-assigned+label:E-Easy+-label:\"C-has%20open%20PR\"+user:servo&sort=updated"
    });

    var lessEasy = $.ajax({
        dataType: "json",
        url: issuesUrl,
        data: "q=updated:" + olderThanTwoWeeks + "+state:open+label:C-assigned+label:\"E-Less%20easy\"+-label:\"C-has%20open%20PR\"+user:servo&sort=updated"
    });

    var dataExtractor = extractFunction(callback);

    $.when(easy, lessEasy).done(dataExtractor);
};

var getOpenIssues = function (callback) {

    var easy = $.ajax({
        dataType: "json",
        url: issuesUrl,
        data: "q=state:open+-label:C-assigned+-label:S-blocked-on-external+label:E-Easy+user:servo&sort=created"
    });

    var lessEasy = $.ajax({
        dataType: "json",
        url: issuesUrl,
        data: "q=state:open+-label:C-assigned+-label:S-blocked-on-external+label:\"E-Less%20easy\"+user:servo&sort=created"
    });

    var dataExtractor = extractFunction(callback);

    $.when(easy, lessEasy).done(dataExtractor);
};

var replacers = [
  {matcher: /^L-(.*)/, replacement: "Language: "},
  {matcher: /^A-(.*)/, replacement: "Area: "},
  {matcher: /^(?:S-|C-)(.*)/, replacement: "Status: "},
  {matcher: /^P-(.*)/, replacement: "Platform: "},
  {matcher: /^B-(.*)/, replacement: ""},
  {matcher: /^I-(.*)/, replacement: "Category: "}
];

var makeLabelFriendly = function (label) {
  var newLabel = label;

  var labelMap = {
    "E-easy": "Good first PR",
    "E-Easy": "Good first PR",
    "E-less easy": "Mentored"
  };

  if (labelMap[label]) {
    return labelMap[label];
  }

  replacers.forEach(function (item) {
    if (item.matcher.test(label)) {
      newLabel = item.replacement + extractLabel(label, item.matcher);
    }
  });

  return newLabel;
};

var label = function (data) {
    var color = (data.color === "d7e102" ||
                 data.color === "bfd4f2" ||
                 data.color === "d4c5f9" ||
                 data.color === "02d7e1") ? "black" : "white";

    var friendlyLabel = makeLabelFriendly(data.name);

    return d.span(
        {className: "label", style: {backgroundColor: "#" + data.color, color: color}},
        friendlyLabel
    );
};

var FeelingAdventurous = React.createClass({
    gotoRandomIssue: function () {
        var issues = this.props.issues;

        var randomIndex = Math.floor(Math.random() * (issues.length + 1));

        window.location.href = issues[randomIndex].html_url;
    },

    render: function () {
        return d.button(
            {className: "button", onClick: this.gotoRandomIssue},
            "I'm Feeling Adventurous..."
        );
    }
});

var feelingAdventurous = function (issues) {
    return React.createElement(FeelingAdventurous, {issues: issues});
};

var Labels = React.createClass({
    render: function() {
        return d.div(
            {className: "labels"},
            this.props.labels.map(label)
        );
    }
});

var labels = function (data) {
    return React.createElement(Labels, data);
};

var FilterLabel = React.createClass({
  render: function () {
    var color = (this.props.color === "d7e102" ||
                 this.props.color === "bfd4f2" ||
                 this.props.color === "d4c5f9" ||
                 this.props.color === "02d7e1") ? "black" : "white";

    var friendlyLabel = makeLabelFriendly(this.props.name);
    return d.span({className: "label",
                   style: {backgroundColor: "#" + this.props.color,
                           color: color,
                           cursor: "pointer",
                           opacity: this.props.selected ? 1.0 : 0.5},
                   onClick: this.props.onClick.bind(null, this.props)},
                   friendlyLabel);
  }
});

var filterLabel = function (data) {
  return React.createElement(FilterLabel, data);
};

var WantToWorkWith = React.createClass({
  render: function () {
    return d.span({className: "labels"}, this.props.labels.map(filterLabel));
  }
});

var wantToWorkWith = function (labels, onClick) {
  return React.createElement(WantToWorkWith, {labels: labels.map(function (label) {
    label.onClick = onClick;
    return label;
  })});
};

var repoUrl = function(url) {
    var urlArray = url.split("/");
    return urlArray[urlArray.length - 3];
};

var Issue = React.createClass({

    render: function () {
        return d.li(
            {className: "issue"},
            d.div(
                {},
                "[ ",
                d.a(
                    {
                        className: "issue-link",
                        href: this.props.html_url,
                        title: this.props.title
                    },
                    repoUrl(this.props.html_url) + " " + this.props.number
                ),
                " ] - ",
                d.span(
                    {className: "issue-desc"},
                    this.props.title
                )
            ),
            labels(this.props),
            d.div(
              {className: "time-wrapper"},
              d.span({className: "time"},
              d.i({className: "fa fa-clock-o"}),
              " Last activity: " + moment(this.props.updated_at).fromNow())
            )
        );
    }
});

var issueItem = function (data) {
    return React.createElement(Issue, data);
};

var IssueList = React.createClass({
    getInitialState: function () {
        return {
            limited: true
        };
    },

    getIssuesWithSelectedLabels: function (issues, selectedLabels) {
        var labelNames = selectedLabels.map(function (label) {
            return label.name;
        });

        var filteredIssues = issues.filter(function (issue) {
            for (var i = 0; i < issue.labels.length; i++) {
                if (labelNames.indexOf(issue.labels[i].name) > -1) {
                    return true;
                }
            }
            return false;
        });

        return filteredIssues;
    },

    render: function () {
        if (this.props.loading) {
            return d.div({id: "loading"});
        } else {
            var issues = this.getIssuesWithSelectedLabels(
                this.props.issues, this.props.selectedLabels);

            if (this.state.limited && issues.length > 5) {
                issues = issues.map(issueItem)
                                          .slice(0, 5)
                                          .concat(
                                              d.div(
                                                  {
                                                      className: "view-all",
                                                      onClick: function() {
                                                          this.setState({limited: false});
                                                      }.bind(this)
                                                  },
                                                  "view all..."
                                              )
                                          );
            } else {
                issues = issues.map(issueItem);
            }

            return d.ul(
                {id: "issues"},
                issues
            );
        }
    }
});

var issueList = function (issues, loading, filters) {
    var selectedLabels = filters.filter(function (label) {
        return label.selected;
    });
    return React.createElement(IssueList, {issues: issues, loading: loading, selectedLabels: selectedLabels});
};

var App = React.createClass({
    selectFilter: function (filterProps) {
        for (var i = 0; i < this.state.languageFilters.length; i++) {
            if (this.state.languageFilters[i].name === filterProps.name) {
                break;
            }
        }

        this.setState(function (oldState) {
            var lf = oldState.languageFilters;
            lf[i].selected = !lf[i].selected;
            return {languageFilters: lf};
        });
    },

    componentDidMount: function () {
        getOpenIssues(function (data) {
            this.setState({
                openIssues: data,
                openIssuesLoading: false
            });
        }.bind(this));

        getPotentiallyOpenIssues(function (data) {
            this.setState({
                potentiallyOpenIssues: data,
                potentiallyOpenIssuesLoading: false
            });
        }.bind(this));
    },

    getInitialState: function () {
        return {
            openIssuesLoading: true,
            potentiallyOpenIssuesLoading: true,
            openIssues: [],
            potentiallyOpenIssues: [],
            languageFilters: langLabels
        };
    },

    render: function () {
        return d.div(
            {},
            this.state.openIssuesLoading ? [] : feelingAdventurous(this.state.openIssues),

            this.state.openIssuesLoading ? [] : d.div({className: "language-picker"},
                d.h5({}, "and I want to work with: "),
                wantToWorkWith(this.state.languageFilters, this.selectFilter)),

            d.h2({}, "Open Issues"),
            issueList(this.state.openIssues, this.state.openIssuesLoading, this.state.languageFilters),

            d.h2({}, "Potentially Open Issues"),
            issueList(this.state.potentiallyOpenIssues, this.state.potentiallyOpenIssuesLoading, this.state.languageFilters)
        );
    }
});

React.render(
    React.createElement(App, {}),
    document.getElementById("app")
);
<commit_msg>Mark buildbotstatus as JS-based.<commit_after>/* global $, React*/

var d = React.DOM;

var timeSort = function (l, r) {
    return r.created_at.localeCompare(l.created_at);
};

var repoDefaults = [
    { repo: "highfive", language: "L-python" },
    { repo: "servo-starters", language: "L-javascript" },
    { repo: "crowbot", language: "L-javascript" },
    { repo: "saltfs", language: "L-saltstack" },
    { repo: "buildbotstatus", language: "L-javascript" },
];

var langLabels = [{
        name: "L-rust",
        color: "bfd4f2",
        url:  "https://api.github.com/servo/servo/labels/L-rust",
        selected: true
    },
    {
        name: "L-python",
        color: "bfd4f2",
        url:  "https://api.github.com/servo/servo/labels/L-python",
        selected: true
    },
    {
        name: "L-javascript",
        color: "bfd4f2",
        url:  "https://api.github.com/servo/servo/labels/L-javascript",
        selected: true
    },
    {
        name: "L-saltstack",
        color: "bfd4f2",
        url:  "https://api.github.com/servo/servo/labels/L-saltstack",
        selected: true
    },
];

var getIssueLanguageLabel = function (issue) {
    for (var i = 0; i < issue.labels.length; i++) {
        var label = issue.labels[i];
        if (/^L-(.*)/.test(label.name)) {
            return label;
        }
    }
    return null;
};

var addDefaultLanguageLabel = function (issue) {
    if (getIssueLanguageLabel(issue) !== null) {
        return issue;
    }

    var defaultLanguageLabelName = "L-rust";
    for (var i = 0; i < repoDefaults.length; i++) {
        var repoUrlSubStr = "servo/" + repoDefaults[i].repo + "/issues";
        if (issue.url && issue.url.match(repoUrlSubStr)) {
            defaultLanguageLabelName = repoDefaults[i].language;
            break;
        }
    }

    var defaultLanguageLabelIndex = langLabels.map(function (l) {
        return l.name;
    }).indexOf(defaultLanguageLabelName);

    if (langLabels[defaultLanguageLabelIndex]) {
        issue.labels.push(langLabels[defaultLanguageLabelIndex]);
    }
    return issue;
};

var extractFunction = function (callback) {
  return function (r1, r2) {
    var easies = r1[0].items,
        lessEasies = r2[0].items,
        all = easies.concat(lessEasies);

    all.sort(timeSort);
    all.map(addDefaultLanguageLabel);
    callback(all);
  };
};

var extractLabel = function (label, regex) {
  return label.match(regex)[1];
};

var issuesUrl = "https://api.github.com/search/issues";

var getPotentiallyOpenIssues = function (callback) {

    var today = new Date(),
        twoWeeksAgo = new Date(today - 86400000 * 14),
        olderThanTwoWeeks = "<" + twoWeeksAgo.toISOString().slice(0, 10);

    var easy = $.ajax({
        dataType: "json",
        url: issuesUrl,
        data: "q=updated:" + olderThanTwoWeeks + "+state:open+label:C-assigned+label:E-Easy+-label:\"C-has%20open%20PR\"+user:servo&sort=updated"
    });

    var lessEasy = $.ajax({
        dataType: "json",
        url: issuesUrl,
        data: "q=updated:" + olderThanTwoWeeks + "+state:open+label:C-assigned+label:\"E-Less%20easy\"+-label:\"C-has%20open%20PR\"+user:servo&sort=updated"
    });

    var dataExtractor = extractFunction(callback);

    $.when(easy, lessEasy).done(dataExtractor);
};

var getOpenIssues = function (callback) {

    var easy = $.ajax({
        dataType: "json",
        url: issuesUrl,
        data: "q=state:open+-label:C-assigned+-label:S-blocked-on-external+label:E-Easy+user:servo&sort=created"
    });

    var lessEasy = $.ajax({
        dataType: "json",
        url: issuesUrl,
        data: "q=state:open+-label:C-assigned+-label:S-blocked-on-external+label:\"E-Less%20easy\"+user:servo&sort=created"
    });

    var dataExtractor = extractFunction(callback);

    $.when(easy, lessEasy).done(dataExtractor);
};

var replacers = [
  {matcher: /^L-(.*)/, replacement: "Language: "},
  {matcher: /^A-(.*)/, replacement: "Area: "},
  {matcher: /^(?:S-|C-)(.*)/, replacement: "Status: "},
  {matcher: /^P-(.*)/, replacement: "Platform: "},
  {matcher: /^B-(.*)/, replacement: ""},
  {matcher: /^I-(.*)/, replacement: "Category: "}
];

var makeLabelFriendly = function (label) {
  var newLabel = label;

  var labelMap = {
    "E-easy": "Good first PR",
    "E-Easy": "Good first PR",
    "E-less easy": "Mentored"
  };

  if (labelMap[label]) {
    return labelMap[label];
  }

  replacers.forEach(function (item) {
    if (item.matcher.test(label)) {
      newLabel = item.replacement + extractLabel(label, item.matcher);
    }
  });

  return newLabel;
};

var label = function (data) {
    var color = (data.color === "d7e102" ||
                 data.color === "bfd4f2" ||
                 data.color === "d4c5f9" ||
                 data.color === "02d7e1") ? "black" : "white";

    var friendlyLabel = makeLabelFriendly(data.name);

    return d.span(
        {className: "label", style: {backgroundColor: "#" + data.color, color: color}},
        friendlyLabel
    );
};

var FeelingAdventurous = React.createClass({
    gotoRandomIssue: function () {
        var issues = this.props.issues;

        var randomIndex = Math.floor(Math.random() * (issues.length + 1));

        window.location.href = issues[randomIndex].html_url;
    },

    render: function () {
        return d.button(
            {className: "button", onClick: this.gotoRandomIssue},
            "I'm Feeling Adventurous..."
        );
    }
});

var feelingAdventurous = function (issues) {
    return React.createElement(FeelingAdventurous, {issues: issues});
};

var Labels = React.createClass({
    render: function() {
        return d.div(
            {className: "labels"},
            this.props.labels.map(label)
        );
    }
});

var labels = function (data) {
    return React.createElement(Labels, data);
};

var FilterLabel = React.createClass({
  render: function () {
    var color = (this.props.color === "d7e102" ||
                 this.props.color === "bfd4f2" ||
                 this.props.color === "d4c5f9" ||
                 this.props.color === "02d7e1") ? "black" : "white";

    var friendlyLabel = makeLabelFriendly(this.props.name);
    return d.span({className: "label",
                   style: {backgroundColor: "#" + this.props.color,
                           color: color,
                           cursor: "pointer",
                           opacity: this.props.selected ? 1.0 : 0.5},
                   onClick: this.props.onClick.bind(null, this.props)},
                   friendlyLabel);
  }
});

var filterLabel = function (data) {
  return React.createElement(FilterLabel, data);
};

var WantToWorkWith = React.createClass({
  render: function () {
    return d.span({className: "labels"}, this.props.labels.map(filterLabel));
  }
});

var wantToWorkWith = function (labels, onClick) {
  return React.createElement(WantToWorkWith, {labels: labels.map(function (label) {
    label.onClick = onClick;
    return label;
  })});
};

var repoUrl = function(url) {
    var urlArray = url.split("/");
    return urlArray[urlArray.length - 3];
};

var Issue = React.createClass({

    render: function () {
        return d.li(
            {className: "issue"},
            d.div(
                {},
                "[ ",
                d.a(
                    {
                        className: "issue-link",
                        href: this.props.html_url,
                        title: this.props.title
                    },
                    repoUrl(this.props.html_url) + " " + this.props.number
                ),
                " ] - ",
                d.span(
                    {className: "issue-desc"},
                    this.props.title
                )
            ),
            labels(this.props),
            d.div(
              {className: "time-wrapper"},
              d.span({className: "time"},
              d.i({className: "fa fa-clock-o"}),
              " Last activity: " + moment(this.props.updated_at).fromNow())
            )
        );
    }
});

var issueItem = function (data) {
    return React.createElement(Issue, data);
};

var IssueList = React.createClass({
    getInitialState: function () {
        return {
            limited: true
        };
    },

    getIssuesWithSelectedLabels: function (issues, selectedLabels) {
        var labelNames = selectedLabels.map(function (label) {
            return label.name;
        });

        var filteredIssues = issues.filter(function (issue) {
            for (var i = 0; i < issue.labels.length; i++) {
                if (labelNames.indexOf(issue.labels[i].name) > -1) {
                    return true;
                }
            }
            return false;
        });

        return filteredIssues;
    },

    render: function () {
        if (this.props.loading) {
            return d.div({id: "loading"});
        } else {
            var issues = this.getIssuesWithSelectedLabels(
                this.props.issues, this.props.selectedLabels);

            if (this.state.limited && issues.length > 5) {
                issues = issues.map(issueItem)
                                          .slice(0, 5)
                                          .concat(
                                              d.div(
                                                  {
                                                      className: "view-all",
                                                      onClick: function() {
                                                          this.setState({limited: false});
                                                      }.bind(this)
                                                  },
                                                  "view all..."
                                              )
                                          );
            } else {
                issues = issues.map(issueItem);
            }

            return d.ul(
                {id: "issues"},
                issues
            );
        }
    }
});

var issueList = function (issues, loading, filters) {
    var selectedLabels = filters.filter(function (label) {
        return label.selected;
    });
    return React.createElement(IssueList, {issues: issues, loading: loading, selectedLabels: selectedLabels});
};

var App = React.createClass({
    selectFilter: function (filterProps) {
        for (var i = 0; i < this.state.languageFilters.length; i++) {
            if (this.state.languageFilters[i].name === filterProps.name) {
                break;
            }
        }

        this.setState(function (oldState) {
            var lf = oldState.languageFilters;
            lf[i].selected = !lf[i].selected;
            return {languageFilters: lf};
        });
    },

    componentDidMount: function () {
        getOpenIssues(function (data) {
            this.setState({
                openIssues: data,
                openIssuesLoading: false
            });
        }.bind(this));

        getPotentiallyOpenIssues(function (data) {
            this.setState({
                potentiallyOpenIssues: data,
                potentiallyOpenIssuesLoading: false
            });
        }.bind(this));
    },

    getInitialState: function () {
        return {
            openIssuesLoading: true,
            potentiallyOpenIssuesLoading: true,
            openIssues: [],
            potentiallyOpenIssues: [],
            languageFilters: langLabels
        };
    },

    render: function () {
        return d.div(
            {},
            this.state.openIssuesLoading ? [] : feelingAdventurous(this.state.openIssues),

            this.state.openIssuesLoading ? [] : d.div({className: "language-picker"},
                d.h5({}, "and I want to work with: "),
                wantToWorkWith(this.state.languageFilters, this.selectFilter)),

            d.h2({}, "Open Issues"),
            issueList(this.state.openIssues, this.state.openIssuesLoading, this.state.languageFilters),

            d.h2({}, "Potentially Open Issues"),
            issueList(this.state.potentiallyOpenIssues, this.state.potentiallyOpenIssuesLoading, this.state.languageFilters)
        );
    }
});

React.render(
    React.createElement(App, {}),
    document.getElementById("app")
);
<|endoftext|>