MediaWiki:Split!.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.
/**
 * Split!
 * A script for history splitting.
 * Does not depend on any of Wikimedia Commons scripts
 * --> Less stable, easier portable.
 * Only tested with browser supporting CSS3 and HTML5.
 * 
 * @author Rillke, 2014
 * 
 * License: Choose one or more of the following:
 * GPL v.2 and later, LGPL (all versions), CC-By-SA 3.0, GFDL, Apache License 2 and later.
 */

(function($, mw) {

var mwCfg = mw.config.get(['wgNamespaceNumber', 'wgArticleId', 'wgPageName']),
	pageName = mwCfg.wgPageName.replace(/_/g, ' '),
	title = new mw.Title(pageName);

function firstItem(o) { for (var i in o) { if (o.hasOwnProperty(i)) { return o[i]; } } }

/************************************
 *  Message logic
 */
var msgs = {
	'com-split-tab-label': "Split!",
	'com-split-tab-tooltip': "Split file",
	'com-split-usage': "Click a separator and confirm to split the revision history. For pages with no text revision, a copy of the latest revision will be submitted. Note that you cannot split revisions having the same timestamp.",
	'com-split-separator-tooltip': "split the revision history here",
	'com-split-file-input-placeholder': "Destination file name",
	'com-split-file-input-label': "Destination file name:",
	'com-split-confirm-button': "Split now!"
};
mw.messages.set(msgs);
var msg = function(k) {
	return mw.msg('com-split-' + k);
};
/**
 *  END: Message logic
 ***********************************/


var split = {
	installLink: function() {
		var $tab = $( mw.util.addPortletLink(
				'p-cactions',
				document.location + '#!action=split',
				msg('tab-label'),
				'ca-user-js-split',
				msg('tab-tooltip'),
				'',
				document.getElementById('ca-edit')
			) );

		$tab.click(function(e) {
			e.preventDefault();
			split.createUI();
		});
	},
	sortMerge: function(prop, first, second) {
		// This could be more performant ...
		var retVal = first.slice(0).concat(second);
		retVal.sort(function(a, b) {
			var aProp = a[prop],
				bProp = b[prop];

			if (aProp > bProp) {
				return 1;
			} else if(aProp === bProp) {
				return 0;
			} else {
				return -1;
			}
		});
		return retVal;
	},
	formatTS: function(ts) {
		return ts.replace('T', ' ').replace('Z', '');
	},
	/* Format a number > 1 (1000 --> 1 000) */
	formattNumber: function( iNr ) {
		iNr += '';
		var rx = /(\d+)(\d{3})/;
		while (rx.test(iNr)) {
			iNr = iNr.replace(rx, '$1' + '<span style="font-size:40%;font-weight:100">&nbsp;</span>' + '$2');
		}
		return iNr;
	},
	formatSize: function(h, w, fs) {
		return h + '&nbsp;×&nbsp;' + w + '&nbsp;&nbsp;(' + split.formattNumber( fs >> 10 ) + '&nbsp;<abbr title="1 KibiByte= 1024 Bytes">KiB</abbr>)';
	},
	createUI: function() {
		mw.loader.using(['jquery.lengthLimit'], function() {
			var $targetNode = $('#mw-content-text'),
				$ui = split.getUI();

			$ui.prependTo($targetNode);

			split.initUI().done(function(r) {
				var pg = firstItem(r.query.pages),
					rvs = pg.revisions,
					iis = pg.imageinfo,
					merged = split.sortMerge('timestamp', rvs, iis);
				
				$.each(merged, function(i, rev) {
					if (rev.url) {
						$ui.imgRevItem(rev);
					} else {
						$ui.textRevItem(rev);
					}
				});
			}).fail(function() {
				// TODO
			});
		});
	},
	getUI: function() {
		var $ui = $('<div>')
				.addClass('com-split-ui')
				.text(msg('usage')),
			$confirm = $('<button>')
				.addClass('com-split-confirm')
				.attr('type', 'button')
				.text(msg('confirm-button'))
				.appendTo($ui),
			$separator = $('<a>')
				.addClass('com-split-separator')
				.attr('href', '#')
				.attr('title', msg('separator-tooltip'))
				.text(' '),
			$textRevItem = $('<div>')
				.addClass('com-split-textrev-item'),
			$txtComment = $('<span>')
				.addClass('com-split-txt-comment'),
			$txtUser = $('<code>')
				.addClass('com-split-txt-user'),
			$txtTime = $('<span>')
				.addClass('com-split-txt-time'),
			$imageRevItem = $('<div>')
				.addClass('com-split-imagerev-item'),
			$imgImageContainer = $('<div>')
				.addClass('com-split-img-img-container'),
			$imgDescription = $('<div>')
				.addClass('com-split-img-desc'),
			$imgComment = $('<span>')
				.addClass('com-split-img-comment'),
			$imgTime = $('<span>')
				.addClass('com-split-img-time'),
			$imgImage = $('<img>')
				.addClass('com-split-img-img'),
			$imgSize = $('<span>')
				.addClass('com-split-img-size'),
			$imgMIME = $('<span>')
				.addClass('com-split-img-mime'),
			$imgUser =  $('<code>')
				.addClass('com-split-img-user'),
			$fileName = $('<div>')
				.addClass('com-split-filename')
				.hide(),
			$fileNameInputLabel = $('<span>')
				.text(msg('file-input-label'))
				.hide(),
			$fileNameInput = $('<input>')
				.attr('placeholder', msg('file-input-placeholder'))
				.attr('title', msg('file-input-placeholder'))
				.addClass('com-split-filename-input'),
			items = [];

			onSeparatorClick = function(e) {
				var $sep = $(this);
				e.preventDefault();
				$sep.toggleClass('com-split-separator-selected');
				$sep.next().toggle();
			};
			$ui.textRevItem = function(data) {
				var $textRevItemClone, $fileNameClone, $fileNameInputLabelClone, $fileNameInputClone, $separatorClone;

				$separatorClone = $separator.clone().click(onSeparatorClick).appendTo($ui);
				$fileNameClone = $fileName.clone().appendTo($ui);
				$fileNameInputLabelClone = $fileNameInputLabel.clone()
					.appendTo($fileNameClone);
				 $fileNameInputClone = $fileNameInput.clone()
					.val(title.getNameText() + ' ' + data.revid  + '.' + title.getExtension())
					.byteLimit(240 - title.getExtension().length - 1)
					.keyup()
					.focus(function() {
						$fileNameInputLabelClone.fadeTo(400, 1);
					})
					.blur(function() {
						$fileNameInputLabelClone.fadeTo(400, 0);
					})
					.appendTo($fileNameClone);
				$textRevItemClone = $textRevItem.clone().appendTo($ui);
				$txtTime.clone().html(split.formatTS(data.timestamp)).appendTo($textRevItemClone);
				$txtUser.clone().text('[[User:' + data.user + ']]').appendTo($textRevItemClone);
				$txtComment.clone().html(data.parsedcomment).appendTo($textRevItemClone);
				items.push({
					$sep: $separatorClone,
					$inp: $fileNameInputClone,
					ts: data.timestamp,
					isText: true
				});
			};
			$ui.imgRevItem = function(data) {
				var $imageRevItemClone, $imgDescriptionClone, $imgImageContainerClone, $fileNameClone, $fileNameInputLabelClone, $fileNameInputClone;

				$separatorClone = $separator.clone().click(onSeparatorClick).appendTo($ui);
				$fileNameClone = $fileName.clone().appendTo($ui);
				$fileNameInputLabelClone = $fileNameInputLabel.clone()
					.appendTo($fileNameClone);
				$fileNameInputClone = $fileNameInput.clone()
					.val(title.getNameText() + ' ' + data.timestamp + '.' + title.getExtension())
					.byteLimit(240 - title.getExtension().length - 1)
					.keyup()
					.focus(function() {
						$fileNameInputLabelClone.fadeTo(400, 1);
					})
					.blur(function() {
						$fileNameInputLabelClone.fadeTo(400, 0);
					})
					.appendTo($fileNameClone);
				$imageRevItemClone = $imageRevItem.clone().appendTo($ui);
				$imgDescriptionClone = $imgDescription.clone().appendTo($imageRevItemClone);
				$imgImageContainerClone = $imgImageContainer.clone().appendTo($imageRevItemClone);

				$imgImage.clone().attr({
					src: data.thumburl,
					height: data.thumbheight,
					width: data.thumbwidth
				}).appendTo($imgImageContainerClone);
				$imgMIME.clone().text(data.mime).appendTo($imgImageContainerClone);
				$imgTime.clone().html( split.formatTS(data.timestamp) ).appendTo($imgDescriptionClone);
				$imgUser.clone().text('[[User:' + data.user + ']]').appendTo($imgDescriptionClone);
				$imgSize.clone().html( split.formatSize(data.width, data.height, data.size) ).appendTo($imgDescriptionClone);
				$imgComment.clone().html(data.parsedcomment).appendTo($imgDescriptionClone);
				items.push({
					$sep: $separatorClone,
					$inp: $fileNameInputClone,
					ts: data.timestamp
				});
			};
			$confirm.click(function(e) {
				e.preventDefault();
				var parts = [];
				$.each(items, function(i, item) {
					if (item.$sep.hasClass('com-split-separator-selected')) {
						if (!$.trim(item.$inp.val())) {
							alert('Empty destination input detected!');
							throw new Error('Empty destination input detected!');
						}
						// Create a new part
						parts.push([]);
					}
					if (!parts.length) {
						// No split -- keep here!
						parts.push([]);
						var lastPart = parts[parts.length - 1];
						lastPart.push($.extend({}, item, {
							$inp: $('<input>').val(pageName)
						}));
					} else {
						var lastPart = parts[parts.length - 1];
						lastPart.push(item);
					}
				});

				var dest, ts2archiveName = {}, timestamps = [], pageText, needsText;

				function undelete() {
					if (0 === parts.length) {
						return alert('All work done!');
					}
					var part = parts.pop();
					needsText = true;
					dest = part[0].$inp.val();
					dest = 'File:' + dest.replace(/[Ff]ile:/, '');
					// TODO: Link destination file instead
					part[0].$sep.click();
					$.each(part, function(i, p) {
						if (p.ts) {
							timestamps.push(p.ts);
						}
						if (p.isText) {
							needsText = false;
						}
					});
					if (timestamps.length) {
						new mw.Api().postWithToken( 'delete', {
								action: 'undelete',
								title: pageName,
								reason: '[[COM:SPLIT|History splitting]].',
								timestamps: timestamps.join('|')
							} )
							.done(function(r) {
								if (!r['undelete']) {
									// TODO
									return;
								}
								faUndelete();
							})
							.fail(function(r) {
								// TODO
							});
							timestamps = [];
					} else {
						return undelete();
					}
				}
				function faUndelete() {
					
				}
				function move() {
					new mw.Api().postWithToken( 'move', {
							action: 'move',
							from: pageName,
							to: dest,
							noredirect: 1,
							reason: '[[COM:SPLIT|History splitting]].'
						} )
						.done(function(r) {
							if (!r['move']) {
								// TODO
								return;
							}
							if (needsText) {
								edit();
							} else {
								undelete();
							}
						})
						.fail(function(r) {
							// TODO
						});
				}
				function edit() {
					new mw.Api().postWithToken( 'edit', {
							action: 'edit',
							title: dest,
							text: pageText,
							reason: '[[COM:SPLIT|History splitting]]. Adding last revision text from [[' + pageName + ']]'
						} )
						.done(function(r) {
							if (!r['edit']) {
								// TODO
								return;
							}
							undelete();
						})
						.fail(function(r) {
							// TODO
						});
				}
				function delInfo() {
					new mw.Api().get({
						action: 'query',
						list: 'filearchive',
						fafrom: pageName,
						fato: pageName,
						falimit: 'max',
						faprop: 'timestamp|archivename'
					})
					.done(function(r) {
						$.each(r.query.filearchive, function(i, fa) {
							ts2archiveName[fa.timestamp] = fa.name;
						});
						undelete();
					})
					.fail(function() {
						// TODO
					});
				}
				function del() {
					new mw.Api().postWithToken( 'delete', {
							action: 'delete',
							title: pageName,
							reason: '[[COM:SPLIT|History splitting]].'
						} )
						.done(function(r) {
							if (!r['delete'] || !r['delete'].title) {
								// TODO
								return;
							}
							delInfo();
						})
						.fail(function(r) {
							// TODO
						});
				}
				new mw.Api().get({
					action: 'query',
					prop: 'revisions',
					rvprop: 'content',
					titles: pageName
				})
				.done(function(r) {
					pageText = firstItem(r.query.pages).revisions[0]['*'];
					del();
				})
				.fail(function() {
					// TODO
				});
			});
		return $ui;
	},
	initUI: function() {
		return new mw.Api().post({
			action: 'query',
			titles: pageName,
			prop: 'info|revisions|imageinfo',
			rvprop: 'timestamp|user|ids|parsedcomment',
			rvlimit: 'max',
			iiprop: 'timestamp|user|ids|url|parsedcomment|size|mime|archivename',
			iilimit: 'max',
			iiurlwidth: 120,
			iiurlheight: 120,
			intoken: 'delete|move'
		});
	}
};

window.splitBang = split;


if (mwCfg.wgNamespaceNumber !== 6 || !mwCfg.wgArticleId || mw.user.isAnon()) {
	return;
}

$(function() {
	if (window.location.hash.indexOf('!action=split') !== -1) {
		window.location.hash = '';
		split.createUI();
	}
	split.installLink();
});

}(jQuery, mediaWiki));