User:Ioaxxere/fixTimeoutErrors.js

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

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.

// Fixes timeout errors on long pages such as [[a]].
// Note: the script does not run other gadgets after replacing the HTML aside from [[MediaWiki:Gadget-defaultVisibilityToggles.js]]
// <nowiki>

(() => {

let actionAPI = new mw.Api({ ajax: { headers: { "Api-User-Agent": "Gadget developed by [[User:Ioaxxere]]" } } });
let pageTitle = mw.config.values.wgTitle;
let mobileHTMLformat = Boolean(document.querySelector(".mw-parser-output > section"));

// Collect all the elements until reaching the next L2 header.
const L2_contentOf = L2_header => {
	let contentElems = [];
	let elem = L2_header.nextElementSibling;
	while (elem && !elem.matches(".mw-heading2")) {
		contentElems.push(elem);
		elem = elem.nextElementSibling;
	}
	return contentElems;
}

// Check whether an element contains a timeout error.
// e.g. <span class="scribunto-error" id="mw-scribunto-error-3d8151e8">The time allocated for running scripts has expired.</span>
const hasTimeoutError = elem => Array.from(elem.querySelectorAll(".scribunto-error"))
	.some(error => error.textContent === "The time allocated for running scripts has expired.");

let sections = Array.from(document.querySelectorAll(".mw-heading"));
let queries = [];

// Find the section number to use in the API.
for (let i = 0; i < sections.length; i++) {
	// Check whether the section is a broken L2 containing a timeout error.
	if (sections[i].matches(".mw-heading2") && L2_contentOf(sections[i]).some(hasTimeoutError)) {
		// Sections are one-indexed (section 0 comes before the first header)
		let params1 = {
			action: "parse",
			prop: "wikitext",
			page: pageTitle,
			section: i + 1,
			format: "json"
		}
		// Query wikitext of that section (API query 1), then convert that wikitext into HTML (API query 2)
		queries.push(actionAPI.get(params1).then(response1 => {
			let params2 = {
				action: "parse",
				format: "json",
				title: pageTitle,
				prop: "text",
				text: response1.parse.wikitext["*"]
			};
			return actionAPI.post(params2);
		}).then(response2 => {
			let responseDocument = new DOMParser().parseFromString(response2.parse.text["*"], "text/html");

			// Remove TOC.
			let TOC = responseDocument.querySelector("#toc");
			if (TOC) TOC.remove();

			// Replace original (error-filled) HTML with fixed HTML.
			L2_contentOf(sections[i]).forEach(elem => elem.remove());
			let nextL2 = sections[i].nextElementSibling; // possibly null
			for (let elem of Array.from(responseDocument.querySelector(".mw-parser-output").children)) {
				sections[i].parentNode.insertBefore(elem, nextL2);
				elem.querySelectorAll("ol > li").forEach(window.setupHiddenQuotes);
				elem.querySelectorAll(".NavFrame").forEach(window.createNavToggle);
				elem.querySelectorAll(".vsSwitcher").forEach(window.viewSwitching);
			}

			// Remove extra heading element(s).
			sections[i].nextElementSibling.remove();
			if (mobileHTMLformat)
				sections[i].nextElementSibling.remove();

			// Sync up header open/close status with the content.
			if (mobileHTMLformat) {
				if (sections[i].matches(".open-block")) {
					sections[i].nextElementSibling.classList.add("open-block");
				} else {
					sections[i].nextElementSibling.setAttribute("hidden", "until-found");
				}
			}
		}));
	}
}


Promise.all(queries).then(() => {
	// Fix edit links.
	let editlinks = Array.from(document.querySelectorAll(".mw-editsection"));
	for (let i = 0; i < editlinks.length; i++) {
		let editlink = editlinks[i].querySelector("a");
		// Edit links added by the API don't have this class for some reason.
		if (!editlink.matches(".edithere-target")) {
			// One-indexed as before.
			editlink.href = editlink.href.replace(/&section=\d+$/, "&section=" + (i + 1));
		}
	}

	// Fix header IDs.
	let existingHeaderIds = new Set();
	for (let header of document.querySelectorAll(".mw-heading")) {
		let headerID = header.firstElementChild.textContent.replaceAll(" ", "_");
		if (!existingHeaderIds.has(headerID)) {
			header.firstElementChild.setAttribute("id", headerID);
			existingHeaderIds.add(headerID);
		} else {
			let counter = 2;
			while (existingHeaderIds.has(headerID + "_" + counter))
				counter++;
			existingHeaderIds.add(headerID + "_" + counter);
			header.firstElementChild.setAttribute("id", headerID + "_" + counter);
		}
	}

	// Fix section classes and IDs, skipping section 0.
	let sectionElems = document.querySelectorAll(".mw-parser-output > section");
	for (let i = 1; i < sectionElems.length; i++) {
		sectionElems[i].classList.remove("mf-section-1");
		sectionElems[i].classList.add("mf-section-" + i);
		sectionElems[i].setAttribute("id", "content-collapsible-block-" + (i - 1));
	}
});

})();
// </nowiki>