Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
////////////////////////////////////////////////////////////////////////////
// Enables user to set permanent labels and color backgrounds on accounts //
////////////////////////////////////////////////////////////////////////////
// Based on User:Cumbril's IP labeler
// This code is licensed under the CC BY-SA 3.0 License and the GFDL.

// WARNING! CODE IS TERRIBLE
importStylesheet('User:What cat?/userlabeller.css');

// encapsulate
(function(){
	// allow user to override colorCodes in common.js, make sure property '4' stays empty
	if (!window.colorCodes) {
		window.colorCodes = {
			'Red':"#FC8888",	// red
			'Yellow':"#ffe299",	// yellow
			'Green':"#99FF33",	// green
			'Blank':""			// white (must be empty)
		};
	} else {
		window.colorCodes['Blank'] = "";
	}

	var messages = {
		errNoStorage: 'No storage available. Seems that either:\n \
						a) your browser does not support local storage,\n \
						b) local storage is turned off, or\n \
						c) is full.',
		formAddLabel: 'add label',
		formChange  : 'change',
		formLabel   : 'Label',
		formColor   : 'Color',
		errNoName: 'Error: missing username!',
		lblDeleted  : 'Label removed!',
		lblMissing  : 'Missing label!',
		lblChanged  : 'Label changed!',
		lblAdded    : 'Label added!',
		errAction   : 'Error: unrecognized action!'			
	}

	preload([
		'https://upload.wikimedia.org/wikipedia/commons/thumb/f/fb/Yes_check.svg/240px-Yes_check.svg.png',
		'https://upload.wikimedia.org/wikipedia/commons/thumb/a/a2/X_mark.svg/210px-X_mark.svg.png',
		'https://upload.wikimedia.org/wikipedia/et/4/42/Ip_label_form_delete.png',
		'https://upload.wikimedia.org/wikipedia/et/3/38/Ip_label_form_submit.png'
	]);

	window.userlabel = {get:getData, set:setData, del:deleteData}

	// TODO: Make these obviously identifiable as callbacks (12/8/2016)
	window.userlabel.changecolor = function(e) {
		$('#ul-example').css({'background-color':e.target.value});
	}
	window.userlabel.changelabel = function(e) {
		$('#ul-example-label').text(e.target.value);
	}
	// END TODO
	
	window.userlabel.reload = function() { 
		createLinks(true);
		createEvents();
	}
	
	$(document).ready(function(){
		if(typeof(Storage) === "undefined") {
			// if there is no local storage available, display message and do nothing
			throw new Error( messages.errNoStorage );
		} else {
			if (typeof(localStorage['lbls']) === "undefined") { localStorage['lbls'] = "{}"; }
			// define two custom methods for storing objects
			Storage.prototype.setObject = function(key, value) {
				this.setItem(key, JSON.stringify(value));
			};
			Storage.prototype.getObject = function(key) {
				var value = this.getItem(key);
				return value && JSON.parse(value);
			};
			createLinks (false);
			createEvents ();
		}
	});

	$(document).mouseup(function (e) {
		var container = $("div.ul-popup");
		var link = $("a.mw-userlink");
		if (!container.is(e.target) // if the target of the click isn't the container...
			&& container.has(e.target).length === 0) // ... nor a descendant of the container
		{
			container.hide();
			$(link).css({"font-weight": "normal"});
		}
		var fbcontainer = $("div.ul-feedback");
		if (!fbcontainer.is(e.target)
			&& fbcontainer.has(e.target).length === 0)
		{
			fbcontainer.hide();
		}	
	});

	function createLinks( refresh ) {
		var spacer = " ";
		
		if (refresh) {
			$("span.ul-container").remove();
			$("a.mw-userlink").css("background-color","");
		}
		
		$("a.mw-userlink").each(function() {
			// create link after every name; add the name into the link html code
			var username = $(this).text();
			var linkText = messages.formAddLabel;
			
			var dataResult = getData( username );
			var keyExists = dataResult.exists;
			var dataObj = dataResult.obj;
			
			console.log(username,dataResult);
			if ( keyExists ) {
				if (dataObj.label) {
					linkText = dataObj.label;
				} else if (dataObj.color) {
					linkText = messages.formChange;
				}
				if (dataObj.color) {
					$(this).css("background-color", dataObj.color);
				}
			}
			var link = $("<span class='ul-container'></div>").html("<a href='#' class='ul-link' data-name='" + username + "'>"+ linkText + "</a>");
			$(this).after(spacer, link);
		});
	}

	function createEvents () {
		$("a.ul-link").click(function(event) {
			// if the link was clicked ...
			event.preventDefault();
			
			var name = $(this).data("name");
			
			var data = getData( name );
			
			// legacy variables just in case I miss one
			var username = name; var dataResult = data; 
			var keyExists = dataResult.exists;
			var dataObj = dataResult.obj;
					
			// ... make the username bold ...
			var userlink = $(this).parent().prev();
			userlink.css({"font-weight": "bold"});
			if(typeof(dataObj)!=="undefined"){
				dlabel = dataObj.label;
				dcolor = dataObj.color;
			} else {
				dcolor = "";
				dlabel = "";
			}
			var Window = new Morebits.simpleWindow( 600, 400 );
			Window.setScriptName("User label");
			Window.setTitle("Add/edit label for "+name);
						
			var form = new Morebits.quickForm( function(e){
				if(e.target.label === "" && e.target.color === "") {deleteData(name)}
				else{
				setData(name,{label:e.target.label.value,color:e.target.getChecked('color')[0]});
				Window.close();
				createLinks(true);
				createEvents();
				}
			} );//window.userlabel.processForm 

			var nameexample = form.append({type: 'div', label: "If you're seeing this, something has gone wrong", id:'ul-example-container'}); // unless you're reading the source code, then it's fine

			var colorlist = form.append( { type:'field', label:'Colors' } );

			colorlist.append({type: 'radio', name:'color', list: colorsToList(window.colorCodes,dcolor),
					event: function( e ) {
						window.userlabel.changecolor( e );
						e.stopPropagation();
					}
				}
			);

			form.append({type: 'input', name:'label', label:'Label text: ', value: dlabel,
					event: function( e ) {
						window.userlabel.changelabel( e );
						e.stopPropagation();
					}
				}
			);
			form.append( { type:'submit', label:"Set label" } );
			var result = form.render();

			// this is pretty much the only real way to do this seamlessly before showing the form
			result.querySelector('#ul-example-container').innerHTML = $("<div id='ul-example-container'>"+
																			"<span id='ul-example'>"+name+"</span>"+
																			"<div class='ul-container'>"+
																				"<a href='#' id='ul-example-label' data-name='"+name+"'>"+ "Add label" + "</a>"+
																			"</div>"+
																		"</div>").html();

			Window.setContent( result );
			Window.display();

		});
	}
	function colorsToList(colors,select) {
		var list = [];
		// awful terrible hacky for-loop; k is key, v is value, i is iterator value
		for (var i=0;i<Object.keys(window.colorCodes).length;i++){var k=Object.keys(window.colorCodes)[i];var v=window.colorCodes[k];
			list.push({label:k,value:v,style:'background-color:'+v,checked:(v==select)}) // the fact that this actually works is great
		}
		return list
	}
	function getData ( key ) {
		var labelListObj = localStorage.getObject("lbls");
		var labelObj = labelListObj[key]
		var keyExists = ( (typeof labelObj === "undefined" || labelObj === null) ? false : true );

		var dataObj;
		if (keyExists) {
			if(typeof(labelObj.label)==='undefined') {
				var dataObj = {
					label:labelObj.l,
					color:labelObj.c
				};
			} else {
				var dataObj = {
					label:labelObj.label,
					color:labelObj.color
				};
			}
		}	
		return {
				exists: keyExists,
				obj: dataObj
		};  	
	}

	function deleteData ( key ) {
		var labelObj = localStorage.getObject("lbls")
		delete labelObj[key];
		localStorage.setObject("lbls", labelObj);
	}

	function setData( key, dataObj ) {
		var labelObj = localStorage.getObject("lbls")
		var p = { l: dataObj.label, c: dataObj.color }	// the p stands for parsed
		if((p.l === "" || typeof(p.l) !== "string") && (p.c === "" || typeof(p.c) !== "string")) {
			deleteData(key)
		}
		labelObj[key] = p;
		localStorage.setObject("lbls", labelObj);
	}

	function isBlank (str) {
		return (!str || /^\s*$/.test(str));
	}

	function isEmpty(str) {
		return (!str || 0 === str.length);
	}

	function preload( arrayOfImages ) {
		$(arrayOfImages).each(function(){
			$('<img/>')[0].src = this;
		});
	}
	function colorsToList(colors, color) {
		var list = [];
		// awful terrible hacky for-loop; k is key, v is value, i is iterator value
		for (var i=0;i<Object.keys(window.colorCodes).length;i++){var k=Object.keys(window.colorCodes)[i];var v=window.colorCodes[k];
			list.push({label:k,value:v,style:'background-color:'+v,checked:(v===color)}) // i'm surprised this works
		}
		return list
	}
})()