MediaWiki:Gadget-Streamline.js
Jump to navigation
Jump to search
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
"use strict";
// {{documentation}}
// <nowiki>
/*jshint strict:true, undef:true, latedef:true, esversion:6 */
/* global mw, createNavToggle */
const STREAMLINE_SYMBOL = "⌰";
// [[MediaWiki:Gadget-WiktGadgetPrefs.js]]
const preferences = mw.wiktGadgetPrefs.get(
"streamline",
{
label: {
en: "Streamline",
},
},
{
collapseElements: {
"type": "strenum",
"default": "aboveDefinitions",
"label": {
"en": "Elements to collapse"
},
"choices": [
"aboveDefinitions",
"allExceptNotes"
],
"choiceLabels": {
"en": {
"aboveDefinitions": "Sections before definitions",
"allExceptNotes": "Sections before and after definitions"
}
}
},
runInReconstructions: {
"type": "boolean",
"default": true,
"label": {
"en": "Apply also to reconstruction pages"
}
}
}
);
function makeNavFrame(heading, editLinks, toggleCat, contents) {
// now make a collapsible box
const navHead = document.createElement("div");
navHead.className = "NavHead";
navHead.style.cursor = "pointer";
navHead.style.fontSize = "1.1em";
const navContent = document.createElement("div");
navContent.className = "NavContent boxcontent streamline-boxcontent";
navContent.style.textAlign = "left";
const navFrame = document.createElement("div");
navFrame.className = "NavFrame NavFrame-streamline";
if (toggleCat)
navFrame.setAttribute("data-toggle-category", toggleCat);
navFrame.style.display = "block";
navFrame.append(navHead, navContent);
navHead.append(`${STREAMLINE_SYMBOL} ${heading}`);
try {
if (createNavToggle) createNavToggle(navFrame);
} catch (e) { }
if (editLinks) {
navContent.append(editLinks);
editLinks.style.float = "right";
// try displaying the edit links next to the show button
editLinks.style.marginTop = "-1.5em";
editLinks.style.marginRight = "8ch";
}
navContent.append(...contents);
return navFrame;
}
function isNavFrameOpen(frame) {
const content = frame.querySelector(".NavContent");
if (!content) return false;
const computedStyle = window.getComputedStyle(content);
if (!computedStyle) return false;
return computedStyle.display !== "none";
}
function openNavFrame(frame) {
if (!isNavFrameOpen(frame)) {
const navHead = frame.querySelector(".NavHead");
if (navHead)
navHead.click();
}
}
function anchorInUrl(anchor) {
if (window.location.hash) {
return window.location.hash.replace(/^#/, "") === anchor;
}
return false;
}
function hasElementClass(element, className) {
return element.classList.contains(className);
}
function shouldEndCollapsible(element) {
return element.matches("h1, h2, h3, h4, h5, h6, hr") // end on heading or horizontal line
|| hasElementClass(element, "NavFrame-streamline") // no nested streamline navboxes
|| hasElementClass(element, "mw-heading") // Parsoid
; // this is on its own line so that it's easier to add new rules
}
function shouldExcludeFromCollapsible(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, "template-anchor") // anchor
|| hasElementClass(element, "t-thumbs-outer") // thumbs
|| hasElementClass(element, "interproject-box") // interwiki boxes
|| 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
; // this is on its own line so that it's easier to add new rules
}
function shouldCollapseForTheSakeOf(element) {
if (element.nodeType === 1) {
// don't collapse for the sake of senseid or etymid alone
if (element.matches(".senseid, .etymid"))
return false;
if (element.matches("p, div, 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;
}
}
function getHeadingQuery(onlyL3) {
return (onlyL3 ? ["h2", "h3"] : ["h2", "h3", "h4", "h5"])
.map(heading => `#mw-content-text ${heading}`)
.join(", ");
}
const navFramesById = {};
function collapseHeadingInNavbox(headingCandidate, heading, toggleCat,
headingMatcher, addEditLinks) {
let headingId;
const hl = headingCandidate.querySelector(".mw-headline");
if (hl && headingMatcher(hl.textContent))
headingId = hl.id;
// Parsoid
if (hasElementClass(headingCandidate.parentElement, "mw-heading") &&
headingMatcher(headingCandidate.textContent)) {
headingId = headingCandidate.id;
headingCandidate = headingCandidate.parentElement;
}
if (!headingId) return [undefined, undefined];
// pick all elements until next heading
const contents = [];
let nx = headingCandidate.nextElementSibling;
let collapse = false;
while (nx) {
if (shouldEndCollapsible(nx))
break;
if (!shouldExcludeFromCollapsible(nx)) {
contents.push(nx);
collapse = collapse || shouldCollapseForTheSakeOf(nx);
}
nx = nx.nextElementSibling;
}
if (!collapse) return [undefined, undefined];
const navFrame = makeNavFrame(heading,
addEditLinks ? headingCandidate.querySelector(".mw-editsection") : null,
toggleCat, contents);
navFrame.id = headingId;
navFramesById[navFrame.id] = navFrame;
return [navFrame, headingCandidate];
}
function makeSingleCollapsible(heading, toggleCat, onlyL3) {
document.querySelectorAll(getHeadingQuery(onlyL3)).forEach((e) => {
const [navFrame, headingElement] = collapseHeadingInNavbox(e,
heading, toggleCat,
(headingText) => headingText === heading, true);
if (navFrame)
headingElement.parentNode.replaceChild(navFrame, headingElement);
});
}
function makeNumberedCollapsible(heading, toggleCat) {
const re = new RegExp("^" + heading + " \\d+(?:\.\\d+)?$");
document.querySelectorAll(getHeadingQuery(false)).forEach((e) => {
const [navFrame, headingElement] = collapseHeadingInNavbox(e,
heading, toggleCat,
(headingText) => headingText.match(re), false);
if (navFrame)
headingElement.parentNode.insertBefore(navFrame,
headingElement.nextElementSibling);
});
}
function moveAnagrams() {
// move the Anagrams box above Further reading and References
document.querySelectorAll(".NavFrame[data-toggle-category=\"anagrams\"]").forEach((e) => {
let p = e.previousElementSibling;
while (p) {
if (p.tagName === "H2") {
break;
}
if (p.tagName === "H3") {
const 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((sl) => {
const prev = sl.previousElementSibling;
const next = sl.nextElementSibling;
let tn;
if (prev) {
tn = prev.tagName;
if (tn === "OL" || tn === "UL") {
sl.style.marginTop = "1em";
}
}
if (next) {
tn = next.tagName;
if (tn === "HR") {
sl.style.marginBottom = "1em";
}
}
});
}
function defaultStreamline() {
makeSingleCollapsible("Alternative forms", "alternative forms", true);
makeSingleCollapsible("Alternative reconstructions", "alternative reconstructions", true);
makeSingleCollapsible("Etymology", "etymology");
makeSingleCollapsible("Glyph origin", "etymology");
makeSingleCollapsible("Description", "description");
makeSingleCollapsible("Pronunciation", "pronunciations");
makeSingleCollapsible("Production", "productions");
makeNumberedCollapsible("Etymology", "etymology");
makeNumberedCollapsible("Pronunciation", "pronunciations");
makeNumberedCollapsible("Production", "productions");
addGaps();
}
function superStreamline() {
makeSingleCollapsible("Alternative forms", "alternative forms", false);
makeSingleCollapsible("Alternative reconstructions", "alternative reconstructions", 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("Collocations", "collocations");
makeSingleCollapsible("Descendants", "descendants");
makeSingleCollapsible("Trivia", "trivia");
makeSingleCollapsible("See also", "related terms");
makeSingleCollapsible("References", "references");
makeSingleCollapsible("Further reading", "further reading");
makeSingleCollapsible("Anagrams", "anagrams");
moveAnagrams();
addGaps();
}
function openNavFrameForElementWithId(targetId) {
const navFrameTarget = navFramesById[targetId];
if (navFrameTarget)
openNavFrame(navFrameTarget);
// if what we targeted is inside a navframe, open it too
const targetElement = document.getElementById(targetId);
if (targetElement && targetElement.closest) {
const navFrameContainingTarget = targetElement.closest(".NavFrame-streamline");
if (navFrameContainingTarget)
openNavFrame(navFrameContainingTarget);
}
}
const { collapseElements, runInReconstructions } = preferences;
// only run in content namespaces.
// this must also run on 4 in [[MediaWiki:Gadgets-definition]] so that [[WT:GPREFS]] works.
if (mw.config.get("wgNamespaceNumber") === 0 || (runInReconstructions && mw.config.get("wgNamespaceNumber") === 118)) {
mw.hook("wikipage.content").add(() => {
try {
defaultStreamline();
if (collapseElements !== "aboveDefinitions")
superStreamline();
// refocus URL target in case the page layout changed, which it probably did
// also open a navframe if targeted
window.requestAnimationFrame(() => {
if (window.location.hash && window.location.hash.match(/^#/)) {
const targetId = window.location.hash.replace(/^#/, "");
const targetElement = document.getElementById(targetId);
if (targetElement)
targetElement.scrollIntoView();
openNavFrameForElementWithId(targetId);
}
});
// open a navframe if an internal link targets a section
window.addEventListener("hashchange", () => {
if (window.location.hash) {
const targetId = window.location.hash.replace(/^#/, "");
openNavFrameForElementWithId(targetId);
}
});
} catch (e) {
console.error(e);
}
});
}
// </nowiki>