MediaWiki:Gadget-TabbedLanguages.js

(function ( $ ) { // // This is a (very) modified version of User:Atelaes/TabbedLanguages.js. // Tabbed languages with tabs on the side. // Tabs design by User:Jorm (WMF)

/*jshint maxerr:1048576, strict:true, undef:true, latedef:true, es5:true */ /*global mw, jQuery, importScript, importScriptURI, $, ObjectStorage */

if (($.cookie('disable-tabbed-languages') !== null) || (location.search.indexOf("tabbedlanguages=off") !== -1)) return; if (!((mw.config.get('wgNamespaceNumber') === 0) || (mw.config.get('wgPageName') === "Wiktionary:Sandbox"))) return;

var bodyContent = $(".mw-content-ltr .mw-parser-output")[0], // NOT #bodyContent languageLinks, ttr, languageButtons, caption, bodyContentFragment, complete = false, MO = window.MutationObserver || window.WebKitMutationObserver, observerStyleSheet;

// Setting up the tabs has not yet been started. function makeTabsfromScratch { // Set up the variables... window.tabbedLanguages = []; window.languageContainers = []; window.currentLanguageTab = 0; window.languageHeaderEditButtons = []; languageButtons = []; window.tabstable = newNode('table', {id: 'tabstable'},		newNode('tbody', ttr = newNode('tr',				newNode('td', {'style': 'padding-top:0px;vertical-align:top;'}, newNode('table', {'style': 'margin-top: -2px;'},						languageLinks = newNode('tbody', {id: 'languageLinks'})))))); window.loadremovecatbuttons = false;

bodyContentFragment = document.createDocumentFragment;

// If bodyContent is complete, do it all at once. // Otherwise, only start working, but don't attempt the whole thing. // Use catlinks+*, as gEBCN isn't always available. var catlinks = document.getElementById( "catlinks" ); if( bodyContent && catlinks && catlinks.nextSibling ) { allAtOnce; } else { createObserver; } }

function allAtOnce { var languageContainer, toc = document.getElementById( "toc" ); currentLanguageTab = 0; while ( bodyContent.firstChild ) { if ( bodyContent.firstChild.nodeName === "SECTION" ) { // Unwrap and remove top-level tags in Parsoid content // https://www.mediawiki.org/wiki/Specs/HTML/2.8.0#Headings_and_Sections while ( bodyContent.firstChild.firstChild ) { bodyContentFragment.appendChild( bodyContent.firstChild.firstChild ); }			bodyContent.removeChild( bodyContent.firstChild ); } else { bodyContentFragment.appendChild( bodyContent.firstChild ); }	}	try { for( var child = bodyContentFragment.firstChild; child && !isHeader( child ); ){ child = child.nextSibling; }		for (			child = child && bodyContentFragment.insertBefore(tabstable, child).nextSibling;			child && child.className !== 'printfooter' && child.className !== 'catlinks';			child = child.nextSibling		) { if ( isHeader( child ) ) { var langspan = getHeaderContent( child ), language = langspan && ( langspan.innerText || langspan.textContent );

if ( language ) { newTab( tabbedLanguages.push(language) - 1, language ); processEditButton( child.getElementsByClassName('mw-editsection')[ 0 ] ); // should probably be set from a return value of above. languageContainer = languageContainers[ languageContainers.length - 1 ];

bodyContentFragment.removeChild( child );

child = tabstable;

}			} else { if ( child.nodeName !== "HR" ) { languageContainer.insertBefore(child, languageContainer.lastChild); } else { bodyContentFragment.removeChild(child); }				child = tabstable; }		}

if( tabbedLanguages.length ) { if( toc ) { toc.parentNode.removeChild( toc ); }			sortCats; bodyContent.appendChild( bodyContentFragment ); setUpHashChange; if( location.hash === '' ) { location.replace( "#" + tabbedLanguages[ currentLanguageTab ] ); }			complete = true; } else { bodyContent.appendChild( bodyContentFragment ); }	} catch( e ) { window.console && console.error( e ); bodyContent.appendChild( bodyContentFragment ); complete || setUpHashChange; } }

// Set up a MutationObserver to detect when new elements are loaded. function createObserver {

// If .ready happens early (or MO and animstart aren't supported), go ahead. // TODO the following code was commented out 2023-11-09 as $.isReady was suddenly // always evaluating to true, meaning that TabbedLanguages would not load. //if( $.isReady ) { //	return; // ??? How did we get $.isReady if bC or catlinks aren't loaded? //} else { $( document ).ready( function {			if( tabbedLanguages.length === 0 ) {				removeObserver;				if( !bodyContent ) {					bodyContent = $(".mw-content-ltr")[0];				}				if( bodyContent ) {					allAtOnce;				}			}		}); //}

if( !document.getElementsByClassName ) { return; }

if( !MO ) { var supportsAnimations = false; // Check if we can use animations as a fallback. If not, abort. // Basically yoinked from Modernizr. $.each(			"animationName WebkitAnimationName MozAnimationName OAnimationName msAnimationName".split(" "),			function(a, b){				// Do documentElements even always have .style? If not, this'll need fixing.				if( document.documentElement.style[ b ] !== undefined ) {					supportsAnimations = true;					return false;				}			}		); if( supportsAnimations === false ) { return; }	}

var foundHeader = false, toc, recentChild, timer = false, // It's the setTimeout value, or false otherwise. recentHeader = -1, checkTab, tabFound = false, // If anyone on this project even considers making a random element // on a page have the class "visualClear"... visualClear = document.getElementsByClassName( "visualClear" ), languageContainer, observer;

// TODO: Deal with the little jumping elements below the tabstable. // ...How?

function elemFound { if( timer === false ) { timer = setTimeout( function {				reactToObserver;				timer = false;			}, 1); }	}

if( MO ) { observer = new MO( elemFound ); observer.observe( document, { childList: true, subtree: true } ); } else { // Fallback for browsers that don't support MO, but do support animations: // IE10, FF5-13, Chrome 17<, Safari 4-5.1, Opera 12-12.1 // Set up a stylesheet that uses animations/keyframes to allow // animationStart to see each time a new node loads onto the bodyContent.

observerStyleSheet = document.getElementsByTagName( 'head' )[0].appendChild(				document.createElement( "style" )			); var oSSText = "@/@-moz-/@-webkit-/@-ms-/@-o-/".split("/") .join("keyframes nodeInserted{" +				"from{outline-color:#fff;}" +				"to{outline-color:#000;}" +			"}\n") + ".mw-content-ltr>*,.mw-content-ltr+*,.visualClear{" + // TODO: Fix duplication here. "/-moz-/-webkit-/-ms-/-o-/".split("/").join("animation-duration:0.01s;") + "/-moz-/-webkit-/-ms-/-o-/".split("/").join("animation-name:nodeInserted;") + "}";		// Pretty much copied from mw.util. // Don't have time to wait until it would ordinarily load. if( observerStyleSheet.styleSheet ) { observerStyleSheet.styleSheet.cssText = oSSText; } else { observerStyleSheet.appendChild( document.createTextNode( oSSText ) ); }		observerStyleSheet = observerStyleSheet.sheet || observerStyleSheet.styleSheet || observerStyleSheet; document.addEventListener('animationstart', elemFound, false); document.addEventListener('MSAnimationStart', elemFound, false); document.addEventListener('webkitAnimationStart', elemFound, false); }

function reactToObserver { if( complete ) { return; }		// TODO: Surround main areas in try{} so that if it breaks, at least // the content gets dumped back into visibility.

// First: What part are we up to? if( !foundHeader ) { // search for header or toc if( !recentChild ) { // Just starting, apparently. if( !bodyContent ) { bodyContent = $(".mw-content-ltr")[0]; if( !bodyContent ) { // bC hasn't loaded yet. Nothing to do here. return; }				}				// Check if we can do the whole thing in one go. if( visualClear.length ) { removeObserver; allAtOnce; return; }

recentChild = bodyContent.firstChild; if( !recentChild ) { return; // Somehow got activated between bodyContent load // and bodyContent's content's load. }			}			for(!isHeader( recentChild ) && recentChild.nextSibling; ) { recentChild = recentChild.nextSibling; }			if( isHeader( recentChild ) ) { // should simplify check. Maybe !recentChild.nextSibling? // We have our first header. foundHeader = true;

checkTab = setUpHashChange;

// Default to the first tab. currentLanguageTab = 0;

toc = document.getElementById('toc');

if ( toc ) { if ( toc.nextSibling ) { // And we have a usable ToC. Makes things much easier. // Analyze the ToC. We'll be using it to determine what // the tabs are going to be. $( ".toclevel-1 > a > .toctext" ).each( function {							var language = $( this ).text;							tabbedLanguages.push( language );						});

// Build all the tabs. $( tabbedLanguages ).each( newTab );

toc.parentNode.removeChild( toc );

// Afterwards, we'll check if the right section // is already available. } else { // Potential panic situation: Evil formatting places // ToC *after* the first header, so it's actually // *partly* loaded at this point, with no nextSibling. // (Alternatively, someone put it alone in a box.) CSS // builds it up as a huge tabbing block, but we can't // remove it here. Oy. // "Solution": // Temporarily hide the ToC until ready fires, then // remove it. I'm not removing it right away, as I have // no idea what doom might occur if something like this // is removed while stuff is being loaded into it. toc.style.display = "none"; ( function ( toc ) {							$( function { if( toc.parentNode ) { toc.parentNode.removeChild( toc ); }							});						})( toc ); toc = undefined; window.console && console.error( "TL notice: " +							"Malformed entry. ToC either appears after " +							"headers, or is placed in a box. Mind fixing " +							"it or alerting me? Thanks. -- YR" ); }				}

languageContainer = languageContainers[ 0 ];

// Edit buttons. processEditButton(					recentChild.getElementsByClassName( "mw-editsection" )[ 0 ]				);

// Either way, start displaying the tabs right. bodyContent.insertBefore( tabstable, recentChild );

if( toc ) { tabFound = checkTab; }			}		}		if( foundHeader ) { // Note that there may or may not be a ToC available... // Should this be reworked so that the toc check is inside the loop? if( recentChild.nextSibling ) { // I hope I'm not going to regret leaving out a recentChild && condition here... for( var nextChild; recentChild.nextSibling; ) { nextChild = recentChild.nextSibling; if( isHeader( recentChild ) ) { var langspan = getHeaderContent( recentChild ); var editspan = recentChild.getElementsByClassName('mw-editsection')[ 0 ]; if( recentHeader >= 0 ) { languageContainer.insertBefore( bodyContentFragment, languageContainer.lastChild ); }						recentHeader++;

var language = ( language = langspan ).innerText || language.textContent;

// Make sure it actually matches, when necessary. if( toc && language !== tabbedLanguages[ recentHeader ]) { // PANIC!!! // Okay, maybe don't panic. TL has encountered // a header that doesn't match the ToC's // description of the page. Possibilities // include an h1 on the page, a fake header // around somewhere, or some other malformed // kind of header. This really isn't supposed // to happen ever.

// Response: Clear everything after this point, // and from there act as though the ToC never // existed. if( recentHeader > 0 ) { toggleLanguageTabs( tabbedLanguages[ 0 ] ); } else { currentLanguageTab = 0; }							while( tabbedLanguages.length > recentHeader ) { tabbedLanguages.pop; ttr.removeChild(									languageContainers.pop								); languageLinks.removeChild(									languageButtons.pop.parentNode								); }							toc = undefined; // Never. Existed. tabFound = false;

window.console && console.error( "TL notice: " +								"Malformed entry. ToC does not match " +								"headers. Possibly a misplaced H1, fake " +								"header, or header with incorrect " +								"contents. Mind fixing it or " +								"alerting me? Thanks. -- YR "							); }						if( !toc ) { tabbedLanguages.push( language ); newTab( recentHeader, language ); }

// Check to see if the target tab is found. // TODO: Need a better system of knowing when to check: //  If it's found, stop checking. //  Whether or not we have a ToC, checking can be necessary. //  If we do have a ToC, but no hash, we don't check, right? //  Put another way: //    No hash? W/ ToC, don't check. W/o ToC, only check when new headers come in. //    Hash? Depends. //      W/ ToC: Technically, target could come in at any non-header element... ??? //      W/o ToC: Could be whenever. Target header coming in is rather likely, in fact. Oy. //      Either way: If the target, whether header or random element, is found, stop looking. //    Does delaying the tab switch to a element target until full load matter? Not sure. //						//	Plan B: Regardless of ToC presence, only //	check for potential target elems //	at each new header, unless target is found. if( recentHeader > 0 && tabFound === false ) { tabFound = checkTab; }						languageContainer = languageContainers[ recentHeader ]; if( recentHeader > 0 ) { // I have too many of these checks... // Don't duplicate earlier pEB for first header. processEditButton( editspan ); }						// All done. Clear. bodyContent.removeChild( recentChild ); } else { if( recentChild.nodeName === "HR" ) { // Kill unnecessary bars. bodyContent.removeChild( recentChild ); } else { // Regular content. Prepare for dumping into the // latest tab. bodyContentFragment.appendChild( recentChild ); }					}					recentChild = nextChild; }			}			/*			// If we're all done, finish up. // How to tell? // Option one: Repeatedly check for nextSibling. // 	 - Won't work. bodyContent doesn't always have a nS at the end. //  - Currently in use only for dealing with the last elem. (Uses O2 as fallback.) // Option two: Set up getElemsByClassName, and poll for length. //	 - presumably visualClear. This would probably be pretty heavy. //  - Currently in use. // Option three: Use $.ready. //	 - Potentially introduces substantial delay... // Option four: In the listener, check for classnames each time. //	 - Every time there's a new node? Lots of processing. // Option five: ... */			if( bodyContent.nextSibling && bodyContent.lastChild === recentChild ) { // are we done? ...				// bodyContent.nextSibling doesn't always exist, but if it // does, get a head start on the stuff that can be done already. bodyContentFragment.appendChild( bodyContent.lastChild ); /*				// Problem: recentChild is now inside the languageContainer, // and it's possible that reactToObserver will be called again // before the post-visualClear stuff runs. // Option one: recentChild = tabstable; // - Nope. That would mean bodyContent.lastChild = recentChild again // Option two: recentChild = false; // - Nope. The check at the top would cause us to start over again. // Blargh. This is an awful solution that probably violates every... */				recentChild = 1; // "1" is hereby the indicator that we're not doing anything with // this particular variable, okay? // I'll try to make this nicer later. }

// Dump collected elems into most recent languageContainer. if( bodyContentFragment.firstChild ) { // Exact duplicate of above code. TODO: Fix. languageContainer.insertBefore( bodyContentFragment, languageContainer.lastChild ); }

if( visualClear.length ) { complete = true; if( bodyContent.lastChild === recentChild ) { // bC.nS didn't exist. Move the last one left now. languageContainer.insertBefore( bodyContent.lastChild, languageContainer.lastChild ); }				removeObserver; sortCats; if( tabFound !== true ) { checkTab; if( location.hash === '' ) { location.replace( "#" + tabbedLanguages[currentLanguageTab] ); }				}			}		}	}	function removeObserver { if( observer ) { observer.disconnect; } else if( observerStyleSheet ) { document.removeEventListener('animationstart', elemFound, false); document.removeEventListener('MSAnimationStart', elemFound, false); document.removeEventListener('webkitAnimationStart', elemFound, false); observerStyleSheet.disabled = true; }		if( timer !== false ) { clearTimeout( timer ); }	} }

function isHeader( elem ) { // Handle two versions of the markup: https://www.mediawiki.org/wiki/Heading_HTML_changes return elem && elem.nodeType === Node.ELEMENT_NODE && (		elem.nodeName === "H2" && elem.getElementsByClassName( "mw-headline" ).length !== 0 ||		elem.classList.contains( "mw-heading2" ) && elem.getElementsByTagName( "H2" ).length !== 0	); }

function getHeaderContent( elem ) { // Handle two versions of the markup: https://www.mediawiki.org/wiki/Heading_HTML_changes return elem.nodeName === "H2" ? elem.getElementsByClassName( "mw-headline" )[0] : elem.getElementsByTagName( "H2" )[0]; }

function newTab( index, language ) { var active = index === currentLanguageTab; var languageContainer = ttr.appendChild( newNode('td', { 'class': 'languageContainer', 'id': language + 'container' }, active ? undefined : { 'style' : 'display:none;' } )); languageContainers.push( languageContainer ); newCategoryBox( languageContainer, language ); // lB contains .(un)?selectedTab nodes languageButtons.push(		languageLinks.appendChild( newNode('tr', newNode('td', { 'class': ( active ? '' : 'un' ) + 'selectedTab' }, newNode('a', language, {			// Note: ' + language' makes this inconsistent with the ordinary links			// themselves, which have the language name encoded. Issue?			// Keep in mind that the parser itself actually corrects for this,			// changing #!Xóõ links to the proper #.C7.83X.C3.B3.C3.B5 links.			// Probably nothing to worry about.			'href': location.pathname + location.search + '#' + language.replace(/ /g, '_')		}), ' '))).firstChild	); }

function newCategoryBox( container, name ) { // Put a container in each for categories. return container.appendChild( newNode('div', name + ' categories: ', newNode('ul'), { 'class': 'catlinks', 'id': 'catlinks' })); }

function processEditButton( button ) { if( button ) { // Yes, theoretically if you have some fake H2s the edit buttons will // be misplaced. Yet another TODO... var len = languageHeaderEditButtons.push( button ); if( len === 1 ) { caption = document.createElement('caption'); tabstable.insertBefore( caption, tabstable.firstChild ); }		button.className += " editlangsection"; // use a argument or variable ( from .push? ) instead of .length? if( len - 1 === currentLanguageTab ) { caption.firstChild && caption.removeChild( caption.firstChild ); caption.appendChild( button ); }	} }

// Sets up the hash toggle system. // Returns the checkTab function, which returns true if // we found whatever it was. function setUpHashChange {

// Important Note: The decodeURI mess is a real mess. // #.C7.83X.C3.B3.C3.B5 should go to tab "!Xóõ", with // the id "!Xóõcontainer" (no encoding). Urgh. // Also, "#Old_English" needs to go to decoded "Old English".

// Remember to .substr( 1 ) before passing here. function decodeHash( hash ) { return decodeURI(			hash				.replace(/\.(?=[0-9A-F]{2})/g, '%')				.replace(/_/g, ' ')		); }

// Called by onhashchange. function hashToggleLT { var destination = decodeHash( location.hash.substr(1) ); toggleLanguageTabs( destination ); tabbedLanguages[currentLanguageTab] !== destination && resetHash; }

// For updating page positioning. // Doesn't activate hashchange, at least in Chrome. Not sure about others. function resetHash { location.replace( location.hash ); }

// Need to decide if language arg is encoded or decoded. // Will work with either for the moment. Decoded works earlier. // Currently passed as decoded by every function but itself. // The hashes are encoded, but hashToggleLT decodes them.

// Toggles to a different language tab. window.toggleLanguageTabs = function (language) { // Find the destination language.

var destinationLanguageTab = $.inArray( language, tabbedLanguages );

if( destinationLanguageTab === -1 ) { var decoded = decodeHash( language ); if( decoded !== language ) { destinationLanguageTab = $.inArray( decoded, tabbedLanguages ); }		}

// Style the right toggle button, hide the old language section and show the new one.

// var languageButtons = $("#languageLinks .selectedTab, #languageLinks .unselectedTab"); if (destinationLanguageTab !== -1 ) { if( destinationLanguageTab !== currentLanguageTab ) { languageButtons[currentLanguageTab].className = 'unselectedTab'; languageContainers[currentLanguageTab].style.display = 'none'; currentLanguageTab = destinationLanguageTab; languageButtons[destinationLanguageTab].className = 'selectedTab'; languageContainers[destinationLanguageTab].style.display = ''; if (caption) { // extra checks shouldn't be necessary... caption.firstChild && caption.removeChild(caption.firstChild); languageHeaderEditButtons[currentLanguageTab] && caption.appendChild(languageHeaderEditButtons[currentLanguageTab]); }			}		} else { // Does the hash match the id of a node in a tab? language = encodeURI( language.replace(/\ /g, '_') ); // Yes, I just possibly undid the decoding from hashToggleLT. // Worse, this might double-encode, breaking things. // Or maybe not? I think everything might be decoded before being // sent here? // I'll deal with it later.

// Find the node, and go up the node tree until // you hit .languageContainer, or nothing. language = document.getElementById( language ); for(language && ( language = language.parentNode ) &&				language.className !== 'languageContainer'; ) ;			// language = language && language.parentNode.parentNode; if ( language ) { // If someone maliciously makes a languageContainer with a // non-compliant ID, boom. toggleLanguageTabs( language.id.split('container')[0] ); }			// Possible doom bug: Endless loop? }	};

// This function gets returned as checkTab, btw. // For during or immediately after load: Check if we have a	// good "starting" tab. Return true if we have a definitive find. function checkTab { // If there's a location hash, the window may have scrolled down before // we got a chance to reorganize everything. // If the destination was a subsection or sense id, switch to the right // tab, and rescroll. // If it was simply a language, switch to the appropriate tab, and // scroll back up. // If there's no hash at all, work off the TargetedTranslations prefs. try { var hash = ''; var destination = 'English';

if ( location.hash !== '' ) { hash = ( location.hash ).substr( 1 ); // does hash.substr always exist? destination = decodeHash( hash ); }

// 'k, this is still awful. if ( $.inArray( destination, tabbedLanguages ) !== -1 ) { toggleLanguageTabs( destination ); window.scrollY && window.scroll(0, 0); return true; } else if ( hash !== '' && document.getElementById( hash ) ) { // This was going // to receive an area to search from an argument, but it turns // out that elem.getElementById doesn't actually exist. Meh. toggleLanguageTabs( destination ); resetHash; // Scroll to the element. (Necessary since we				// this isn't running through hashToggleLT, which normally does				// it in these situations.) return true; }

// No hash. Work from TargetedTranslations. if ('localStorage' in window) { if (tabbedLanguages[0] !== 'Translingual' && tabbedLanguages[0] !== 'English' && localStorage.TargetedTranslations) { for (						var tt_ = localStorage.TargetedTranslations.split("|"), tt = tt_[0].split(";").concat( $.grep(								tt_[1].replace(/[^;\/]+\//g, '').split(";"),								function (z) {									return z && z !== "Latin" && z !== "Hebrew" && z !== "Arabic";								}							) ).concat( tt_[1].replace(/\/[^;]+/g, '').split(";") ), i = 0;						i < tt.length;						i++					) { if ($.inArray(tt[i], tabbedLanguages) !== -1) { toggleLanguageTabs(tabbedLanguages[$.inArray(tt[i], tabbedLanguages)]); break; }					}				}			}

return false; } catch ( e ) { // This probably isn't all that unlikely to happen. Too complicated. :(			window.console && console.error( "TL error: checkTab broke.", e );		}	}	if ("onhashchange" in window && (document.documentMode === undefined || document.documentMode > 7)) {		window.onhashchange = hashToggleLT;	} else {		$( bodyContent ).on( 'click', 'a[href^="' + location.pathname + location.search + '#"], a[href^="#"]', function { setTimeout( hashToggleLT, 10 ); }		);	}

return checkTab; }

function sortCats { var catDiv = document.getElementById('mw-normal-catlinks'), currentCatDiv; if (catDiv) { var cats = catDiv.getElementsByTagName('li'), catname, langcurrent = 0, catskip = 1; do { while (cats.length > 0) { catname = cats[ 0 ].getElementsByTagName('a')[0].innerHTML; if (catname.indexOf(tabbedLanguages[langcurrent + catskip]) === 0 &&					!/letter\snames$|script\scharacters$|mythology$/.test(catname)				) { langcurrent += catskip; catskip = 1; }				currentCatDiv = languageContainers[langcurrent].lastChild; currentCatDiv.lastChild.appendChild(cats[0]); }			if (langcurrent + 1 < languageContainers.length - catskip) { // Didn't make it to the end, which means there's a section // without any categories. Dump what's left back and try again. while (currentCatDiv.lastChild.firstChild) { catDiv.lastChild.appendChild(currentCatDiv.lastChild.firstChild); }				catskip++; } else { break; }		} while (true); }

if( currentCatDiv ) { // place patrol link at the bottom of the page var pl = currentCatDiv.previousSibling; // languageContainer.lastChild.previousSibling; // Got it, the patrollink is sometimes the lagging last elem. if( pl && pl.className === "patrollink" ) { tabstable.parentNode.appendChild(pl); }	}	/*	// category editing buttons if (mw.config.get('wgAction') === "view" && !/&printable=yes|&diff=|&oldid=/.test(location.search)) { for (z = 0; z < languageContainers.length; z++) { // TODO: import // addTabbedLanguageNewCatButton(z); }		if ( window.loadremovecatbuttons === true ) { $.get(				mw.config.get( 'wgScript' ),				{ 'title' : mw.config.get('wgPageName'), 'action' : 'raw' },				// Note that this isn't actually defined. I'm considering not				// actually importing the function, and just dumping this section.				addRemoveCatButtons			); }	}	*/	// Remove old cat box, allow display of hidden cats box. if (catDiv && !(catDiv.nextSibling && catDiv.nextSibling.className === "mw-hidden-catlinks mw-hidden-cats-user-shown")) { catDiv.parentNode.style.display = 'none'; } else { if (catDiv) { catDiv.style.display = 'none'; }	} }

function testStuff { var delay = 5000, f = document.createDocumentFragment; var x = [ function { // $("#toc").remove; var x = document.getElementById( "mw-content-text" ); while( x.nextSibling ) { f.appendChild( x.nextSibling ); }		}, function { var y = $( ".mw-content-ltr" )[ 0 ]; var q = document.createDocumentFragment; while( y.firstChild ) { q.appendChild( y.firstChild ); }			function u( i, e ) { setTimeout( function {					y.appendChild( q.firstChild );				}, i / e * delay ); }			for( var i = 0, e = q.childNodes.length; i < e; i++ ) { u( i, e ); // encapsulate }			makeTabsfromScratch; }, function { function u( i, e ) { setTimeout( function {					$(".mw-content-ltr").after( f.firstChild );				}, i / e * delay / 5 ); }			for( var i = 0, e = f.childNodes.length; i < e; i++ ) { u( i, e ); // encapsulate }			console.log(9); }	];

x[0]; for( var i = 1; i < x.length; i++ ) { setTimeout( x[ i ], (i-1) * delay + 50 ); } }

//testStuff; makeTabsfromScratch;

// This is officially deprecated as of MW1.22. Should be replaced by mw.hook, // but the docs don't say what event, so... $(mw).on('LivePreviewDone', function {	bodyContent = $(".mw-content-ltr")[0]; // reset	makeTabsfromScratch; });

})( window.jQuery );