MediaWiki:Invisible characters unveiled.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.
/**
 * Used on [[Commons:User scripts/Invisible charaters]]
 * where it creates a textbox when loaded.
 * When text is entered in that textbox, it shows *all*
 * characters the text consists of including char codes
 * and performs a check against the title blacklist
 * 
 * This script is available under CC-By-SA 3.0, GFDL, GPL (see below),
 * LGPL. Choose the license(s) you like best :-)
 * 
 * jshint valid
 * <nowiki>
 */

/*
 * Copyright (C) 2013 Rainer Rillke
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
 
/*global jQuery:false, mediaWiki:false, importScript:false*/
/*jshint curly:false*/

(function($, mw) {
	'use strict';
	
	if (mw.config.get('wgPageName') !== 'Commons:User_scripts/Invisible_characters') return;
	if (mw.config.get('wgAction') !== 'view') return;
	
	
	if (null === mw.loader.getState('mediawiki.commons.BadTitleRegExps')) mw.loader.implement('mediawiki.commons.BadTitleRegExps', ["//commons.wikimedia.org/w/index.php?action=raw&ctype=text/javascript&title=MediaWiki:BadTitleRegExps.js"]);
	if (null === mw.loader.getState('mediawiki.commons.Unorm')) mw.loader.implement('mediawiki.commons.Unorm', ["//commons.wikimedia.org/w/index.php?action=raw&ctype=text/javascript&title=MediaWiki:Unorm.js"]);

	
	var $target = $('#ComDiag').empty(),
		$loader = $('#com-unveiled-loading'),
		$input  = $('<input type="text" size="100" style="width:99%; padding: 0.5em" class="com-icu-input"/>')
			.appendTo($target),
		$link   = $('<div style="border:1px solid #AAA; background:#EEE; width:99%; padding: 0.5em; overflow:auto"></div>')
			.appendTo($target),
		tableDefaultContent = '<tbody><tr><th>Position</th></tr><tr style="white-space:pre; font-family:Courier,monospace;"><th>Character</th></tr><tr><th>Dec</th></tr><tr><th>Hex</th></tr><tr><th>Hex in byte representation</th></tr></tbody>',
		$outDiv = $('<div style="overflow:auto; border:1px dotted #AAA;"></div>')
			.appendTo($target),
		$outTable = $('<table class="wikitable" style="font-size:.8em">' + tableDefaultContent + '</table>')
			.prependTo($outDiv),
		$outIssuesContainer = $('<div style="text-align:start; font-size:0.8em"></div>')
			.text("Issues:")
			.appendTo($target),
		$outIssues = $('<ul>')
			.css('min-height', '1.5em')
			.appendTo($outIssuesContainer),
		$outNFC = $('<li>')
			.addClass('warning')
			.css('min-height', '1.5em').hide()
			.text("Input contains non-NFC (Normalization Form C) Unicode. This is usually converted to the correct form by MediaWiki but some extensions may struggle with it. It is also possible that previous tests against blacklisted characters were inaccurate due to this. It is recommended checking the above suggested sanitized output against this tool again.")
			.appendTo($outIssuesContainer),
		$outTitleBlacklist = $('<div style="text-align:start; font-size:0.8em"></div>')
			.css('min-height', '1.5em')
			.appendTo($target),
		$outSanitizedContainer = $('<div style="text-align:start; font-size:0.8em"></div>')
			.text("Sanitized title (without bad characters, normalized): ")
			.appendTo($target),
		$outSanitized = $('<code>')
			.css('min-height', '1.5em')
			.appendTo($outSanitizedContainer),
		onChange, to, handleChange, handleTB, hasDefaultContent, oldInputVal, pad, amendCell, addIssue;
	
	handleTB = function() {
		var t = $.ucFirst($input.val());
		t = /^File:/.test(t) ? t : 'File:' + t;
		
		mw.libs.commons.api.$query({
			'action': 'titleblacklist',
			'tbtitle': t,
			'tbaction': 'create'
		}).done(function(r) {
			if (!r || !r.titleblacklist || !r.titleblacklist.result) throw new Error('MediaWiki:Invisible characters unveiled.js: r.titleblacklist is undefined.');
			if ('blacklisted' === r.titleblacklist.result) {
				$outTitleBlacklist.text("Titleblacklist: Blacklisted! Matches: ");
				$('<code>').text(r.titleblacklist.line).appendTo($outTitleBlacklist);
				$('<div>').text(r.titleblacklist.message).appendTo($outTitleBlacklist);
				$('<div>').text(r.titleblacklist.reason).appendTo($outTitleBlacklist);
			} else {
				$outTitleBlacklist.text("Titleblacklist: Ok.");
			}
		});
	};
	pad = function(st, len)  {
		return new Array(len + 1 - st.length).join('0') + st;
	};
	amendCell = function($cell, result) {
		if (result === true) return $cell;
		$cell.css('background-color', '#EFAAAB');
		$cell.attr('title', result);
		return $cell;
	};
	addIssue = function(issue, pos, $cell) {
		var $li = $('<li>'),
			$a = $('<a>')
				.attr({
					'href': '#com-hcr-' + pos
				})
				.text(issue)
				.click(function(e) {
					e.preventDefault();
					
					var $divParent = $cell.closest('div');
					$divParent.scrollLeft( $divParent.scrollLeft() + $cell.position().left - 50 );
					$cell.effect('highlight');
				})
				.appendTo($li);
				
		$li.appendTo($outIssues);
	};
	handleChange = function() {
		var t = $input.val(),
			l = t.length,
			$rs = $outTable.find('tr'),
			$rP = $rs.eq(0),
			$rC = $rs.eq(1),
			$rDec = $rs.eq(2),
			$rHex = $rs.eq(3),
			$rDecoded = $rs.eq(4),
			badCharLibHere = !!mw.libs.badTitleRegExps,
			sanitizedName = '',
			normalizedName = '',
			i, c, cc, u, ccEnc, cOk, $cell;

		for (i = 0; i < l; ++i) {
			cOk = true;
			
			c = t.charAt(i);
			
			if (badCharLibHere) {
				$.each(mw.libs.badTitleRegExps, function(groupName, regExpArray) {
					$.each(regExpArray, function(i, re) {
						return ( cOk = !re.test(c) ); // Ugly, ugly, ugly
					});
					if (!cOk) {
						cOk = groupName;
						return false;
					}
				});
			}
			
			$cell = amendCell( $('<td>'), cOk ).attr('id', 'com-hcr-' + i).text(i).appendTo($rP);
			amendCell( $('<td>'), cOk ).text(c).appendTo($rC);
			
			cc = t.charCodeAt(i);
			u = pad(cc.toString(16), 4);
			amendCell( $('<td>'), cOk ).text(cc).appendTo($rDec);
			amendCell( $('<td>'), cOk ).append($('<a>').attr('href', 'http://unicode.org/cldr/utility/character.jsp?' + $.param({ a: u })).text('U+' + u)).appendTo($rHex);
			
			// UTF-8 decoded
			ccEnc = encodeURIComponent(c).split('%');
			ccEnc = $.trim(ccEnc.join(' 0x'));
			$('<td>').text(ccEnc.length > 1 ? ccEnc : '0x' + cc.toString(16)).appendTo($rDecoded);
			
			// Append Issue
			if (cOk !== true) {
				addIssue(cOk, i, $cell);
			} else {
				sanitizedName += c;
			}
		}
		hasDefaultContent = false;
		
		location.hash = '#val/' + t;
		$('<a>').attr('href', location.href).text(location.href).appendTo( $link.empty().text("Result link: ") );
		sanitizedName = sanitizedName.replace(/^[Ff]ile:/, '');
		if (badCharLibHere) {
			$.each(mw.libs.badTitleRegExps, function(groupName, regExpArray) {
				$.each(regExpArray, function(i, re) {
					sanitizedName = sanitizedName.replace(re, '');
				});
			});
		}

		// Should run before checking for issues in theory ...
		normalizedName = sanitizedName.normalize('NFC'); 

		$outSanitized.text('File:' + normalizedName);

		if (normalizedName !== sanitizedName) {
			$outNFC.show();
		}
		mw.loader.using('ext.gadget.libAPI', handleTB);
	};
	onChange = function() {
		var t = $input.val();
		if (t === oldInputVal) return;
		oldInputVal = t;
		
		if (to) clearTimeout(to);
		to = setTimeout(handleChange, 800);
		if (hasDefaultContent) return;
		$outTable.html(tableDefaultContent);
		$outTitleBlacklist.text("Titleblacklist: ?");
		$outIssues.empty();
		$outSanitized.empty();
		$outNFC.hide();
		hasDefaultContent = true;
	};
			
	$input.on('keyup change input', onChange);

	$loader.show();

	mw.loader.using(['jquery.ui', 'mediawiki.commons.BadTitleRegExps', 'mediawiki.commons.Unorm'], function() {
		$input.attr('placeholder', "Enter filename to test here.").effect('highlight');
		var h = (location.hash || '').replace(/^#val\//, '');
		try {
			h = decodeURIComponent(h);
		} catch(ex) {}
		if (h) $input.val(h).change();
		$loader.hide();
	});
}(jQuery, mediaWiki));

// </nowiki>