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 to anonymous IP accounts //
/////////////////////////////////////////////////////////////////////////////////////////

importStylesheet('User:Kaniivel/IPLabeller.css');

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

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',
	errMissingIP: 'Error: missing IP address!',
	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'
]);

$(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 {
		// 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.ip-label-popup");
	var link = $("a.mw-anonuserlink");
	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.ip-label-feedback");
	if (!fbcontainer.is(e.target)
		&& fbcontainer.has(e.target).length === 0)
	{
		fbcontainer.hide();
	}	
});

function createLinks( refresh ) {
	var spacer = " ";
	
	if (refresh) {
		$("div.ip-label-container").remove();
		$("a.mw-anonuserlink").css("background-color","");
	}
	
	$("a.mw-anonuserlink").each(function() {
		// create link after every IP; add the IP into the link html code
		var ip = $(this).text();
		var linkText = messages.formAddLabel;
		
		var dataResult = getData( ip );
		var keyExists = dataResult.keyExists;
		var dataObj = dataResult.dataObj;
			
		if ( keyExists ) {
			if (dataObj.label) {
				linkText = dataObj.label;
			} else if (dataObj.color) {
				linkText = messages.formChange;
			}
			if (dataObj.color) {
				$(this).css("background-color", colorCodes[ dataObj.color ])
			}
		}
		var link = $("<div class='ip-label-container'></div>").html("<a href='#' class='ip-label-link' _ip='" + ip + "'>"+ linkText + "</a>");
		$(this).after(spacer, link);
	});
}

function createEvents () {
	$("a.ip-label-link").click(function(event) {
		// if the link was clicked ...
		event.preventDefault();
		
		var ip = $(this).attr("_ip");
		
		var dataResult = getData( ip );
		var keyExists = dataResult.keyExists;
		var dataObj = dataResult.dataObj;
				
		// ... make the IP address go bold ...
		var iplink = $(this).parent().prev();
		iplink.css({"font-weight": "bold"});

		// ... create div with input form ...
		$(this).after("<div class='ip-label-popup'>" + messages.formLabel + ": <form action='' class='ip-label-popup-form'><input type='hidden' name='ip_address' value='" + ip + "'><input type='text' name='label' autocomplete='off' size='60' value='"+ ( (keyExists && dataObj.label) ? dataObj.label : "") + "' maxlength='60' class='ip-label-popup-input'><div class='ip-label-popup-bottom'>" + messages.formColor + ":<input type='radio' name='color' value=1" + (keyExists && dataObj.color == 1 ? " checked" : "") + "><div class='ip-label-popup-bottom-red'></div><input type='radio' name='color' value=2" + (keyExists && dataObj.color == 2 ? " checked" : "") + "><div class='ip-label-popup-bottom-yellow'></div><input type='radio' name='color' value=3" + (keyExists && dataObj.color == 3 ? " checked" : "") + "><div class='ip-label-popup-bottom-green'></div><input type='radio' name='color' value=4" + (keyExists && dataObj.color == 4 ? " checked" : "") + "><div class='ip-label-popup-bottom-white'></div><div class='ip-label-submit-buttons'><div class='ip-label-form-delete'></div> <div class='ip-label-form-submit'></div></div></div></form></div>");
		
		// this is the div created in the previous statement 
		var popUpDiv = $(this).next();
		
		// set focus on input field
		$(popUpDiv).find("input.ip-label-popup-input").focus();
		
		// set up submit event for the form (we can have more than one way to submit the form) 
		$(popUpDiv).find("form.ip-label-popup-form").submit(function(event) {
			event.preventDefault();
			var data = $(this).serializeArray();
			var formObj = {};
			$.each(data, function(i, val) {
				formObj[val.name] = val.value;
			});
			// pass container div along with the object holding form data to the processor function  
			processForm(popUpDiv, formObj);
		});
		
		// set up click event for the form + button
		$(popUpDiv).find(".ip-label-form-submit").on("click", function(){
			var form = $(this).closest("form.ip-label-popup-form");
			var input = $("<input>").attr("type", "hidden").attr("name", "action").val("add");
			$(form).append($(input));
			$(form).submit();
			iplink.css({"font-weight": "normal"});
		});
		
		// set up click event for the form - button
		$(popUpDiv).find(".ip-label-form-delete").on("click", function(){
			var form = $(this).closest("form.ip-label-popup-form");
			var input = $("<input>").attr("type", "hidden").attr("name", "action").val("del");
			$(form).append($(input));
			$(form).submit();
			iplink.css({"font-weight": "normal"});
		});
	});
}

function processForm (popUpDiv, formObj) {
	var fb_msg, isOK;

	if ( !formObj.ip_address ) {
		// if somehow there is no IP address, call showError function
		showError( popUpDiv, messages.errMissingIP );
	}

	// the key for storage is IP address
	var ip = formObj.ip_address;

	// check if the key already exists in storage to set up flag for later use
	
	var dataResult = getData( ip );
	var keyExists = dataResult.keyExists;
			
	if ( formObj.action == "add" ) {
		if ( isBlank(formObj.label) && ( isEmpty(formObj.color) || formObj.color == 4 ) ) {
			// if ip label is empty or whitespace and color is undefined or white then delete the entry
			deleteData( ip );
			if (keyExists) {
				var fb_msg = messages.lblDeleted;
				var isOK = true;
			} else {
				var fb_msg = messages.lblMissing;
				var isOK = false;
			}
			showMessage (popUpDiv, fb_msg, isOK);
		} else {
			// the form has data to save
			if ( isEmpty (formObj.color) ) 
			{ 
				formObj.color = 4;
			}
			var newLabel = {
				label:$.trim(formObj.label),
				color:formObj.color
			};
			setData( ip, newLabel );
			if (keyExists) {
				var fb_msg = messages.lblChanged;
				var isOK = true;		
			} else {
				var fb_msg = messages.lblAdded;
				var isOK = true;
			}
			showMessage (popUpDiv, fb_msg, isOK);
		}
	} else if ( formObj.action == "del" ) {
		// delete was clicked, do delete
		deleteData ( ip );
		if (keyExists) {
			var fb_msg = messages.lblDeleted;
			var isOK = true;		
		} else {
			var fb_msg = messages.lblMissing;
			var isOK = false;
		}
		showMessage (popUpDiv, fb_msg, isOK);
	} else {
		showError( popUpDiv, messages.errAction );
	}

	popUpDiv.remove();
}

function showError (div, errormsg) {
	// show error message, destroy popup div and exit
	throw new Error( errormsg );
	$(div).remove();
}

function showMessage (div, fb_msg, isOK) {
	// show feedback message and hide/destroy popup divs
	// call refresh list function afterwards
	
	$(div).hide();
	
	var img_Y = 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/fb/Yes_check.svg/240px-Yes_check.svg.png';
	var img_X = 'https://upload.wikimedia.org/wikipedia/commons/thumb/a/a2/X_mark.svg/210px-X_mark.svg.png';
	
	// create div box for the message
	var msg_div = $("<div class='ip-label-feedback'><div class='ip-label-feedback-inner'><img src='" + (isOK ? img_Y : img_X) + "'><div class='ip-label-feedback-inner-message'>" + fb_msg + "</div></div></div>");
	
	$(div).after(msg_div);
	
	msg_div.delay( 1100 ).fadeOut( 1000, function(){
		msg_div.remove();
		$(div).remove();
		createLinks (true);
		createEvents ();
    });
}

function getData ( key ) {
	key = "lbl_" + key;
	var labelObj = localStorage.getObject( key );
	var keyExists = ( (typeof labelObj === undefined || labelObj === null) ? false : true );

	var dataObj;
	if (keyExists) {
		var dataObj = {
			label:labelObj.l,
			color:labelObj.c
		};
	}	
	return {
			keyExists: keyExists,
			dataObj: dataObj
	};  	
}

function deleteData ( key ) {
	key = "lbl_" + key;
	localStorage.removeItem( key );
}

function setData( key, dataObj ) {
	key = "lbl_" + key;
	var labelObject = { l: dataObj.label, c: dataObj.color };	
	localStorage.setObject(key, labelObject);
}

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;
    });
}