MediaWiki:Gadget-DelReqHandler.js

From Wikipedia

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
//<source lang="javascript">
/*
  Support for quick deletions and closing of deletion requests at the Commons.
 
  Authors: [[User:Lupo]], October 2007 - January 2008,
           [[User:DieBuche]], February 2011

  License: Quadruple licensed GFDL, GPL, LGPL and Creative Commons Attribution 3.0 (CC-BY-3.0)
 
  Choose whichever license of these you like best :-)

  IE not supported
*/
//Guard against double inclusions
//Enable the whole shebang only for sysops.
if (typeof(DelReqHandler) == 'undefined' && mw.config.get('wgUserGroups').join(' ').indexOf('sysop') >= 0) {

var DelReqHandler = {

/*------------------------------------------------------------------------------------------
Deletion request closing: add "[del]" and "[keep]" links to the left of the section edit
links of a deletion request. [del] and [keep] prompt for an (optional) reason, then 
add "delh" and "delf" with "Deleted." or "Kept." plus the reason and signature (four tildes).

Links are added to every non-deleted image mentioned on a deletion request page. The "[del]" link
triggers deletion (auto-completed!) of the image, with a deletion summary linking to the
deletion request. If the image has a talk page, it is deleted as well. The "[keep]" link
automatically removes the "delete" template from the image page and adds the "kept" template
to the image talk page, both linking back to the deletion request.
   ------------------------------------------------------------------------------------------*/
running: [],
parse: function () {
	var $content = $('#bodyContent, #mw_contentholder');
	if (!$content.length) return;
	mw.util.addCSS( '.reqHandlerLinks {font-size: 85%;}');
	$h3s = $content.find('h3');

	$h3s.each(function () {
		var $t = $(this);
		var unclosed = ($t.parents('.delh').length < 1);
		var discussion = $t.nextUntil('h3, .printfooter, .delh');
		var wholeDiscussion = discussion.add($t);

		var editLink = $t.find('span.editsection a').eq(0);
		var headLink = $t.find('span.mw-headline a').eq(0);

		var title = (headLink.length && !headLink.is('.new')) ? DelReqHandler.titleFromHref(headLink.attr('href')) : "";

		var archiveRegex = /Commons:Deletion_requests\/20\d\d\//;
		var linkRegex = /Commons:Deletion_requests\/.*?&section=(T-){0,1}1$/;

		var requestHref = editLink.attr('href');
		if (archiveRegex.test(requestHref) || !linkRegex.test(requestHref)) return true;
		// It's really an edit lk to a deletion request subpage, and not a section
		// edit for a daily subpage or something else
		var requestPage = DelReqHandler.titleFromHref(requestHref);

		if (unclosed) DelReqHandler.addLinks(requestPage, editLink, title, true, wholeDiscussion);

		var links = discussion.find('a');
		links = links.add(headLink).not('.new');
		links.each(function () {
			var href = this.href;
			var title = DelReqHandler.titleFromHref(href);
			if (title.indexOf('File:') == 0 && title.indexOf('/') < 0) {
				//We have an image link
				DelReqHandler.addLinks(requestPage, $(this), title, false, wholeDiscussion);
			}
		});
	});
},
titleFromHref: function (href) {
	if (!href) return "";
	if (mw.util.getParamValue('title', href) != null) return mw.util.getParamValue('title', href).replace(/_/g, ' ');
	var prefix = wgArticlePath.replace('$1', "");
	// Fully expanded URL?
	if (href.indexOf(prefix) != 0) prefix = mw.config.get('wgServer') + prefix;
	if (href.indexOf(prefix) != 0 && prefix.indexOf('//') == 0) prefix = document.location.protocol + prefix; // protocol-relative wgServer?
	if (href.indexOf(prefix) == 0) return decodeURIComponent(href.substring(prefix.length)).replace(/_/g, ' ');
	return "";
},
addLinks: function (requestPage, location, imagePage, closeRequest, discussion) {
	if (closeRequest) {
		var link = $('<a href="#">Close: Kept</a>');
		var link2 = $('<a href="#">Close: Deleted</a>');
		var span = $('<span class="reqHandlerLinks2"></span>');
	} else {
		var link = $('<a href="#">keep</a>');
		var link2 = $('<a href="#">del</a>');
		var span = $('<span class="reqHandlerLinks"></span>');
	}
	span.append(' [').append(link).append('] [').append(link2);
	location.after(span);
	if (closeRequest) span.before(']');
	else span.append(']');
	link.click(function (event) {
		event.preventDefault();
		var n = new DelReqHandler.process(true, closeRequest, requestPage, imagePage, [location, span, discussion]);
		DelReqHandler.running.push(n);
	});
	link2.click(function (event) {
		event.preventDefault();
		var n = new DelReqHandler.process(false, closeRequest, requestPage, imagePage, [location, span, discussion]);
		DelReqHandler.running.push(n);
	});
},
setup: function () {
	if (mw.config.get('wgPageName').indexOf('Commons:Deletion_requests/') != -1 && mw.config.get('wgAction') == 'view' && document.URL.search(/[?&]oldid=/) == -1) {
		// We're on COM:DEL or one of its daily subpages
		// Don't do anything if we're not viewing the current version of the page
		this.parse();
	}
},
process: function (keep, closeRequestBool, requestPage, imagePage, domElements) {
	//Merge the page processing functions into our new process
	for (var j in DelReqHandler.processHelpers) {
		this[j] = DelReqHandler.processHelpers[j];
	}
	this.tasks = [];
	this.requestPage = requestPage.replace(/_/g, ' ');
	this.keep = keep;
	this.closeRequestBool = closeRequestBool;
	this.imagePage = imagePage;
	this.imageTalkPage = imagePage.replace(/^File:/, 'File talk:');
	this.summary = 'Per [[' + requestPage + ']]';
	this.domElements = domElements;
	//getToken
	this.addTask('getPages');
	if (closeRequestBool) {
		if (keep) {
			this.reason = prompt('Why did you decide to keep this file?');
			//User canceled
			if (this.reason == null) return;
			this.pagesToGet = [requestPage, imagePage];
			//this.addTask('markAsKept');
			//this.addTask('getDate');
		} else {
			this.reason = prompt('Why did you decide to delete this file?');
			//User canceled
			if (this.reason == null) return;

			this.pagesToGet = [requestPage];
			//if (imagePage != "") {
			//	this.addTask('deleteFile');
			//	this.addTask('deleteFileTalk');
			//}
		}
		this.addTask('closeRequest');
	} else {
		this.pagesToGet = [imagePage];
		if (keep) {
			this.addTask('markAsKept');
			this.addTask('getDate');
			//FIXME first letter lowercase
			this.summary = 'Kept ' + this.summary;
		} else {
			this.addTask('deleteFile');
			this.addTask('nothing');
		}
		this.summary = prompt("Summary:", this.summary);
		//User canceled
		if (this.summary == null) return;

	}
	this.addTask('fakeReload');
	this.nextTask();
	this.showProgress();
}
};
DelReqHandler.processHelpers = {
getPages: function () {
	var query = {
		action: 'query',
		prop: 'revisions|info',
		rvprop: 'content|timestamp',
		intoken: 'edit',
		titles: this.pagesToGet.join('|')
	};
	this.doAPICall(query, 'getPagesCallback');
},
getPagesCallback: function (result) {
	var pages = result.query.pages;
	for (var id in pages) { // there should be only one, but we don't know it's ID
		// The edittoken only changes between logins
		this.edittoken = pages[id].edittoken;
		var type;
		switch (pages[id].title) {
		case this.imagePage:
			type = 'imagePage';
			break;
		case this.requestPage:
			type = 'requestPage';
			break;
		default:
			type = 'unknown';
			break;
		}
		this[type + 'Result'] = {
			pageContent: pages[id].revisions[0]['*'],
			starttimestamp: pages[id].starttimestamp,
			timestamp: pages[id].revisions[0]['timestamp']
		};
	}
	this.nextTask();
},
closeRequest: function () {
	var text = this.requestPageResult.pageContent;
	this.decision = (this.keep) ? 'Kept' : 'Deleted';
	this.decision += (this.reason) ? ':' : '.';
	// text = '{{delh}}\n' + text + '\n{{delf|';
	// text += d + '|' + this.reason + ' \~\~\~\~}}';
	text = '{{delh}}\n' + $.trim(text) + '\n----\n';
	text += "'''" + this.decision + "''' " + this.reason + ' \~\~\~\~ \n {{delf}}';

	var page = {
		title: this.requestPage,
		text: text,
		summary: this.decision + ' ' + this.reason,
		editType: 'text'
	};
	this.savePage(page, 'nextTask');
},

markAsKept: function () {
	var text = this.imagePageResult.pageContent;
	text = this.removeTemplate(text);

	var page = {
		title: this.imagePage,
		text: text,
		summary: this.summary,
		editType: 'text'
	};
	this.savePage(page, 'nextTask');

},
removeTemplate: function (text) {
	var start = text.indexOf('{{delete');
	if (start < 0) start = text.indexOf('{{Delete');
	if (start < 0) start = text.indexOf('{{vfd');
	if (start < 0) start = text.indexOf('{{Vfd');
	if (start < 0) start = text.indexOf('{{ifd');
	if (start < 0) start = text.indexOf('{{Ifd');
	if (start >= 0) {
		var level = 0;
		var curr = start + 2;
		var end = 0;
		while (curr < text.length && end == 0) {
			var opening = text.indexOf('{{', curr);
			var closing = text.indexOf('}}', curr);
			if (opening >= 0 && opening < closing) {
				level = level + 1;
				curr = opening + 2;
			} else {
				if (closing < 0) {
					// No closing braces found
					curr = text.length;
				} else {
					if (level > 0) level = level - 1;
					else end = closing + 2;
					curr = closing + 2;
				}
			}
		}
		if (end > start) {
			// Also strip whitespace after the "delete" template
			if (start > 0) {
				text = text.substring(0, start) + text.substring(end).replace(/^\s*/, '');
			} else {
				text = text.substring(end).replace(/^\s*/, '');
			}
			return text;
		}
	}
	alert('Couldn\'t remove the {{delete}} template, please check the file ' + this.imagePage + ' manually.');
	return text;
},

getDate: function () {
	var query = {
		action: 'query',
		prop: 'revisions',
		rvlimit: 1,
		rvprop: 'timestamp',
		rvdir: 'newer',
		titles: this.requestPage
	};
	this.doAPICall(query, 'addKeepToTalk');
},

addKeepToTalk: function (result) {
	var pages = result.query.pages;
	var date = "";
	for (var id in pages) {
		// there should be only one, but we don't know it's ID
		var ts = pages[id].revisions[0].timestamp;
		if (ts) {
			// Extract year, month, and day from the timestamp. 
			// We don't care about the exact time.
			var year = ts.substr(0, 4);
			var month = ts.substr(5, 2);
			var day = ts.substr(8, 2);
			date = year + '-' + month + '-' + day;
		}
	}
	var page = {
		title: this.imageTalkPage,
		text: '{{kept|' + date + '|' + this.requestPage + '}}\n',
		summary: 'Adding {{kept}}',
		editType: 'prependtext'
	};
	this.savePage(page, 'nextTask');
},
reload: function () {
	window.location.reload();
},
fakeReload: function () {
	var dE = this.domElements;
	dE[3].unblock();
	//Remove links
	dE[1].remove();

	if (this.closeRequestBool) {
		dE[3].toggleClass('delh delreqworking');
		dE[2].eq(0).before('<i>This deletion debate is now closed. Please do not make any edits to this archive.</i>');
		dE[2].eq(-1).after('<br><span style="color:green">Saved sucessfully.<br>This is just an approximate rendering. Reload to see the actual request.</span>');
		dE[2].eq(-1).after('<b>' + this.decision + '</b> ' + this.reason + ' --' + mw.config.get('wgUserName'));
		dE[2].eq(-1).after('<hr>');
	} else {
		//Color link red
		if (this.keep == false) dE[0].addClass('new');
	}
},

apiURL: mw.config.get('wgServer') + wgScriptPath + "/api.php",

/**
 ** Simple task queue.  addTask() adds a new task to the queue, nextTask() executes
 ** the next scheduled task.  Tasks are specified as method names to call.
 **/
// list of pending tasks
currentTask: '',
// current task, for error reporting
addTask: function (task) {
	this.tasks.push(task);
},
nextTask: function () {
	var task = this.currentTask = this.tasks.shift();
	try {
		this[task]();
  } catch (e) {
   this.fail(e);
  }
},
deleteFile: function () {
	var edit = {
		action: 'delete',
		reason: this.summary,
		title: this.imagePage,
		token: this.edittoken,
		recreate: ''
	};
	this.doAPICall(edit, 'nextTask');
	edit = {
		action: 'delete',
		reason: "Talk page of deleted image",
		title: this.imageTalkPage,
		token: this.edittoken,
		recreate: ''
	};
	this.doAPICall(edit, 'nextTask', true);
},
savePage: function (page, callback) {
	var edit = {
		action: 'edit',
		summary: page.summary,
		title: page.title,
		token: this.edittoken
	};

	edit[page.editType] = page.text;
	this.doAPICall(edit, callback);
},
fail: function (e) {
	alert(e);
},
doAPICall: function (params, callback, ignoreErrors) {
	var k = this;
	params.format = 'json';
	$.ajax({
		url: this.apiURL,
		cache: false,
		dataType: 'json',
		data: params,
		type: 'POST',
		success: function (result, status, x) {
			if (ignoreErrors) {
				k[callback](result);
				return;
			}
			if (!result) return k.fail("Receive empty API response:\n" + x.responseText);
			// In case we get the mysterious 231 unknown error, just try again
			if (result.error && result.error.info.indexOf('231') != -1) return setTimeout(function () {
				k.doAPICall(params, callback);
			}, 500);
			if (result.error) return k.fail("API request failed (" + result.error.code + "): " + result.error.info);
			k[callback](result);
		},
		error: function (x, status, error) {
			return k.fail("API request returned code " + x.status + " " + status + "Error code is " + error);
		}
	});
},
showProgress: function () {
	if (this.closeRequestBool){
		this.domElements[2].wrapAll('<div class="delreqworking"></div>');
		this.domElements[3] = this.domElements[2].parent('.delreqworking');
		this.domElements[3].block({ 
			message: 'Working...', 
			css: { border: '3px solid #A0C828', fontSize: '135%' } 
		});
	} else {
		this.domElements[3] = this.domElements[0].parent();
		this.domElements[3].block({ 
			message: 'Working...', 
			css: { color: '#A0C828', fontWeight: 'bold', background:'none', border:'none' } 
		});
		
	}
  
},
nothing: function () {
	//nothing
}
};
$(document).ready(function () {
	DelReqHandler.setup();
});

} // End of sysop check
//</source>

/*!
 * jQuery blockUI plugin
 * Version 2.37 (29-JAN-2011)
 * @requires jQuery v1.2.3 or later
 *
 * Examples at: http://malsup.com/jquery/block/
 * Copyright (c) 2007-2010 M. Alsup
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
 *
 * Thanks to Amir-Hossein Sobhi for some excellent contributions!
 */

(function($) {


$.fn._fadeIn = $.fn.fadeIn;

var noOp = function() {};

// global $ methods for blocking/unblocking the entire page
$.blockUI   = function(opts) { install(window, opts); };
$.unblockUI = function(opts) { remove(window, opts); };

// plugin method for blocking element content
$.fn.block = function(opts) {
	return this.unblock({ fadeOut: 0 }).each(function() {
		if ($.css(this,'position') == 'static')
			this.style.position = 'relative';
		if ($.browser.msie)
			this.style.zoom = 1; // force 'hasLayout'
		install(this, opts);
	});
};

// plugin method for unblocking element content
$.fn.unblock = function(opts) {
	return this.each(function() {
		remove(this, opts);
	});
};

$.blockUI.version = 2.37; // 2nd generation blocking at no extra cost!

// override these in your code to change the default behavior and style
$.blockUI.defaults = {
	// message displayed when blocking (use null for no message)
	message:  '<h1>Please wait...</h1>',

	title: null,	  // title string; only used when theme == true
	draggable: true,  // only used when theme == true (requires jquery-ui.js to be loaded)
	
	theme: false, // set to true to use with jQuery UI themes
	
	// styles for the message when blocking; if you wish to disable
	// these and use an external stylesheet then do this in your code:
	// $.blockUI.defaults.css = {};
	css: {
		padding:	0,
		margin:		0,
		width:		'30%',
		top:		'40%',
		left:		'35%',
		textAlign:	'center',
		color:		'#000',
		border:		'3px solid #aaa',
		backgroundColor:'#fff',
		cursor:		'wait'
	},
	
	// minimal style set used when themes are used
	themedCSS: {
		width:	'30%',
		top:	'40%',
		left:	'35%'
	},

	// styles for the overlay
	overlayCSS:  {
		backgroundColor: '#000',
		opacity:	  	 0.6,
		cursor:		  	 'wait'
	},

	
	// IE issues: 'about:blank' fails on HTTPS and javascript:false is s-l-o-w
	// (hat tip to Jorge H. N. de Vasconcelos)
	iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank',

	// force usage of iframe in non-IE browsers (handy for blocking applets)
	forceIframe: false,

	// z-index for the blocking overlay
	baseZ: 1000,

	// set these to true to have the message automatically centered
	centerX: true, // <-- only effects element blocking (page block controlled via css above)
	centerY: true,

	// allow body element to be stetched in ie6; this makes blocking look better
	// on "short" pages.  disable if you wish to prevent changes to the body height
	allowBodyStretch: true,

	// enable if you want key and mouse events to be disabled for content that is blocked
	bindEvents: true,

	// be default blockUI will supress tab navigation from leaving blocking content
	// (if bindEvents is true)
	constrainTabKey: true,

	// fadeIn time in millis; set to 0 to disable fadeIn on block
	fadeIn:  200,

	// fadeOut time in millis; set to 0 to disable fadeOut on unblock
	fadeOut:  400,

	// time in millis to wait before auto-unblocking; set to 0 to disable auto-unblock
	timeout: 0,

	// disable if you don't want to show the overlay
	showOverlay: true,

	// if true, focus will be placed in the first available input field when
	// page blocking
	focusInput: true,

	// suppresses the use of overlay styles on FF/Linux (due to performance issues with opacity)
	applyPlatformOpacityRules: true,
	
	// callback method invoked when fadeIn has completed and blocking message is visible
	onBlock: null,

	// callback method invoked when unblocking has completed; the callback is
	// passed the element that has been unblocked (which is the window object for page
	// blocks) and the options that were passed to the unblock call:
	//	 onUnblock(element, options)
	onUnblock: null,

	// don't ask; if you really must know: http://groups.google.com/group/jquery-en/browse_thread/thread/36640a8730503595/2f6a79a77a78e493#2f6a79a77a78e493
	quirksmodeOffsetHack: 4,

	// class name of the message block
	blockMsgClass: 'blockMsg'
};

// private data and functions follow...

var pageBlock = null;
var pageBlockEls = [];

function install(el, opts) {
	var full = (el == window);
	var msg = opts && opts.message !== undefined ? opts.message : undefined;
	opts = $.extend({}, $.blockUI.defaults, opts || {});
	opts.overlayCSS = $.extend({}, $.blockUI.defaults.overlayCSS, opts.overlayCSS || {});
	var css = $.extend({}, $.blockUI.defaults.css, opts.css || {});
	var themedCSS = $.extend({}, $.blockUI.defaults.themedCSS, opts.themedCSS || {});
	msg = msg === undefined ? opts.message : msg;

	// remove the current block (if there is one)
	if (full && pageBlock)
		remove(window, {fadeOut:0});

	// if an existing element is being used as the blocking content then we capture
	// its current place in the DOM (and current display style) so we can restore
	// it when we unblock
	if (msg && typeof msg != 'string' && (msg.parentNode || msg.jquery)) {
		var node = msg.jquery ? msg[0] : msg;
		var data = {};
		$(el).data('blockUI.history', data);
		data.el = node;
		data.parent = node.parentNode;
		data.display = node.style.display;
		data.position = node.style.position;
		if (data.parent)
			data.parent.removeChild(node);
	}

	var z = opts.baseZ;

	// blockUI uses 3 layers for blocking, for simplicity they are all used on every platform;
	// layer1 is the iframe layer which is used to supress bleed through of underlying content
	// layer2 is the overlay layer which has opacity and a wait cursor (by default)
	// layer3 is the message content that is displayed while blocking

	var lyr1 = ($.browser.msie || opts.forceIframe) 
		? $('<iframe class="blockUI" style="z-index:'+ (z++) +';display:none;border:none;margin:0;padding:0;position:absolute;width:100%;height:100%;top:0;left:0" src="'+opts.iframeSrc+'"></iframe>')
		: $('<div class="blockUI" style="display:none"></div>');
	var lyr2 = $('<div class="blockUI blockOverlay" style="z-index:'+ (z++) +';display:none;border:none;margin:0;padding:0;width:100%;height:100%;top:0;left:0"></div>');
	
	var lyr3, s;
	if (opts.theme && full) {
		s = '<div class="blockUI ' + opts.blockMsgClass + ' blockPage ui-dialog ui-widget ui-corner-all" style="z-index:'+z+';display:none;position:fixed">' +
				'<div class="ui-widget-header ui-dialog-titlebar ui-corner-all blockTitle">'+(opts.title || '&nbsp;')+'</div>' +
				'<div class="ui-widget-content ui-dialog-content"></div>' +
			'</div>';
	}
	else if (opts.theme) {
		s = '<div class="blockUI ' + opts.blockMsgClass + ' blockElement ui-dialog ui-widget ui-corner-all" style="z-index:'+z+';display:none;position:absolute">' +
				'<div class="ui-widget-header ui-dialog-titlebar ui-corner-all blockTitle">'+(opts.title || '&nbsp;')+'</div>' +
				'<div class="ui-widget-content ui-dialog-content"></div>' +
			'</div>';
	}
	else if (full) {
		s = '<div class="blockUI ' + opts.blockMsgClass + ' blockPage" style="z-index:'+z+';display:none;position:fixed"></div>';
	}			
	else {
		s = '<div class="blockUI ' + opts.blockMsgClass + ' blockElement" style="z-index:'+z+';display:none;position:absolute"></div>';
	}
	lyr3 = $(s);

	// if we have a message, style it
	if (msg) {
		if (opts.theme) {
			lyr3.css(themedCSS);
			lyr3.addClass('ui-widget-content');
		}
		else 
			lyr3.css(css);
	}

	// style the overlay
	if (!opts.applyPlatformOpacityRules || !($.browser.mozilla && /Linux/.test(navigator.platform)))
		lyr2.css(opts.overlayCSS);
	lyr2.css('position', full ? 'fixed' : 'absolute');

	// make iframe layer transparent in IE
	if ($.browser.msie || opts.forceIframe)
		lyr1.css('opacity',0.0);

	//$([lyr1[0],lyr2[0],lyr3[0]]).appendTo(full ? 'body' : el);
	var layers = [lyr1,lyr2,lyr3], $par = full ? $('body') : $(el);
	$.each(layers, function() {
		this.appendTo($par);
	});
	
	if (opts.theme && opts.draggable && $.fn.draggable) {
		lyr3.draggable({
			handle: '.ui-dialog-titlebar',
			cancel: 'li'
		});
	}

	
	// show the message
	if (msg) {
		if (opts.theme)
			lyr3.find('.ui-widget-content').append(msg);
		else
			lyr3.append(msg);
		if (msg.jquery || msg.nodeType)
			$(msg).show();
	}

	if (($.browser.msie || opts.forceIframe) && opts.showOverlay)
		lyr1.show(); // opacity is zero
	if (opts.fadeIn) {
		var cb = opts.onBlock ? opts.onBlock : noOp;
		var cb1 = (opts.showOverlay && !msg) ? cb : noOp;
		var cb2 = msg ? cb : noOp;
		if (opts.showOverlay)
			lyr2._fadeIn(opts.fadeIn, cb1);
		if (msg)
			lyr3._fadeIn(opts.fadeIn, cb2);
	}
	else {
		if (opts.showOverlay)
			lyr2.show();
		if (msg)
			lyr3.show();
		if (opts.onBlock)
			opts.onBlock();
	}

	// bind key and mouse events
	bind(1, el, opts);

	if (full) {
		pageBlock = lyr3[0];
		pageBlockEls = $(':input:enabled:visible',pageBlock);
		if (opts.focusInput)
			setTimeout(focus, 20);
	}
	else
		center(lyr3[0], opts.centerX, opts.centerY);

	if (opts.timeout) {
		// auto-unblock
		var to = setTimeout(function() {
			full ? $.unblockUI(opts) : $(el).unblock(opts);
		}, opts.timeout);
		$(el).data('blockUI.timeout', to);
	}
}

// remove the block
function remove(el, opts) {
	var full = (el == window);
	var $el = $(el);
	var data = $el.data('blockUI.history');
	var to = $el.data('blockUI.timeout');
	if (to) {
		clearTimeout(to);
		$el.removeData('blockUI.timeout');
	}
	opts = $.extend({}, $.blockUI.defaults, opts || {});
	bind(0, el, opts); // unbind events
	
	var els;
	if (full) // crazy selector to handle odd field errors in ie6/7
		els = $('body').children().filter('.blockUI').add('body > .blockUI');
	else
		els = $('.blockUI', el);

	if (full)
		pageBlock = pageBlockEls = null;

	if (opts.fadeOut) {
		els.fadeOut(opts.fadeOut);
		setTimeout(function() { reset(els,data,opts,el); }, opts.fadeOut);
	}
	else
		reset(els, data, opts, el);
}

// move blocking element back into the DOM where it started
function reset(els,data,opts,el) {
	els.each(function(i,o) {
		// remove via DOM calls so we don't lose event handlers
		if (this.parentNode)
			this.parentNode.removeChild(this);
	});

	if (data && data.el) {
		data.el.style.display = data.display;
		data.el.style.position = data.position;
		if (data.parent)
			data.parent.appendChild(data.el);
		$(el).removeData('blockUI.history');
	}

	if (typeof opts.onUnblock == 'function')
		opts.onUnblock(el,opts);
}

// bind/unbind the handler
function bind(b, el, opts) {
	var full = el == window, $el = $(el);

	// don't bother unbinding if there is nothing to unbind
	if (!b && (full && !pageBlock || !full && !$el.data('blockUI.isBlocked')))
		return;
	if (!full)
		$el.data('blockUI.isBlocked', b);

	// don't bind events when overlay is not in use or if bindEvents is false
	if (!opts.bindEvents || (b && !opts.showOverlay)) 
		return;

	// bind anchors and inputs for mouse and key events
	var events = 'mousedown mouseup keydown keypress';
	b ? $(document).bind(events, opts, handler) : $(document).off(events, handler);

// former impl...
//	   var $e = $('a,:input');
//	   b ? $e.bind(events, opts, handler) : $e.off(events, handler);
}

// event handler to suppress keyboard/mouse events when blocking
function handler(e) {
	// allow tab navigation (conditionally)
	if (e.keyCode && e.keyCode == 9) {
		if (pageBlock && e.data.constrainTabKey) {
			var els = pageBlockEls;
			var fwd = !e.shiftKey && e.target === els[els.length-1];
			var back = e.shiftKey && e.target === els[0];
			if (fwd || back) {
				setTimeout(function(){focus(back);},10);
				return false;
			}
		}
	}
	var opts = e.data;
	// allow events within the message content
	if ($(e.target).parents('div.' + opts.blockMsgClass).length > 0)
		return true;

	// allow events for content that is not being blocked
	return $(e.target).parents().children().filter('div.blockUI').length == 0;
}

function focus(back) {
	if (!pageBlockEls)
		return;
	var e = pageBlockEls[back===true ? pageBlockEls.length-1 : 0];
	if (e)
		e.focus();
}

function center(el, x, y) {
	var p = el.parentNode, s = el.style;
	var l = ((p.offsetWidth - el.offsetWidth)/2) - sz(p,'borderLeftWidth');
	var t = ((p.offsetHeight - el.offsetHeight)/2) - sz(p,'borderTopWidth');
	if (x) s.left = l > 0 ? (l+'px') : '0';
	if (y) s.top  = t > 0 ? (t+'px') : '0';
}

function sz(el, p) {
	return parseInt($.css(el,p))||0;
}

})(jQuery);