User:Karelklic/editor.js

/** Code written by Conrad Irwin **/

/* DOM abbreviation function */ function newNode(tagname){

var node = document.createElement(tagname); for( var i=1;i<arguments.length;i++ ){ if(typeof arguments[i] == 'string'){ //Text node.appendChild( document.createTextNode(arguments[i]) ); }else if(typeof arguments[i] == 'object'){ if(arguments[i].nodeName){ //If it is a DOM Node node.appendChild(arguments[i]); }else{ //Attributes (hopefully) for(var j in arguments[i]){ if(j == 'class'){ //Classname different because... node.className = arguments[i][j]; }else if(j == 'style'){ //Style is special node.style.cssText = arguments[i][j]; }else if(typeof arguments[i][j] == 'function'){ //Basic event handlers try{ node.addEventListener(j,arguments[i][j],false); //W3C }catch(e){try{ node.attachEvent('on'+j,arguments[i][j],"Language"); //MSIE }catch(e){ node['on'+j]=arguments[i][j]; }}; //Legacy }else{ node.setAttribute(j,arguments[i][j]); //Normal attributes }       }      }    }  }  return node; }

/* Wrapper around API */ function API {

function request (query, callback) {       var xhr = sajax_init_object;

xhr.open('POST', '/w/api.php?format=json', true); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.send(query); xhr.onreadystatechange = function {           if (xhr.readyState == 4) {               callback(eval("("+xhr.responseText+")")); }       }    }

function encode_array (arg, args) {       if (arg instanceof Array) args = arg;

return encodeURIComponent(Array.prototype.join.call(args,"|")); }

function Query (what) {       return function (props, callback) {           request('action=query&' + what + "&" + props, callback); }   }

var query = {

titles: function (arg) { return Query('titles=' + encode_array(arg, arguments)) },

pageids: function (arg) { return Query('pageids=' + encode_array(arg, arguments)) },

revids: function (arg) { return Query('revids=' + encode_array(arg, arguments)) },       page: function (title) {           //self is this without the interference from javascript var self = { query: query.titles(title), title: title,

edit: function (callback, section) {                   var q = 'prop=info|revisions&intoken=edit&rvprop=content|timestamp';

if (section != null) section = '&rvsection=' + section; else section = '';

q += section;

self.query(q, function (res)                   {                        // should only be one pageid                        for (var pageid in res.query.pages)                        {                            var page = res.query.pages[pageid];                            var text = '';

if (page.revisions) text = page.revisions[0]['*']

//the "save" function callback(text, function (ntext, summary, postsave)                           {                                if (text == ntext || !ntext)                                    return;

if(!summary) summary = "";

request('action=edit&title=' + encodeURIComponent(self.title) +                                       '&text=' + encodeURIComponent(ntext) + section +                                        '&summary=' + encodeURIComponent(summary) +                                        '&token=' + encodeURIComponent(page.edittoken) +                                        '&starttimestamp=' + encodeURIComponent(page.revisions[0].timestamp),                                        postsave                                    ) });                       }                    });                },

create: function (text, summary, minor) {                   self.edit(function(otext, save)                    {                        save(text, summary, minor);                    }); },

parse: function (text, callback) {                   request('action=parse&title=' + encodeURIComponent(self.title) + '&text=' + encodeURIComponent(text.replace('subst:','')), callback) },

parseFragment: function (text, callback) //To prevent 's being added {                   self.parse(' ' + text + ' ', function (res)                    {                        res.parse.text['*'] = res.parse.text['*'].replace(/^ /,).replace(/<\/div>$/,);                        callback(res);                    }); }            }            return self; }   };

return query; }

//Oh, why did I use that self nonsense above. let's use this properly... //A class to make settings persistant, and to store changes to them - yay function Preferences (context) {   //Repeated calls with the same context should get the same preferences object. if (arguments.callee[context]) return arguments.callee[context]; else arguments.callee[context] = this;

/**    * Subscribe to changes to a preference. *    * This will call the callback each time the preference is changed. And, if    * the currentvalue that you pass differs from the value in the storage, * then it will call the callback immediately with the true value. If you * pass in a currentvalue, and it has not yet got a value for this pref, * then it will use the value you pass. *    * @param {string} name  The name of this preference. * @param {function(string):void} The callback to call when it changes. * @param {string} currentvalue The optional currentvalue. */   this.subscribe = function (name, callback, currentvalue) {       if (storage[name] === null && currentvalue !== null) storage[name] == currentvalue;

if (!callbacks[name]) callbacks[name] = [];

callbacks[name].push(callback);

if (storage[name] !== null && storage[name] !== currentvalue) callback(storage[name]) }

/**    * Change the value of a preference. *    * This will cause all the people subscribed to the function to recieve an     * update. *    * @param {string} name  The name of the preference * @param {string} value The new value of the preference. */   this.set = function (name, value) {       if (value === null || storage[name] === value) return;

storage[name] = value;

if (callbacks[name]) for (var i=0; i < callbacks[name].length; i++) callbacks[name][i](value);

updateCookie; }

this.get = function (name, def) {       if (storage[name]) return storage[name]; else return def; }

var storage = {}; var callbacks = {};

// Save storage into the cookie. function updateCookie {       var value = ""; for (var name in storage) {           value += '&' + encodeURIComponent(name) + "=" + encodeURIComponent(storage[name]); }

setCookie('preferences' + context, value) }   // Load storage from the cookie. function updateStorage {       var value = getCookie('preferences' + context, value); var pairs = value.split('&');

for (var i=1; i < pairs.length; i++) {           var val = pairs[i].split('='); //alert("get"+val[0]);

if (storage[val[0]] === val[1]) continue;

if (callbacks[val[0]]) {               for (var j=0; j < callbacks[val[0]].length; j++) callbacks[val[0]][j](val[1]); }

storage[val[0]] = val[1]; }   }

//__init__ updateStorage; } /** * A generic page editor, three points of intersection, * * 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 * */ function Editor {   //Singleton if (arguments.callee.instance) return arguments.callee.instance else arguments.callee.instance = this;

/**    * Get the API page object associated with this editor */   this.page = API.page(wgPageName);

/**    * Add the specific edit to the page. *    * If the node is specified it will be highlighted now, and unhighlighted * when the change is saved. *    * @param {edit}  The edit {redo:, undo:, edit:, summary:} * @param {*node} The node to highlight. */   this.addEdit = function (edit, node, fromRedo) {       if (node) {           nodestack.push(node); node.style.cssText = "border: 2px #00FF00 dashed;" }

if(! fromRedo) redostack = [];

if(! (loaded && !loading)) load else {           var ntext = false; try {               ntext = edit.edit(currentText); }           catch (e) {               this.error("ERROR:" + e); }

if (ntext) {               currentText = ntext; edit.redo; fixButtons; }           else return false; }       editstack.push(edit); }

/**    * Display an error message to the user. *    * @param {string}  The message. */   this.error = function (message) {        if (!errorlog) {           errorlog = newNode('ul',{'style': "background-color: #FFDDDD; margin: 0px -10px -10px -10px; padding: 10px;"}); presence.appendChild(errorlog); }       errorlog.appendChild(newNode('li', message)); }

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 loaded = false; // Is the page-text loaded? var loading = false; // Is the page-text loading?

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

var savebutton; var undobutton; var redobutton;

var saveCallback; // The callback returned by the api's edit function to save.

var presence; // The HTML element in the top-left var errorlog; // The ul for sticking errors in.

// Disable useless buttons, enable useful ones. function fixButtons {       if(! loaded) return; undobutton.disabled = (editstack.length ? false : true); savebutton.disabled = undobutton.disabled; redobutton.disabled = (redostack.length ? false : true); }

function updateCurrentText {       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; editstack[i].redo; }           else {               editstack = editstack.splice(0, i); break; }       }        currentText = text; fixButtons; }

function undo {       if (editstack.length == 0) return; var edit = editstack.pop; redostack.push(edit); edit.undo;

updateCurrentText; fixButtons; }

function redo {       if (redostack.length == 0) return; var edit = redostack.pop; thiz.addEdit(edit, null, true); fixButtons; }

function load {       loading = true; thiz.page.edit(function (text, _save)       {            originalText = text;            saveCallback = _save;            loading = false;            loaded = true;

savebutton = newNode('button',"Save Changes", {'click': save}); undobutton = newNode('button',"Undo", {'click': undo}); redobutton = newNode('button', "Redo", {'click':redo});

if (!presence) presence = newNode('div',{'style':"position: fixed; top:0px; left: 0px; background-color: #CCCCFF; z-index: 10;padding: 10px;"}) else presence.innerHTML = ""

presence.appendChild(newNode('p', newNode('b', "Page Editing"), newNode('br'), savebutton, newNode('br'), undobutton, redobutton))

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

updateCurrentText; });   }

function save (summary) {       if (loading) window.setTimeout(save, 500);

if (!loaded) throw "Not loaded...";

var sum = {}; for (var i=0; i<editstack.length; i++) {           sum[editstack[i].summary] = true; }       var summary = ""; for (var name in sum) {           summary += name + " "; }       editstack = []; redostack = []; loaded = false; presence.innerHTML = "Saving: " + summary; originalText = currentText; saveCallback(currentText, summary + "(Assisted)", function (res)       {            try {                presence.innerHTML = "";                presence.appendChild(newNode('p', newNode('b', "Saved"), ": "+ summary,                    newNode('a', {'href': wgScript + '?title=' + encodeURIComponent(mw.config.get('wgPageName')) + '&diff=' + encodeURIComponent(res.edit.newrevid) + '&oldid=' + encodeURIComponent(res.edit.oldrevid)}, "(Show changes)")));           }catch(e){                presence.innerHTML = ""                presence.appendChild(newNode('p',String(e)))            }

var node; var nst = [] while (node = nodestack.pop) {               node.style.cssText = "background-color: #0F0;border: 2px #0F0 solid;"; nst.push(node); }           window.setTimeout(function  {                var node;                while (node = nst.pop)                    node.style.cssText = "";            }, 400); });   } }

function AdderProtocol(editor, adders) {   for(var i=0; i < adders.length; i++) {       var adder = adders[i](editor);

adder.registerFormPositions(function (id, node, nextChild)       {            var context = adder.getFormContext(id, node, nextChild);            var form = adder.createForm(context);

if (nextChild) node.insertBefore(form, nextChild); else node.appendChild(form);

form.status = newNode('p'); form.appendChild(form.status); form.adder = adder;

form.onsubmit = function {               try {               var values = {}; var form = this; var adder = form.adder; form.status.innerHTML = ""; var submit = true; for (var i=0; i < form.elements.length; i++) //IE7 fix. {                   if(!form.elements[form.elements[i].name]) form.elements[form.elements[i].name] = form.elements[i]; }               for (var name in adder.fields) {                   if (adder.fields[name] == 'checkbox') {                       values[name] = form.elements[name].checked ? name : false; }                   else {                       values[name] = adder.fields[name](form.elements[name].value || '', function (msg) { form.status.innerHTML += msg + " "; return false}, form.elements); if (values[name] === false) {                           submit = false; }                   }                }                if (!submit) return false;

if (adder.resetFields) adder.resetFields(form); else for (var name in adder.fields) form[name].value = "";

form.status.innerHTML = 'Loading...'; adder.registerWikitext(context, values, function (text, type)               {                    if (text)                    {                        editor.page.parseFragment(text, function (res) {                           adder.registerEdits(context, values, text, res.parse.text['*'], type, editor.addEdit); form.status.innerHTML = ""; });                   }                    else                    {                        adder.registerEdits(context, values, text, text, type, editor.addEdit);                        form.status.innerHTML = "";                    }                }); } catch(e) {throw(e);form.status.innerHTML = "ERROR:" + e.description; return false;}

return false; }       });

} }

var util = { getVanillaIndexOf: function (str, text, pos) {       if (!pos) pos = 0; var cpos = 0, tpos = 0, wpos = 0, spos = 0; do       { cpos = text.indexOf('', pos) + 3;

else if (pos == wpos) pos = text.indexOf(' ', pos) + 9;

else if (pos == tpos) //FIXME pos = text.indexOf('}}', pos) + 2;

} while (pos < Infinity) return -1; },

validateNoWikisyntax: function(field, nonempty) {       return function(txt, error) {           if(/[\[\{\|#\}\]]/.test(txt)) return error("Please don't use wiki markup ([]{}#|) in the " + field +"."); if(nonempty && !txt) return error("Please specify a " + field + "."); return txt; }   },

escapeRe: function(txt) {       return txt.replace(/([\\{}(\|)[\].?*+])/g, "\\$1"); },

getTransTable: function (text, gloss) {       var pos = 0; var transect = []; while(pos > -1) {           pos = util.getVanillaIndexOf('{'+'{trans-top', text, pos+1) if (util.matchGloss(text.substr(pos, text.indexOf('\n', pos)-pos), gloss)) {               transect.push(pos); }       }        if (transect.length == 1) {           pos = transect[0]; pos = text.indexOf('}}\n', pos) + 3; var endpos = text.indexOf('{'+'{trans-bottom}}', pos); if (endpos > -1 && pos > -1) return [pos, endpos]; }

return false; },

matchGloss: function (line, gloss) {       line = line.replace('{'+'{trans-top}}','{'+'{trans-top|Translations}}'); var words = gloss.split(' '); var pos = 0; for (var i=0; i < words.length; i++) {           pos = line.indexOf(words[i], pos); if (pos == -1) return false; }       return pos > -1; },

getTransGlossText: function (node) { var ret = ''; var children = node.childNodes; for (var i=0; i<children.length; i++) {       if (children[i].nodeType == 3) ret += children[i].nodeValue; else if (children[i].tagName.match(/^(i|b)$/i)) ret += util.getTransGlossText(children[i]); }     // all characters except a-zA-Z0-9 are changed to spaces return ret.replace(/\W/g, ' '); },

getTransGloss: function (ul) {       var node = ul; while (node && node.className.indexOf('NavFrame') == -1) node = node.parentNode;

if (!node) return '';

var children = node.childNodes; for (var i=0; i< children.length; i++) {           if(children[i].className && children[i].className.indexOf('NavHead') > -1) return util.getTransGlossText(children[i]); }       return ''; } };

/** * An editor has the following: * Callbacks are used to allow generator-like code in the absense of generators * * fields {object[string] = function(string, function(string))} *  An object with the keys equal to the names of the input elements to extract *  data from. (Must be added by create form). *  The function is used to validate and preprocess, it can return a string *  or false, the error function passed in displays an error message and returns *  false. (you can display an error and return a value if you want). * * registerFormPositions {function(callback)} *  A function that calls the callback(id, parentNode, ?nextChild) to *   specify where forms should be created. * * getFormContext {function(id, parentNode, ?nextChild):context} *  A function that should return a custom object containing any context to *   be passed to the createForm function. * * createForm {function(context):form} *  A function to create an edit form based on the given context * * registerWikitext {context, values, callback(str, type)} *  This should callback for every string of wikitext that the *  edit needs. These will then be parsed and passed into *  registerEdits. If multiple strings are calledback, the type *  parameneter to the callback can identify them. Use '' if no wikitext is needed. * * registerEdits {context, values, wikitext, renderedwikitext, type, callback(edit)} *  Return an actual edit, as described below. It should do as much work as possible, *  making the undo and redo functions very simple; and should use editor.error to *   interact with the user. * * An edit has the following: * summary {string} *  A summary to be added to the edit summary * * redo {function} *  A function that can be used to create a preview of the edit. This function may *  be called multiple times (though each will be interspersed with undo). * * undo {function} *  A function that exactly undoes the effect of redo, with no side-effects. * * edit {function(text):text} *  A function that given an entries text can edit it and return a new text. *  It should return null or false to signal and error and abort the edit, it *   should also use editor.error to alert the user to errors. * */ function TranslationAdder (editor, page) {   var prefs = new Preferences('TranslationAdder'); var self = { fields: { 'lang': function (txt, error) {               if (/^[a-z][a-z][a-z]?$/.test(txt)) return txt; return error("Please use a language code. (en, fr, aaa)") },           'word': util.validateNoWikisyntax('translation', true), 'qual': util.validateNoWikisyntax('qualifier'), 'tr': util.validateNoWikisyntax('transcription'), 'alt': util.validateNoWikisyntax('display name'), 'sc': function (txt, error, fields) {               if (txt && !/^([A-Z][a-z]{3}|[a-z]{2}-Arab|polytonic|unicode)$/.test(txt)) return error("Please use a script template. (e.g. fa-Arab, Deva, polytonic)")

if (!txt) txt = prefs.get('script-' + fields.lang.value, guessScript(fields.lang.value) || ''); if (txt == 'Latn') txt = ''; return txt; },           'm': 'checkbox', 'f': 'checkbox', 'n': 'checkbox', 'c': 'checkbox', 'p': 'checkbox' //           'language': util.validateNoWikisyntax('langauge') },

resetFields: function (form) {           prefs.set('more-display', form.lang.extras.style.display); form.word.value = form.tr.value = form.alt.value = ''; form.m.checked = form.f.checked = form.n.checked = form.c.checked = form.p.checked = false; prefs.set('curlang', form.lang.value); if (form.sc.value) {               prefs.set('script-'+form.lang.value, form.sc.value); form.lang.update; }           form.sc.value = ''; },

registerFormPositions: function (callback) {           var tables = document.getElementsByTagName('table'); for (var i=0; i -1) {                   var _lists = tables[i].getElementsByTagName('ul'); var lists = []; for (var j=0; j<_lists.length; j++) if (_lists[j].parentNode.nodeName.toLowerCase == 'td') lists.push(_lists[j]);

if (lists.length == 0) {                       tables[i].getElementsByTagName('td')[0].appendChild(newNode('ul')); lists = tables[i].getElementsByTagName('ul'); }                   if (lists.length == 1) {                       var table = tables[i].getElementsByTagName('td')[2] if (table) {                           table.appendChild(newNode('ul')); lists = tables[i].getElementsByTagName('ul'); }                   }                    var li = newNode('li'); lists[lists.length - 1].appendChild(li); callback(i, li); }           }        },

getFormContext: function (id, node, nextNode) {return {'id': id, 'ul':node.parentNode}},

createForm: function (context) {           var scriptGuess = newNode('span'); var extras = newNode('p', {'style': 'display: ' + prefs.get('more-display', 'none')}, //               "Gender: ", newNode('input', {'size': 4, 'name': 'gender'}), newNode('br'),                 newNode('a', {href: '/wiki/Wiktionary:Transliteration'},"Transliteration"),": ", newNode('input', {'name': 'tr','title':"The word transliterated into the Latin alphabet."}), " (e.g. ázbuka for азбука)", newNode('br'),                "Qualifier: ", newNode('input', {'name':'qual', 'title':"A qualifier for the word"}), " (e.g. literally, formally, slang)", newNode('br'),                "Display form: ", newNode('input', {'name': 'alt','title':"The word with all of the dictionary-only diacritics."}), " (e.g. amō for amo)", newNode('br'),                "Override ", newNode('a', {href: '/wiki/Category:Script_templates'},"script"),": ", newNode('input', {'name':'sc','size':8,'title':"The script template to render this word in."}), scriptGuess, newNode('br') /* "Language name: ", newNode('input', {'name':'language'}) */)

function updateScriptGuess{ this.value = cleanLangCode(this.value); var guess = prefs.get('script-' + this.value, guessScript(this.value)); if (guess) scriptGuess.innerHTML = " (using "+guess+")"; else scriptGuess.innerHTML = ""; }           context.langInput = newNode('input', {'size': 4, 'name': 'lang', 'value': prefs.get('curlang',''),'title':'The two or three letter language code','change':updateScriptGuess}) context.langInput.update = updateScriptGuess; context.langInput.update; context.langInput.extras = extras; var showButton = newNode('span',{'click': function            {                if (extras.style.display != "block")                {                    extras.style.display = "block";                    showButton.innerHTML = " Less";                }                else                {                    extras.style.display = "none";                    showButton.innerHTML = " More";                }            }, 'style':"color: #0000FF;cursor: pointer;"}, prefs.get('more-display', 'none') == 'none' ? " More" : " Less"); context.form = newNode('form', {'context': context},                       newNode('p', newNode('b', 'Add',newNode('a',{href:"/wiki/User_talk:Conrad.Irwin/editor.js"},"?"),': '), context.langInput, ' ', newNode('input', {'name': 'word'}), newNode('input',{'type': 'submit', 'value':'Preview'}), showButton ),                newNode('input', {type: 'checkbox', name: 'm'}), 'male ',                 newNode('input', {type: 'checkbox', name: 'f'}), 'female ',                newNode('input', {type: 'checkbox', name: 'n'}), 'neuter ',                newNode('input', {type: 'checkbox', name: 'c'}), 'common gender ',                newNode('input', {type: 'checkbox', name: 'p'}), 'plural ', newNode('br'),                    extras                    ) return context.form; },

registerEdits: function (context, values, wikitext, content, type, register) {           var li = newNode('li'); li.innerHTML = content; var lang = getLangName(li);

if (lang) {               //Get all li's in this table row. - TODO: sort out nesting var lis = context.ul.parentNode.parentNode.getElementsByTagName('li'); for (var j=0; j < lis.length; j++) {                   var ln = getLangName(lis[j]); if (ln == lang) {                       //FIXME: don't do this if we have nested languages. var span = newNode('span'); span.innerHTML = ", " + content.substr(content.indexOf(':') + 1); var parent = lis[j]; register({                           'redo': function  { parent.appendChild(span) },                            'undo': function  { parent.removeChild(span) },                            'edit': self.getEditFunction(context, values, wikitext, ln, values.lang, function (text, ipos) {                                        var lineend = text.indexOf('\n', ipos); wikitext = wikitext.replace('subst:',''); wikitext = wikitext.substr(wikitext.indexOf(':') + 1); return text.substr(0, lineend) + ", " + wikitext + text.substr(lineend); }),                           'summary': 't+'+values.lang+':'+values.word+''                        }, span); return; }                   else if (ln && ln > lang) {                       var parent = lis[j]; register({                           'redo': function  {parent.parentNode.insertBefore(li, lis[j]);},                            'undo': function  {parent.parentNode.removeChild(li)},                            'edit': self.getEditFunction(context, values, wikitext, ln, getLangCode(lis[j]), function (text, ipos) {                                       var lineend = text.lastIndexOf('\n', ipos); return text.substr(0, lineend) + "\n* " + wikitext + text.substr(lineend); }),                           'summary': 't+'+values.lang+':'+values.word+''                        }, li); return; }               }            }            register ({                'redo': function  {context.ul.insertBefore(li, context.ul.lastChild);},                'undo': function  {context.ul.removeChild(li)},                'edit': self.getEditFunction(context, values, wikitext),                'summary': 't+'+values.lang+':'+values.word+''            }, li);

},

registerWikitext: function (context, values, callback) {           callback( '{'+'{subst:' + values.lang + '}}: ' +             (values.qual? '{'+'{italbrac|' + values.qual + '}} ' : '') +             '{'+'{t' + (hasWiktionary(values.lang) ? '' : 'ø') +           '|' + values.lang + '|' + values.word +             (values.m ? '|m' : '') +           (values.f ? '|f' : '') +           (values.n ? '|n' : '') +           (values.c ? '|c' : '') +           (values.p ? '|p' : '') +           (values.tr ? '|tr=' + values.tr : '') +            (values.alt ? '|alt=' + values.alt : '') +           (values.sc ? '|sc=' + values.sc : '') + '}}'); },

getEditFunction: function (context, values, wikitext, findLanguage, findLangCode, callback) {           return function(text) {               var p = util.getTransTable(text, util.getTransGloss(context.ul));

if (!p) return editor.error("Could not find translation table for '" + values.lang + ":" + values.word + "'. Please improve glosses DEBUG");

var stapos = p[0]; var endpos = p[1];

if (findLanguage) {                   var ipos = text.indexOf(findLanguage + ":", stapos); if (ipos < 0) ipos = text.indexOf(findLanguage + "]]:", stapos); if (ipos < 0) ipos = text.indexOf('{'+'{subst:'+findLangCode+'}}:', stapos); if (ipos > stapos && ipos < endpos) {                       return callback(text, ipos); }                   else {                       return editor.error("Could not find translation entry for '" + values.lang + ":" +values.word + "'. Please reformat"); }               }

return text.substr(0, endpos) + "* " + wikitext + "\n" + text.substr(endpos); };       }    }

function hasWiktionary(lang) {       var val = ",en,fr,tr,vi,ru,io,lt,el,pl,zh,fi,hu,it,ta,sv,de,ko,lo,pt,nl,ku,es,ja,id,te,gl,bg,ro,vo,ar,et,no,li,ca,sr,is,fa,af,uk,scn,br,th,fy,oc,he,sl,simple,hy,sq,tt,cs,la,zh-min-nan,da,sw,ast,ur,kk,hsb,ky,ml,hr,ang,eo,hi,gn,ia,az,co,ga,sk,csb,st,ms,nds,kl,wo,sd,ug,ti,mk,tl,an,my,gu,km,ka,cy,ts,qu,bs,fo,rw,am,mr,kn,eu,tk,su,chr,lv,wa,mn,nah,ie,yi,be,om,gd,mg,zu,iu,pa,bn,nn,si,mt,mi,tpi,dv,ps,jv,so,tg,roa-rup,ik,ha,gv,sh,ss,kw,sa,ay,uz,na,ne,jbo,tn,as,sg,lb,ks,fj,ln,mo,sm,za,pi,ba,xh,mh,bh,sn,or,ak,yo,bi,rn,av,bm,ab,to,aa,tw,dz,als,bo,rm,sc,ch,cr,tokipona,".indexOf(","+lang+",") > -1;

return val; }

function guessScript(lang) {       var dict = {ar:'Arab',hy:'Armn',xcr:'Cari',be:'Cyrl',bg:'Cyrl',mk:'Cyrl',ru:'Cyrl',uk:'Cyrl',cu:'Cyrs',bhb:'Deva',hi:'Deva',ne:'Deva',ma:'Deva',sa:'Deva',am:'Ethi',gez:'Ethi',har:'Ethi',ti:'Ethi',tig:'Ethi',xst:'Ethi',fa:'fa-Arab',ka:'Geor',got:'Goth',el:'Grek',he:'Hebr',iw:'Hebr',tmr:'Hebr',yi:'Hebr',ett:'Ital',ims:'Ital',osc:'Ital',spx:'Ital',xae:'Ital',xfa:'Ital',xrr:'Ital',xum:'Ital',xve:'Ital',xvo:'Ital',ja:'Jpan',ku:'ku-Arab',km:'Khmr',ko:'Kore',lo:'Laoo',gmy:'Linb',xlc:'Lyci',xld:'Lydi',phn:'Phnx',grc:'polytonic',si:'Sinh',syr:'Syrc',uga:'Ugar',ta:'Taml',te:'Telu',th:'Thai',peo:'Xpeo',akk:'Xsux',hit:'Xsux',sux:'Xsux',xlu:'Xsux'}

if (dict[lang]) return dict[lang];

return false; }

function cleanLangCode(lang) {       //This is ISO 639-1, it serves the purposes of mapping ISO 639-3 to ISO 639-1 and of converting some language names to ISO 639-1 codes. var key = lang.toLowerCase.replace(' ',''); var dict = {aar:"aa",afar:"aa",abk:"ab",abkhazian:"ab",afr:"af",afrikaans:"af",aka:"ak",akan:"ak",amh:"am",amharic:"am",ara:"ar",arabic:"ar",arg:"an",aragonese:"an",asm:"as",assamese:"as",ava:"av",avaric:"av",ave:"ae",avestan:"ae",aym:"ay",aymara:"ay",aze:"az",azerbaijani:"az",bak:"ba",bashkir:"ba",bam:"bm",bambara:"bm",bel:"be",belarusian:"be",ben:"bn",bengali:"bn",bis:"bi",bislama:"bi",bod:"bo",tibetan:"bo",bos:"bs",bosnian:"bs",bre:"br",breton:"br",bul:"bg",bulgarian:"bg",cat:"ca",catalan:"ca",ces:"cs",czech:"cs",cha:"ch",chamorro:"ch",che:"ce",chechen:"ce",chu:"cu",churchslavic:"cu",chv:"cv",chuvash:"cv",cor:"kw",cornish:"kw",cos:"co",corsican:"co",cre:"cr",cree:"cr",cym:"cy",welsh:"cy",dan:"da",danish:"da",deu:"de",german:"de",div:"dv",dhivehi:"dv",dzo:"dz",dzongkha:"dz",ell:"el",greek:"el",eng:"en",english:"en",epo:"eo",esperanto:"eo",est:"et",estonian:"et",eus:"eu",basque:"eu",ewe:"ee",fao:"fo",faroese:"fo",fas:"fa",persian:"fa",fij:"fj",fijian:"fj",fin:"fi",finnish:"fi",fra:"fr",french:"fr",fry:"fy",westernfrisian:"fy",ful:"ff",fulah:"ff",gla:"gd",scottishgaelic:"gd",gle:"ga",irish:"ga",glg:"gl",galician:"gl",glv:"gv",manx:"gv",grn:"gn",guarani:"gn",guj:"gu",gujarati:"gu",hat:"ht",haitian:"ht",hau:"ha",hausa:"ha",heb:"he",hebrew:"he",her:"hz",herero:"hz",hin:"hi",hindi:"hi",hmo:"ho",hirimotu:"ho",hrv:"hr",croatian:"hr",hun:"hu",hungarian:"hu",hye:"hy",armenian:"hy",ibo:"ig",igbo:"ig",ido:"io",iii:"ii",sichuanyi:"ii",iku:"iu",inuktitut:"iu",ile:"ie",interlingue:"ie",ina:"ia",interlingua:"ia",ind:"id",indonesian:"id",ipk:"ik",inupiaq:"ik",isl:"is",icelandic:"is",ita:"it",italian:"it",jav:"jv",javanese:"jv",jpn:"ja",japanese:"ja",kal:"kl",kalaallisut:"kl",kan:"kn",kannada:"kn",kas:"ks",kashmiri:"ks",kat:"ka",georgian:"ka",kau:"kr",kanuri:"kr",kaz:"kk",kazakh:"kk",khm:"km",centralkhmer:"km",kik:"ki",kikuyu:"ki",kin:"rw",kinyarwanda:"rw",kir:"ky",kirghiz:"ky",kom:"kv",komi:"kv",kon:"kg",kongo:"kg",kor:"ko",korean:"ko",kua:"kj",kuanyama:"kj",kur:"ku",kurdish:"ku",lao:"lo",lat:"la",latin:"la",lav:"lv",latvian:"lv",lim:"li",limburgan:"li",lin:"ln",lingala:"ln",lit:"lt",lithuanian:"lt",ltz:"lb",luxembourgish:"lb",lub:"lu",lubakatanga:"lu",lug:"lg",ganda:"lg",mah:"mh",marshallese:"mh",mal:"ml",malayalam:"ml",mar:"mr",marathi:"mr",mkd:"mk",macedonian:"mk",mlg:"mg",malagasy:"mg",mlt:"mt",maltese:"mt",mon:"mn",mongolian:"mn",mri:"mi",maori:"mi",msa:"ms",malay:"ms",mya:"my",burmese:"my",nau:"na",nauru:"na",nav:"nv",navajo:"nv",nbl:"nr",southndebele:"nr",nde:"nd",northndebele:"nd",ndo:"ng",ndonga:"ng",nep:"ne",nepali:"ne",nld:"nl",dutch:"nl",nno:"nn",norwegiannynorsk:"nn",nob:"nb",norwegianbokmal:"nb",nor:"no",norwegian:"no",nya:"ny",nyanja:"ny",oci:"oc",occitan:"oc",oji:"oj",ojibwa:"oj",ori:"or",oriya:"or",orm:"om",oromo:"om",oss:"os",ossetian:"os",pan:"pa",panjabi:"pa",pli:"pi",pali:"pi",pol:"pl",polish:"pl",por:"pt",portuguese:"pt",pus:"ps",pushto:"ps",que:"qu",quechua:"qu",roh:"rm",romansh:"rm",ron:"ro",romanian:"ro",run:"rn",rundi:"rn",rus:"ru",russian:"ru",sag:"sg",sango:"sg",san:"sa",sanskrit:"sa",sin:"si",sinhala:"si",slk:"sk",slovak:"sk",slv:"sl",slovenian:"sl",sme:"se",northernsami:"se",smo:"sm",samoan:"sm",sna:"sn",shona:"sn",snd:"sd",sindhi:"sd",som:"so",somali:"so",sot:"st",southernsotho:"st",spa:"es",spanish:"es",sqi:"sq",albanian:"sq",srd:"sc",sardinian:"sc",srp:"sr",serbian:"sr",ssw:"ss",swati:"ss",sun:"su",sundanese:"su",swa:"sw",swahili:"sw",swe:"sv",swedish:"sv",tah:"ty",tahitian:"ty",tam:"ta",tamil:"ta",tat:"tt",tatar:"tt",tel:"te",telugu:"te",tgk:"tg",tajik:"tg",tgl:"tl",tagalog:"tl",tha:"th",thai:"th",tir:"ti",tigrinya:"ti",ton:"to",tonga:"to",tsn:"tn",tswana:"tn",tso:"ts",tsonga:"ts",tuk:"tk",turkmen:"tk",tur:"tr",turkish:"tr",twi:"tw",uig:"ug",uighur:"ug",ukr:"uk",ukrainian:"uk",urd:"ur",urdu:"ur",uzb:"uz",uzbek:"uz",ven:"ve",venda:"ve",vie:"vi",vietnamese:"vi",vol:"vo",volapuk:"vo",wln:"wa",walloon:"wa",wol:"wo",wolof:"wo",xho:"xh",xhosa:"xh",yid:"yi",yiddish:"yi",yor:"yo",yoruba:"yo",zha:"za",zhuang:"za",zho:"zh",chinese:"zh",zul:"zu",zulu:"zu"}; if (dict[key]) return dict[key]; else return lang; }

function getLangName(li) {       var guess = li.textContent.substr(0, li.textContent.indexOf(':')); if (guess == 'Template') return false; return guess; }

function getLangCode(li) {       var spans = li.getElementsByTagName('span'); for (var i=0; i < spans.length; i++) {           if (spans[i].className == "tlc") return spans[i].innerHTML; }       return false; }

return self; }

function TranslationBalancers(editor) {   function TranslationBalancer (insertTd) {       var left; var right; var moveLeft; var moveRight;

function init {           var cns = insertTd.parentNode.childNodes; var tds = []; for (var i=0; i 0) left = left[0]; else {               left = newNode('ul'); tds[0].appendChild(left); }

right = tds[2].getElementsByTagName('ul'); if (right.length > 0) right = right[0]; else {               right = newNode('ul'); tds[2].appendChild(right); }

moveLeft = newNode('input',{'type':'submit','name':'ml', 'value':'←', 'click': function{return getEditObject('←')}}); moveRight = newNode('input',{'type':'submit','name':'mr', 'value':'→', 'click': function{return getEditObject('→')}});

var form = newNode('form', moveLeft, newNode('br'), moveRight); insertTd.appendChild(form); form.onsubmit = function { return false; } }       function getEditObject(move) {           if (move == '→') {               var li = left.lastChild; while (li && li.nodeName.toLowerCase != 'li') li = li.previousSibling;

if (li && li.childNodes.length > li.getElementsByTagName('form').length) {                   editor.addEdit({                        'redo': function  {left.removeChild(li); right.insertBefore(li, right.firstChild);},                        'undo': function  {right.removeChild(li); left.appendChild(li);},                        'edit': getEdit(util.getTransGloss(moveRight.parentNode), true),                        'summary': 't-balance'                    }); }           }            else if (move == '←') {               var li = right.firstChild; while (li && li.nodeName.toLowerCase != 'li') li = li.nextSibling;

if (li && li.childNodes.length > li.getElementsByTagName('form').length) {                   editor.addEdit({                        'redo': function  {right.removeChild(li); left.appendChild(li);},                        'undo': function  {left.removeChild(li); right.insertBefore(li, right.firstChild);},                        'edit': getEdit(util.getTransGloss(moveLeft.parentNode), false),                        'summary': 't-balance'                    }); }           }        }

function getEdit(gloss, moveRight) {           return function (text) {               var p = util.getTransTable(text, gloss);

if (!p) return editor.error("Could not find translation table, please improve glosses.");

var stapos = p[0]; var endpos = p[1];

var midpos = text.indexOf('{'+'{trans-mid}}', stapos);

if (midpos < stapos || midpos > endpos) return editor.error("Could not find {"+"{trans-mid}}, please correct page.");

var midstart = text.lastIndexOf("\n", midpos); var midend = text.indexOf("\n", midpos);

if (moveRight) {                   var linestart = text.lastIndexOf("\n", midstart - 3); return text.substr(0, linestart) + text.substr(midstart, midend - midstart) + text.substr(linestart, midstart - linestart) + text.substr(midend); }               else {                   var lineend = text.indexOf("\n", midend + 3); return text.substr(0, midstart) + text.substr(midend, lineend - midend) + text.substr(midstart, midend - midstart) + text.substr(lineend); }           }        }        init; }

var tables = document.getElementsByTagName('table'); for (var i=0; i -1) {           var tr = tables[i].getElementsByTagName('tr')[0];

var passed = 0; for (var j=0; j<tr.childNodes.length; j++) {               if (tr.childNodes[j].nodeName.toUpperCase == 'TD') {                   if (passed == 1) {                       TranslationBalancer(tr.childNodes[j]); }                   passed ++; }           }        }    }

}

$(function {    var editor = new Editor;    AdderProtocol(editor, [TranslationAdder]);    TranslationBalancers(editor); })