// 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;
}
};