MediaWiki:Gadget-Editor.js
Jump to navigation
Jump to search
Note: You may have to bypass your browser’s cache to see the changes. In addition, after saving a sitewide CSS file such as MediaWiki:Common.css, it will take 5-10 minutes before the changes take effect, even if you clear your cache.
- Mozilla / Firefox / Safari: hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (Command-R on a Macintosh);
- Konqueror and Chrome: click Reload or press F5;
- Opera: clear the cache in Tools → Preferences;
- Internet Explorer: hold Ctrl while clicking Refresh, or press Ctrl-F5.
- The following documentation is located at MediaWiki:Gadget-Editor.js/documentation. [edit]
- This script is a part of the
Editor
gadget (edit definitions)- Description (edit): ⧼Gadget-Editor⧽
- Useful links: subpage list • links • redirects
This actually is not a standalone gadget but rather a library.
This piece of code used to be in the same source as TranslationAdder. It was taken out of it because
- theoretically one could have translation adder turned off
- separation of concerns.
// 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: ['ids', '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.baserevid = revision.revid;
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',
baserevid: this.baserevid,
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) {
console.trace(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", "0px")
.css("left", "0px")
.css("background-color", "#00FF00")
.css("z-index", "10")
.css("padding", "30px")[0];
window.setTimeout(function() {
presence.style.backgroundColor = "#CCCCFF";
presence.style.padding = "10px";
}, 400);
presence.appendChild($('<div>').css("position", "relative")
.css("top", "0px")
.css("left", "0px")
.css("margin", "-10px")
.css("color", "#0000FF")
.css("cursor", "pointer")
.on("click", performSequentially(close))
.text("X")[0]);
document.body.insertBefore(presence, document.body.firstChild);
var contents = $('<p>').css('text-align', 'center')
.append($('<b>Page Editing</b></br>'));
if (!broken) {
contents.append($('<button>').text("Save Changes")
.attr('title', 'Save your changes [s]')
.attr('accesskey', 's')
.on("click", save));
contents.append($('<br>'));
contents.append($('<button>').text("Undo")
.attr('title', 'Undo last change [z]')
.attr('accesskey', 'z')
.on("click", thiz.undo));
contents.append($('<button>').text("Redo").on('click', thiz.redo));
mw.loader.using('mediawiki.util').then(function() {
contents.children().updateTooltipAccessKeys();
});
}
presence.appendChild(contents[0]);
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>Saving:' + summary + '...</li>');
withPresenceShowing(false, function(presence) {
if (!$savelog) {
$savelog = $('<ul>').css("background-color", "#DDFFDD")
.css("margin", "0px -10px -10px -10px")
.css("padding", "10px");
$(presence).append($savelog);
}
$savelog.append(saveLi);
if (originalText == currentText)
return thiz.error("No changes were made to the page.");
else if (!currentText)
return thiz.error("ERROR: page has become blank.");
});
originalText = currentText;
var nst = []
var node;
while (node = nodestack.pop()) {
nst.push(node);
}
thiz.page.Save(currentText, {
summary: summary + "([[WT:EDIT|Assisted]])",
notminor: true
}).then(function(res) {
if (res == null)
return thiz.error("An error occurred while saving.");
try {
saveLi.append(
$('<span>')
.append($("<b>Saved</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("(Show changes)")));
} catch (e) {
if (res.error) {
thiz.error("Not saved: " + 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) {
console.trace("In AdderWrapper v1.0");
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>Loading...</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>Could not connect to the server</p>').css("color", "red")[0]);
});
});
} catch (e) {
status.innerHTML = "ERROR:" + e.description;
return false;
}
return false;
}
};