MediaWiki:FileContentsByBot.js

From Wikimedia Commons, the free media repository
Jump to navigation Jump to search
Note: After saving, you have to bypass your browser's cache to see the changes. Internet Explorer: press Ctrl-F5, Mozilla: hold down Shift while clicking Reload (or press Ctrl-Shift-R), Opera/Konqueror: press F5, Safari: hold down Shift + Alt while clicking Reload, Chrome: hold down Shift while clicking Reload.
/**
 * - FileContentsByBot - 
 * Script to transform, read and show hidden information by bot
 * used by on [[Template:FileContentsByBot]]
 *
 * (c) Copyright 2012 by Rainer Rillke
 * Attribution of redistributed code must be in 
 * EULA or UI.
 * Use it for good, not for evil.
 *
 * @rev 3 (2012-08-01)
 * @rev 4 (2012-11-12) Introduced API
 * @author Rillke, 2012
 */

// Invoke automated jsHint-validation on save: A feature on WikimediaCommons
// Interested? See [[:commons:MediaWiki:JSValidator.js]].
 
/*global jQuery:false, mediaWiki:false*/
/*jshint curly:false*/
(function($, mw, storeKey) {
'use strict';

if (6 !== mw.config.get('wgNamespaceNumber') && 'User:DrTrigon/User:DrTrigonBot/logging' !== mw.config.get('wgPageName')) return;

var defaultGroups = {},
	groups = $.extend(true, groups, defaultGroups),
	groupDefinitionPrefix = '.bot-group-',
	groupDefinitionMatrix = { 
		key: 'name',
		toolTip: 'toolTip',
		border: 'border',
		multiple: 'multiple',
		children: 'children'
	},
	getGroupParams = function($g) {
		var gps = {};
		$.each(groupDefinitionMatrix, function(key, def) {
			gps[key] = $g.children(groupDefinitionPrefix + def).text();
		});
		return gps;
	},
	readGroups = function() {
		var $groups = $('.bot-groupdefinition');
		$groups.each(function(i, $g) {
			$g = $($g);
			var gps = getGroupParams($g);
			if (!gps.key) return;
			groups[gps.key] = gps;
			if (gps.children) {
				gps.children = {};
				var $childGroups = $groups.children('.bot-child-groupdefinition');
				$childGroups.each(function(i, $gc) {
					$gc = $($gc);
					var gpsc = getGroupParams($gc);
					if (!gpsc.key) return;
					gps.children[gpsc.key] = gpsc;
				});
			}
		});
	};
	
// Template has specified "groups".
// Extract them from HTML now
// For more information, please confer to 
// https://commons.wikimedia.org/wiki/Template:FileContentsByBot/API
readGroups();

// Show the template/ infos, Reference to the file-div
var $table = $('#FileContentsByBot').show(),
	$file = $('#file').find('a > img').parents('div:first'),
	options = {},
	optionCheckboxes = {},
	$optButtonContainer = $('<div>', { 
		css: { 
			position: 'absolute', 
			bottom: '0px', 
			right: '0px',
			margin: '5px',
			padding: '5px',
			border: '1px dotted black',
			'background-color': 'rgba(220,220,220,0.7)',
			overflow: 'hidden'
		} 
	}),
	$hoverPane = $('<div>', { 
		css: {
			padding: '3px',
			background: 'rgba(100,25,200,0.4)',
			color: 'white',
			'min-height': '10px',
			'text-shadow': '0 1px 0 rgba(150,150,150,0.5)',
			cursor: 'pointer'
		},
		text: " Save…"
	}).appendTo($optButtonContainer),
	$saveGroup = $('<fieldset>').append($('<legend>').text("Save selection into the")).appendTo($optButtonContainer),
	$removeGroup = $('<fieldset>').append($('<legend>').text("Remove saved selection from")).hide().appendTo($optButtonContainer),
	$saveAccount = $('<button>', { 
		title: "Save your selection in your user namespace",
		text: "Account"
	}).appendTo($saveGroup),
	$saveBrowser = $('<button>', { 
		title: "Save your selection in your web browser",
		text: "Browser"
	}).appendTo($saveGroup),
	prefs,
	remoteOption;
	
// Hide the reminder/ hint that there are infos
$('#FileContentsByBot-Advertiser').remove();
mw.util.addCSS(
	'div.growlUI { background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAACfFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgBUAAAAAAAAAAAAAAAAAAAAAAAALdRwTgSMAAAANeR8ReyAUgCMMfhweiC0XgyZgsmgPgRxhtWcZgyhRpVc6mkVMplUahCkYgicMdBYUgCMOeiJ5w3oVgTEahCkTgSAdhyotkTlbr2MMdhsWgCWJ04sUfiMdhyyCyoQNeR8RfSBnt20zkT4Udi1wvHUkijB1v3cchisJexwNeSFIolFxu3QTfSIpiTRVq10tjzkUfiMniTMWfiV1v3kNdx9ksmkOfCAAgAAVfSRksml7w30ehi0QfiBLoVMjiTFVp1sNexo1k0AQeh0ZgSgNfSIkiDJ2vnhxvXRHn1IQcCAYgidtt3AtjzlzvXMSfiEJGgATeyJZqV84lEMIVBVhs2lCnE12vnkcVgAehi1BnUo5l0QObhlAmEhtt28Pcx15wXxfsWUVgSQAbgAVgSR8xnwLVRYhhS8afiYFGQkTfSJisGdAsEBPv09Lu0tJuUlWxlZUxFRGuEZYyFhCskJEtkQwojAzozN+xoA5qzl3wXdQuFB2xnRQulA6qjpfy19vzW9JtEZYuFhStFJBr0F6xnqK1Ipculxbu1s7rTtvx29703uI0ohOtE41pzV5w3qX25dKrUeEzoRx0XF5wXlPtU9KtEpawFp1wXN0xHJatFpKukqG0IRkxmR1xXN503mF1YVNvU1lvWVxx3FRwVFhyWF8zHxitmJLt0tlv2VbxlhfvV9SsFI8rDx5xXd3w3c+rj43qTdlx2U/qz+J04k2pjYuoC5ftV9sxmqN1438JI1wAAAAhnRSTlMAAxAYCggNHAwBIBQTBiMEFgQHCxonKhsYkiwmPHQrxanxRPG77Obqpq0WjEn8DMFQwt7vKaD+WtH+E1j14w313PzRGyjr+Xbq7eTo3dD7J/VZBq70/NJ+7tTwTeY/wSfb/vnrEMf64v6qCqXz6CLv5/4D1Ovnfe78nPr0rges/l7s1TVe8XMkZZgAAAGWSURBVHhexdBTkyUxGIDhtA6NsW3btte2bRt9rLFtG2vb+4e2u+ZiqvuczO0+F0mq3lTlq4D/Jdrfd90eGjjq5+UJ4LwTWyb+lgTzYd0taMVoNA6crQIQUd3vdDrdk1g5pLvHter1+pdZSQAi+ccLyuB22Ahp6Utqtfqbz25Il2cvPqWMxwAIjwMzGo3mT0A8pEcefaXVahdSQlghNSx89eAS0WQwGPrPOTP7hYsjlzzo7nz5udlsfrPZBTAl3GhovHnrNgB37raZTKbljExmf/S4lyTf5+Tm5Re8JUlyurAIMBXPfaFM1ZaW1ZLU4VM5+5MrKtv7KJ1d53uobai6BrBs2LjpWR3tI7183gJsbd022/x1VccOd2DHzl3134dpP+v3ALv27ts/ZqXMH3QD9h06fOSDxWI9dhzS+ciJk6de//p9+owSJxQIn1kRMc5BRbIrVyevXXdSYTIRysHFyFqXEjxUiHEFknv3Hzx0dJAIuJgQ5RFS2wsOjq6ujAusJzCVE5fLeIIxpILAlTwOh2cz5Pr+AdZMhGvAEGFPAAAAAElFTkSuQmCC") no-repeat 10px 10px } div.growlUI h1, div.growlUI h2 {color: white; padding: 5px 5px 5px 75px; text-align: left }'
);
	
var process = function() {
	$optButtonContainer.appendTo($table.parent().css('position', 'relative'));
	
	var group,
		// padding: 3*2
		hoverPaneHeight = $hoverPane.height() + 8,
		// padding: 5*2
		buttonContainerHeight = $optButtonContainer.height() + 10;
	
	$optButtonContainer.height(hoverPaneHeight).hide();
	
	var __onOver = function() { 
		$optButtonContainer.stop(true).animate({ height: buttonContainerHeight }, function() { $optButtonContainer.css('height', 'auto'); }); 
	};
	// Event handlers for the UI
	$optButtonContainer.hoverIntent({
		interval: 300,
		over: __onOver,
		out: function() { $optButtonContainer.stop(true).animate({ height: hoverPaneHeight }); },
		timeout: 800
	});
	$hoverPane.prepend($.createIcon('ui-icon-close').css('cursor', 'pointer').attr('title', "close button pane").click(function() {
		// Remove event handler to prevent stopping the removal
		$optButtonContainer.off();
		$optButtonContainer.slideUp();
	})).click(__onOver);
	
	if ($table.length !== 1 || $file.length !== 1) {
		$('.hproduct-by-bot > table').show();
		return;
	}
	
	// Prepare options
	for (group in groups) {
		if (groups.hasOwnProperty(group)) {
			options[group] = true;
		}
	}
	// Get options from local storage
	prefs = $.jStorage.get(storeKey);
	if (prefs) {
		$.extend(options, prefs);
		$('<button>', { text: "Browser", title: "Remove saved preferences from browser" }).click(function() {
			$.jStorage.deleteKey(storeKey);
			$(this).fadeOut();
		}).button().appendTo($removeGroup.show());
	}
	// Get option from user JS
	remoteOption = mw.libs.settingsManager.option( { 
		saveAt: storeKey,
		editSummary: '[[MediaWiki:FileContentsByBot.js]] is updating preferences'
	} );
	remoteOption.fetchValue(function(remoteValue) {
		if (!remoteValue) return;
		$.extend(options, remoteValue);
		for (var key in optionCheckboxes) {
			if (optionCheckboxes.hasOwnProperty(key)) {
				if (!options[key]) {
					optionCheckboxes[key][0].checked = false;
					optionCheckboxes[key].triggerHandler('change');
				}
			}
		}
		$('<button>', { text: "Account", title: "Remove saved preferences from account" }).click(function() {
			var $btn = $(this),
				oldText = $btn.text();
				
			$btn.button({ disabled: true, label: "Removing…" });
			remoteOption.setValue('').save().done(function() {
				$btn.button({ disabled: false, label: oldText });
				$btn.fadeOut();
			});
		}).button().appendTo($removeGroup.show());
	});
	
	// Show little face-icons
	var $faceIcons = $('div.bot-Faces-Position-icon');
	$faceIcons.each(function (i, el) {
		var $el = $(el), // turn DOM-node into jQuery-object (array-like-object)
			$scaleDiv = $el.children('div:first'), // find scale-div
			$img = $scaleDiv.find('a.image > img'), // find image
			ratio = parseFloat($scaleDiv.attr('class'), 10); // Making explicit we want a float

		// Prevent running twice
		if ($el.is(':visible')) return false;
		// Scaling
		$img.width($img.width() * ratio);
		$img.height($img.height() * ratio);
		$el.find('a').click(function(e) {
			e.preventDefault();
		});
	}).show(); // Finally show the image
	
	// Retrieve scale
	var dimX = parseInt($table.find('#FileContentsByBot-DimX').text(), 10),
		dimY = parseInt($table.find('#FileContentsByBot-DimY').text(), 10),
		wRatio = $file.find('img').width()/dimX,
		hRatio = $file.find('img').height()/dimY;
		
	mw.util.addCSS('.botHighlighted { background: #cce !important; }');
	$file.css('position', 'relative');
	
	var _noteHoverIn = function(e) {
		var $note = $(this),
			$accociated = $note.data('botData_highlight');
			
		$note.children('div').css('border', 'none').css('background', $note.data('botData_color')).stop(true).fadeTo(0, 0).fadeTo(500, 0.5);
		if ($accociated) $accociated.css('background', '#dbb');
	};
	var _noteHoverOut = function(e) {
		var $note = $(this),
			$accociated = $note.data('botData_highlight');
			
		$note.children('div').css('border', '2px solid #555').css('background', 'none').stop(true).fadeTo(0, 1);
		if ($accociated) $accociated.css('background', 'none');
	};
	var _noteClick = function(e) {
		var $el = $(this),
			$accociated = $el.data('botData_highlight'),
			jumpTo = $el.data('botData_jumpTo') || '#file',
			removeOnly = $el.hasClass('botHighlighted');
			
		$('.botHighlighted').removeClass('botHighlighted');
		if (removeOnly) return;
		$el.addClass('botHighlighted');
		if ($accociated) $accociated.addClass('botHighlighted');
		window.location.hash = jumpTo;
	};
	var _switchNotes = function(e) {
		var $chb = $(this),
			method = this.checked ? 'show' : 'hide';
		$.each($chb.data('botData_notes_affected'), function(i, $note) {
			$note[method]();
		});
		options[$chb.data('bot_option')] = this.checked;
	};
	var legends = {}, hasSave;
	var addSaveButton = function() {
		if (hasSave) return;
		hasSave = true;
		
		$saveAccount.button({ disabled: mw.user.isAnon() });
		$saveBrowser.button().appendTo($saveGroup);
		$optButtonContainer.show();
		
		$saveAccount.click(function() {
			var $btn = $(this),
				oldText = $btn.text();
				
			$btn.button({ disabled: true, label: "Saving…" });
			remoteOption.setValue(options).save().done(function() {
				$btn.button({ disabled: false, label: oldText });
				$.growlUI("Saved", "Your selection was saved in your user namespace and will be automatically applied in future");
			});
		});
		$saveBrowser.click(function() {
			$.jStorage.set(storeKey, options);
			$.growlUI("Saved", "Your selection was saved to the current browser and will be automatically applied in future");
		});
	};
	var botImageNote = function(x, y, w, h, content, color, $colorNode, $toHighlight, toJumpTo, border, background, $legend, key) {
		if (!w || !h) return;
		var _getColor = function() {
			return Math.round(Math.random()*255);
		};
		if (!color && $colorNode) {
			color = 'rgb(' + _getColor() + ',' + _getColor() + ',' + _getColor() + ')';
		}
		if (!$toHighlight && $colorNode) $toHighlight = $colorNode;
		if (!color) color = 'yellow';
		if (!border) border = '2px solid';
		if (!background) background = 'none';
		$colorNode.css('border', border + ' ' + color);
		
		var pos = 'top:' + Math.round(y*hRatio) + 'px; left:' + Math.round(x*wRatio) + 'px; height:' + Math.round(h*hRatio) + 'px; width:' + Math.round(w*wRatio) + 'px';
		var $note = $('<div>', { style: 'position:absolute; ' + pos + '; background:' + background + '; border:' + border + ' ' + color + '; cursor:pointer;' }).appendTo($file);
		
		var $content = $('<div>', { style: 'height:100%; width:100%; border:solid 1px #555;', title: content }).appendTo($note);
		
		$note.data('botData_color', color).data('botData_highlight', $toHighlight).hover(_noteHoverIn, _noteHoverOut);
		
		if ($toHighlight) {
			$toHighlight.css('cursor', 'pointer');
			$toHighlight.data('botData_highlight', $note);
			$note.data('botData_jumpTo', toJumpTo);
			$toHighlight.click(_noteClick);
			$toHighlight.attr('title', 'click to highlight in image');
			$note.click(_noteClick);
		}
		if ($legend) {
			var s = $legend.selector,
				checked = options[key] ? 'checked="checked"' : '';
				
			if (!checked) $note.hide();
			if (!legends[s]) {
				legends[s] = [$note];
				optionCheckboxes[key] = $('<input type="checkbox" ' + checked + ' title="Click to toggle image notes">')
					.change(_switchNotes).data('bot_option', key).data('botData_notes_affected', legends[s]).appendTo($legend);
					
			} else {
				legends[s].push($note);
			}
		}
		// Save-button
		addSaveButton();
		return $note;
	};
	
	$.each(groups, function(groupName, groupProperties) {
		var pfx = '.bot-' + groupName,
			groupSelector = pfx,
			positionSelector = pfx + '-Position',
			posLeft = pfx + '-Position-left',
			posTop = pfx + '-Position-top',
			posWidth = pfx + '-Position-width',
			posHeight = pfx + '-Position-height',
			idSelector = pfx + '-ID',
			colorSelector = pfx + '-Color',
			rgbSelector = pfx + '-RGB',
			$wholeSection = $table.find('#FileContentsByBot-' + groupName),
			$legend = $wholeSection.find(pfx + '-legend'),
			hashLink = '#FileContentsByBot-' + groupName;
		
		$wholeSection.find(groupSelector).each(function(i, el) {
			var $el = $(el),
				$pos = $el.find(positionSelector),
				$rgb = $el.find(rgbSelector);
				
			if (0 === $pos.length) return;
			botImageNote(
				$pos.find(posLeft).text(),
				$pos.find(posTop).text(),
				$pos.find(posWidth).text(),
				$pos.find(posHeight).text(),
				groupProperties.toolTip
					.replace('%ID%', $el.find(idSelector).text())
					.replace('%COLOR%', $el.find(colorSelector).text()),
				$rgb.length ? 'rgb' + $rgb.text() : '',
				$pos,
				null,
				hashLink,
				groupProperties.border,
				null,
				$legend,
				groupName
			);
			
			// Prevent undefined-error
			if (!groupProperties.children) groupProperties.children = [];
			
			// Let's do not overcomplicate it with recursive children-processing
			$.each(groupProperties.children, function(groupName_c, groupProperties_c) {
				groupProperties_c.multiple = groupProperties_c.multiple || 1;
				var pfx_c = '.bot-' + groupName_c,
					ms = groupProperties_c.multiple > 1 ? '-%NO%' : '',
					groupSelector_c = pfx_c,
					positionSelector_c = pfx_c + '-Position',
					posLeft_c = pfx_c + ms + '-Position-left',
					posTop_c = pfx_c + ms + '-Position-top',
					posWidth_c = pfx_c + ms + '-Position-width',
					posHeight_c = pfx_c + ms + '-Position-height',
					colorSelector_c = pfx_c + '-Color',
					rgbSelector_c = pfx_c + 'RGB';
					
				$el.find(groupSelector_c).each(function(c_number, child) {
					var $child = $(child),
						$rgb = $child.find(rgbSelector);
						
					for (var counter = 1; counter < groupProperties_c.multiple + 1; counter++) {
						botImageNote(
							$child.find(posLeft_c.replace('%NO%', counter)).text(),
							$child.find(posTop_c.replace('%NO%', counter)).text(),
							$child.find(posWidth_c.replace('%NO%', counter)).text(),
							$child.find(posHeight_c.replace('%NO%', counter)).text(),
							groupProperties_c.toolTip
								.replace('%ID%', $el.find(idSelector).text())
								.replace('%NO%', counter)
								.replace('%COLOR%', $child.find(colorSelector).text()),
							$rgb.length ? 'rgb' + $rgb.text() : '',
							$child,
							null,
							hashLink,
							groupProperties_c.border,
							null,
							$legend,
							groupName
						);
					}
				});
			});
		});
	});
	$(document).triggerHandler('scriptLoaded', ['FileContentsByBot']);
};

$(function() {
	mw.loader.using([
		'mediawiki.user', 
		'jquery.hoverIntent', 
		'jquery.jStorage', 
		'ext.gadget.SettingsManager', 
		'ext.gadget.jquery.blockUI', 
		'ext.gadget.libJQuery'
	], process);
});
})(jQuery, mediaWiki, 'FileContentsByBotOpt');