User:Surjection/streamline.js

From Wiktionary, the free dictionary
Jump to navigation Jump to search

Note – after saving, you may have to bypass your browser’s cache to see the changes.

  • 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.

/**
 * make various non-definition headers collapsible boxes
 * 
 *	mw.loader.load('//en.wiktionary.org/w/index.php?title=User:Surjection/streamline.js&action=raw&ctype=text/javascript');
 * 
 */

makeNavFrame = function(heading, editLinks, toggleCat, contents) {
	// now make a collapsible box
	var navHead = document.createElement("div");
	navHead.className = "NavHead";
	navHead.style.cursor = "pointer";
	
	var navContent = document.createElement("div");
	navContent.className = "NavContent";
	navContent.style.textAlign = "left";
	
	var navFrame = document.createElement("div");
	navFrame.className = "NavFrame NavFrame-streamline";
	if (toggleCat)
		navFrame.setAttribute("data-toggle-category", toggleCat);
	
	navFrame.appendChild(navHead);
	navFrame.appendChild(navContent);
		
	navHead.appendChild(document.createTextNode(heading));
	
	createNavToggle(navFrame);
	
	if (editLinks) {
		navContent.appendChild(editLinks);
		editLinks.style.float = "right";
	}
	contents.forEach(function(ex) {
		navContent.appendChild(ex);
	});
	
	return navFrame;
};

isNavFrameOpen = function(frame) {
	var content = frame.querySelector(".NavContent");
	return content ? window.getComputedStyle(content).style.display != "none" : false;
}

openNavFrame = function(frame) {
	if (!isNavFrameOpen(frame)) {
		frame.querySelector(".NavHead").click();
	}
}

anchorInUrl = function(anchor) {
	if (window.location.hash) {
		return window.location.hash == anchor;
	}
	return false;
}

hasElementClass = function(element, className) {
	if (element.classList)
		return element.classList.contains(className);
	return (" " + element.className + " ").indexOf(" " + className + " ") >= 0;
}

shouldEndCollapsible = function(element) {
	return element.tagName.match(/^h[\dr]$/i)							// end on heading or horizontal line
		|| hasElementClass(element, "NavFrame-streamline")	 			// no nested streamline navboxes
}

shouldExcludeFromCollapsible = function(element) {
	return hasElementClass(element, "thumb")					 		// images
	    || hasElementClass(element, "sister-project")					// sister project box
		|| hasElementClass(element, "kanji-table")				 		// ja-kanjitab
		|| hasElementClass(element, "floatright")				 		// various tables
		|| hasElementClass(element, "etymid")					 		// etymid
		|| hasElementClass(element, "template-anchor")					// anchor
		|| element.getAttribute("typeof") == "mw:File/Thumb"			// new floating image boxes
		|| hasElementClass(element, "mw-halign-right")					// mw-halign-right, by new floating image boxes
		|| element.getAttribute("align") == "right"						// anything that (explicitly) aligns
		|| element.style.float == "right"								// anything that (explicitly) floats
}

shouldCollapseForTheSakeOf = function(element) {
	if (element.nodeType == 1) {
		if (element.tagName == "P" || element.tagName == "DIV" || element.tagName == "SPAN")
			return Array.prototype.some.call(element.childNodes, shouldCollapseForTheSakeOf);
		return element.tagName != "BR";
	} else if (element.nodeType == 3) {
		return !!element.textContent.trim(); // only if not entirely whitespace
	} else {
		return false;
	}
}

getHeadingQuery = function(onlyL3) {
	var head = ".ns-0 #mw-content-text ";
	if (onlyL3) {
		return head + "h2, " + head + "h3";
	} else {
		return head + "h2, " + head + "h3, " + head + "h4";
	}
}

makeSingleCollapsible = function(heading, toggleCat, onlyL3) {
	document.querySelectorAll(getHeadingQuery(onlyL3)).forEach(function(e) {
		var hl = e.querySelector(".mw-headline");
		if (hl && hl.textContent == heading) {
			// pick all elements until next heading
			var contents = [];
			var nx = e.nextElementSibling;
			var collapse = false;
			while (nx) {
				if (shouldEndCollapsible(nx))
					break;
				if (!shouldExcludeFromCollapsible(nx)) {
					contents.push(nx);
					collapse = collapse || shouldCollapseForTheSakeOf(nx);
				}
				nx = nx.nextElementSibling;
			}
			
			if (!collapse) {
				return;
			}
			
			navFrame = makeNavFrame(heading, e.querySelector(".mw-editsection"), toggleCat, contents);
			
			navFrame.id = hl.id;
			e.parentNode.replaceChild(navFrame, e);
			
			if (anchorInUrl(navFrame.id))
				openNavFrame(navFrame);
		}
	});
};

makeNumberedCollapsible = function(heading, toggleCat) {
	var re = new RegExp("^" + heading + " \\d+$");
	document.querySelectorAll(getHeadingQuery(false)).forEach(function(e) {
		var hl = e.querySelector(".mw-headline");
		if (hl && hl.textContent.match(re)) {
			// pick all elements until next heading
			var contents = [];
			var nx = e.nextElementSibling;
			var collapse = false;
			while (nx) {
				if (shouldEndCollapsible(nx))
					break;
				if (!shouldExcludeFromCollapsible(nx)) {
					contents.push(nx);
					collapse = collapse || shouldCollapseForTheSakeOf(nx);
				}
				nx = nx.nextElementSibling;
			}
			
			if (!collapse) {
				return;
			}
			
			navFrame = makeNavFrame(heading, null, toggleCat, contents);
			
			navFrame.id = hl.id;
			e.parentNode.insertBefore(navFrame, e.nextElementSibling);
			
			if (anchorInUrl(navFrame.id))
				openNavFrame(navFrame);
		}
	});
};

function moveAnagrams() {
	// move the Anagrams box above Further reading and References
	document.querySelectorAll(".NavFrame[data-toggle-category=\"anagrams\"]").forEach(function(e) {
		var p = e.previousElementSibling;
		while (p) {
			if (p.tagName.toUpperCase() == "H2") {
				break;
			}
			
			if (p.tagName.toUpperCase() == "H3") {
				var hl = p.querySelector(".mw-headline");
				if (hl && (hl.textContent == "Further reading" || hl.textContent == "References")) {
					e.parentElement.insertBefore(e, p);
				}
			}
			p = p.previousElementSibling;
		}
	});
}

function addGaps() {
	// add gaps before streamline navboxes if preceded by certain elements
	document.querySelectorAll(".NavFrame-streamline").forEach(function(sl) {
		var prev = sl.previousElementSibling;
		var next = sl.nextElementSibling;
		if (prev) {
			var tn = prev.tagName.toUpperCase();
			if (tn == "OL" || tn == "UL") {
				sl.style.marginTop = "1em";
			}
		}
		if (next) {
			var tn = next.tagName.toUpperCase();
			if (tn == "HR") {
				sl.style.marginBottom = "1em";
			}
		}
	});
}

function defaultStreamline() {
	makeSingleCollapsible("Alternative forms", "alternative forms", true);
	makeSingleCollapsible("Etymology", "etymology");
	makeSingleCollapsible("Glyph origin", "etymology");
	makeSingleCollapsible("Description", "description");
	makeSingleCollapsible("Pronunciation", "pronunciations");
	
	makeNumberedCollapsible("Etymology", "etymology");
	makeNumberedCollapsible("Pronunciation", "pronunciations");
	
	addGaps();
}

function superStreamline() {
	makeSingleCollapsible("Alternative forms", "alternative forms", false);
	makeSingleCollapsible("Inflection", "inflection");
	makeSingleCollapsible("Declension", "inflection");
	makeSingleCollapsible("Conjugation", "inflection");
	makeSingleCollapsible("Mutation", "mutation");
	makeSingleCollapsible("Synonyms", "synonyms");
	makeSingleCollapsible("Antonyms", "antonyms");
	makeSingleCollapsible("Hypernyms", "hypernyms");
	makeSingleCollapsible("Hyponyms", "hyponyms");
	makeSingleCollapsible("Meronyms", "meronyms");
	makeSingleCollapsible("Holonyms", "holonyms");
	makeSingleCollapsible("Troponyms", "troponyms");
	makeSingleCollapsible("Coordinate terms", "coordinate terms");
	makeSingleCollapsible("Derived terms", "derived terms");
	makeSingleCollapsible("Compounds", "derived terms");
	makeSingleCollapsible("Related terms", "related terms");
	makeSingleCollapsible("Descendants", "descendants");
	makeSingleCollapsible("See also", "related terms");
	makeSingleCollapsible("Anagrams", "anagrams");
	moveAnagrams();
	
	addGaps();
}

mw.hook("wikipage.content").add(function($) {
	if (createNavToggle) {
		try {
			defaultStreamline();
			if (window.doSuperStreamline)
				superStreamline();
		} catch (e) {
			console.error(e);
		}
	} else
		console.warn("no createNavToggle, cannot create navboxes. cannot streamline")
});