MediaWiki:Gadget-Duzenleyici.js

Vikisözlük sitesinden
// This page consists of Editor and AdderWrapper
// Author: Conrad Irwin
/*jshint maxerr:1048576, strict:true, undef:true, latedef:true, es5:true */
/*global mw, jQuery, importScript, importScriptURI, $ */
window.PageEditor = function(title) {
	this.CheckOutForEdit = function() {
		return new mw.Api().get({
				action: 'query',
				prop: 'revisions',
				rvprop: ['content', 'timestamp'],
				titles: String(title),
				formatversion: '2',
				curtimestamp: true
			})
			.then(function(data) {
				var page, revision;
				if (!data.query || !data.query.pages) {
					return $.Deferred().reject('unknown');
				}
				page = data.query.pages[0];
				if (!page || page.missing) {
					return $.Deferred().reject('nocreate-missing');
				}
				revision = page.revisions[0];
				this.basetimestamp = revision.timestamp;
				this.curtimestamp = data.curtimestamp;
				return revision.content;
			});
	}
	this.Save = function(newWikitext, params) {
		var editParams = typeof params === 'object' ? params : {
			text: String(params)
		};
		return new mw.Api().postWithEditToken($.extend({
			action: 'edit',
			title: title,
			formatversion: '2',
			text: newWikitext,

			// Protect against errors and conflicts
			assert: mw.user.isAnon() ? undefined : 'user',
			basetimestamp: this.basetimestamp,
			starttimestamp: this.curtimestamp,
			nocreate: true
		}, editParams));
	}
}

/**
 * A generic page editor for the current page.
 *
 * This is a singleton and it displays a small interface in the top left after
 * the first edit has been registered.
 *
 * @public
 * this.page
 * this.addEdit
 * this.error
 *
 */

window.Editor = function() {
	//Singleton
	if (arguments.callee.instance)
		return arguments.callee.instance;
	else
		arguments.callee.instance = this;

	this.page = new PageEditor(mw.config.get('wgPageName'));

	// get the current text of the article and call the callback with it
	// NOTE: This function also acts as a loose non-re-entrant lock to protect currentText.
	this.withCurrentText = function(callback) {
		if (callbacks.length == 0) {
			callbacks = [callback];
			for (var i = 0; i < callbacks.length; i++) {
				callbacks[i](currentText);
			}
			return callbacks = [];
		}

		if (callbacks.length > 0) {
			return callbacks.push(callback);
		}


		callbacks = [callback];
		thiz.page.CheckOutForEdit().then(function(wikitext) {
			if (wikitext === null)
				return thiz.error("Could not connect to server");

			currentText = originalText = wikitext;

			for (var i = 0; i < callbacks.length; i++) {
				callbacks[i](currentText);
			}
			callbacks = [];
		});
	}
	// A decorator for withCurrentText
	function performSequentially(f) {
		return (function() {
			var the_arguments = arguments;
			thiz.withCurrentText(function() {
				f.apply(thiz, the_arguments);
			});
		});
	}

	// add an edit to the editstack
	function addEdit(edit, node, fromRedo) {
		withPresenceShowing(false, function() {
			if (node) {
				nodestack.push(node);
				node.style.cssText = "border: 2px #00FF00 dashed;"
			}

			if (!fromRedo)
				redostack = [];

			var ntext = false;
			try {
				ntext = edit.edit(currentText);

				if (ntext && ntext != currentText) {
					edit.redo();
					currentText = ntext;
				} else
					return false;
			} catch (e) {
				// TODO Uncaught TypeError: Object [object Window] has no method 'error'
				// I may have just fixed this by changing "this" below to "thiz" ...
				thiz.error("ERROR:" + e);
			}

			editstack.push(edit);
		});
	}
	this.addEdit = performSequentially(addEdit);

	// display an error to the user
	this.error = function(message) {
		if (!errorlog) {
			errorlog = $('<ul>').css("background-color", "#FFDDDD")
				.css("margin", "0px -10px -10px -10px")
				.css("padding", "10px")[0];
			withPresenceShowing(true, function(presence) {
				presence.appendChild(errorlog);
			});
		}
		errorlog.appendChild($('<li>').text(message)[0]);
	}

	var thiz = this; // this is set incorrectly when private functions are used as callbacks.

	var editstack = []; // A list of the edits that have been applied to get currentText
	var redostack = []; // A list of the edits that have been recently undone.
	var nodestack = []; // A lst of nodes to which we have added highlighting
	var callbacks = {}; // A list of onload callbacks (initially .length == undefined)

	var originalText = ""; // What was the contents of the page before we fiddled?
	var currentText = ""; // What is the contents now?

	var errorlog; // The ul for sticking errors in.
	var $savelog; // The ul for save messages.

	//Move an edit from the editstack to the redostack 
	function undo() {
		if (editstack.length == 0)
			return false;
		var edit = editstack.pop();
		redostack.push(edit);
		edit.undo();

		var text = originalText;
		for (var i = 0; i < editstack.length; i++) {
			var ntext = false;
			try {
				ntext = editstack[i].edit(text);
			} catch (e) {
				thiz.error("ERROR:" + e);
			}
			if (ntext && ntext != text) {
				text = ntext;
			} else {
				editstack[i].undo();
				editstack = editstack.splice(0, i);
				break;
			}
		}
		currentText = text;
		return true;
	}
	this.undo = performSequentially(undo);

	//Move an edit from the redostack to the editstack
	function redo() {
		if (redostack.length == 0)
			return;
		var edit = redostack.pop();
		addEdit(edit, null, true);
	}
	this.redo = performSequentially(redo);

	function withPresenceShowing(broken, callback) {
		if (arguments.callee.presence) {
			arguments.callee.presence.style.display = "block";
			return callback(arguments.callee.presence);
		}

		var presence = $('<div>').css("position", "fixed")
			.css("top", "66px")
			.css("left", "3.25em")
			.css("z-index", "10")[0];
			
		var inside = $("<div>").css("background-color", "#f8f9fa")
			.css("border", "1px solid #c8ccd1")
			.css("box-shadow", "0 1px 1px rgba(0,0,0,0.15)")
			.css("display", "inline-block")
			.css("padding", "1rem");

		window.setTimeout(function() {
			$(presence).children().eq(0).css("background-color", "#f8f9fa");
		}, 400);

		inside.append($("<b style='display: inline-block; padding-right: 20px;'>Sayfa düzenleme</b>"));
		inside.append($('<div>').css("position", "relative")
			.css("cursor", "pointer")
			.css("display", "inline-block")
			.on("click", performSequentially(close))
			.text("X"));

		document.body.insertBefore(presence, document.body.firstChild);

		var contents = $('<p>').css('text-align', 'center');

		if (!broken) {
			var kaydet = new OO.ui.ButtonWidget({ 
				label: 'Kaydet',
				title: 'Değişiklikleri kaydet [s]',
				accessKey: 's'
			}).on("click", save);
			
			var gerial = new OO.ui.ButtonWidget({ 
				label: 'Geri al',
				title: 'Son değişikliği geri al [z]',
				accessKey: 'z'
			}).on("click", thiz.undo);
			
			var tekrarla = new OO.ui.ButtonWidget({ 
				label: 'Tekrarla',
			}).on('click', thiz.redo);
			
			contents.append(kaydet.$element);
			contents.append($('<div>').css("margin-top", "5px"));
			contents.append(gerial.$element);
			contents.append(tekrarla.$element);

			mw.loader.using('mediawiki.util').then(function() {
				contents.children().updateTooltipAccessKeys();
			});
		}
		inside.append(contents);
		
		$(presence).append(inside);

		arguments.callee.presence = presence;
		callback(presence);
	}

	// Remove the button
	function close() {
		while (undo())
		;

		withPresenceShowing(true, function(presence) {
			presence.style.display = "none";
			if (errorlog) {
				errorlog.parentNode.removeChild(errorlog);
				errorlog = false;
			}
		});
	}

	//Send the currentText back to the server to save.
	function save() {
		thiz.withCurrentText(function() {
			if (editstack.length == 0)
				return;

			var cleanup_callbacks = callbacks;
			callbacks = [];
			var sum = {};
			for (var i = 0; i < editstack.length; i++) {
				sum[editstack[i].summary] = true;
				if (editstack[i].after_save)
					cleanup_callbacks.push(editstack[i].after_save);
			}
			var summary = "";
			for (var name in sum) {
				summary += name + " ";
			}
			editstack = [];
			redostack = [];
			var saveLi = $('<li>Kaydediliyor:' + summary + '... </li>')
				.css("background-color", "#DDFFDD")
				.css("border", "1px solid #c8ccd1")
				.css("box-shadow", "0 1px 1px rgba(0,0,0,0.15)")
				.css("padding", "1rem")
				.css("margin", "2px 0");
			withPresenceShowing(false, function(presence) {
				if (!$savelog) {
					$savelog = $('<ul>')
						.css("margin", "0px -10px -10px -10px")
						.css("padding", "10px")
						.css("list-style", "none");
					$(presence).append($savelog);
				}
				$savelog.append(saveLi);

				if (originalText == currentText)
					return thiz.error("Sayfada herhangi bir değişiklik yapılamadı.");

				else if (!currentText)
					return thiz.error("HATA: sayfa boşaltıldı.");
			});

			originalText = currentText;
			var nst = []
			var node;
			while (node = nodestack.pop()) {
				nst.push(node);
			}
			thiz.page.Save(currentText, {
				summary: summary + "([[Vikisözlük:Çeviriler|Destekli]])",
				notminor: true
			}).then(function(res) {
				if (res == null)
					return thiz.error("Kaydederken bir hata oluştu.");

				try {
					saveLi.append(
						$('<span>')
						.append($("<b>Kaydedildi</b>"))
						.append($('<a>').attr("href", mw.config.get('wgScript') +
								'?title=' + encodeURIComponent(mw.config.get('wgPageName')) +
								'&diff=' + encodeURIComponent(res.edit.newrevid) +
								'&oldid=' + encodeURIComponent(res.edit.oldrevid))
							.text("(Değişiklikleri göster)")));
				} catch (e) {
					if (res.error) {
						thiz.error("Kaydedilemedi: " + String(res.error.info));
					} else {
						thiz.error($('<p>').text(String(e))[0]);
					}
				}

				for (var i = 0; i < nst.length; i++)
					nst[i].style.cssText = "background-color: #0F0;border: 2px #0F0 solid;";

				window.setTimeout(function() {
					var node;
					while (node = nst.pop())
						node.style.cssText = "";
				}, 400);

				// restore any callbacks that were waiting for currentText before we started
				for (var i = 0; i < cleanup_callbacks.length; i++)
					thiz.withCurrentText(cleanup_callbacks[i]);

			});
		});
	}
}



/**
 * A small amount of common code that can be usefully applied to adder forms.
 *
 * An adder is assumed to be an object that has:
 *
 * .fields  A object mapping field names to either validation functions used
 *          for text fields, or the word 'checkbox'
 *
 * .createForm  A function () that returns a newNode('form') to be added to the
 *              document (by appending to insertNode)
 *
 * .onsubmit  A function (values, register (wikitext, callback)) that accepts
 *            the validated set of values and processes them, the register
 *            function accepts wikitext and a continuation function to be
 *            called with the result of rendering it.
 *
 * Before onsubmit or any validation functions are called, but after running
 * createForm, a new property .elements will be added to the adder which is a
 * dictionary mapping field names to HTML input elements.
 *
 * @param {editor}  The current editor.
 * @param {adder}  The relevant adder.
 * @param {insertNode}  Where to insert this in the document.
 * @param {insertSibling} Where to insert this within insertNode.
 */
window.AdderWrapper = function(editor, adder, insertNode, insertSibling) {
	var form = adder.createForm()
	var status = $('<span>')[0];

	form.appendChild(status);
	if (insertSibling)
		insertNode.insertBefore(form, insertSibling);
	else
		insertNode.appendChild(form);

	adder.elements = {};

	//This is all because IE doesn't reliably allow form.elements['name']
	for (var i = 0; i < form.elements.length; i++) {
		adder.elements[form.elements[i].name] = form.elements[i];
	}

	form.onsubmit = function() {
		try {
			var submit = true;
			var values = {}

			status.innerHTML = "";
			for (var name in adder.fields) {
				if (adder.fields[name] == 'checkbox') {
					values[name] = adder.elements[name].checked ? name : false;
				} else {
					adder.elements[name].style.border = ''; // clear error styles
					values[name] = adder.fields[name](adder.elements[name].value || '', function(msg) {
						status.appendChild(
							$('<span>').css("color", "red")
							.append($('<img>').attr('src', 'http://upload.wikimedia.org/wikipedia/commons/4/4e/MW-Icon-AlertMark.png'))
							.append(msg)
							.append($('<br>'))[0]);
						adder.elements[name].style.border = "solid #CC0000 2px";
						return false
					});

					if (values[name] === false)
						submit = false;
				}
			}
			if (!submit)
				return false;

			var loading = $('<span>Yükleniyor...</span>')[0];
			status.appendChild(loading);

			adder.onsubmit(values, function(text, callback) {
				//text = "<p style='display:inline;'>" + text + "</p>";
				//text = "<div id='editorjs-temp'>" + text + "</div>";
				new mw.Api().parse(text, {
						title: mw.config.get('wgPageName'),
						pst: true, //pst makes subst work as expected
						disablelimitreport: true
					})
					.then(function(r) {
						var cleanedHtml = $.parseHTML(r)[0].children[0].innerHTML; //first child of .mw-parser-output
						callback(cleanedHtml);
						status.removeChild(loading);
					}).fail(function(r) {
						if (r) console.log("ERROR IN Editor.js:" + r);
						loading.appendChild($('<p>Sunucuya bağlanılamadı</p>').css("color", "red")[0]);
					});
			});
		} catch (e) {
			status.innerHTML = "ERROR:" + e.description;
			return false;
		}
		return false;
	}
};