User:Kephir/gadgets/xte.js

// more information: User:Kephir/gadgets/xte /*jshint shadow:true, scripturl:true, undef:true, latedef:true, boss:true, loopfunc:true, unused:true */ // /*global mw, jQuery */ mw.loader.using(['mediawiki.util', 'mediawiki.api', 'mediawiki.Uri'], function { "use strict";

var code2nameLookup = null; var code2scLookup = null;

var name2codeLookup = { // "false" values indicate translation groups, i.e. names with no own code under which // distinct languages are kept together, like Chinese which contains the Mandarin (cmn), // Cantonese (yue) and Min Nan (nan) varieties as sub-items. xte warns when translations // are found directly under group headers, and ignores them otherwise. // Greek (el) for example should not be added here, because translations directly under // "Greek" item are accepted, even though "Greek" serves as a parent for Ancient Greek (grc) "Apache"              : false, "Sorbian"             : false, "Berber"              : false, "Sami"                : false, "Marquesan"           : false, "Miwok"               : false,

// nulls are explicit negative entries to avtoid having xte round-trip // to servers with known bad names. why does it even try these? when encountering // a translation sub-item, xte prepends the sub-item name before the item name. // this works well for "Egyptian" (otherwise egy) under "Arabic" (otherwise ar) // = "Egyptian Arabic" (arz), but it also results in "Lower Sorbian Sorbian". "Lower Sorbian Sorbian": null, "Upper Sorbian Sorbian": null };

var wgPageName = mw.config.get('wgPageName');

jQuery.ajax({	'url': mw.config.get('wgServer') + mw.config.get('wgScript') + '?action=raw&templates=expand&ctype=text/javascript&maxage=172800&title=User:Kephir/gadgets/xte/langdata.js',	'cache': true,	'dataType': 'json',	'success': function (data) {		try {			code2nameLookup = {};			code2scLookup = {};			for (var key in data) {				code2nameLookup[key] = data[key].names[0];				code2scLookup[key] = data[key].scripts[0];			}			for (var key in code2nameLookup) {				name2codeLookup[code2nameLookup[key]] = key;			}		} catch (e) {			mw.util.jsMessage('xte: error while processing language data. See console for details.');			throw e;		}	},	'error': function (xhr, message) {		if (message !== 'abort')			mw.util.jsMessage('xte: failed to grab language data. See console for details.');		console.error(arguments);	} });

/*

Scripts
*/ var scname2scLookup = { "Roman"   : "Latn", "Latin"   : "Latn", "Cyrillic" : "Cyrl", "Mandarin" : null, "Cantonese": null };

function code2name(code) { if (code in code2nameLookup) return code2nameLookup[code]; else { var api = new mw.Api; var result = null; api.get({			'action': 'expandtemplates',			'text': ''		}, {			async: false,			success: function (obj) {				result = obj.expandtemplates['*'];				if (/class="error"/.test(result))					result = null;				code2nameLookup[code] = result;			}		}); return result; } }

function name2code(name) { if (name in name2codeLookup) return name2codeLookup[name]; else { var api = new mw.Api; var result = null; api.get({			'action': 'expandtemplates',			'text': 		}, {			async: false,			success: function (obj) {				result = obj.expandtemplates['*'];				if (result === )					result = null;				if (/^\[\[/.test(result))					result = null;				if (/class="error"/.test(result))					result = null;				name2codeLookup[name] = result;			}		}); return result; } }

function code2sc(code) { if (code in code2scLookup) return code2scLookup[code]; else { console.warn('script for "' + code + '" is missing from the lookup table; falling back to API query'); var api = new mw.Api; var result = null; api.get({			'action': 'expandtemplates',			'text': ''		}, {			async: false,			success: function (obj) {				result = obj.expandtemplates['*'];				if (/class="error"/.test(result))					result = null;				code2scLookup[code] = result = obj.expandtemplates['*'];			}		}); return result; } }

function scname2sc(scname) { if (scname in scname2scLookup) return scname2scLookup[scname]; else { console.warn('script code for "' + scname + '" is missing from the lookup table; falling back to API query'); var api = new mw.Api; var result = null; api.get({			'action': 'expandtemplates',			'text': 		}, {			async: false,			success: function (obj) {				result = obj.expandtemplates['*'];				if (result === )					result = null;				scname2scLookup[scname] = result;			}		}); return result; } }

function el(tag, child, attr, events) { var node = document.createElement(tag);

if (child) for (var i = 0; i < child.length; ++i) { var ch = child[i]; if (typeof ch === 'string') ch = document.createTextNode(ch); else if ((ch === null) || (ch === void(null))) continue; node.appendChild(ch); }

if (attr) for (var key in attr) { node.setAttribute(key, attr[key]); }

if (events) for (var key in events) { node.addEventListener(key, events[key], false); }

return node; }

function ih(name, val) { var it = el('input', null, { 'type': 'hidden', 'name': name }); if (val) it.value = val; return it; }

function count(subs, s) { var i = 0, j = -subs.length; while ((j = s.indexOf(subs, j + subs.length)) !== -1) ++i; return i; }

function fix(wikicode, susp, errors) { var i, dirty = false; var name = null, code = null, before, subbef; function addError(message) { errors[errors.length] = { 'line': i + 1, 'code': code, 'mesg': message };	}	var m, lines = wikicode.split('\n'); var mode = 0; if (!(susp instanceof Array)) susp = []; if (!(errors instanceof Array)) errors = []; for (i = 0; i < lines.length; ++i) { var ttbcline = false, issub = false, entrysc = ''; if (m = /^\s*\{\{(checktrans|ttbc)-top(\||}})/.exec(lines[i])) { name = code = null; mode = 2; continue; } else if (m = /^\s*\{\{(checktrans|trans|ttbc)-bottom(\||}})\s*/.exec(lines[i])) { name = code = null; if (m[0] !== lines[i]) addError('Stray markup after translation table end'); mode = 0; continue; } else if (m = /^\s*\{\{trans-top(?:-also)?(\||}})\s*/.exec(lines[i])) { name = code = null; mode = 1; continue; }		if (mode === 0) continue; if (m = /^\*(?![\*:])\s*(?:\[\[)?([^:\{]+?)(?:\]\])?\s*:\s*/.exec(lines[i])) { before = subbef = m[1]; name = m[1]; code = name2code(m[1]); if (code === false) { // language group, "Sorbian", which serves as a group for "Lower Sorbian" and "Upper Sorbian" if (lines[i] !== m[0]) { addError('Language group "' + m[1] + '" has direct items; these should be placed under a more specific header.'); susp.push('"' + m[1] + '"'); lines[i] = '* : ' + lines[i].substr(m[0].length); dirty = true; continue; } else continue; }			if (!code) { if ((name === 'Serbian') || (name === 'Croatian') || (name === 'Bosnian')) { ttbcline = true; addError('Warning: converting "' + name + '" into "Serbo-Croatian" [sh].'); name = 'Serbo-Croatian'; subbef = 'Serbo-Croatian :'; code = 'sh'; susp.push("sh"); } else { code = name = null; addError('"' + m[1] + '" does not refer to a known language.'); susp.push('"' + m[1] + '"'); lines[i] = '* : ' + lines[i].substr(m[0].length); dirty = true; continue; }			}		} else if (m = /^\*\s*(\{\{ttbc\s*\|([^|]*?)}})(\s*\{\{\s*attention\s*\|.*?}})?\s*:\s*/.exec(lines[i])) { before = subbef = m[1]; code = m[2]; name = code2name(m[2]); ttbcline = true; if (!name) { if (code = name2code(m[2])) { name = m[2]; before = subbef = ''; } else { code = name = null; addError('"' + m[2] + '" under ttbc does not refer to a known language.'); susp.push('"' + m[2] + '"'); continue; }			}		} else if (m = /^\*[*:]\s*(?:\[\[)?([^:\{<]+?)(?:\]\])?\s*:\s*/.exec(lines[i])) { subbef = m[1]; var s;			if (s = scname2sc(subbef)) { if (code === null) continue; entrysc = s; } else if (s = name2code(subbef + ' ' + name)) { // prepend sub-item name before group name: "Ancient" + "Greek", "Lower" + "Sorbian", etc.				code = s;				name = subbef; } else if (s = name2code(subbef)) { code = s;				name = subbef; } else if (s = name2code(subbef + ' ' + before)) { code = s;				name = subbef; } else { if (subbef === 'Teochew') { ttbcline = true; code = 'nan'; addError('Warning: treating "Teochew" header as if it were Min Nan [nan]. If you want to merge it with Min Nan, use .'); susp.push('nan (Teochew)'); } else { addError('Failed to recognize "' + subbef + '" sub-item for "' + before + '"'); lines[i] = '*: : ' + lines[i].substr(m[0].length); dirty = true; continue; }			}			issub = true; } else if (m = /^\s*\{\{(checktrans|trans|ttbc)-mid(\||}})\s*/.exec(lines[i])) { name = code = null; if (m[0] !== lines[i]) addError('Stray markup after translation table midbreak'); continue; } else if (m = /^\*\s*\{\{trreq\|(.*?)}}\s*?(?=\s|$)/.exec(lines[i])) { name = code = null; if (!(name = code2name(m[1]))) { if (!(code = name2code(m[1]))) { addError('"' + m[1] + '" under is not recognised'); } else { if (m[0] !== lines[i]) addError('Stray markup after '); lines[i] = '* ' + lines[i].substr(m[0].length); continue; }			}			if (m[0] !== lines[i]) addError('Stray markup after '); continue; } else { addError('Line not recognised'); continue; }		var parsed = lines[i].substr(m[0].length); var token = ''; var t9ns = []; var or = 0, oc = 0, os = 0, ot = 0; while (parsed !== '') { if (!(m = /\s*[,;\/]\s*/.exec(parsed))) { t9ns[t9ns.length] = token + parsed; break; }			var pre = parsed.substr(0, parsed.indexOf(m[0])); or += count('(', pre) - count(')', pre); oc += count('{', pre) - count('}', pre); os += count('[', pre) - count(']', pre); ot += count('<', pre) - count('>', pre); if ((or === 0) && (oc === 0) && (os === 0) && (ot === 0)) { t9ns[t9ns.length] = token + pre; token = ''; } else { token += pre + m[0]; }			parsed = parsed.substr(parsed.indexOf(m[0]) + m[0].length); }		var allok = true; for (var j = 0; j < t9ns.length; ++j) { if (/\{\{\s*(t[-+]check|t-needed)\|/.test(t9ns[j])) continue; var suspc = (mode === 2) || ttbcline;

// validate (rudimentary!) if (!t9ns[j].replace(//g, ).replace(/\s*\{\{t\+?\|(\|tr=\{\{IPAchar\|[^}|]+}}|[^}])+}}\s*/, ).replace(/\s*\{\{(qualifier|i)\|[^}]+}}\s*/g, '')) { if (suspc) { t9ns[j] = t9ns[j].replace(/\{\{t(\+?)\|/, function (m, t) {						return '{{t' + (t ? '+' : '-') + 'check|';					}); }				continue; }			if (/\{\{t/.test(t9ns[j])) { addError('Translation item "' + t9ns[j] + '" failed to validate despite containing undefined. Will reconstruct.'); suspc = true; }

var inp = t9ns[j]; // validation failed. deconstruct undefineds inp = inp.replace(/\{\{t\+?(\|[^|}]+\|[^|}]+)((\|tr=\{\{IPAchar\|[^}|]+}}|\|[^}|]+)*)}}(?!\))/g, function (m, ct, rest) { var newrest = , g = , tr = ''; while (rest) { var r = rest.replace(/^(\|[^|=]+)+(?=\||$)/, function (m) {						g += m;						return ;						}).replace(/^\|alt=([^|}]+)/, function (m, a) {						if (newrest)							return m;						newrest = '|' + a;						return ;					}).replace(/^\|sc=([^|}]+)/, ''); if (r === rest) return m; // failed to deconstruct undefined rest = r;				} return  + (tr ? ' (' + tr + ')' : ) + (g ? ' ' : ''); });			if (/\{\{t/.test(inp)) {				addError('Deconstruction failed for "' + t9ns[j] + '". Please fix it manually.');				allok = false;				continue;			}

console.info('transforming ', t9ns[j]); var qual = , word = , gend = , tr = , sc = entrysc, alt = ''; inp = inp.replace(/\{\{l[\|/]([a-z\-]+)\|([^=|}]+(?:\|[^=|}]+)?)}}/g, function (m, lc, cont) {				if (lc !== code) {					// I have never actually encountered this, but just to be safe...					addError('Warning: template used with non-matching language code ' + lc);					suspc = true;					return m;				}				return  + cont + ;			}); if (m = /\s*\{\{(?:qualifier|i)\|(.+?)}}\s*/.exec(inp)) { qual = '; ' + m[1]; inp = inp.replace(m[0], ''); } else { inp = inp.replace(/\s*(?:\(|\([A-Za-z,;:'"\- ]+?)(?:\)|\))/g, function(m, inside) {					console.info('probably qualifier: ', arguments);					qual += '; ' + inside;					return '';				});			}			var getWord = function (m) {				var n;				console.info('words: ', m);				if (!/^\s*(\[\]*?|[^ \[\]\{\}'<>]+)$/.test(m)) {					var n = /^([^\[\{]*)\[\[([^#\|\]]*?)(?:#([^\|\]]*?))?(?:\|([^#\]]*?))?]]([^\[\{]*)$/.exec(m);					if (n) {						if (!(((n[2] === n[4]) || !n[2]) && (n[3] === name))) {							addError('Warning: single piped or non-clean link in an item "' + m + '". Assuming an inflected form or vocalised spelling. See User:Kephir/gadgets/xte.');							suspc = 'was "' + m + '" - assumed inflected form or vocalised spelling; please verify if linking is acceptable. see User:Kephir/gadgets/xte';							susp.push(code);							alt = n[1] + (n[4] || n[2] || wgPageName) + n[5]; word = n[2] || wgPageName; return ''; }					} else { addError('Warning: multiple words in an item: "' + m + '". Assuming it is a sum-of-parts. If it is not, remove the links to individual words.'); word = m;						if (!/\[\[.*?]]/.test(word)) { word = word.replace(/([^{}\[\]\(\) \t,]+)/g, function (wm) {								return  + wm + ;							}); }						suspc = 'was "' + m + '" - assumed sum-of-parts; if an idiom, remove the wikilinks to the individual words. see User:Kephir/gadgets/xte'; susp.push(code); return ''; }				}				word = m.replace(/\[\[(?:[^|\]]+?\|)?(.+?)]]/g, '$1').replace(/\s+/g, ' '); return ''; };			if (m = /^\{\{((?:[a-z][a-z]-)?[A-Z][a-z][a-z][a-z])\s*\|\s*(.+?)\s*}}/.exec(inp)) { sc = m[1]; getWord(m[2]); inp = inp.replace(m[0], ''); } else inp = inp.replace(/^((\s*)(?!''|<!--)(?:\[\[(?:[^|\]]+?\|)?(.+?)]]|([^\{\(\[<' ]+)))+/, getWord);			if (/^\s*$/.test(word)) {				addError('Failed to process item: "' + t9ns[j] + '"');				allok = false;				continue;			}			if (!sc)				sc = code2sc(code);			if (sc !== 'Latn') {				inp = inp.replace(/\{\{(IPAchar)\|\(?(.+?)\)?}}/, function (m, w, t) { console.info('most probably transcription: ', arguments, '; script: ', sc); tr = ''; return ''; });			}				inp = inp.replace(/\((.+?)\)/, function (m, t) { console.info('text in parens: ', arguments, '; script: ', sc); if (/[Ѐ-ӿ]/.test(t)) // SOP Russian translation return m;				if (!tr && (sc !== 'Latn')) { // probably a transcription tr = t;					return ''; } else if (/^[^\[\]]+$/.test(t)) { // probably qualifier qual += '; ' + t;					return ''; }				return m; // ??? });			inp = inp.replace(/\{\{g(\|((?:[mfncsp]|pf|impf)(?:-[mfncsp]|impf|pf)*)*)}}/g, function (m, g) { gend = g;				console.info('gender: ', arguments); return ''; });			if (qual.indexOf('(') !== -1) { addError('Opening parenthesis inside qualifier for item "' + t9ns[j] + '". Not touching.'); allok = false; continue; }			if (tr.indexOf('(') !== -1) {				addError('Opening parenthesis inside transliteration for item "' + t9ns[j] + '". Not touching.');				allok = false;				continue;			}

var debris = ''; if (!/^\s*$/.test(inp)) { debris = ' ' + inp.trim; addError('Warning: some markup with unclear purpose detected: "' + inp + '". Putting after undefined invocation.'); suspc = true; susp.push(code); }			t9ns[j] = ''; if (qual !== '') t9ns[j] += ' '; t9ns[j] += '' + debris; if (typeof suspc === 'string') t9ns[j] += ' '; console.info('transformed into ', t9ns[j]); }		if (allok && (subbef === (''))) subbef = name; var newl = (issub ? '*: ' + subbef : ('* ' + subbef)) + ':' + (t9ns.length ? ' ' + t9ns.join(', ') : ''); if (lines[i] !== newl) { lines[i] = newl; dirty = true; }	}	if (!dirty) return null; return lines.join('\n'); }

function fmtts(ts) { function pad(wut) { return wut < 10 ? '0' + wut : wut.toString; }	var d = new Date(ts); return d.getUTCFullYear.toString + pad(d.getUTCMonth + 1) + pad(d.getUTCDate) + pad(d.getUTCHours) + pad(d.getUTCMinutes) + pad(d.getUTCSeconds); }

function fixer(secnum) { return function { if (!code2nameLookup) { mw.util.jsMessage('Please wait a moment until language data is downloaded.'); }		var formcont, formstart, formtoken, formedit, formwatch, formsumm; var form = el('form', [			formcont = ih('wpTextbox1'),			formstart = ih('wpStarttime'),			formedit = ih('wpEdittime'),			formtoken = ih('wpEdittoken'),			formwatch = ih('wpWatchthis', '1'),			ih('oldid', '0'),			secnum !== null ? ih('wpSection', secnum.toString) : '',			formsumm = ih('wpSummary', 'Fixing translation table to use undefined (assisted)'),			ih('wpDiff', 'wpDiff')		], {			'action': mw.config.get('wgScript') + '?xtewarn&action=submit' + (secnum !== null ? '&section=' + secnum : '') + '&title=' + encodeURIComponent(wgPageName),			'method': 'post',			'enctype': 'multipart/form-data'		});

var api = new mw.Api; api.get({			action: 'query',			prop: 'info|revisions',			rvprop: 'timestamp|content',			rvsection: secnum !== null ? secnum : void(0),			rvlimit: 1,			rvdir: 'older',			intoken: 'edit',			inprop: 'watched',			titles: wgPageName		}, {			'success': function (result) {				var pid = Object.keys(result.query.pages)[0];				var pg = result.query.pages[pid];				var susp = [], errs = [];				var fixd = fix(pg.revisions[0]['*'], susp, errs);				if (errs.length) {					try {						var cnam = 'kephir-xte-errors-' + wgPageName;						var cval = JSON.stringify(errs);						window.sessionStorage.setItem(cnam, cval);						if (window.sessionStorage.getItem(cnam) !== cval) {							throw new Error("sessionStorage failed to work");						}					} catch (e) {						alert('Errors while processing:\n\n' + errs.map(function (item) {							return 'Line ' + item.line + (item.code === null ? '' : ' [' + item.code + ']') + ': ' + item.mesg;						}).join('\n'));					}				}				if (fixd === null) {					if (confirm('Nothing fixed automatically (' + errs.length + ' messages). Edit manually?')) {						location.href = mw.config.get('wgScript') + '?xtewarn&action=edit&title=' + encodeURIComponent(wgPageName) + (secnum !== null ? '&section=' + secnum.toString : '');					}					return;				}				if (susp.length) {					susp.sort;					var last;					formsumm.value += ' — please review: ' + susp.filter(function(it) { if (it !== last) { last = it; return true; }						return false; }).join(', ');				}				formedit.value = fmtts(pg.revisions[0].timestamp);				formstart.value = fmtts(pg.starttimestamp);				formtoken.value = pg.edittoken;				formcont.value = fixd;				if (!(('watched' in pg) || (mw.user.options.get('watchdefault') == '1'))) {					formwatch.parentNode.removeChild(formwatch);				}				document.body.appendChild(form);				form.submit;			},			'error': function {				console.error(arguments);				alert('Error while requesting page source. See console for details.');			}		}); }; }

var menuList = el('ul'); var menu = el('div', [menuList]); menu.style.display = 'none'; menu.style.border = '1px solid black'; menu.style.padding = '2px'; menu.style.fontSize = '9pt'; menu.style.background = '#eee'; menu.style.zIndex = 31337;

function addMenuLink(label, title, handler, key) { var item; menuList.appendChild(el('li', [ item = el('a', [label], { 'href': 'javascript:void(0);', 'title': title, 'accesskey': key }, {			'click': function (ev) {				menu.style.display = 'none';				handler.apply(this, arguments);			}		}) ]));	return item; }

function displacement(node) { var x = 0, y = 0; while (node.offsetParent) { x += node.offsetLeft; y += node.offsetTop; node = node.offsetParent; }	return [x, y]; }

var menuHead = mw.util.addPortletLink(mw.config.get('skin') === 'vector' ? 'p-views' : 'p-cactions',	'javascript:void(0);', 'xte', 'p-kephir-xte', "the experimental translator's extension", 'o' ); menu.style.position = 'absolute'; menu.style.zIndex = '666'; document.body.appendChild(menu); menuHead.addEventListener('click', function (ev) {	menu.style.display = menu.style.display ===  ? 'none' : ;	if (menu.style.display !== 'none') {		var dhead = displacement(menuHead);		var dpar = displacement(menu.offsetParent);		menu.style.left = (dhead[0] - dpar[0]) + 'px';		menu.style.top = (dhead[1] - dpar[1] + menuHead.offsetHeight + 4) + 'px';	} }, false);

addMenuLink('Query', 'Ask about language codes or names', function {	var query = prompt('What do you want to know about?', 'code or name');	var t, name, code, sc;	if (query.toLowerCase === query) {		if (t = code2name(query)) {			code = query;			name = t;		} else {			code = name2code(query);			name = query;		}	} else {		if (t = name2code(query)) {			name = query;			code = t;		} else {			if (name = code2name(query))				code = query;		}	}	if (code) {		sc = code2sc(code);		alert('Code: ' + code + '\nLanguage: ' + name + (sc ? '\nTypical script code: ' + sc : ''));	} else		alert('Unrecognized query.'); }, '/');

if ((mw.config.get('wgAction') === 'view') && (mw.config.get('wgNamespaceNumber') === 0)) { var hls = mw.util.$content[0].getElementsByClassName('mw-headline'); for (var i = 0; i < hls.length; ++i) { if (hls[i].textContent !== 'Translations') continue; var m, secnum, as = hls[i].parentNode.getElementsByTagName('a'); for (var j = 0; j < as.length; ++j) if (m = /&action=edit&section=(\d+)$/.exec(as[j].href)) secnum = m[1]; hls[i].parentNode.appendChild(el('span', [ '[', el('a', ['fix'], { 'href': 'javascript:void(null);' }, { 'click': fixer(secnum) }), ']'], { 'class': 'mw-editsection' } ));	}

addMenuLink('Fix translations', 'Make translations use undefined', function (ev) {		fixer(null);	}, '\\'); }

if (((mw.config.get('wgAction') === 'submit') || (mw.config.get('wgAction') === 'edit'))) { var genErrorList = function (errlist) { function lineSelector(ln) { return function { var start = 0; for (var i = 1; i < ln; ++i) start = textbox.value.indexOf('\n', start) + 1; var end = textbox.value.indexOf('\n', start);

textbox.focus; if (textbox.createTextRange) { // MSIE var range = textbox.createTextRange; range.collapse(true); range.moveStart('character', start); range.moveEnd('character', end); range.select; } else if (textbox.setSelectionRange) { // browsers textbox.setSelectionRange(start, end); var phantom = document.createElement('div'); var style = window.getComputedStyle(textbox, ''); phantom.style.padding = '0'; phantom.style.lineHeight = style.lineHeight; phantom.style.fontFamily = style.fontFamily; phantom.style.fontSize = style.fontSize; phantom.style.fontStyle = style.fontStyle; phantom.style.fontVariant = style.fontVariant; phantom.style.letterSpacing = style.letterSpacing; phantom.style.border = style.border; phantom.style.outline = style.outline; try { phantom.style.whiteSpace = "-moz-pre-wrap" } catch(e) {} try { phantom.style.whiteSpace = "-o-pre-wrap" } catch(e) {} try { phantom.style.whiteSpace = "-pre-wrap" } catch(e) {} try { phantom.style.whiteSpace = "pre-wrap" } catch(e) {} phantom.textContent = textbox.value.substr(0, start); document.body.appendChild(phantom); // XXX: do I need this? textbox.scrollTop = phantom.scrollHeight - (textbox.clientHeight / 2); document.body.removeChild(phantom); }				textbox.scrollIntoViewIfNeeded; };		}		var errul = el('ul'); var listdom = el('div', [			el('p', ['Errors and warnings while processing:']),			errul		]); if (!errlist.length) return null; for (var i = 0; i < errlist.length; ++i) { var linelink = el('a', ['Line ' + errlist[i].line], { 'href': 'javascript:void(0);' }); linelink.addEventListener('click', lineSelector(errlist[i].line)); var m, errmsg = [], mesg = errlist[i].mesg; while (m = /(.*?)(\[\[(.*?)(\|.*?)?\]\])/.exec(mesg)) { errmsg.push(m[1]); errmsg.push(el('a', [m[2]], { 'href': mw.util.getUrl(m[3]) })); mesg = mesg.substr(m[0].length); }			errmsg.push(mesg); errul.appendChild(el('li', [ linelink, (errlist[i].code ? ' [' + errlist[i].code + ']' : ''), ': ' ].concat(errmsg))); }		return listdom; };	var diff = document.getElementById('wikiDiff'); var xtebox = document.createElement('div'); var fishy = false; var textbox = document.getElementById('wpTextbox1'); if (diff) { diff.parentNode.insertBefore(xtebox, diff); if (/[?&]xtewarn/.test(location.search)) { xtebox.appendChild(el('p', [ el('strong', ["Warning"]), ": the translation fixing mechanism is less than perfect and may generate erroneous markup in some unusual cases. Thoroughly review and correct the diff before saving the page. When unsure what to do, keep the old version of a line and leave a message in the edit summary, or not save at all. You take all responsibility for any changes you save." ], { 'class': 'warning' }), diff); try { var errlist = JSON.parse(window.sessionStorage.getItem('kephir-xte-errors-' + wgPageName)); var eldom = genErrorList(errlist); if (eldom) xtebox.appendChild(eldom); window.sessionStorage.removeItem('kephir-xte-errors-' + wgPageName); } catch (e) { console.error('failed to parse error list ', e.message, e); }		}		var restorer = function (linenum, linetext, linechk) { return function { if (fishy) return; var oldline = textbox.value.split(/\n/g)[linenum - 1]; };		};

var rows = diff.getElementsByTagName('tr'); var curlin = null; for (var i = 0; i < rows.length; ++i) { var dln = rows[i].getElementsByClassName('diff-lineno'); if (dln.length === 2) { var m = /(\d+):$/.exec(dln[1].textContent); curlin = parseInt(m[1], 10); continue; }			if (curlin === null) continue; var dm = rows[i].getElementsByClassName('diff-marker'); if (!dm.length) { fishy = true; alert('Something is fishy with the diff.'); continue; }			if (dm[0].nextElementSibling.className == 'diff-deletedline') { var text = dm[0].nextElementSibling.textContent; var link = el('a', [], { 'href': 'javascript:void(0);' }, { 'click': restorer(curlin, text) }); while (dm[0].hasChildNodes) { link.appendChild(dm[0].firstChild); }				dm[0].appendChild(link); }			++curlin; }	}

addMenuLink('Fix translations', 'Make translations use undefined', function {		var susp = [], errors = [];		var fixed = fix(textbox.value, susp, errors);		if (fixed === null) {			alert('No fixes made. Errors while processing:\n\n' + errors.map(function (item) {				return 'Line ' + item.line + (item.code === null ? '' : ' [' + item.code + ']') + ': ' + item.mesg;			}).join('\n'));			return;		}		while (xtebox.firstChild)			xtebox.removeChild(xtebox.firstChild);		xtebox.appendChild(el('p', [			el('strong', ["Warning"]), ": the translation fixing mechanism is less than perfect and may generate erroneous markup in some unusual cases. Thoroughly review and correct the diff before saving the page. When unsure what to do, keep the old version of a line and leave a message in the edit summary, or not save at all. You take all responsibility for any changes you save."		], { 'class': 'warning' }));		var eldom = genErrorList(errors);		if (eldom)			xtebox.appendChild(eldom);		textbox.value = fixed;	}, '\\'); }

(function {

var basket;

function getBasket { basket = JSON.parse(localStorage.getItem('kephir-xte-basket')); }

function saveBasket { localStorage.setItem('kephir-xte-basket', JSON.stringify(basket)); }

try { if (!window.localStorage) throw new Error("localStorage is absent"); if (!window.localStorage.getItem('kephir-xte-basket')) { window.localStorage.setItem('kephir-xte-basket', '[]'); if (window.localStorage.getItem('kephir-xte-basket') !== '[]') { throw new Error("localStorage does not work"); }	} } catch (e) { return; }

getBasket;

addMenuLink('Show basket', 'Show basket contents', function {	getBasket;	mw.util.jsMessage(' Contents of the basket ('+ basket.length +' items): ' + basket.map(function(item) { return '' + item + ''; }).join('') + ''); });

addMenuLink('Clear basket', 'Remove all pages from the basket', function {	basket = [];	saveBasket;	mw.util.jsMessage('Basket cleared'); });

addMenuLink('Add to/remove this page', 'Add this page to the basket', function {	getBasket;	var ind = basket.indexOf(wgPageName);	if (ind === -1) {		basket.push(wgPageName);		mw.util.jsMessage('Added ' + wgPageName + ' to the basket.');	} else {		basket.splice(ind, 1);		mw.util.jsMessage('Removed ' + wgPageName + ' from the basket.');	}	saveBasket; });

if (mw.config.get('wgAction') === 'view') addMenuLink('Add links on this page', 'Add all wikilinks from this page to the main namespace into the basket', function {	getBasket;	var links = document.getElementById('mw-content-text').getElementsByTagName('a');	var added = 0, m;	for (var j = 0; j < links.length; ++j) {		var u = new mw.Uri(links[j].href);		if (u.host !== location.host)			continue;		if (m = /^\/wiki\/([^?#{}|[\]]+)$/.exec(u.path)) {			var t = new mw.Title(decodeURIComponent(m[1]));			if (t.getNamespaceId !== 0) continue;			basket.push(t.getPrefixedDb);			added++;		}	}	saveBasket;	mw.util.jsMessage('' + added + ' pages added.'); });

if (((mw.config.get('wgAction') === 'submit') || (mw.config.get('wgAction') === 'edit'))) {

addMenuLink('Add links in the editor', 'Add all (direct) wikilinks to the main namespace in the editor into the basket', function {	getBasket;	var editor = document.getElementById('wpTextbox1');	var added = 0;	editor.value.replace(/\[\[([^#|{}[\]]*?)(?:]]|\|)/g, function (m, target) { var t = new mw.Title(target); if (t.ns !== 0) return; basket.push(t.getPrefixedDb); added++; });	saveBasket;	mw.util.jsMessage('' + added + ' pages added.'); });

addMenuLink('Put links into the editor', 'Append a list of pages in the basket into the editor', function {	getBasket;	var editor = document.getElementById('wpTextbox1');	if (editor.value && editor.value.substr(-1) !== '\n')		editor.value += '\n';	for (var j = 0; j < basket.length; ++j) {		editor.value += '* ' + basket[j] + '\n';	} });

}

addMenuLink('Pick a random page', 'Pick a random page from the basket to work on', function (ev) {	getBasket;	var j = Math.floor(Math.random * basket.length);	var page = basket.splice(j, 1);	saveBasket;	location.href = mw.util.getUrl(page[0]); }, ']');

});

addMenuLink('Feedback', 'Send feedback to script author', function {	location.href = mw.config.get('wgScript') + '?title=User_talk:Kephir/gadgets/xte&action=edit&section=new&preloadtitle=Awesome+script!'; });

});

//