MediaWiki:Gadget-TranslationAdder.js
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.
- Skrip ini kekurangan sublaman pendokumenan. Sila mencipta laman pendokumenan berkaitan.
- Skrip ini bukannya sebahagian daripada mana-mana gajet (sunting definisi).
- Pautan berguna: senarai sublaman • pautan • lencongan
// <syntaxhighlight lang="javascript">
// <nowiki>
// implicit dependencies : ext.gadget.Editor,ext.gadget.LegacyScriptsNewNode,mediawiki.cookie,ext.gadget.LanguageUtils,mediawiki.util
/* jshint maxerr:1048576, strict:true, undef:true, latedef:true, sub:true */
/* global mw, newNode, importScript, importScriptURI, $, AdderWrapper, ScriptUtils, LangMetadata */
/**
* Storage of "string" preferences.
*/
function CookiePreferences(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;
var storage = {};
var defaults = {};
/**
* Change the value of a preference and store into a cookie.
*/
this.set = function (name, value) {
if (value === null || storage[name] === value)
return;
storage[name] = value;
updateCookie();
};
/**
* Get the value of a preference from the cookie or default
*
* If the preference isn't set, return the second argument or undefined.
*/
this.get = function (name, def) {
return storage[name] || defaults[name] || def;
};
/**
* let the default for get(name) be value for this session
*/
this.setDefault = function (name, value) {
defaults[name] = value;
};
// Save storage into the cookie, and if available, the local storage.
function updateCookie() {
if ('localStorage' in window && window.localStorage) {
window.localStorage.setItem('TranslationAdderSettings', JSON.stringify(storage));
}
var value = "";
for (var name in storage) {
value += '&' + encodeURIComponent(name) + "=" + encodeURIComponent(storage[name]);
}
mw.cookie.set('preferences' + context, value, {
expires: 30
});
}
// Load storage from the cookie.
// NOTE: If you wish to update the cookie format, both loading and storing
// must continue to work for 30 days.
function updateStorage() {
var value;
if ('localStorage' in window && window.localStorage) {
value = window.localStorage.getItem('TranslationAdderSettings');
if (value) {
try {
var inLocal = JSON.parse(value);
for (var key in inLocal) {
if (inLocal.hasOwnProperty(key)) {
storage[key] = inLocal[key];
}
}
return;
} catch (e) { }
}
}
value = mw.cookie.set('preferences' + context, value, {
expires: 30
}) || '';
var pairs = value.split('&');
for (var i = 1; i < pairs.length; i++) {
var val = pairs[i].split('=');
if (storage[val[0]] === val[1])
continue;
storage[val[0]] = val[1];
}
}
//__init__
updateStorage();
updateCookie(); // update this too
}
var util = {
getVanillaIndexOf: function (str, text, pos) {
function getIndex(string, regex, pos) {
var newPos = string.substring(pos).search(regex);
return newPos === -1 ? -1 : newPos + pos;
}
if (!pos)
pos = 0;
var cpos = 0,
mpos = 0;
tpos = 0,
wpos = 0,
spos = 0;
do {
cpos = text.indexOf('<!--', pos);
mpos = getIndex(text, /\{\{\s*multitrans\s*\|/, pos);
tpos = text.indexOf('{{', pos);
wpos = text.indexOf('<nowiki>', pos);
spos = text.indexOf(str, pos);
pos = Math.min(
cpos == -1 ? Infinity : cpos,
tpos == -1 ? Infinity : tpos,
wpos == -1 ? Infinity : wpos,
spos == -1 ? Infinity : spos
);
if (pos == spos)
return pos == Infinity ? -1 : pos;
else if (pos == cpos)
pos = text.indexOf('-->', pos) + 3;
else if (pos == wpos)
pos = text.indexOf('<\/nowiki>', pos) + 9;
else if (pos == mpos)
pos = getIndex(text, /\|\s*data\s*=/, pos);
else if (pos == tpos)
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;
};
},
// pos is a position in the line containing the gloss
getWikitextGloss: function (txt, pos) {
var start = txt.lastIndexOf('\n{{trans-top', pos) + 1;
var end = txt.indexOf('\n', pos);
var line = txt.substring(start, end);
var parametersMatch = /\{\{trans-top[^\|\}]*(\|[^\{\}]+)/.exec(line);
var gloss = "";
if (parametersMatch) {
var parameterText = parametersMatch[1];
var parameterRegex = /\|(?:([^=]*)=([^\|]+)|([^\|]*))/g;
var parameterMatch;
var index = 0;
while ((parameterMatch = parameterRegex.exec(parameterText))) {
var key = parameterMatch[1],
value = parameterMatch[2],
unnamedValue = parameterMatch[3];
if (key) {
if (key === "1") {
gloss = value;
}
} else {
index++;
if (index === 1) {
gloss = unnamedValue;
}
}
}
}
return gloss;
},
// get [start_pos, end_pos] of position of wikitext for trans_table containing node in text
getTransTable: function (text, node, recursive) {
var gloss = util.getTransGloss(node);
var pos = 0;
var transect = [];
while (pos > -1) {
pos = util.getVanillaIndexOf('{{trans-top', text, pos + 1);
if (pos > -1 && util.matchGloss(util.getWikitextGloss(text, pos), gloss)) {
transect.push(pos);
}
}
if (transect.length > 1) {
var poss = transect;
transect = [];
for (var i = 0; i < poss.length; i++) {
pos = poss[i];
if (util.matchGloss(gloss, util.getWikitextGloss(text, pos))) {
transect.push(pos);
}
}
if (transect.length > 1 && !recursive)
transect = util.tieBreakTransTable(text, transect, node);
}
if (transect.length == 1) {
pos = transect[0];
pos = util.getVanillaIndexOf("\n", text, pos) + 1;
var endpos = text.indexOf('{{trans-bottom}}', pos);
if (endpos > -1 && pos > 0)
return [pos, endpos];
}
console.trace("getTransTable failure; text = %o, node = %o, recursive = %o, gloss = %o", text, node, recursive, gloss);
return false;
},
// try to narrow down the correct poss if multiple matching trans tables
tieBreakTransTable: function (text, poss, node) {
if (node.nodeName == 'DIV') {
while (node && !(node.classList && node.classList.contains('NavFrame')))
node = node.parentNode;
var nodes = node.getElementsByTagName('table');
if (!nodes.length)
return poss;
node = nodes[0];
} else {
while (node && node.nodeName != 'TABLE')
node = node.parentNode;
}
var before_count = 0;
var after_count = 0;
var is_found = false;
$("table.translations").each(function () {
var gloss = util.getTransGloss(this);
if (gloss == "Translations to be checked")
return;
if (this == node) {
is_found = true;
return;
}
var pos = util.getTransTable(text, this, true);
if (pos) {
for (var j = 0; j < poss.length; j++) {
if (poss[j] == pos)
return util.tieBreakTransTable(poss.splice(j, 1), node);
}
} else {
var matched = 0;
for (var j = 0; j < poss.length; j++) {
if (util.matchGloss(util.getWikitextGloss(text, poss[j]), gloss) &&
util.matchGloss(gloss, util.getWikitextGloss(text, poss[j]))) {
matched++;
}
}
if (matched == poss.length) {
if (is_found)
after_count++;
else
before_count++;
}
}
});
if (before_count + 1 + after_count == poss.length)
return [poss[before_count]];
else
return poss;
},
matchGloss: function (line, gloss) {
if (gloss.match(/^ *$/))
return !!(line.match(/\{\{trans-top\|? *\}\}/) || line.match(/^ *$/));
var words = gloss.split(/\W+/);
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;
},
// If the translationNode is a NavHead, this goes up to the NavFrame and then
// down to the translation table. If the translationNode is the translation
// table or is inside the translation table, it goes up till it finds the
// translation table. Then it retrieves the data-gloss="..." attribute
// that is provided by {{trans-top}} and the other translation header
// templates.
// This function returns a gloss after it has been transformed by the parser
// into HTML. So the gloss will not match the gloss in the wikitext if the
// gloss in the wikitext contains templates, for instance.
getTransGloss: function(translationNode) {
var node = translationNode;
if (translationNode.nodeType === Node.ELEMENT_NODE
&& translationNode.nodeName === 'DIV'
&& translationNode.classList.contains('NavHead')) {
var navFrame = translationNode.parentNode;
if (!navFrame.classList.contains('NavFrame')) {
console.trace('No NavFrame parent of %o', translationNode);
return '';
}
node = navFrame.getElementsByClassName('translations')[0];
if (!node) {
console.trace('No child of parent of %o with "translations" class', translationNode);
return '';
}
}
while (true) {
if (node.nodeType === Node.ELEMENT_NODE
&& node.nodeName === 'TABLE'
&& node.classList.contains('translations')) {
return node.dataset.gloss;
}
node = node.parentNode;
if (!node || node.id === 'mw-parser-output') break;
}
console.trace('Found no gloss in <table class="translations" data-gloss="..."> at or above %o', translationNode);
return '';
},
isTrreq: function (li) {
var span = li.getElementsByTagName('span')[0];
return (span && span.classList.contains("trreq"));
},
genderList: {
"s": "singular", "d": "dual", "p": "plural",
"m": "masc.", "m-d": "masc. dual", "m-p": "masc. pl.",
"f": "fem.", "f-d": "fem. dual", "f-p": "fem. pl.",
"c": "common", "c-d": "common dual", "c-p": "common pl.",
"n": "neuter", "n-d": "neuter dual", "n-p": "neuter pl.",
"impf": "imperfective", "pf": "perfective"
}
};
// An adder for translations on en.wikt
function TranslationAdders(editor) {
function TranslationLabeller(insertDiv) {
var original_span;
var adder_form;
var initial_value;
var edit_button;
var editing = false;
var adderInterface = {
'fields': {
'gloss': function (txt, error) {
return util.validateNoWikisyntax('gloss', true)(txt, error);
}
},
'createForm': function () {
var thisId = "a" + String(Math.random()).replace(".", "");
adder_form = newNode('form', {
style: 'display: inline',
width: 'auto',
click: kill_event
},
newNode('label', {
'for': thisId
}, "Gloss: "),
newNode('input', {
type: 'text',
name: 'gloss',
value: initial_value,
style: 'width: 50%',
title: 'Insert a summary of the relevant definition',
id: thisId
}),
newNode('input', {
type: 'submit',
name: 'preview',
value: 'Preview'
}),
newNode('a', {
href: '/wiki/Help:Glosses'
}, 'Help?!')
);
return adder_form;
},
'onsubmit': function (values, render) {
render(values.gloss, function (new_html) {
if (editing)
toggle_editing(false);
var old_html = original_span.innerHTML;
editor.addEdit({
'undo': function () {
original_span.innerHTML = old_html;
if (!editing) toggle_editing();
},
'redo': function () {
original_span.innerHTML = new_html;
if (editing) toggle_editing();
},
'edit': function (text) {
return perform_edit(text, values.gloss);
},
'summary': 'tgloss:"' + (values.gloss.length > 50 ? values.gloss.substr(0, 50) + '...' : values.gloss + '"')
}, original_span);
});
}
};
// The actual modification to the wikitext
function perform_edit(wikitext, gloss) {
var pos = util.getTransTable(wikitext, insertDiv)[0] - 4;
var g_start = wikitext.lastIndexOf('\n{{trans-top', pos) + 1;
var g_end = wikitext.indexOf('}}\n', pos) + 2;
if (g_start === 0 || wikitext.substr(g_start, g_end - g_start).indexOf("\n") > -1) {
editor.error("Could not find translation table.");
return wikitext;
} else {
return wikitext.substr(0, g_start) + '{{trans-top|' + gloss + '}}' + wikitext.substr(g_end);
}
}
// Don't open and close box when interacting with form.
function kill_event(e) {
e.stopPropagation();
}
// What to do when the +/- button is clicked.
function toggle_editing() {
if (editing) {
adder_form.style.display = "none";
original_span.style.display = "inline";
editing = false;
return;
}
editing = true;
edit_button.text("Loading...");
editor.withCurrentText(function (currentText) {
var pos = util.getTransTable(currentText, insertDiv);
edit_button.text('±');
if (!pos)
return editor.error("Could not find translation table");
var gloss_line = currentText.substr(currentText.lastIndexOf('\n', pos[0] - 2) + 1);
gloss_line = gloss_line.substr(0, gloss_line.indexOf('\n'));
initial_value = gloss_line.replace(/^\{\{trans-top(\|(.*)|)\}\}\s*$/, "$2");
if (initial_value.indexOf("\n") > 0)
return editor.error("Internal error: guess spanning multiple lines");
if (!original_span) {
original_span = newNode('span', {
'class': 'wt-edit-recurse'
});
for (var i = 0; i < insertDiv.childNodes.length; i++) {
var child = insertDiv.childNodes[i];
if (child != edit_button[0] && child.className !== 'NavToggle') {
original_span.appendChild(insertDiv.removeChild(child));
i--;
}
}
insertDiv.appendChild(original_span);
new AdderWrapper(editor, adderInterface, insertDiv, original_span);
}
original_span.style.display = "none";
adder_form.style.display = "inline";
adder_form.getElementsByTagName('input')[0].focus();
});
}
edit_button = $('<a href="#">±</a>')
//.addClass("translation-gloss-edit-button")
.attr("title", "Edit table heading").css("padding", "2px").css("margin-left", "-5px")
.on("click", function (e) {
if (e && e.preventDefault)
e.preventDefault();
kill_event(e);
toggle_editing();
return false;
});
$(insertDiv).prepend(edit_button); //XXX: this places anchor before NavToggle which may not be a good idea
}
function TranslationAdder(insertUl) {
// Hippietrail
var langmetadata = new LangMetadata();
this.fields = {
lang: function (txt, error) {
if (txt == 'en')
return error("Please choose a foreign language. (fr, es, aaa)");
if (txt == 'nds')
return error("Please use the code nds-de for German Low German or nds-nl for Dutch Low Saxon");
if (!/^[a-z]{2,3}(?:-[a-z]{2,3}){0,2}$/.test(txt))
return error("Please use a language code. (fr, es, aaa)");
return txt;
},
word: function (txt, error) {
if (txt == '{{trreq}}')
return '{{t-needed}}';
if (txt == '{{t-needed}}')
return txt;
if (txt.indexOf(',') == -1 || forceComma) {
forceComma = false;
if (langmetadata.expectedCase(thiz.elements.lang.value, mw.config.get('wgTitle'), txt) || forceCase) {
forceCase = false;
return util.validateNoWikisyntax('translation', true)(txt, error);
}
if (prefs.get('case-knowledge', 'none') == 'none') {
return error(newNode('span',
"Translations normally don't have capital letters. If you're certain it does, you can ",
newNode('span', {
style: "color: blue; text-decoration: underline; cursor: pointer;",
click: function () {
forceCase = true;
inputForm.onsubmit();
prefs.set('case-knowledge', 'guru');
}
}, "continue by clicking here.")));
} else {
var msg = newNode('span',
newNode('span', {
style: "color: blue; text-decoration: underline; cursor: pointer;",
click: function () {
prefs.set('case-knowledge', 'none');
try {
msg.parentNode.removeChild(msg);
} catch (e) {
}
editor.undo();
}
}, "Please click undo"), " unless you are certain that this translation has a capital letter.");
error(msg);
return txt;
}
}
if (prefs.get('comma-knowledge', 'none') == 'none') {
return error(newNode('span',
"You can only add one translation at a time. If this is one translation that contains a comma, you can ",
newNode('span', {
style: "color: blue; text-decoration: underline; cursor: pointer;",
click: function () {
forceComma = true;
inputForm.onsubmit();
prefs.set('comma-knowledge', 'guru');
}
}, "add it by clicking here.")));
} else {
var msg = newNode('span',
newNode('span', {
style: "color: blue; text-decoration: underline; cursor: pointer;",
click: function () {
prefs.set('comma-knowledge', 'none');
try {
msg.parentNode.removeChild(msg);
} catch (e) {
}
editor.undo();
}
}, "Please click undo"), " if you were trying to create a list of translations in one go, this is currently not supported.");
error(msg);
return txt;
}
},
qual: util.validateNoWikisyntax('qualifier'),
tr: util.validateNoWikisyntax('transcription'),
lit: util.validateNoWikisyntax('literal'),
rawPageName: util.validateNoWikisyntax('raw page name'),
sc: function (txt, error) {
if (txt && !/^((?:[a-z][a-z][a-z]?-)?[A-Z][a-z][a-z][a-z]|Polyt|polytonic|Latnx|Latinx)$/.test(txt))
return error(newNode('span', "Please use a ", newNode('a', {
href: '/wiki/Wiktionary:List_of_scripts'
}, "valid script code"), "(e.g. fa-Arab, Deva, Polyt)"));
var scripts = (new ScriptUtils()).GetScriptsByLangCode(thiz.elements.lang.value);
if (txt && scripts.length > 0 && scripts.indexOf(txt) == -1) {
var plural = scripts.length > 1;
return error(newNode('span',
"Please use a valid script code. "
+ (plural ? "Available script codes" : "The script code used")
+ " for this language " + (plural ? "are" : "is") + " "
+ scripts.join(", ")));
}
if (!txt)
txt = prefs.get('script-' + thiz.elements.lang.value, langmetadata.guessScript(thiz.elements.lang.value) || '');
if (txt == 'Latn')
txt = '';
return txt;
},
nested: util.validateNoWikisyntax('nested'),
"s": 'checkbox',
"d": 'checkbox',
"p": 'checkbox',
"m": 'checkbox',
"m-d": 'checkbox',
"m-p": 'checkbox',
"f": 'checkbox',
"f-d": 'checkbox',
"f-p": 'checkbox',
"c": 'checkbox',
"c-d": 'checkbox',
"c-p": 'checkbox',
"n": 'checkbox',
"n-d": 'checkbox',
"n-p": 'checkbox',
"impf": 'checkbox',
"pf": 'checkbox',
nclass1: util.validateNoWikisyntax('noun class'),
nclass2: util.validateNoWikisyntax('plural class')
};
this.createForm = function () {
function createGenderNode(name, text) {
var inp = document.createElement("input");
inp.type = "checkbox";
inp.name = name;
var lbl = document.createElement("label");
lbl.appendChild(inp);
lbl.appendChild(document.createTextNode(text));
return lbl;
}
var genderControls = {};
for (var g in util.genderList) genderControls[g] = createGenderNode(g, util.genderList[g]);
var controls = {
lang: newNode('input', {
size: 4,
type: 'text',
name: 'lang',
value: prefs.get('curlang', ''),
title: 'The two or three letter ISO 639 language code'
}),
transliteration: newNode('p', newNode('a', {
href: '/wiki/Wiktionary:Transliteration'
}, "Transliteration"), ": ",
newNode('input', {
name: "tr",
title: "The word transliterated into the Latin alphabet."
}), " (e.g. zìmǔ for 字母)"),
literal: newNode('p', "Literal translation: ",
newNode('input', {
name: "lit",
title: "The literal translation of the term."
})),
qualifier: newNode('p', "Qualifier: ", newNode('input', {
name: 'qual',
title: "A qualifier for the word"
}), " (e.g. literally, formally, slang)"),
display: newNode('p', "Raw page name: ", newNode('input', {
name: 'rawPageName',
title: "A link to lemma, if a translation is not a lemma."
}), " (e.g. 疲れる for 疲れた)"),
script: newNode('p', newNode('a', {
href: '/wiki/Wiktionary:List_of_scripts'
}, "Script code"), ": ",
newNode('input', {
name: 'sc',
size: 6,
title: "The script code to apply to this word."
}), "(e.g. Cyrl for Cyrillic, Latn for Latin)", newNode('br')),
nested: newNode('p', "Nesting: ", newNode('input', {
name: 'nested',
title: "The nesting of this language"
}), " (e.g. Serbo-Croatian/Cyrillic)"),
// Genders
genders: genderControls,
// Noun class
nclass: newNode('p',
"Noun class: ", newNode('input', {
type: 'text',
size: 4,
name: 'nclass1',
title: "The noun class of the word in the singular or citation form."
}),
" Plural class: ", newNode('input', {
type: 'text',
size: 4,
name: 'nclass2',
title: "The noun class of the word in the plural form, if any."
}))
};
function createGenderDiv(genderz) {
var $div = $("<div>");
for (var i = 1; i < arguments.length; i++) $div.append(genderz[arguments[i]]);
return $div;
}
var $mdiv = createGenderDiv(controls.genders, "m", "m-d", "m-p");
var $fdiv = createGenderDiv(controls.genders, "f", "f-d", "f-p");
var $cdiv = createGenderDiv(controls.genders, "c", "c-d", "c-p");
var $ndiv = createGenderDiv(controls.genders, "n", "n-d", "n-p");
var $sdpdiv = createGenderDiv(controls.genders, "s", "d", "p");
var $pfdiv = createGenderDiv(controls.genders, "impf", "pf");
controls.gender = $("<p>").append($mdiv).append($fdiv).append($cdiv).append($ndiv).append($sdpdiv).append($pfdiv)[0];
langInput = controls.lang;
var showButton = newNode('span', {
'click': function () {
if (!advancedMode) {
advancedMode = true;
showButton.innerHTML = " Less";
} else {
advancedMode = false;
showButton.innerHTML = " More";
}
updateScriptGuess.call(langInput, true);
},
'style': "color: #0000FF;cursor: pointer;"
}, advancedMode ? " Less" : " More");
function autoTransliterate() {
var rawPageName = langmetadata.computeRawPageName(thiz.elements.lang.value, thiz.elements.word.value);
if (rawPageName && rawPageName !== thiz.elements.word.value)
thiz.elements.rawPageName.value = rawPageName;
}
function updateScriptGuess(preserve) {
preserve = (preserve === true);
//show all arguments
function show() {
for (var i = 0; i < arguments.length; i++) {
if (arguments[i].nodeName == 'P')
arguments[i].style.display = "block";
else
arguments[i].style.display = "inline";
}
}
//hide all arguments
function hide() {
for (var i = 0; i < arguments.length; i++)
arguments[i].style.display = "none";
}
//if the first argument is false hide the remaining arguments, otherwise show them.
function toggle(condition) {
if (condition) //eww...
show.apply(this, [].splice.call(arguments, 1, arguments.length - 1));
else
hide.apply(this, [].splice.call(arguments, 1, arguments.length - 1));
}
if (!preserve)
langInput.value = langmetadata.cleanLangCode(langInput.value);
var guess = prefs.get('script-' + langInput.value, langmetadata.guessScript(langInput.value || ''));
if (!preserve) {
if (guess)
thiz.elements.sc.value = guess;
else
thiz.elements.sc.value = '';
thiz.elements.nested.value = langmetadata.getNested(langInput.value || '');
autoTransliterate();
}
var lang = langInput.value;
if (!advancedMode) {
var g = langmetadata.getGenders(lang);
if (!lang) {
hide(controls.gender);
}
else if (g === undefined) {
hide(controls.gender);
}
else {
for (var genderName in util.genderList) {
toggle(g.indexOf(genderName) != -1, controls.genders[genderName]);
}
}
toggle(g, controls.gender);
toggle(langmetadata.hasNounClasses(lang), controls.nclass);
var hasAutomaticTransliteration = new window.LanguageUtils().HasAutomaticTransliteration(lang);
toggle(guess && guess != 'Latn' && !hasAutomaticTransliteration, controls.transliteration);
toggle(langmetadata.needsRawPageName(lang), controls.display);
hide(controls.literal, controls.qualifier, controls.nested); //only in more
//should be hidden if the language has only one script
toggle(langmetadata.getScripts(lang).length != 1, controls.script);
} else {
show(
controls.gender,
controls.genders['s'], controls.genders['d'], controls.genders['p'],
controls.genders['m'], controls.genders['m-d'], controls.genders['m-p'],
controls.genders['f'], controls.genders['f-d'], controls.genders['f-p'],
controls.genders['c'], controls.genders['c-d'], controls.genders['c-p'],
controls.genders['n'], controls.genders['n-d'], controls.genders['n-p'],
controls.genders['impf'], controls.genders['pf'],
controls.nclass, controls.transliteration, controls.literal, controls.qualifier, controls.display,
controls.script, controls.nested);
}
}
//autocomplete language names
function langAutoFill(e) {
e = (e || event).keyCode;
if ((e >= 33 && e <= 40) || e == 8 || e == 46 || e == 27 || e == 16) {
return;
}
var t = this,
langPrefix = t.value;
if (langPrefix.substr(0, 1) != langPrefix.substr(0, 1).toUpperCase()) {
return;
}
new mw.Api().get({
"action": "expandtemplates",
"format": "json",
"text": "{{#invoke:languages/javascript-interface|GetSingleLanguageByLanguagePrefix|" + langPrefix + "}}",
"prop": "wikitext"
}).done(function (r) {
if (r.expandtemplates && r.expandtemplates.wikitext !== "") {
var langcode = r.expandtemplates.wikitext.split(":")[0];
var langname = r.expandtemplates.wikitext.split(":")[1];
var oldLength = t.value.length;
t.value = langname;
$(t).data("langcode", langcode);
if (t.setSelectionRange) {
t.setSelectionRange(oldLength, langname.length);
} else if (t.createTextRange) {
var z = t.createTextRange();
z.moveEnd('character', 0 - z.move('character', [t.value.length, t.value = langname][0]) + t.value.length);
z.select();
}
} else {
$(t).removeData("langcode");
}
});
}
langInput.onkeyup = langAutoFill;
langInput.onblur = function () {
if ($(this).data("langcode"))
$(this).val($(this).data("langcode"));
updateScriptGuess.call(langInput);
};
window.setTimeout(function () {
updateScriptGuess.call(langInput);
}, 0);
inputForm = newNode('form',
newNode('p', newNode('a', {
href: "/wiki/User_talk:Conrad.Irwin/editor.js#Usage"
}, "Add translation"), ' ',
langInput, newNode('b', ': '), newNode('input', {
'name': 'word',
size: 20,
keyup: autoTransliterate,
change: autoTransliterate
}),
newNode('input', {
'type': 'submit',
'value': 'Preview translation'
}), showButton
),
controls.gender,
controls.nclass,
controls.transliteration,
controls.literal,
controls.display,
controls.qualifier,
controls.script,
controls.nested
);
return inputForm;
};
this.onsubmit = function (values, render) {
var wikitext;
function callRender() {
render(wikitext, function (html) {
registerEdits(values, wikitext, html);
});
}
var languagePrefix = '{{subst:#invoke:languages/templates|getByCodeAllowEtym|' + values.lang + '|getCanonicalName}}: ';
if (values.word.indexOf('{{t-needed') === 0) {
wikitext = languagePrefix + ' {{t-needed|' + values.lang + '}}';
callRender();
} else {
langmetadata.retrieveRawPageName(values.lang, values.word, values.rawPageName, function (rawPageName, needsRawPageName) {
if (needsRawPageName) {
values.rawPageName = rawPageName;
values.wordMinus = '';
} else {
values.rawPageName = '';
values.wordMinus = rawPageName;
}
langmetadata.hasWiktionaryWithEntry(values.lang, values.rawPageName || values.wordMinus, function (hasInterwiki) {
var templateBody = '|' + values.lang + '|' +
langmetadata.applyOrthographicalCorrections(values.lang, values.rawPageName || values.word) +
(values.nclass1 ? '|c' + values.nclass1 : '') +
(values.nclass2 ? '|c' + values.nclass2 : '');
// Add gender codes
templateBody += [
's', 'd', 'p', 'm', 'm-d', 'm-p', 'f', 'f-d', 'f-p',
'c', 'c-d', 'c-p', 'n', 'n-d', 'n-p', 'impf', 'pf'
].map(function(k) {
return values[k] ? '|' + k : '';
}).join("");
templateBody +=
(values.tr ? '|tr=' + values.tr : '') +
(values.lit ? '|lit=' + values.lit : '') +
(values.rawPageName ? '|alt=' + values.word : '');
/* (values.sc ? '|sc=' + values.sc : '') */
var qualifier = values.qual ? ' {{qualifier|' + values.qual + '}}' : '';
wikitext = languagePrefix + "{{" + (hasInterwiki ? "t+" : "t") + templateBody + "}}" + qualifier;
callRender();
});
});
}
};
var thiz = this;
var prefs = new CookiePreferences('EditorJs');
var langInput;
var inputForm;
var advancedMode = prefs.get('more-display', 'none') != 'none';
var forceComma = false;
var forceCase = false;
//Reset elements to default values.
function resetElements() {
if (prefs.get('more-display', 'none') != advancedMode ? 'block' : 'none')
prefs.set('more-display', advancedMode ? 'block' : 'none'); //named for compatibility
thiz.elements.word.value = thiz.elements.nclass1.value = thiz.elements.nclass2.value = thiz.elements.lit.value = thiz.elements.tr.value = thiz.elements.rawPageName.value = thiz.elements.qual.value = '';
thiz.elements['s'].checked = false;
thiz.elements['d'].checked = false;
thiz.elements['p'].checked = false;
thiz.elements['m'].checked = false;
thiz.elements['m-d'].checked = false;
thiz.elements['m-p'].checked = false;
thiz.elements['f'].checked = false;
thiz.elements['f-d'].checked = false;
thiz.elements['f-p'].checked = false;
thiz.elements['c'].checked = false;
thiz.elements['c-d'].checked = false;
thiz.elements['c-p'].checked = false;
thiz.elements['n'].checked = false;
thiz.elements['n-d'].checked = false;
thiz.elements['n-p'].checked = false;
thiz.elements['impf'].checked = false;
thiz.elements['pf'].checked = false;
prefs.set('curlang', thiz.elements.lang.value);
if ((thiz.elements.sc.value || 'Latn') != (prefs.get('script-' + thiz.elements.lang.value, langmetadata.guessScript(thiz.elements.lang.value) || 'Latn'))) {
prefs.set('script-' + thiz.elements.lang.value, thiz.elements.sc.value);
// Uncaught TypeError: Object #<HTMLInputElement> has no method 'update'
thiz.elements.lang.update();
}
}
// This is onsubmit after the wikitext has been rendered to give content
function registerEdits(values, wikitext, content) {
var li = newNode('li', {
'class': 'trans-' + values.lang
});
li.innerHTML = content;
var lang = getLangName(li);
var x = langmetadata.applyOrthographicalCorrections(values.lang, values.rawPageName || values.word);
var resp = x;
if (x.indexOf("{{") < 0) {
new mw.Api().get({
"action": "expandtemplates",
"format": "json",
"text": "{{ll|" + values.lang + "|" + x + "}}",
"prop": "wikitext"
}, {"async": false}).done(function (r) {
if (r.expandtemplates && r.expandtemplates.wikitext) {
// [[page#Language|display]]
// resp = r.expandtemplates.wikitext;
// [[page|display]]
// resp = r.expandtemplates.wikitext.replace(/#[^|]*/, "");
// [[page]]
resp = r.expandtemplates.wikitext.replace(/#[^\]]*/, "");
}
}).fail(function(r) {
console.error(r);
resp = resp.includes("[[") ? resp : "[[" + resp + "]]";
});
}
var summary = 't+' + values.lang + ':' + resp;
var insertBefore = null;
var nextLanguage = null;
var nestedHeading, nestedLang;
var nested = values.nested.split('/');
if (nested.length > 1) {
nestedHeading = nested[0];
nestedLang = nested[1];
if (nestedHeading === '')
nestedHeading = lang;
content = content.replace(/.*: /, nestedLang + ": ");
wikitext = wikitext.replace("subst:", "").replace(/.*: /, nestedLang + ": ");
} else {
nestedHeading = values.nested;
nestedLang = lang;
}
var nestedWikitext = "\n* " + nestedHeading + ":\n*: " + wikitext;
function addEdit(edit, span) {
var fields = ["lang", "word", "nclass1", "nclass2", "rawPageName", "qual", "tr", "lit", "sc"];
editor.addEdit({
'undo': function () {
edit.undo();
if (thiz.elements.word.value === "" &&
thiz.elements.nclass1.value === "" &&
thiz.elements.nclass2.value === "" &&
thiz.elements.tr.value === "" &&
thiz.elements.lit.value === "" &&
thiz.elements.rawPageName.value === "" &&
thiz.elements.qual.value === "") {
for (var i = 0; i < fields.length; i++) {
thiz.elements[fields[i]].value = values[fields[i]];
}
for (var genderName in util.genderList) {
thiz.elements[genderName].checked = values[genderName];
}
}
},
'redo': function () {
edit.redo();
for (var i = 0; i < fields.length; i++) {
if (thiz.elements[fields[i]].value != values[fields[i]])
return;
}
resetElements();
},
'edit': edit.edit,
'summary': summary
}, span);
}
if (lang) {
//Get all li's in this table row.
var lss = $(insertUl).parent().parent().find('li').get();
var dds = $(insertUl).parent().parent().find('dd').get();
var lis = lss.concat(dds);
for (var j = 0; j < lis.length; j++) {
if (lis[j].getElementsByTagName('form').length > 0)
continue;
var ln = getLangName(lis[j]);
if (ln == lang) {
if (values.word == '{{t-needed}}') {
addEdit({
'redo': function () {
},
'undo': function () {
},
'edit': function () {
return editor.error("Can not add a translation request for a language with translations");
}
});
return resetElements();
}
var span = newNode('span');
var parent = lis[j];
if (util.isTrreq(parent)) {
span.innerHTML = content.substr(content.indexOf(':') + 1);
var trspan = parent.getElementsByTagName('span')[0];
addEdit({
'redo': function () {
parent.removeChild(trspan);
parent.appendChild(span);
},
'undo': function () {
parent.removeChild(span);
parent.appendChild(trspan);
},
'edit': getEditFunction(values, wikitext, ln, values.lang, true, function (text, ipos) {
//Converting a Translation request into a translation
var lineend = text.indexOf('\n', ipos);
return text.substr(0, ipos) + wikitext + text.substr(lineend);
})
}, span);
return resetElements();
} else {
if (parent.getElementsByTagName('ul').length + parent.getElementsByTagName('dl').length == 0) {
span.innerHTML = ", " + content.substr(content.indexOf(':') + 1);
addEdit({
'redo': function () {
parent.appendChild(span)
},
'undo': function () {
parent.removeChild(span)
},
'edit': getEditFunction(values, wikitext, ln, values.lang, false, function (text, ipos) {
//We are adding the wikitext to a list of translations that already exists.
var lineend = text.indexOf('\n', ipos);
var wt = wikitext.replace('subst:#invoke:', '');
wt = wt.substr(wt.indexOf(':') + 1);
return text.substr(0, lineend) + "," + wt + text.substr(lineend);
})
}, span);
return resetElements();
} else {
var node = parent.firstChild;
var hastrans = false;
while (node) {
if (node.nodeType == 1) {
var nn = node.nodeName;
if (nn == 'UL' || nn == 'DL') {
// If we want to use the dialectical nesting for orthographical nesting
// then we need to skip this (otherwise perfect) match.
if (!hastrans && nestedHeading == ln) {
node = node.nextSibling;
continue;
}
span.innerHTML = (hastrans ? ", " : " ") + content.substr(content.indexOf(':') + 1);
addEdit({
'redo': function () {
parent.insertBefore(span, node)
},
'undo': function () {
parent.removeChild(span)
},
'edit': getEditFunction(values, wikitext, ln, values.lang, false, function (text, ipos) {
//Adding the translation to a language that has nested translations under it
var lineend = text.indexOf('\n', ipos);
var wt = wikitext.replace('subst:#invoke:', '');
wt = wt.substr(wt.indexOf(':') + 1);
return text.substr(0, lineend) + (hastrans ? "," : "") + wt + text.substr(lineend);
})
}, span);
return resetElements();
} else {
hastrans = true;
}
}
node = node.nextSibling;
}
}
}
} else if (ln && ln > lang && (!nextLanguage || ln < nextLanguage) && lis[j].parentNode.parentNode.nodeName != 'LI') {
nextLanguage = ln;
var parent = lis[j];
insertBefore = [{
'redo': function () {
parent.parentNode.insertBefore(li, parent);
},
'undo': function () {
parent.parentNode.removeChild(li)
},
'edit': getEditFunction(values, wikitext, ln, getLangCode(parent), util.isTrreq(parent), function (text, ipos) {
//Adding a new language's translation before another language's translation
var lineend = text.lastIndexOf('\n', ipos);
return text.substr(0, lineend) + "\n* " + wikitext + text.substr(lineend);
})
}, li];
}
}
}
if (values.nested) {
nextLanguage = null;
insertBefore = null;
li.innerHTML = nestedHeading + ":" + "<dl><dd class=\"trans-" + values.lang + "\">" + content + "</dd></dl>";
var lis = insertUl.parentNode.parentNode.getElementsByTagName('li');
for (var j = 0; j < lis.length; j++) {
//Ignore the editor form
if (lis[j].getElementsByTagName('form').length > 0)
continue;
//Don't look at nested translations
if (lis[j].parentNode.parentNode.nodeName != 'TD')
continue;
var ln = getLangName(lis[j]);
if (ln == nestedHeading) {
var sublis = lis[j].getElementsByTagName('li');
if (!sublis.length)
sublis = lis[j].getElementsByTagName('dd');
if (sublis.length == 0) {
var parent = lis[j];
var dd = newNode('dd', {
'class': 'trans-' + values.lang
});
var dl = newNode('dl', dd);
dd.innerHTML = content;
addEdit({
'redo': function () {
parent.appendChild(dl);
},
'undo': function () {
parent.removeChild(dl);
},
'edit': getEditFunction(values, wikitext, nestedHeading, getLangCode(parent), util.isTrreq(parent), function (text, ipos) {
//Adding a new dl to an existing translation line
var lineend = text.indexOf('\n', ipos);
return text.substr(0, lineend) + "\n*: " + wikitext + text.substr(lineend);
})
}, dd);
return resetElements();
} else {
var dd = newNode(sublis[0].nodeName, {
'class': 'trans-' + values.lang
});
var linestart = dd.nodeName == 'DD' ? '\n*: ' : '\n** ';
dd.innerHTML = content;
for (var k = 0; k < sublis.length; k++) {
var subln = getLangName(sublis[k]);
var parent = sublis[k];
if (subln == nestedLang) {
var span = newNode('span');
span.innerHTML = ", " + content.substr(content.indexOf(':') + 1);
addEdit({
'redo': function () {
parent.appendChild(span)
},
'undo': function () {
parent.removeChild(span)
},
'edit': getEditFunction(values, wikitext, nestedLang, values.lang, false, function (text, ipos) {
// Adding the wikitext to a list of translations that already exists.
var lineend = text.indexOf('\n', ipos);
var wt = wikitext.replace('subst:#invoke:', '');
wt = wt.substr(wt.indexOf(':') + 1);
return text.substr(0, lineend) + "," + wt + text.substr(lineend);
})
}, span);
return resetElements();
} else if (langmetadata.nestsBefore(nestedLang, subln)) {
addEdit({
'redo': function () {
parent.parentNode.insertBefore(dd, parent);
},
'undo': function () {
parent.parentNode.removeChild(dd);
},
'edit': getEditFunction(values, wikitext, subln, getLangCode(parent), util.isTrreq(parent),
function (text, ipos) {
// Adding a nested translation in-order
var lineend = text.lastIndexOf('\n', ipos);
return text.substr(0, lineend) + linestart + wikitext + text.substr(lineend);
})
}, dd);
return resetElements();
}
}
addEdit({
'redo': function () {
parent.parentNode.appendChild(dd);
},
'undo': function () {
parent.parentNode.removeChild(dd);
},
'edit': getEditFunction(values, wikitext, subln, getLangCode(parent), util.isTrreq(parent),
function (text, ipos) {
// Adding a nested translation at the end of its group
var lineend = text.indexOf('\n', ipos);
return text.substr(0, lineend) + linestart + wikitext + text.substr(lineend);
})
}, dd);
return resetElements();
}
} else if (ln && ln > nestedHeading && (!nextLanguage || ln < nextLanguage)) {
nextLanguage = ln;
var parent = lis[j];
insertBefore = [{
'redo': function () {
parent.parentNode.insertBefore(li, parent);
},
'undo': function () {
parent.parentNode.removeChild(li)
},
'edit': getEditFunction(values, wikitext, ln, getLangCode(parent), util.isTrreq(parent), function (text, ipos) {
//Adding a new nested translation section.
var lineend = text.lastIndexOf('\n', ipos);
return text.substr(0, lineend) + nestedWikitext + text.substr(lineend);
})
}, li];
}
}
wikitext = nestedHeading + ":\n*: " + wikitext;
}
li.className = "trans-" + values.lang;
if (insertBefore) {
addEdit(insertBefore[0], insertBefore[1]);
} else {
//Append the translations to the end (no better way found)
addEdit({
'redo': function () {
insertUl.appendChild(li);
},
'undo': function () {
insertUl.removeChild(li)
},
'edit': getEditFunction(values, wikitext)
}, li);
}
return resetElements();
}
//Get the wikitext modification for the current form submission.
function getEditFunction(values, wikitext, findLanguage, findLangCode, trreq, callback) {
return function (text) {
var p = util.getTransTable(text, insertUl);
if (!p)
return editor.error("Could not find translation table for '"
+ values.lang + ":" + values.word
+ "'. Glosses should be unique");
var stapos = p[0];
var endpos = p[1];
var trreqLegacy = false;
if (findLanguage) {
var ipos = 0;
if (trreq) {
ipos = text.substr(stapos).search(new RegExp(mw.util.escapeRegExp(findLanguage) + ":\\s*\\{\\{t-needed(?:\\|" + findLangCode + "}})?")) + stapos;
if (ipos < stapos || ipos > endpos)
ipos = text.indexOf('{{trreq|' + findLanguage + '}}', stapos);
if (ipos < stapos || ipos > endpos)
ipos = text.indexOf('{{trreq|' + findLangCode + '}}', stapos);
}
// If we have a nested trreq, then we still need to look for the non-trreq form of the heading language
if (!trreq || ipos < stapos || ipos > endpos) {
var escapedLang = mw.util.escapeRegExp(findLanguage);
//if the translation should not be nested it should not go before any nested language
var regexByShouldFindSubLanguage = values.nested != "" ? "[:*]?" : "";
var possibleRegexes = [
RegExp("\\*" + regexByShouldFindSubLanguage + " ?\\[\\[" + escapedLang + "\\]\\]:"),
RegExp("\\*" + regexByShouldFindSubLanguage + " ?" + escapedLang + ":"),
RegExp("\\*" + regexByShouldFindSubLanguage + " ?\\{\\{ttbc\\|" + escapedLang + "}}:")
];
ipos = text.substr(stapos).search(possibleRegexes[0]) + stapos;
if (ipos < stapos || ipos > endpos)
ipos = text.substr(stapos).search(possibleRegexes[1]) + stapos;
if (ipos < stapos || ipos > endpos)
ipos = text.substr(stapos).search(possibleRegexes[2]) + stapos;
if (ipos < stapos || ipos > endpos)
ipos = text.indexOf('{{subst:#invoke:languages/templates|getByCodeAllowEtym|' + findLangCode + '|getCanonicalName}}:', stapos);
}
if (ipos >= stapos && ipos < endpos) {
return callback(text, ipos, trreq);
} 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);
};
}
// For an <li> in well-formed translation sections, return the language name.
function getLangName(li) {
var guess = li.textContent || li.innerText;
if (guess)
guess = guess.substr(0, guess.indexOf(':'));
if (guess == 'Template') {
return false;
}
return guess.replace(/^[\s\n]*/, '');
}
// Try to get the language code from an <li> containing {{t}}, {{t+}} or {{t-}}
function getLangCode(li) {
if (li.className.indexOf('trans-') === 0)
return li.className.substr(6);
var spans = li.getElementsByTagName('span');
for (var i = 0; i < spans.length; i++) {
if (spans[i].lang) {
return spans[i].lang;
}
if (spans[i].dataset && spans[i].dataset.lang) {
return spans[i].dataset.lang;
}
}
return false;
}
}
function createTranslationAdderRow(li) {
var $ul = $("<ul>").append(li);
return $("<tr>")
.append($("<td>"))
.append($("<td>"))
.append($("<td>").css("text-align", "left").append($ul));
}
$('table.translations').each(function () {
if (util.getTransGloss(this) != 'Translations to be checked') {
var uls = $(this).find("td:not(.mw-hiero-table td) > ul").get();
if (uls.length == 0) {
$(this).find('td:not(.mw-hiero-table td)')[0].appendChild(newNode('ul'));
uls = this.getElementsByTagName('ul');
}
if (uls.length == 1) {
var td = $(this).find('td:not(.mw-hiero-table td)')[2];
if (td) {
td.appendChild(newNode('ul'));
uls = this.getElementsByTagName('ul');
}
}
if (uls) {
var li = document.createElement('li');
var ul = uls[uls.length - 1];
$(this).append(createTranslationAdderRow(li));
new AdderWrapper(editor, new TranslationAdder(ul), li);
if (true) {// XXX: why not? (new CookiePreferences('EditorJs')).get('labeller') == 'true') {
var div = $(this).parent().parent().find('div.NavHead')[0];
if (div) {
new TranslationLabeller(div)
}
}
}
}
});
}
$(function () {
// Check if we are on a sensible page
var isOnPrintable = mw.util.getParamValue("printable") !== null;
var isDiff = mw.util.getParamValue("diff") !== null || mw.util.getParamValue("oldid") !== null;
if (mw.config.get('wgAction') != 'view' || isOnPrintable || isDiff)
return;
var editor = new Editor();
TranslationAdders(editor);
/*
// Check that we have not been disabled
var prefs = new CookiePreferences('EditorJs');
if (prefs.get('enabled', 'true') == 'true') {
if (!window.loadedEditor) {
prefs.setDefault('labeller', mw.config.get('wgUserName') ? 'true' : 'false');
window.loadedEditor = true;
}
}
// The enable-disable button on WT:EDIT
var node = document.getElementById('editor-js-disable-button');
if (node) {
node.innerHTML = "";
var toggle = newNode('span', {
click: function() {
if (prefs.get('enabled', 'true') == 'true') {
toggle.innerHTML = "Enable";
prefs.set('enabled', 'false');
} else {
toggle.innerHTML = "Disable";
prefs.set('enabled', 'true');
}
}
}, (prefs.get('enabled', 'true') == 'true' ? 'Disable' : 'Enable'))
node.appendChild(toggle);
}
var node = document.getElementById("editor-js-labeller-button");
if (node) {
node.innerHTML = "";
var toggle2 = newNode('span', {
click: function() {
if (prefs.get('labeller') == 'true') {
toggle2.innerHTML = "Enable";
prefs.set('labeller', 'false');
} else {
toggle2.innerHTML = "Disable";
prefs.set('labeller', 'true');
}
}
}, (prefs.get('labeller') == 'true' ? 'Disable' : 'Enable'))
node.appendChild(toggle2);
}*/
});
//</nowiki>
// </syntaxhighlight>