User:Opencooper/highlightStrings.js

// Highlight some errors // License: CC0 // Attribution for configure icon (MIT license): https://commons.wikimedia.org/wiki/File:OOjs_UI_icon_advanced_apex.svg // Attribution for report icon (MIT license): https://commons.wikimedia.org/wiki/File:OOjs_UI_icon_feedback-ltr.svg

// TODO: Create a configuration page where rules can be enabled/disabled. // TODO: test this and other scripts on other skins, including redesign // TODO: Check out TreeWalker API; maybe useful

/*   Principles: * Focus on formatting, not content (e.g. spelling) – testing out if necessary * Minimize false positives, and make highlights optional if they are too noisy * Preserve original formatting of article if possible * Try to do something in the DOM first rather than matching the HTML if it can be done elegantly

/* jshint esversion: 11 */ /* jshint jquery: true */ /* jshint laxbreak: true */ /* global mw */ /* global document */ /* global window */ /* global navigator */ /* global location */ /* global console */ /* global alert */ /* global CSS */ //

// TODO: compare to https://en.wikipedia.org/wiki/Wikipedia:WikiProject_Check_Wikipedia/List_of_errors //      and https://en.wikipedia.org/wiki/Wikipedia:AutoWikiBrowser/General_fixes

"use strict";

function printError(message, source, lineno, colno, error) { if (source.includes("highlightStrings")) { $("#contentSub").after("highlightStrings.js: Error: "	                          + mw.html.escape(message) + " [line: " + lineno	                           + ", column: " + colno + "] "); addReportButton; }

return false; }

const matchDescriptions = {}; const filterList = []; const refSectionsSelector = "#References, #Notes, #Citations, #Bibliography, #Endnotes, #Notes_and_references, #Sources, #Works_cited, #General_sources, #General_references, #Footnotes"; const stateNames = ["Alabama", "Alaska", "Arizona", "Arkansas", "California", "Colorado", "Connecticut", "Delaware", "Florida", "Georgia", "Hawaii", "Idaho", "Illinois", "Indiana", "Iowa", "Kansas", "Kentucky", "Louisiana", "Maine", "Maryland", "Massachusetts", "Michigan", "Minnesota", "Mississippi", "Missouri", "Montana", "Nebraska", "Nevada", "New Hampshire", "New Jersey", "New Mexico", "New York", "North Carolina", "North Dakota", "Ohio", "Oklahoma", "Oregon", "Pennsylvania", "Rhode Island", "South Carolina", "South Dakota", "Tennessee", "Texas", "Utah", "Vermont", "Virginia", "Washington", "West Virginia", "Wisconsin", "Wyoming", "D.C."]; const countryNames = ["Afghanistan", "Albania", "Algeria", "Andorra", "Angola", "Antigua and Barbuda", "Argentina", "Armenia", "Australia", "Austria", "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin", "Bhutan", "Bolivia", "Bosnia and Herzegovina", "Botswana", "Brazil", "Brunei", "Bulgaria", "Burkina Faso", "Burundi", "Cabo Verde", "Cambodia", "Cameroon", "Canada", "Central African Republic", "CAR", "Chad", "Chile", "China", "Colombia", "Comoros", "Democratic Republic of the Congo", "Republic of the Congo", "Costa Rica", "Cote d'Ivoire", "Croatia", "Cuba", "Cyprus", "Czechia", "Czech Republic", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Eswatini", "Ethiopia", "Fiji", "Finland", "France", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Greece", "Grenada", "Guatemala", "Guinea", "Guinea-Bissau", "Guyana", "Haiti", "Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran", "Iraq", "Ireland", "Israel", "Italy", "Jamaica", "Japan", "Jordan", "Kazakhstan", "Kenya", "Kiribati", "Kosovo", "Kuwait", "Kyrgyzstan", "Laos", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania", "Luxembourg", "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Mauritania", "Mauritius", "Mexico", "Micronesia", "Moldova", "Monaco", "Mongolia", "Montenegro", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru", "Nepal", "Netherlands", "New Zealand", "Nicaragua", "Niger", "Nigeria", "North Korea", "North Macedonia", "Norway", "Oman", "Pakistan", "Palau", "Palestine", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines", "Poland", "Portugal", "Qatar", "Romania", "Russia", "Rwanda", "Saint Kitts and Nevis", "Saint Lucia", "Saint Vincent and the Grenadines", "Samoa", "San Marino", "Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore", "Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa", "South Korea", "South Sudan", "Spain", "Sri Lanka", "Sudan", "Suriname", "Sweden", "Switzerland", "Syria", "Taiwan", "Tajikistan", "Tanzania", "Thailand", "Timor-Leste", "Togo", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "UAE", "United Kingdom", "UK", "United States of America", "United States", "USA", "Uruguay", "Uzbekistan", "Vanuatu", "Vatican City", "Holy See", "Venezuela", "Vietnam", "Yemen", "Zambia", "Zimbabwe"]; function highlightStrings { window.onerror = printError; mw.loader.load("//en.wikipedia.org/w/index.php?title=User:Opencooper/highlightStrings.css&action=raw&ctype=text/css", "text/css"); preClean; manipulateDOM; prepHTML; replaceHTML; postClean; displayMatches; getWikitext; getItalics; getDeadInterwikis; tweakDisplay;

}

function addReportButton { $(".oHL_error, .oHL_warning").each(function addButton {		if ($(this).next.is(".oHL_reportButton")) {			return;		}		const error = $(this).text;		const article = mw.config.get("wgTitle");		const currentRevision = mw.config.get("wgRevisionId");		// const latestRevision = mw.config.get("wgCurRevisionId");		const skin = mw.config.get("skin");		const userAgent = navigator.userAgent;		const signature = "~";		const reportLink = "/wiki/User_talk:Opencooper/highlightStrings?action=edit&section=new&preloadtitle=Bug%20report&preload=User:Opencooper/highlightStringsReportPreload.js"		                  + "&preloadparams[]=" + encodeURIComponent(article).replaceAll("'", "%27")		                   + "&preloadparams[]=" + currentRevision		                   + "&preloadparams[]=" + encodeURIComponent(error).replaceAll("'", "%27")		                   + "&preloadparams[]=" + encodeURIComponent(skin).replaceAll("'", "%27") + "&preloadparams[]=" + encodeURIComponent(userAgent).replaceAll("'", "%27") + "&preloadparams[]=" + signature; const feedbackIconURL = "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0a/OOjs_UI_icon_feedback-ltr.svg/20px-OOjs_UI_icon_feedback-ltr.svg.png"; $(this).after("  Report Problem "); }); }

// Sanitize to avoid false positives function preClean { // Prefetch $("head").append(""); // Remove highlight button so we don't run twice document.getElementById("hStrings")?.remove; // Link element document.querySelectorAll("#mw-content-text link")?.forEach(e => e.remove);

// Links document.querySelectorAll(".Z3988")?.forEach(e => e.remove);

// Workaround for coordinates document.getElementById("coordinates")?.remove; document.querySelectorAll(".geo-nondefault, .geo, .geo-inline-hidden")?.forEach(e => e.remove); // Rm template for discussion template document.getElementById("tfd")?.remove; // Rm redirect notice document.querySelectorAll(".mw-redirectedfrom")?.forEach(e => e.remove); // Old revision header // document.querySelectorAll(".mw-revision")?.forEach(e => e.remove); // Draft submission box document.querySelectorAll(".ombox")?.forEach(e => e.remove); // "[update]" document.querySelectorAll(".asof-tag")?.forEach(e => e.remove); // Audio boxes document.querySelectorAll(".ui-icon-play")?.forEach(e => e.closest(".haudio")?.remove); document.querySelectorAll("audio")?.forEach(e => e.removeAttribute("data-durationhint")); // Img srcset document.querySelectorAll("#mw-content-text img")?.forEach(e => e.removeAttribute("srcset")); // Video payload document.querySelectorAll("[videopayload]")?.forEach(e => e.removeAttribute("videopayload")); // Table sorting document.querySelectorAll("[data-sort-value]")?.forEach(e => e.removeAttribute("data-sort-value")); // Empty paragraphs added by the parser document.querySelectorAll(".mw-empty-elt")?.forEach(e => e.remove); // Hidden footer document.querySelectorAll(".printfooter")?.forEach(e => e.remove); // Maps document.querySelectorAll(".mw-graph")?.forEach(e => e.removeAttribute("data-graph-id")); document.querySelectorAll(".mw-kartographer-link")?.forEach(e => e.removeAttribute("data-overlays")); // MIDI files document.querySelectorAll(".mw-ext-score")?.forEach(e => e.removeAttribute("data-midi")); // Infobox wrapping document.querySelectorAll(".infobox .nowrap")?.forEach(e => e.classList.remove("nowrap"));

// ARIA attributes document.querySelectorAll("[aria-label]")?.forEach(e => e.removeAttribute("aria-label")); document.querySelectorAll("[aria-labelledby]")?.forEach(e => e.removeAttribute("aria-labelledby"));

// Talk page section subscription document.querySelectorAll("[data-mw-thread-id]")?.forEach(e => e.removeAttribute("data-mw-thread-id")); document.querySelectorAll("[data-mw-comment-end]")?.forEach(e => e.removeAttribute("data-mw-comment-end"));

// Remove userscript-added stuff document.getElementById("siteSub")?.remove; // document.getElementById("lastEdit")?.remove; // Redundant to above document.getElementById("wikidataDescription")?.remove; document.getElementById("kanjiInfo")?.remove; document.getElementById("xtools")?.remove; document.getElementById("otherImage")?.remove; // Use one consistent class for all references document.querySelectorAll(".reflist")?.forEach(e => e.classList.add("mw-references-wrap")); // Add class to section anchors if (mw.config.get("skin") != "minerva") { document.querySelectorAll("a[id^='sectiontitlecopy']")?.forEach(e => e.classList.add("oHL_anchorLink")); } else { document.querySelectorAll("a[id^='sectiontitlecopy']")?.forEach(e => e.remove); }   // Sometimes the ToC is wrapped in toclimit- document.querySelectorAll("[class*='toclimit'] #toc")?.forEach(e => e.parentElement.before(e)); // Remove show/hide toggle for collapsed elements document.querySelectorAll(".mw-collapsible-toggle")?.forEach(e => e.remove); // Navboxes contain their own ids which can clash with those on the page, // causing issues when the HTML is reparsed, such as CSS being switched document.querySelectorAll(".navbox div[id]")?.forEach(e => e.removeAttribute("id")); document.querySelectorAll("#mw-content-text a[href^='/wiki/']")?.forEach(e => e.classList.add("oHL_wikilink")); document.querySelectorAll(".oHL_anchorLink")?.forEach(e => e.classList.remove("oHL_wikilink")); document.querySelectorAll(".image")?.forEach(e => e.classList.remove("oHL_wikilink")); document.querySelectorAll(".mw-file-magnify")?.forEach(e => e.classList.remove("oHL_wikilink")); }

function manipulateDOM { const isNonDisambigPage = $(".dmbox").length === 0; const isNonSandboxPage = mw.config.get("wgPageName") != "User:Opencooper/sandbox";

// Portal in See also matchDescriptions["oHL-seeAlso-portal"] = ["Portal bar misplacement", "Portal bars should not be placed in the See also section. (MOS:ORDER)"]; $("#See_also").parent.nextUntil("h2").filter(".navbox").after("[Portal↓] ");

// Redlinks in see also matchDescriptions["oHL-seeAlso-redlink"] = ["Red link in See also", "The See also section should not contain red links. (MOS:NOTSEEALSO)"]; $("#See_also").parent.nextUntil("h2").filter("ul").find("a.new").addClass("oHL oHL-seeAlso-redlink");

// Title case headers matchDescriptions["oHL oHL-header-titlecase"] = ["Header case", "Section headers should use title case. (MOS:HEADINGS)"]; $("#See_Also, #External_Links").addClass("oHL oHL-header-titlecase");

// Bolded pseudoheader matchDescriptions["oHL-pseudoheader"] = ["Pseudoheader", "False headers should not be created using bolding or definition list markup, instead using equal signs. (MOS:PSEUDOHEAD)"]; $("p b:only-child").each(function findPseudoheaders {       if (this.previousSibling === null && this.nextSibling?.textContent === "\n") {            $(this).addClass("oHL oHL-pseudoheader");        }    }); $("dl dt:only-child").addClass("oHL oHL-pseudoheader"); filterList.push(".navbox .oHL-pseudoheader", ".sidebar .oHL-pseudoheader");

// Unattached inline template matchDescriptions["oHL-lone-inline"] = ["Unattached inline template", "Inline tags should be preceded by text. (WP:CITEFOOT)"]; $("p .reference:only-child, p .Inline-Template:only-child").each(function findLoneInlines {       if (this.previousSibling === null && this.nextSibling?.textContent === "\n") {            $(this).addClass("oHL oHL-lone-inline");        }    }); filterList.push("blockquote .oHL-lone-inline");

// Sections without list item matchDescriptions["oHL-non-list"] = ["Section needing list", "Sections such as See also should contain a bulleted list. (MOS:SEEALSO, MOS:ELLAYOUT)"]; $("#See_also, #External_links").each(function findNonLists {       const list = $(this).parent.nextUntil("h2").filter("ul");        if (list.length === 0) {            $(this).parent.nextUntil("h2").filter("p").prepend("[*] ");        }    });

// Missing Commons template matchDescriptions["oHL-commons-template"] = ["Missing Commons template", "The page has a linked Commons category, but lacks a template."]; if ($(".wb-otherproject-commons").length !== 0       && $("img[src*='Commons-logo.svg']").length === 0) { $("#mw-content-text h2").last.after(" [Needs Commons template] "); }   // Missing Wikisource template matchDescriptions["oHL-wikisource-template"] = ["Missing Wikisource template", "The page has a linked Wikisource page, but lacks a template."]; if ($(".wb-otherproject-wikisource").length !== 0       && $("img[src*='Wikisource-logo.svg']").length === 0) { $("#mw-content-text h2").last.after(" [Needs Wikisource template] "); }

// Commons template that should be made inline matchDescriptions["oHL-commons-inline"] = ["Lone block template", "The External links section only has a single template, so it should use an inline equivalent, e.g. `* `."]; $("#External_links").parent.siblings(".sistersitebox").each(function checkCommonsTemplate {   	const sibling = this.nextElementSibling;    	if (sibling === null || sibling.className.includes("navbox-styles")) {    		$(this).after("[* Make template inline] ");    	}    });

// Captions with bolding matchDescriptions["oHL-bold-caption"] = ["Bolding in caption", "Captions should not be normally specially formatted, including bolded. (MOS:CAPTION)"]; $(".thumbcaption b, .thumbcaption .selflink,"     + " figcaption b, figcaption .selflink").addClass("oHL oHL-bold-caption"); // Empty captions matchDescriptions["oHL-missing-caption"] = ["Missing caption", "Images should usually have captions. (MOS:CAPTION)"]; $(".thumbcaption, figcaption").each(function findEmptyCaptions {   	if ($(this).text == "") {    		$(this).append("[Needs caption] ");    	}    });

// Wikilinks in bold text matchDescriptions["oHL-bolded-link"] = ["Bolded wikilink", "Bolded text should not contain wikilinks. (MOS:BOLDLINK)"]; $("b .oHL_wikilink").addClass("oHL oHL-bolded-link"); filterList.push(".infobox .oHL-bolded-link", ".sidebar .oHL-bolded-link",                   ".navbox .oHL-bolded-link", ".succession-box .oHL-bolded-link",                    ".subjectbar .oHL-bolded-link", ".ambox .oHL-bolded-link",                    ".side-box .oHL-bolded-link");

// Improper header progression matchDescriptions["oHL-nonlinear-header"] = ["Improper header progression", "Section headers should be nested sequentially. (MOS:BADHEAD)"]; $("h2 + h4, h2 + h5, h2 + h6, h3 + h5, h3 + h6, h4 + h6").before(" [Header level] ");

// Thumbnails in infoboxes (uncommon?) matchDescriptions["oHL-infobox-thumbnail"] = ["Infobox thumbnail", "Instead of embedding another thumbnail, infoboxes support the image_size/image_upright parameters to modify the thumbnail size."]; // $(".infobox .thumb").not(".tmulti").not(".mw-kartographer-container").addClass("oHL oHL-infobox-thumbnail"); $(".infobox figure").addClass("oHL oHL-infobox-thumbnail");

// External links in body matchDescriptions["oHL-body-external"] = ["External links in body", "External links do not belong in the body of an article. (WP:ELPOINTS)"]; $("#mw-content-text .external").addClass("oHL_external"); $(".plainlinks .oHL_external, .oHL_external[class*='mw-magiclink'],"     + " .infobox .oHL_external, .sidebar .oHL_external,"      + " .mw-references-wrap .oHL_external, .navbox .oHL_external").removeClass("oHL_external"); $(refSectionsSelector + ", #Further_reading, #Additional_reading,"     + " #External_links, #Publications").parent.nextUntil("h2").find(".oHL_external").removeClass("oHL_external"); $(".oHL_external").addClass("oHL oHL-body-external");

// Unformatted external links matchDescriptions["oHL-unformatted-external"] = ["Unformatted external link", "The links in the External links section should have descriptions instead of being plain links. (MOS:ELLAYOUT)"]; $("#External_links").parent.nextUntil("h2").filter("ul").find(".external.free").addClass("oHL oHL-unformatted-external");

// Auto-numbered links matchDescriptions["oHL-numbered-reflink"] = ["Numbered reference link without title", "Links in citations should have a title and other information for verification. (WP:CS:EMBED)"]; $(".mw-references-wrap .autonumber").addClass("oHL oHL-numbered-reflink"); matchDescriptions["oHL-bare-URL"] = ["Bare reference link without title", "Links in citations should have a title and other information for verification. (WP:CS:EMBED)"]; $(".mw-references-wrap .free").addClass("oHL oHL-bare-URL"); matchDescriptions["oHL-numbered-extlink"] = ["External link without title", "Links should contain a title. (WP:ELCITE)"]; $("#External_links").parent.nextUntil("h2").find(".autonumber").addClass("oHL oHL-numbered-extlink");

// Wikilinks in headers matchDescriptions["oHL-header-wikilink"] = ["Header wikilink", "Headers should not contain wikilinks. (MOS:NOSECTIONLINKS)"]; $(".mw-headline .oHL_wikilink").addClass("oHL oHL-header-wikilink"); filterList.push(".oHL_anchorLink.oHL-header-wikilink");

// Big text matchDescriptions["oHL-big-text"] = ["Big text", "The HTML &ltbig&gt; element is deprecated and changes to font size should be avoided. (MOS:FONTSIZE)"]; $("#mw-content-text big").addClass("oHL oHL-big-text"); // Underlined text matchDescriptions["oHL-underlined"] = ["Underlining", "Italics or headers should be used instead of underlining. (MOS:UNDERLINE)"]; $("#mw-content-text u").addClass("oHL oHL-underlined"); // Struck out text matchDescriptions["oHL-striked-text"] = ["Struck text", "Strikethrough should not be used. (MOS:STRIKETHROUGH)"]; $("#mw-content-text s, #mw-content-text strike").addClass("oHL oHL-striked-text"); // Monospaced text matchDescriptions["oHL-tt-tag"] = ["tt tag", "The  is deprecated. (see MOS:CODE for alternatives)"]; $("#mw-content-text tt").addClass("oHL oHL-tt-tag");

// Text marked as en   matchDescriptions["oHL-lang-en"] = ['Text marked "en"', "The   tag of every Wikipedia article already identifies the language of the content as English. (Exception: English text embedded within text marked as another lanuage)"]; $("#mw-content-text span[lang=en]").addClass("oHL oHL-lang-en"); filterList.push(".mw-ext-cite-error.oHL-lang-en");

// Sister templates next to reflist matchDescriptions["oHL-misplaced-sisterbox"] = ["Sister template next to reflist", "Floating templates cause layout issues with reference lists and should be relocated. (see Template:Sister_project)"]; $(".sistersitebox + .mw-references-wrap,"     + " .mw-references-wrap + .sistersitebox").before(" [Relocate box↕]  ");

// Floated template after and not before list matchDescriptions["oHL-misplaced-template"] = ["Floating template placement", "Floating templates should go before the content they displace."]; $("ul + .sistersitebox, ul + style + .portal").after(" [Move template up↑] ");

// Quote boxes at end of sections matchDescriptions["oHL-misplaced-quotebox"] = ["Floating quote placement", "Quote boxes should be placed after section headers and not before."]; $(".quotebox + h2").prev.append(" [Relocate quote box↕] ");

// Horizontal rules matchDescriptions["oHL-hr"] = ["Horizontal rule", "Horizontal rules should not be used for separation. Instead, use section headings."]; $("hr").before("<span class='oHL-opt oHL-hr oHL_added'>[horizontal rule] "); filterList.push(".sidebar .oHL-hr", ".infobox .oHL-hr", ".navbox .oHL-hr",                   ".listen .oHL-hr");

// Sites using http matchDescriptions["oHL-insecure-site"] = ["Insecure site", "Most modern websites support the HTTPS protocol and external links should be updated to use it."]; const httpsMarkup = " <span class='oHL-opt oHL-insecure-site oHL_added'>[http] "; $(".infobox .url a[href^='http:']").after(httpsMarkup); $("#External_links").parent.nextUntil("h2").filter("ul").find(".external[href^='http:']").after(httpsMarkup);

// Flag icons in infoboxes matchDescriptions["oHL-infobox-flagicon"] = ["Infobox flag icon", "Flag icons in infoboxes are deprecated. (MOS:INFOBOXFLAG)"]; $(".infobox .flagicon, .infobox-data img[src*='Flag_of']").addClass("oHL oHL-infobox-flagicon");

// Breaks in infobox titles matchDescriptions["oHL-infobox-title-br"] = ["Infobox title break", "Content should not be manually line-broken, instead letting the browser word-wrap to the appropriate width. Infoboxes usually have dedicated parameters for alternate names."]; $(".infobox tr").first.find("th br").before(" <span class='oHL-opt oHL-infobox-title-br oHL_added' style='font-size:55%;'>&lt;br&gt; "); $(".oHL-infobox-title-br + br + .honorific-suffix").prev.prev.remove;

// Redundant bolding matchDescriptions["oHL-redundant-bold"] = ["Redundant bolding", "Definition lists and table headers are already bolded."]; $("dt b, th b").addClass("oHL oHL-redundant-bold"); // External links which should be internal matchDescriptions["oHL-external-wikilink"] = ["External wikilink", "Links to Wikipedia pages should use internal linking syntax (square brackets)."]; $(".oHL_external[href*='wikipedia.org']").addClass("oHL oHL-external-wikilink"); filterList.push(".hatnote .oHL-external-wikilink", ".nv-edit .oHL-external-wikilink",                   ".stub .oHL-external-wikilink", ".dmbox-body .oHL-external-wikilink",                    ".ambox .oHL-external-wikilink");

// Cross-namespace wikilinks matchDescriptions["oHL-x-namespace-wl"] = ["Cross-namespace wikilink", "Article content should not link to other namespaces. (MOS:LINKSTYLE)"]; $(".oHL_wikilink[href^='/wiki/Wikipedia:']").addClass("oHL oHL-x-namespace-wl"); filterList.push(".hatnote .oHL-x-namespace-wl", ".stub .oHL-x-namespace-wl",                   "#setindexbox .oHL-x-namespace-wl", ".sidebar .oHL-x-namespace-wl",                    ".navbox-abovebelow .oHL-x-namespace-wl", ".sistersitebox .oHL-x-namespace-wl",                    ".Inline-Template .oHL-x-namespace-wl", ".portal-bar .oHL-x-namespace-wl",                    ".spoken-wikipedia .oHL-x-namespace-wl", ".sister-bar .oHL-x-namespace-wl",                    ".ambox .oHL-x-namespace-wl");

// Dab links matchDescriptions["oHL-dab-link"] = ["Disambiguation link", "Articles should not link to disambiguation pages outside of hatnotes. (MOS:LINK)"]; $(".mw-disambig").each(function findDabLinks {       if ($(this).attr("title")?.includes("(disambiguation)") || $(this).text.includes("(disambiguation)")) {           return true;        }        $(this).addClass("oHL oHL-dab-link");    }); // Japanese romanization matchDescriptions["oHL-romaji"] = ["Japanese romanization", "Unless in the title of a work or a common name, modern romanization should be used for Japanese. (WP:ROMAJI)"]; const romajiRe = /(o[ou]|uu|aa|ī|wo|cch|m[bp]|ô|ê|î|é)/g; $("[lang='ja-Latn']").each(function findRomaji {   	const romaji = this.textContent;        if (romajiRe.test(romaji)) {        	const romajiHighlight = romaji.replace(romajiRe, " $1 ");            this.innerHTML = this.innerHTML.replace(">" + romaji + "<", ">" + romajiHighlight + "<");       }    });

// Thumbnails with link= matchDescriptions["oHL-thumbnail-link"] = ["Thumbnail link", "For proper attribution, the links in thumbnails should not be overridden."]; $("figure > a:not(.mw-file-description, .mw-file-magnify)").parent.addClass("oHL oHL-thumbnail-link"); // $(".thumbimage").each(function findLinkedThumbs { // Don't want divs from CSS crop   //     if ($(this).children(".mw-graph").length) {    //         return true;    //     }    //     // .tsingle ignores multi-images    //     if (!$(this).parent.hasClass("image") && !$(this).parent.hasClass("tsingle") //        && !$(this).parent.hasClass("video") && !$(this).parent.hasClass("audio")) {    //         $(this).parents(".thumb").addClass("oHL oHL-thumbnail-link");    //     }    // });

// Piped interlanguage links matchDescriptions["oHL-interlang"] = ["Piped interlanguage link", "Links to non-English articles should not be obscured. (MOS:EGG) Use undefined instead."]; const interlanguageRe = /[a-z]{2}\.wikipedia.org/; $(".extiw").each(function findPipedInterlangLinks {       if ($(this).text.length === 2) {            return true;        }

if (interlanguageRe.test(this.href)) { $(this).addClass("oHL oHL-interlang"); }   });    filterList.push(".mw-references-wrap .oHL-interlang", ".navbox .oHL-interlang", ".ambox .oHL-interlang");   matchDescriptions["oHL-piped-image"] = ["Piped image link", "Links to images should not be obscured (MOS:EGG). Either embed the image itself or move the link to a parenthetical, making it clear that it's not to an article."];    $(".oHL_wikilink, .extiw").addClass("oHL oHL-piped-image");    filterList.push(".ambox .oHL-piped-image", ".mw-references-wrap .oHL-piped-image", ".mw-tmh-player .oHL-piped-image");    $(".mw-file-element").parent(".oHL-piped-image").removeClass("oHL oHL-opt");

// Internal links that should be external matchDescriptions["oHL-masked-link"] = ["Masked external link", "Links to external websites should not be obscured. (MOS:EGG)"]; $(".extiw[href^='//doi.org']").addClass("oHL oHL-masked-link");

// Poem not inside blockquote or verse translation matchDescriptions["oHL-unwrapped-poem"] = ["Unwrapped poem", "Quoted poem content needs to be wrapped in or . (see MOS:BLOCKQUOTE)"]; $(".poem").each(function findUnwrappedPoems {       if ($(this).parent.is(":not(blockquote):not(td)")) {            $(this).addClass("oHL oHL-unwrapped-poem");        }    });

const leadSection = $("#mw-content-text h2").first.prevUntil("#mw-content-text");

// See also hatnote at top matchDescriptions["oHL-hatnote-misuse"] = ["See also hatnote", "The see also template is not meant to be used as a hatnote, but rather for subsections. (Template:See also)"]; let hatnoteLead; if ($("#mw-content-text h2").length) { hatnoteLead = leadSection; } else { hatnoteLead = $("#mw-content-text .hatnote"); }   hatnoteLead.filter(".hatnote").each(function findHatnoteMisuse {        if (!isNonSandboxPage) { return false; }        if ($(this).text.startsWith("See also:")) {            $(this).prepend("<span class='oHL oHL-hatnote-misuse oHL_added'>[rm] ");        }    }); // Infobox not at top matchDescriptions["oHL-infobox-placement"] = ["Infobox placement", "Infoboxes should be placed before article content. (MOS:ORDER)"]; $("p ~ .infobox").before(" <span class='oHL oHL-infobox-placement oHL_added'>[Move infobox to top↑] "); // Sidebar in the lead matchDescriptions["oHL-sidebar-placement"] = ["Sidebar placement", "Sidebars in the lead are discouraged, and if placed there, preferably after the infobox or lead image. (MOS:LEAD)"]; leadSection.filter(".sidebar").each(function findSidebarMisplacement {   	$(this).after(" <span class='oHL oHL-sidebar-placement oHL_added'>[Move sidebar after lead↓]  ");    }); // Hatnote not at top of a section matchDescriptions["oHL-low-hatnote"] = ["Low hatnote", "Hatnotes should be placed at the top of subsections. (WP:HNP)"]; $("p + .hatnote").after(" <span class='oHL oHL-low-hatnote oHL_added'>[Move hatnote up↑] ");

// Hatnote below maintenance template matchDescriptions["oHL-hatnote-placement"] = ["Hatnote placement", "Hatnotes should be placed above maintenance templates. (WP:HNP)"]; $(".ambox + .hatnote").after(" <span class='oHL oHL-hatnote-placement oHL_added'>[Move hatnote up↑] ");

// Italics for long quotes matchDescriptions["oHL-italquote"] = ["Italicized quote", "Quotations should not be italicized. (MOS:NOITALQUOTE)"]; $("#mw-content-text p i").each(function findItalQuotes {       if ($(this).text.length >= 80) {            let target = this;            if ($(this).parent("a").length) {                target = this.parentElement;            }            $(target).after(" <span class='oHL oHL-italquote oHL_added'>[noitalquote] ");        }    });

// Empty sections matchDescriptions["oHL-empty-section"] = ["Empty section", "Empty sections should be deleted or filled by a placeholder. (e.g. )"]; const emptySectionMarkup = "<span class='oHL oHL-empty-section oHL_added'>[Empty section] "; $("#mw-content-text h2, #mw-content-text h3, #mw-content-text h4,"     + " #mw-content-text h5, #mw-content-text h6").each(function findEmptySections {        // We don't count headers that only contain subheaders        const headerLevel = this.tagName[1];        const nextHeader = $(this).next("h2, h3, h4, h5, h6");        if (nextHeader.length) {            const nextHeaderLevel = nextHeader[0].tagName[1];            if (nextHeaderLevel > headerLevel) {                return true;            }        }

if ($(this).nextUntil(nextHeader).not("style, span").length === 0) { $(this).after(emptySectionMarkup); }   });    const lastSection = $("#mw-content-text h2").last;    if (lastSection.next(".navbox, .navbox-styles").length) {        lastSection.after(emptySectionMarkup);    }    $(".reflist").each(function findEmptyReflists { if ($(this).children.length == 0) { $(this).after(emptySectionMarkup); }   });    filterList.push(".toc .oHL-empty-section");    if (mw.config.get("skin") == "minerva") {    	filterList.push(".mw-heading .oHL-empty-section");    }

// Anchor links inside article itself matchDescriptions["oHL-anchor-link"] = ["Self anchor link", "Piped links that lead to subsections within the same article should not be hidden, but indicated with a section marker such as by using . (see MOS:EGG and principle of least surprise)"]; $("#mw-content-text a[href^='#']").addClass("oHL_sl"); $("#toc .oHL_sl, sup .oHL_sl, .mw-cite-backlink .oHL_sl,"     + " .reference-text .oHL_sl, .mw-kartographer-map.oHL_sl,"      + " .mw-kartographer-link.oHL_sl, .oHL_sl[href^='#CITEREF'],"      + " #catlinks .oHL_sl, .navbox .oHL_sl, .sidebar .oHL_sl").removeClass("oHL_sl"); $(".oHL_sl").each(function findAnchorLinks {       if (!$(this).text.includes("§")) {            $(this).before("<span class='oHL-opt oHL-anchor-link oHL_added'>[§] ");        }    }); filterList.push(".NavHead .oHL-anchor-link", ".wikicite .oHL-anchor-link");

// See also section links for other articles matchDescriptions["oHL-seeAlso-section-link"] = ["See also section link", "Links to subsections of other articles can be indicated using . (see MOS:EGG and principle of least surprise)"]; $("#See_also").parent.nextUntil("h2").find(".oHL_wikilink[href*='#']").each(function findSeeAlsoSectionLinks {       if (!$(this).text.includes("§")) {            $(this).after(" <span class='oHL oHL-seeAlso-section-link oHL_added'>[§] ");        }    }); filterList.push(".navbox .oHL-seeAlso-section-link", ".mw-headline .oHL-seeAlso-section-link");

// Find broken section links matchDescriptions["oHL-broken-section-link"] = ["Broken section link", "A link points to a subsection that was renamed or removed."]; $("#mw-content-text [href^='#']").each(function findBrokenSectionLinks {       const target = $(this).attr("href");        if (target == "#" || target.startsWith("#cite_")) {            return true;        }

const targetId = target.substring(1); const targetSelector = $("#" + $.escapeSelector(targetId)); if (targetSelector.length === 0) { $(this).addClass("oHL oHL-broken-section-link"); }   });    filterList.push("#toc .oHL-broken-section-link", ".mw-kartographer-link.oHL-broken-section-link");

// Images in see also or external links sections matchDescriptions["oHL-misplaced-image"] = ["Misplaced images", "Images should be placed in the body of an article, supporting the text. (MOS:IMAGERELEVANCE)"]; $("#See_also, #External_links").each(function findMisplacedImages {       const images = $(this).parent.nextUntil("h2").find("figure, .tmulti, .gallery");        if (images.length) {            $(this).parent.after(" <span class='oHL oHL-misplaced-image oHL_added'>[Move images]  ");        }    });

// External links section formatted as a reflist matchDescriptions["oHL-ext-reflist"] = ["External links w/ reflist format", "The external links section should not use citation templates. (WP:ELCITE)"]; const externalLinksWrapped = $("#External_links").parent.nextUntil("h2").filter(".refbegin"); if (externalLinksWrapped) { externalLinksWrapped.before(" <span class='oHL oHL-ext-reflist oHL_added'>[Wrapped in Refbegin] "); }

// Adjacent lists matchDescriptions["oHL-spaced-list"] = ["Adjacent lists", "Blank lines between list items creates separate lists. (MOS:BULLETLIST)"]; $("#bodyContent ul + ul, dl + dl").addClass("oHL_adj_li"); $(".gallery.oHL_adj_li, .portalbox + .oHL_adj_li").removeClass("oHL_adj_li"); $(".oHL_adj_li").each(function findSpacedLists {       const previousSibling = $(this).prev;        if (!previousSibling.hasClass("oHL_adj_li")) {            previousSibling.before(" <span class='oHL oHL-spaced-list oHL_added'>[Spaced list]  ");        }    }); filterList.push(".navbox .oHL-spaced-list", ".sidebar .oHL-spaced-list");

// Lowercase in infobox values matchDescriptions["oHL-lower-infobox"] = ["Infobox lowercase", "Text in infoboxes should not be arbritrarily lowercased."]; $(".infobox td").each(function findLowercasedInfoboxes {       const text = $(this).text.trim;        if (text.length === 0) { return true; }        if (text.includes(".")) { return true; }

const firstLetter = text[0]; if (/[a-z]/.test(firstLetter)) { $(this).prepend("<span class='oHL oHL-lower-infobox oHL_added'>[↑] "); }   });

// Lowercase See also items matchDescriptions["oHL-lower-seeAlso"] = ["See also lowercase", "The links in See also sections are normally capitalized."]; $("#See_also").parent.next("ul").children("li").each(function findLowercasedSeeAlsos {       const text = $(this).text.trim;        const firstLetter = text[0];        if (/[a-z]/.test(firstLetter)) {            $(this).prepend("<span class='oHL oHL-lower-seeAlso oHL_added'>[↑] ");        }    });

// Infobox website not using undefined matchDescriptions["oHL-plain-site"] = ["Plain infobox website", "External links in infoboxes should be wrapped in undefined. (e.g. see the website parameter at Template:Infobox person)"]; $(".infobox .external").each(function findFullURLs {       const text = $(this).text;        if (/^http/.test(text)) {            $(this).prepend("<span class='oHL oHL-plain-site oHL_added'>[URL] ");        }    });

// Redundant quote marks in blockquotes matchDescriptions["oHL-redundant-quotes"] = ["Redundant quote marks", "Block quotes should not use enclosing quote marks. (MOS:BLOCKQUOTE)"]; $("blockquote p, .templatequote p, .quotebox-quote").each(function findRedundantQuotes {       const html = $(this).html;        if (html.includes(`" '`)) { //  template        	return true;        }        const text = $(this).text;        if (text.charAt(0) == '"') {            $(this).html(html.slice(1));            $(this).prepend(" \" ");        }    });

// Wikilinked parenthesis or punctuation matchDescriptions["oHL-wikilink-punc"] = ["Wikilinked punctuation", "Punctuation should not be wikilinked, as it is not part of the link."]; $(".oHL_wikilink").each(function findWikilinkedPunctuation {       if (!isNonDisambigPage) { return false; }        const text = $(this).text;        const finalChar = text.at(-1);        if (text.substring(text.length-2) == "..") { return true; } // ellipses        if ('.,;:")]/'.includes(finalChar)) {            if (finalChar == ".") {                if (text.endsWith("Inc.") || text.endsWith("Sr.")                    || text.endsWith("Jr.") || text.endsWith("Bros.")                    || text.endsWith("Co.")) { return true; }                if (/\.[A-Z]\./.test(text)) { return true; }                if (/ [A-Z]\.$/.test(text)) { return true; }                if (/\..*\./.test(text)) { return true; }            }

const html = $(this).html; // use HTML to preserve formatting, e.g. Inception (film) const replaceRe = new RegExp("(.*)\\" + finalChar); $(this).html(html.replace(replaceRe, "$1")); $(this).append(" " + finalChar + " "); }   });    $("#See_also").parent.nextUntil("h2").filter("ul, .columns, .div-col").find(".oHL-wikilink-punc").removeClass("oHL-opt");    filterList.push(".reference .oHL-wikilink-punc", ".external .oHL-wikilink-punc", ".hatnote .oHL-wikilink-punc", ".mw-kartographer-link .oHL-wikilink-punc", ".IPA .oHL-wikilink-punc", ".infobox-label .oHL-wikilink-punc", ".listen .oHL-wikilink-punc");   // Underscore in wikilink    matchDescriptions["oHL-wl-underscore"] = ["Wikilink underscore", "Article text should not contain underscores."];    $(".oHL_wikilink").each(function findUnderscoredWikilinks { if ($(this).text.includes("_")) { $(this).addClass("oHL oHL-wl-underscore"); }   });    // Check proper sections    matchDescriptions["oHL-missing-ref-section"] = ["Missing ref section", "Articles should have a References section. (MOS:LAYOUT)"];    if (isNonDisambigPage && isNonSandboxPage) {        if ($(refSectionsSelector).length === 0) {            $("#mw-content-text").append("<h2 class='oHL oHL-missing-ref-section oHL_added'>[References] ");        }

checkSectionOrder; }   // Tables without headers matchDescriptions["oHL-table-header"] = ["Table without headers", "Tables should have headers for the columns."]; $("table").each(function findHeaderlessTables {       if ($(this).hasClass("succession-box") || $(this).hasClass("sistersitebox") || $(this).hasClass("ambox") || $(this).hasClass("ombox") || $(this).parent.hasClass("stub") || $(this).attr("role") == "presentation") {           return true;         }        if ($(this).find("th").length === 0) {            $(this).prepend("<span class='oHL oHL-table-header oHL_added'>[Missing table headers] ");        }    }); filterList.push("table .oHL-table-header");

// Unindented math matchDescriptions["oHL-math-indent"] = ["Unindented math", "Math placed on its own line should be indented using  (MOS:MATH)"]; $(".mwe-math-element").each(function findUnindentedMath {       if (this.previousSibling === null && this.parentElement.tagName == "P") {            $(this).prepend("<span class='oHL oHL-math-indent oHL_added'>[→] ");        }    }); // Sentences missing a period matchDescriptions["oHL-missing-sentence-period"] = ["Missing sentence period", "Sentences should end with a full stop."]; $(".reference").each(function findUnterminatedSentences {       const prevChar = this.previousSibling?.textContent.slice(-1);        const nextChars = this.nextSibling?.textContent.slice(0, 2);        if (prevChar  === null || nextChars === null) { return true; }        if (/[a-z]/.test(prevChar) && / [A-Z]/.test(nextChars)) {            $(this).before("<span class='oHL oHL-missing-sentence-period oHL_added'>[.] ");        }    }); // Paragraphs missing a period matchDescriptions["oHL-missing-paragraph-period"] = ["Missing paragraph period", "Paragraphs should end with a full stop."]; $("#mw-content-text p").each(function findUnterminatedParagraphs {       if ($(this).children(".oHL-pseudoheader").length) { return true; }        if ($(this).children.first.is(".oHL_added")) { return true; }

const element = this.cloneNode(true); element.querySelectorAll("style, sup")?.forEach(e => e.remove); const paragraph = $(element).text;

// Delete quotes and trailing whitespace const paragraphCleaned = paragraph.replace(/["”'’]/g, "").replace(/\s+$/, "");       const lastCharacter = paragraphCleaned.slice(-1);

const isFinalParagraph = $(this).next.is("h2, h3, h4, h5, h6, #toc"); let searchChars = ".?!"; if (!isFinalParagraph || !isNonDisambigPage) { searchChars += ",:)";       }

if (!searchChars.includes(lastCharacter)) { $(this).append("<span class='oHL oHL-missing-paragraph-period oHL_added'>[.] "); }   });    filterList.push("blockquote .oHL-missing-paragraph-period", ".quotebox .oHL-missing-paragraph-period", ".gallerytext .oHL-missing-paragraph-period", "th .oHL-missing-paragraph-period", ".poem .oHL-missing-paragraph-period", ".infobox .oHL-missing-paragraph-period");

// Breaks between paragraphs matchDescriptions["oHL-br"] = ["Paragraph breaks", "Paragraph breaks should use newlines (not the  element) and there should only be a single break between paragraphs."]; // $("p br").each(function findParagraphBreaks {   //  if (this.previousSibling === null) {    //      $(this).before(" &lt;br&gt; ");    //  }    // }); $("#mw-content-text p br").before("<span class='oHL oHL-br oHL_added'>&lt;br&gt; "); // Filter out stub templates $(".oHL-br").each(function filterStubBreaks {       if ($(this).parent.next("style").next(".stub").length) {            $(this).remove;        }    }); filterList.push(".poem .oHL-br", ".chemf .oHL-br", ".music-symbol .oHL-br");

// Improper infobox lists matchDescriptions["oHL-infobox-br"] = ["Improper infobox list", "Embedded lists in infoboxes should use semantic markup with . (MOS:UBLIST)"]; $(".infobox-data br, .infobox br + a, .infobox br + .url").before("<span class='oHL-opt oHL-infobox-br oHL_added' style='font-size:55%;'>&lt;br&gt; "); filterList.push("b + .oHL-infobox-br", ".infobox-header .oHL-infobox-br",                   ".nickname + .oHL-infobox-br"); $(".oHL-infobox-br + br + .birthplace").prev.prev.remove; $(".oHL-infobox-br + br + .deathplace").prev.prev.remove; $(".oHL-infobox-br + br + b").prev.prev.remove; // Pseudoheaders $(".oHL-infobox-br + br + .oHL-infobox-br").prev.prev.remove; // double matches $("a[title='Least Concern']").prev(".oHL-infobox-br").remove; // Endangered status

// Double quotes which should be nested matchDescriptions["oHL-nested-quote"] = ["Nested quote marks", "Nested quote marks should alternate between double and single. (MOS:QWQ)"]; $("cite, q").each(function findDupeDoubleQuotesKerned {   	const text = this.textContent;    	if (text.includes('"')) {    		let html = this.innerHTML;    		html = html.replaceAll(' "', ' " ');   		html = html.replaceAll('" ', ' " ');    		this.innerHTML = html;    	}    });    $("cite .external, q").each(function findDupeDoubleQuotes {    	const text = this.textContent;    	if (text.includes('"')) {    		let html = this.innerHTML;    		html = html.replace(/(?<=<[^>]*)"(?=[^<]*>)/g, "€€"); // guard    		html = html.replace(/^""/, '" " ');    		html = html.replace(/""$/, ' " "');    		html = html.replace(/ "/g, ' " ');    		html = html.replace(/" /g, ' " ');    		html = html.replaceAll("€€", '"'); // unguard    		this.innerHTML = html;    	}    });

// Images without |thumb| matchDescriptions["oHL-frameless-img"] = ["Frameless image", "Most image thumbnails should use, along with a caption."]; $("span[typeof='mw:File']").addClass("oHL oHL-frameless-img"); // $(".image").each(function findFramelessImages {   //     if ($(this).closest(".thumb, table").length === 0) {    //         $(this).children("img").addClass("oHL oHL-frameless-img");    //     }    // }); // filterList.push(".noviewer.oHL-frameless-img", ".noviewer .oHL-frameless-img",   //                 ".navbox .oHL-frameless-img", ".dmbox .oHL-frameless-img");

// Improper indentation matchDescriptions["oHL-bad-indent"] = ["Improper indentation", "Quotations, tables, formulas, and generic lists are not definition lists and should use proper semantic markup. (see: User:Opencooper/Proper indentation)"]; $("dd, dd ul, dd ol").addClass("oHL_indent"); $("dt + .oHL_indent, dd + .oHL_indent, .oHL_indent dl .oHL_indent").removeClass("oHL_indent"); $(".oHL_indent sub").parent.removeClass("oHL_indent"); // Chem formulas $(".oHL_indent").parent("dl").addClass("oHL_bad-indent");

$(".oHL-spaced-list + .oHL_bad-indent").prev.remove; $(".mwe-math-element").closest(".oHL_bad-indent").removeClass("oHL_bad-indent");

$(".oHL_bad-indent").each(function findBadIndents {       const previousSibling = $(this).prev;        if (!previousSibling.hasClass("oHL_bad-indent")) {            $(this).before(" <span class='oHL oHL-bad-indent oHL_added'>[Improper indent]  ");        }    });

// Unindented definition terms matchDescriptions["oHL-dl-indent"] = ["Unindented term definition", "Definition lists consist of term–definition pairs, the latter of which should be indented. (see: MOS:DEFLIST)"]; $("dl + p + dl, dl + p + h2").prev.prepend("<span class='oHL oHL-dl-indent oHL_added'>[→] ");

// tag matchDescriptions["oHL-center"] = ["Center tag", "The HTML  tag is deprecated. Content, such as captions, should not be arbitrarily centered."]; $("center").each(function findCenterTags {       $(this).before(" <span class='oHL oHL-center oHL_added'>[center tag]  ");    }); // Centered captions matchDescriptions["oHL-caption-center"] = ["Centered caption", "Content, such as captions, should not be arbitrarily centered."]; $("figcaption center, figcaption .center, .thumb .center").addClass("oHL oHL-caption-center");

// Unnecessary ToC matchDescriptions["oHL-toc"] = ["Unnecessary ToC", "Stubs without unique subsections do not need a table of contents."]; const firstSection = $(".toctext").first.text; if (firstSection && "See also, References, Sources, External links".includes(firstSection)) { $("#toc").after(" <span class='oHL-opt oHL-toc oHL_added'>[Hide ToC] "); }

// Missing lead matchDescriptions["oHL-missing-lead"] = ["Missing lead", "The article is missing a lead. (MOS:LEAD)"]; if ($("#toc").length && $("#toc").prev.length === 0) { $("#toc").before(" <span class='oHL oHL-missing-lead oHL_added'>[Missing lead] "); }   // Table with missing cells matchDescriptions["oHL-missing-cells"] = ["Missing table cells", "Tables should not have missing cells. Add empty cells with placeholders instead."]; $(".wikitable").each(function findTables {       const tableElement = this;        const rows = $(this).find("tr");        const firstRow = $(rows).first;        const columnsCount = $(firstRow).find("th").length;        rows.each(function findMissingCells { const cellsCount = $(this).children.length; if (cellsCount < columnsCount) { $(tableElement).after(" <span class='oHL-opt oHL-missing-cells oHL_added'>[Table missing cells] "); return false; }       });    });    // Table with both vertical and horizontal headers // matchDescriptions["oHL-redundant-headers"] = ["Table header misuse", "Tables should not have both horizontal and vertical headers."]; // $(".wikitable").each(function findTableHeaderMisuse {   //     const hasVerticalHeader = $(this).find("tr:first-child th").length != 0;    //     const hasHorizontalHeader = $(this).find("tr:not(:first-child) th").length != 0;    //     if (hasVerticalHeader && hasHorizontalHeader) {    //         $(this).after(" <span class='oHL-opt oHL-redundant-headers oHL_added'>[Table misuses headers]  ");    //     }    // }); // Authority control before navboxes matchDescriptions["oHL-auth-placement"] = ["Authority control placement", "Authority control templates should go after navboxes. (MOS:ORDER)"]; $(".authority-control + .navbox").before(" <span class='oHL oHL-auth-placement oHL_added'>[Move authority control down↓] ");

// Long lists // $("#bodyContent ul").addClass("oHL_list"); // $("#toc .oHL_list, .navbox .oHL_list, #catlinks .oHL_list,"   //   + " .refbegin .oHL_list, .div-col .oHL_list, .sidebar .oHL_list").removeClass("oHL_list"); // $(".oHL_list").each(function findLongLists {   //  const items = $(this).children("li").length;    //  if (items >= 8) {    //      $(this).before(" <span class='oHL-opt oHL-div-col oHL_added'>[Split into columns]  ");    //  }    // });

// Lists broken up by an image matchDescriptions["oHL-list-img"] = ["Image in list", "Images inside lists break them up into separate lists, and should go before the list. (MOS:LIST)"]; $("ul + figure + ul, ul + .tmulti + ul").before(" <span class='oHL oHL-list-img oHL_added'>[Move image interrupting list] ");

// Duplicate references matchDescriptions["oHL-duplicated-ref"] = ["Duplicated ref", "References should not be duplicated, instead using named references. (see: WP:NAMEDREFS)"]; const refLinks = []; $(".mw-references-wrap cite").each(function findReferenceLinks {       const firstLink = $(this).find(".external").first;        if (!firstLink.length) { return true; }        const href = $(firstLink).attr("href");        const hrefCleaned = href.replace(/https?:\/\//, "");        refLinks.push(hrefCleaned);    }); const refLinksUnique = new Set(refLinks); if (refLinksUnique.size != refLinks.length) { for (const link of refLinksUnique) { const count = refLinks.filter(l => l === link).length; if (count > 1) { const dupes = $(".mw-references-wrap a[href*='" + link + "']"); dupes.first.addClass("oHL oHL-duplicated-ref"); dupes.slice(1).addClass("oHL_dupe_ref"); }       }    }

// Duplicate images matchDescriptions["oHL-duplicate-img"] = ["Duplicate image", "In most cases, images should not be repeated."]; let imageLinks = []; $(".mw-file-description").each(function getImages {   	imageLinks.push($(this).attr("href"));    }); const imagesUnique = new Set(imageLinks); if (imagesUnique.size != imageLinks.length) { for (const link of imagesUnique) { const count = imageLinks.filter(l => l === link).length; if (count > 1) { $(".mw-file-description[href='" + link + "']").not(":first").children("img").addClass("oHL oHL-duplicate-img"); }       }    }    filterList.push(".stub .oHL-duplicate-img", ".navbox .oHL-duplicate-img",                    ".sidebar .oHL-duplicate-img", ".ambox .oHL-duplicate-img",                    ".chess-board .oHL-duplicate-img");

// Thumbnails at end end of sections matchDescriptions["oHL-thumb-placement"] = ["Thumbnail placement", "Floating content, such as thumbnails, should go before the content they displace."]; $("p + figure + h2, ul + figure + h2").before(" <span class='oHL oHL-thumb-placement oHL_added'>[Relocate image] "); filterList.push(".mw-halign-center + .oHL-thumb-placement");

// Thumbnails larger than actual size matchDescriptions["oHL-overlarge-img"] = ["Overlarge image", "Thumbnails should not be set to sizes larger than the actual image file, resulting in upscaling."]; $("[typeof^='mw:File'] img").not(".noviewer").each(function findOverlargeThumbnails {   	const src = $(this).attr("src");    	if (src.endsWith(".svg.png")) { return true; }        const displayWidth = $(this).attr("width");        const originalWidth = $(this).attr("data-file-width");        if (parseInt(displayWidth) > parseInt(originalWidth)) {            $(this).addClass("oHL oHL-overlarge-img");        }    }); // Dash in front of quote attributions // Maybe not. Reference: https://en.wikipedia.org/wiki/Wikipedia:Manual_of_Style#Other_uses_(em_dash_only) // $(".quotebox cite").each(function findMissingQuoteDashes {   //     const text = $(this).text;    //     if (!text.startsWith("–") && !text.startsWith("—")) {    //         $(this).prepend("<span class='oHL oHL-attrib-dash oHL_added'>[—] ");    //     }    // }); // Fake footnotes matchDescriptions["oHL-pseudo-footnotes"] = ["Pseudo footnote", "The template is deprecated for citing sources. (Template:Ref)"]; $(".reference.plainlinks").addClass("oHL oHL-pseudo-footnotes");

// Short descriptions matchDescriptions["oHL-description-uppercase"] = ["Short description case", "Short descriptions should begin with an uppercase letter. (WP:SDFORMAT)"]; matchDescriptions["oHL-description-punc"] = ["Short description period", "Short descriptions should not use a full stop. (WP:SDFORMAT)"]; matchDescriptions["oHL-description-length"] = ["Short description period", "Short descriptions should usually use less than 40 characters. (WP:SDLENGTH)"]; matchDescriptions["oHL-description-dupe"] = ["Short description duplication", "Short descriptions should not be the same as the page title. (WP:SDDUPLICATE)"]; const shortDescriptions = $(".shortdescription"); shortDescriptions.first.each(function analyzeShortDescription {   	let text = $(this).text;    	const length = text.length;    	if (length == 0) { return false; }    	const pageTitle = mw.config.get("wgTitle").replace(/ \(.*\)$/, "");    	if (text.toLowerCase == pageTitle.toLowerCase) {    		$(this).append(" <span class='oHL oHL-description-dupe oHL_added'>[duplicates title] ");    	}

const firstChar = text[0]; if (/[a-z]/.test(firstChar)) { text = text.slice(1); $(this).text(text); $(this).prepend(" "   		                + firstChar + " "); }

const finalChar = text.at(-1); if (finalChar == ".") { text = text.slice(0, -1); $(this).text(text); $(this).append(" " + finalChar + " "); }

if (length > 100) { $(this).append(" <span class='oHL-opt oHL-description-length oHL_added'>[too long] (" + length + " chars) "); }   });    // Missing short description    matchDescriptions["oHL-missing-desc"] = ["Missing short description", "All mainspace articles should have a short description. (WP:SHORTDESC)"];    if (shortDescriptions.length == 0 && mw.config.get("skin") != "minerva") {    	$("#mw-content-text").prepend(" <span class='oHL oHL-missing-desc oHL_added'>[Missing short description]  ");    }

// Ref section without list matchDescriptions["oHL-nonlist-ref"] = ["Non-list ref section", "References sections should contain unordered lists."]; $(refSectionsSelector).each(function findNonlistRefsections {   	const ref = $(this).parent.next;    	if ($(ref).prop("tagName") == "P") {    		$(ref).prepend("<span class='oHL oHL-nonlist-ref oHL_added'>[*] ");    	}    });

// Superscripts in headers matchDescriptions["oHL-header-superscript"] = ["Header tag", "Headers should not have references or other inline templates. (MOS:HEADINGS)"]; $(".mw-headline .Inline-Template, .mw-headline .reference").addClass("oHL oHL-header-superscript");

// Redundant italicization matchDescriptions["oHL-redundant-italics"] = ["Redundant italicization", "The template already italicizes text, so italics markup is not necessary."]; $("i > span > i[lang]").addClass("oHL oHL-redundant-italics");

// Emphasis matchDescriptions["oHL-emph"] = ["Emphasis", "When italics are used to emphasize text, the template is more semantic. (MOS:EMPHASIS)"]; $("#mw-content-text i").each(function findEmphasis {   	if (this.parentElement.tagName == "SUP" || this.firstChild?.tagName == "A" || this.hasAttribute("lang") || (this.firstChild?.tagName == "SPAN" && this.firstChild.hasAttribute("lang")) || $(this).closest("a").length) {   		return true;    	}    	const text = $(this).text;    	if (text.length == 0 || text.includes(" ")) {    		return true;    	}    	// Try excluding math variables    	if (text.length == 1 && text != "a") {    		return true;    	}    	const firstLetter = text[0];    	if (/[A-Z]/.test(firstLetter)) {    		return true;    	}    	// Require a preceding space    	const previousSibling = this.previousSibling;    	if (previousSibling != null && previousSibling.nodeName == "#text" && !previousSibling.textContent.endsWith(" ")) {    		return true;    	}    	$(this).addClass("oHL-opt oHL-emph");    }); filterList.push(".mw-references-wrap .oHL-emph", ".texhtml .oHL-emph", ".side-box .oHL-emph");

// Multi-column lists with too many columns matchDescriptions["oHL-col-count"] = ["Column count", "A list should not have so many columns that it hampers scannability. (the list would have more than three columns on a 1920px display at the default Vector font size)"]; $(".div-col").each(function inspectColumnWidths {   	const colWidthCSS = this.style["column-width"];    	if (colWidthCSS == null || !/em$/.test(colWidthCSS)) { return true; }    	const colWidthEm = parseFloat(colWidthCSS.replace("em", ""));    	if (colWidthEm <= 29.3) {    		$(this).before(" <span class='oHL oHL-col-count oHL_added'>[Too many columns]  ");    	}    });

// Self-ref hatnotes matchDescriptions["oHL-self-ref"] = ["Self-ref hatnote", "Hatnotes that link to Wikipedia pages should use the  parameter. (WP:ITSELF)"]; $(".hatnote:not(.selfreference) a[href^='/wiki/Wikipedia']").parent.addClass("oHL oHL-self-ref");

// Stub template spacing matchDescriptions["oHL-stub-space"] = ["Stub template spacing", "Stub templates should be preceded by two blank lines. (WP:STUBSPACING)"]; $(".mw-parser-output > :not(p) + style + .stub").before(" <span class='oHL-opt oHL-stub-space oHL_added'>[&lt;br&gt;] ");

// Non-romanized text outside of parenthesis matchDescriptions["oHL-non-Latin-prose"] = ["Non-Latin Prose", "Article prose should primarily use romanized text, with the non-Latin text in parenthesis. (see MOS:TEXT)"]; $("[lang]").each(function findNonLatinProse {   	if (this.lang.includes("Latn")) {    		return true;    	}    	const sibling = this.parentElement?.nextSibling;    	if (sibling && sibling.nodeType == 3 && sibling.textContent == " (") { const nextElement = sibling?.nextSibling; if (nextElement && nextElement.hasAttribute("lang")) { $(this).addClass("oHL oHL-non-Latin-prose"); }   	}    });    // Italicized non-Latin text    matchDescriptions["oHL-non-Latin-italics"] = ["Non-Latin Italics", "Italics should not be used with non-Latin scripts that don't use them. (MOS:BADITALICS)"];    const nonItalicLangs = ["ja", "zh", "ko", "cmn", "ar", "ur", "hi", "sa"];    $("#mw-content-text i [lang]").each(function findItalicizedNonLatin { if (nonItalicLangs.includes(this.lang)) { $(this).addClass("oHL oHL-non-Latin-italics"); }   });

// Bolding title after lead matchDescriptions["oHL-overbolding"] = ["Overbolding", "Only the first occurence of the article title should be bolded. (MOS:BOLDSYN)"]; let leadMarker; if ($("#toc").length) { leadMarker = $("#toc"); } else { leadMarker = $("#mw-content-text h2").first; }   const leadBolded = leadMarker.prevAll.not(".infobox").not(".ambox, .sidebar, .side-box, .navbox").find("b"); leadBolded.addClass("oHL_title"); const leadNames = leadBolded.toArray.map(e => e.textContent.toLowerCase); $("#mw-content-text b").not(".oHL_title").each(function findOverbolding {   	const boldText = this.textContent.toLowerCase;    	if (leadNames.includes(boldText)) {    		$(this).addClass("oHL oHL-overbolding");    	}    }); filterList.push(".infobox .oHL-overbolding", ".ambox .oHL-overbolding",                   ".navbox .oHL-overbolding", ".sistersitebox .oHL-overbolding",                    ".sidebar .oHL-overbolding", ".side-box .oHL-overbolding"); // Duplicate bolded lead items const boldLeadTitles = []; $(".oHL_title").each(function findDuplicateLeadBolding {   	const title = $(this).text;    	if (boldLeadTitles.includes(title)) {    		$(this).addClass("oHL oHL-overbolding");    	} else {    		boldLeadTitles.push(title);    	}    }); // Bolded quote marks in lead matchDescriptions["oHL-title-quote-bold"] = ["Bolded title quote mark", "Quote marks in the subject's name should not be bolded. (MOS:QUOTENAME)"]; $(".oHL_title").each(function findBoldedNameQuotes {   	const text = this.textContent;    	if (!text.includes('"')) { return true; }    	let html = this.innerHTML;    	html = html.replaceAll(' "', ' " ');    	html = html.replaceAll('" ', ' " ');    	this.innerHTML = html;    });    // Citation overkill    matchDescriptions["oHL-overciting"] = ["Overciting", "Use of more than three adjacent citations should be trimmed or bundled. (WP:OVERCITE)"];   $(".reference").each(function findOverciting {    	// Make sure we're at the first of adjacent citations    	if (this.previousSibling?.nodeName == "SUP"    	    && this.previousSibling.classList.contains("reference")) {    		return true;    	}    	let citeCount = 1;    	let nextElement = this.nextSibling;    	while (nextElement?.classList?.contains("reference")) {    		citeCount += 1;    		const neighbor = nextElement.nextSibling;    		if (neighbor?.nodeName != "SUP") {    			break;    		}    		nextElement = neighbor;    	}

if (citeCount > 3) { $(nextElement).after(" <span class='oHL oHL-overciting oHL_added'>[Overciting] "); }   });    // Sandwiched images    let sandwichSelector = "";    const sandwichVariants = [".tright", ".infobox", ".sidebar", ".quotebox"];    for (const variant of sandwichVariants) {    	sandwichSelector += " .tleft + " + variant + ", ";    	sandwichSelector += variant + " + .tleft,";    }    sandwichSelector = sandwichSelector.replace(/,$/, "");    matchDescriptions["oHL-image-sandwich"] = ["Sandwiched images", "Avoid squishing text between a left-floating image. (MOS:SANDWICH)"];    $(sandwichSelector).after(" <span class='oHL oHL-image-sandwich oHL_added'>[Sandwiched images]  ");

// Orphaned references matchDescriptions["oHL-orphaned-refs"] = ["Orphaned references", "References should normally be housed in the References section. These references were likely cited after the appears."]; if ($(".references").length > 1) { const lastElement = $(".mw-parser-output").children.last; if (lastElement.hasClass("mw-references-wrap")) { lastElement.before(" <span class='oHL oHL-orphaned-refs oHL_added'>[Orphaned references] "); }	   $(refSectionsSelector).parent.nextUntil("h2").filter(".oHL-orphaned-refs").remove; }   // Floating elements clashing with a reflist matchDescriptions["oHL-obstructed-reflist"] = ["Obstructed reflist", "Floating templates should not encroach the space of a multi-column reflist or they will cause layout problems. To fix this, a

should be placed at the end of the section before the references section."];   const columnarReflists = $(".mw-references-columns");    if (columnarReflists.length > 0) {    	const bodyElement = document.getElementById("mw-content-text");        const bodyWidth = window.getComputedStyle(bodyElement).width;        columnarReflists.each(function findObstructedReflists {    	    const reflistWidth = window.getComputedStyle(this).width;    	    if (reflistWidth != bodyWidth) {    	    	$(this).before(" <span class='oHL oHL-obstructed-reflist oHL_added'>[Obstructed reflist]  ");    	    }        });    }

// Special rules for disambiguation pages if (!isNonDisambigPage) { matchDescriptions["oHL-disambig-multi-links"] = ["Multiple wikilinks", "Disambiguation listings should only have one blue link. (MOS:DABONE)"]; $("li .oHL_wikilink:nth-child(2)").addClass("oHL oHL-disambig-multi-links"); }

// Emitted citation errors matchDescriptions["oHL-citation-error"] = ["Citation error", "The article contains a citation error."]; $(".cs1-maint").show; $(".cs1-visible-error:first-of-type, .cs1-maint, .mw-ext-cite-error").addClass("oHL oHL-citation-error");

// Find overlinking checkOverlinking;

// Italics title for works checkTitleItalicization; // Colored backgrounds with poor contrast checkContrast;

// Check ALT text and show full size of images showImageInfo; }

function prepHTML { expandCollapsed;

// Mark ISBNs $(".oHL_wikilink[href^='/wiki/Special:BookSources']").addClass("oHL_ISBN");

// Temporarily remove elements from the DOM $("#toc, .mw-editsection, .mwe-math-element, .mw-cite-backlink, #catlinks,"     + " #mw-content-text style, .IPA, .mw-highlight, code, .oHL_ISBN,"      + " .external[href*='doi.org'], .external[href*='worldcat.org'],"      + " .navbox .uid, .barbox, .mw-kartographer-map, .texhtml, .external.free,"      + " video, canvas, oHL_anchorLink, .mw-tmh-player, .ext-phonos,"      + " .lazy-image-placeholder").each(detachTemp);

/*    * Replace attributes so they don't get caught in our highlighting * Note: id needs to come first because we insert our own ids for the others */   document.querySelectorAll("#mw-content-text [id]")?.forEach(e => mangle(e, "id")); document.querySelectorAll("#mw-content-text [style]")?.forEach(e => mangle(e, "style")); document.querySelectorAll("#mw-content-text [href]")?.forEach(e => mangle(e, "href")); document.querySelectorAll("#mw-content-text [title]")?.forEach(e => mangle(e, "title")); document.querySelectorAll("#mw-content-text img[src]")?.forEach(e => mangle(e, "src")); document.querySelectorAll("#mw-content-text img[alt]")?.forEach(e => mangle(e, "alt")); document.querySelectorAll("#mw-content-text img[resource]")?.forEach(e => mangle(e, "resource")); document.querySelectorAll("h2, h3, h4, h5, h6")?.forEach(e => {   	mangle(e, "onmouseover");    	mangle(e, "onmouseout");    }); }

function expandCollapsed { $(".mw-collapsible").children.children("tr").css("display", ""); $(".mw-collapsible-content").css("display", "");

$(".NavFrame .NavToggle").each(function expandNavs {       if ($(this).text == "[show]") {            $(this).click;        }    }); $(".collapsible-heading").not(".open-block").click; }

function replaceHTML { const contentElement = document.getElementById('mw-content-text'); let html = contentElement.innerHTML;

// Delete comments html = html.replace(//gs, '');

// Keep track of Euro symbols, which we use to guard text we don't want matched const euroCountBefore = (html.match(/€/g) || []).length;

// p. in refs with multiple pages matchDescriptions["oHL-pp"] = ["Multipage cite", "Citations containing multiple pages should use \"pp.\"."]; html = html.replace(/ p.((?: | |&#160;)[0-9]+[-–])/g, ' p. $1');

// Dashes matchDescriptions["oHL-rangedash"] = ["Range dash", "Ranges should use an en dash. (MOS:ENDASH)"]; html = html.replace(/(\w+)-(\w+)-(\w+)/g, '$1€-€$2€-€$3'); // Guard YYYY-MM-DD, 9-1-1, etc.   html = html.replace(/(\d)-(\d)/g, '$1 - $2'); html = html.replace(/(\d)-present/g, '$1 -present '); html = html.replaceAll('€-€', '-'); // Unguard filterList.push(".external .oHL-rangedash");

matchDescriptions["oHL-typewriter-dash"] = ["Typewriter dash", "Dashes should use the proper Unicode character instead of typewriter dashes. ( or  ; MOS:DASH)"]; html = html.replace(/(---|--|–-|-–|-—|—-)/g, ' $1 '); filterList.push(".mw-references-wrap .oHL-typewriter-dash"); matchDescriptions["oHL-spaced-endash"] = ["Spaced dash", "Spaced dashes should use the proper Unicode character for en dashes. (MOS:ENDASH)"]; html = html.replaceAll(' - ', ' - '); html = html.replaceAll(' -', ' - '); filterList.push(".mw-references-wrap .oHL-spaced-endash");

matchDescriptions["oHL-spaced-emdash"] = ["Spaced em dash", "Em dashes should be unspaced. (MOS:EMDASH)"]; html = html.replace(/( —|(?<!—)— )/g, ' $1 '); filterList.push(".mw-references-wrap .oHL-spaced-emdash"); matchDescriptions["oHL-bad-rangedash"] = ["Bad range dash", "Dashes in ranges should use an en dash. (MOS:ENDASH)"]; html = html.replace(/(\d)—(\d)/g, '$1 — $2'); filterList.push(".mw-references-wrap .oHL-bad-rangedash");

matchDescriptions["oHL-spaced-range"] = ["Spaced range", "The en dash in numerical ranges should be unspaced. (MOS:ENDASH)"]; html = html.replace(/(\d{4}) – (\d{1,2} [A-Z])/g, '$1€–€$2'); // Guard YYYY – DD Month YYYY html = html.replace(/(\d{1,2} [A-Z][a-z]+ \d{4}) – (\d{4})/g, '$1€–€$2'); // Guard DD Month YYYY – YYYY html = html.replace(/(\d) – (\d)/g, '$1 – $2'); html = html.replaceAll('€–€', ' – '); // Unguard filterList.push(".mw-references-wrap .oHL-spaced-range");

matchDescriptions["oHL-unspaced-endash"] = ["Unspaced en dash", "En dashes should usually have spaces surrounding them. (MOS:ENDASH; exception: MOS:ENBETWEEN)"]; html = html.replace(/([A-Z][^A-Z \n]+)–([A-Z])/g, '$1€–€$2'); // Guard Capital–Capital html = html.replace(/([Ww]in)–([Ll]oss)/g, '$1€–€$2'); // Guard Win–loss html = html.replace(/([0-9]{2}s)–([0-9]{2})/g, '$1€–€$2'); // Guard 60s–70s html = html.replace(/([0-9]th)–([0-9]+th)/g, '$1€–€$2'); // e.g. 12th–13th century html = html.replace(/([A-Za-z])–/g, '$1 – '); html = html.replaceAll('€–€', '–'); // Unguard filterList.push(".mw-references-wrap .oHL-unspaced-endash", ".stub .oHL-unspaced-endash");

matchDescriptions["oHL-bad-minus"] = ["Bad minus sign", "Instead of a hyphen, minus signs should use the proper Unicode character. (MOS:NEGATIVE)"]; html = html.replace(/([ >(])([-–—])(\d)/g, '$1 $2 $3');   html = html.replace(/ ([A-FO]{1,3})([-–—])([.,;:\)\n<"' ])/g, ' $1 $2 $3');    filterList.push(".mw-references-wrap .oHL-bad-minus");

// Quotes matchDescriptions["oHL-bad-quote"] = ["Bad quote mark", "Wikipedia only uses straight quote marks. (MOS:STRAIGHT)"]; html = html.replace(/([‘’“”`´]|'')/g, ' $1 '); html = html.replaceAll('′s', ' ′ s'); filterList.push(".oHL_img_info .oHL-bad-quote"); matchDescriptions["oHL-foreign-quote"] = ["Non-English quote mark", "Wikipedia only uses straight quote marks. (MOS:STRAIGHT)"]; html = html.replace(/([„«»‹›])/g, ' $1 '); filterList.push(".mw-references-wrap .oHL-foreign-quote"); // Nested quote mark html = html.replace(/([.,;:] ?)""/g, '$1 "" ');

matchDescriptions["oHL-adj-quote"] = ["Adjacent quote marks", "Adjacent quote marks should have their kerning adjusted. (see and )"]; html = html.replace(/((?<= )"'|'"(?!>))/g, ' $1 '); filterList.push(".mw-references-wrap .oHL-adj-quote");

matchDescriptions["oHL-punc-in-quote"] = ["Punctuation in quotes", "Punctuation in quotations should use the logical quotation style. (MOS:LQ)"]; html = html.replace(/(["']\w+)([.,;:])(["'])/g, '$1 $2 $3'); filterList.push("blockquote .oHL-punc-in-quote", ".mw-references-wrap .oHL-punc-in-quote"); // Italics for  and s   matchDescriptions["oHL-aposS-italics"] = ["Apostrophe italics", "Apostrophes after italics should have their kerning adjusted. (see  and ')"]; html = html.replaceAll(/('s?)<\/i>/g, ' $1 </i>');

// Punctuation in italics and bold matchDescriptions["oHL-italpunc"] = ["Italicized punctuation", "Punctuation should not be italicized if it is not part of the title."]; matchDescriptions["oHL-boldpunc"] = ["Bolded punctuation", "Punctuation should not be bolded if it is not part of the title."]; html = html.replace(/(i\.e\.|e\.g\.|et al\.|etc\.)/g, '$1€'); // Guard html = html.replace(/([A-Z])\./g, '$1€'); // Guard html = html.replaceAll('...', '..€'); // Guard html = html.replaceAll(' ', '&nbsp€'); // Guard html = html.replace(/([.,;:"\])/])<\/i>/g, ' $1 </i>');   html = html.replace(/([,;:\])/])<\/b>/g, ' $1 </b>');    html = html.replace(/(i\.e\.|e\.g\.|et al\.|etc\.)€/g, '$1'); // Unguard    html = html.replace(/([A-Z])€/g, '$1.'); // Unguard    html = html.replaceAll('..€', '...'); // Unguard    html = html.replaceAll('&nbsp€', ' '); // Unguard    filterList.push(".mw-references-wrap .oHL-italpunc", ".stub .oHL-italpunc",                    ".listen .oHL-italpunc", ".ambox .oHL-italpunc");    filterList.push(".infobox td .oHL-boldpunc");

matchDescriptions["oHL-formatted-quotemark"] = ["Formatted quote mark", "Quotation marks should not be italicized or bolded. (except when part of a work's title)"]; html = html.replace(/(<[ib]>)(["'])/g, '$1 $2 ');   matchDescriptions["oHL-formatted-bracket"] = ["Formatted bracket", "Brackets should not be italicized or bolded. (except when part of a work's title)"];   html = html.replace(/(<[ib]>)((])/g, '$1 $2 ');    filterList.push(".ambox .oHL-formatted-bracket");    // Text which should use     // html = html.replace(/(<i>[a-z]{2,}<\/i>)/g, '$1 <span class="oHL-opt oHL-emph-italic oHL_added">[em] ');    // Quote marks in bolded title    matchDescriptions["oHL-bold-quotemark"] = ["Bolded quote mark", "Quote marks around a bolded title should not be bolded themselves. (except when they are part of the title)"];    html = html.replace(/(<b>[^<\n]*)"/g, '$1 " ');    // Bolded parenthesis    matchDescriptions["oHL-boldparen"] = ["Bolded parenthesis", "Parentheses should not be italicized or bolded. (except when part of a work's title)"];    html = html.replaceAll(')</b>', ' ) </b>');    // Bolded single letters    matchDescriptions["oHL-bolded-letter"] = ["Bolded letter", "Avoid boldface for emphasis or variables. ([[MOS:NOBOLD; MOS:TEXT)"];    html = html.replace(/(\w)<\/b>/g, '<b> $1 ');    filterList.push(".mw-references-wrap .oHL-bolded-letter",                    "cite .oHL-bolded-letter", ".navbox .oHL-bolded-letter",                    ".music-symbol .oHL-bolded-letter");

// Text without "lang" // See: https://www.unicode.org/Public/UCD/latest/ucd/Scripts.txt matchDescriptions["oHL-lang-han"] = ["Script missing lang tag (CJK)", "Non-English text should be tagged with its language. (MOS:FOREIGN)"]; html = html.replace(/([\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}]+)([\),])(?!<)/gu, ' $1 $2');   matchDescriptions["oHL-lang-kor"] = ["Script missing lang tag (Korean)", "Non-English text should be tagged with its language. (MOS:FOREIGN)"];    html = html.replace(/(\p{Script=Hangul}+)([\),])(?!<)/gu, ' $1 $2'); matchDescriptions["oHL-lang-cyrl"] = ["Script missing lang tag (Cyrillic)", "Non-English text should be tagged with its language. (MOS:FOREIGN)"]; html = html.replace(/(\p{Script=Cyrillic}+)([\),])(?!<)/gu, ' $1 $2');   matchDescriptions["oHL-lang-grk"] = ["Script missing lang tag (Greek)", "Non-English text should be tagged with its language. (MOS:FOREIGN)"];    html = html.replace(/(\p{Script=Greek}+)([\),])(?!<)/gu, ' $1 $2'); matchDescriptions["oHL-lang-deva"] = ["Script missing lang tag (Devanagari)", "Non-English text should be tagged with its language. (MOS:FOREIGN)"]; html = html.replace(/(\p{Script=Devanagari}+)([\),])(?!<)/gu, ' $1 $2');   matchDescriptions["oHL-lang-ara"] = ["Script missing lang tag (Arabic)", "Non-English text should be tagged with its language. (MOS:FOREIGN)"];    html = html.replace(/(\p{Script=Arabic}+)([\),])(?!<)/gu, ' $1 $2'); matchDescriptions["oHL-lang-heb"] = ["Script missing lang tag (Hebrew)", "Non-English text should be tagged with its language. (MOS:FOREIGN)"]; html = html.replace(/(\p{Script=Hebrew}+)([\),])(?!<)/gu, ' $1 $2');   matchDescriptions["oHL-lang-tam"] = ["Script missing lang tag (Tamil)", "Non-English text should be tagged with its language. (MOS:FOREIGN)"];    html = html.replace(/(\p{Script=Tamil}+)([\),])(?!<)/gu, ' $1 $2'); matchDescriptions["oHL-lang-arm"] = ["Script missing lang tag (Armenian)", "Non-English text should be tagged with its language. (MOS:FOREIGN)"]; html = html.replace(/(\p{Script=Armenian}+)([\),])(?!<)/gu, ' $1 $2');   matchDescriptions["oHL-lang-thai"] = ["Script missing lang tag (Thai)", "Non-English text should be tagged with its language. (MOS:FOREIGN)"];    html = html.replace(/(\p{Script=Thai}+)([\),])(?!<)/gu, ' $1 $2');

// Ellipses // Two ellipses matchDescriptions["oHL-two-dots"] = ["Incomplete ellipsis", "Ellipses should have three dots."]; html = html.replace(/([^.])\.\.([^.])/g, '$1 .. $2'); filterList.push(".mw-references-wrap .oHL-two-dots"); // html = html.replaceAll('…', ' … '); // filterList.push(".mw-references-wrap .oHL-ellipsis-char"); // Interspaced ellipses matchDescriptions["oHL-spaced-ellipsis"] = ["Interspaced ellipsis", "Ellipses should not have spaces in between. (MOS:ELLIPSES)"]; html = html.replace(/(\. \. \.|\. \. \.)/g, ' . . . '); // Bracketed ellipses matchDescriptions["oHL-bracketed-ellipsis"] = ["Bracketed ellipsis", "Ellipses indicating omission in a quote should not be enclosed by square brackets. (MOS:BRACKET; Exception: if the quote itself already uses ellipses.)"]; html = html.replaceAll(/((]\.\.\.[)\)/g, ' $1 '); // Unspaced ellipses matchDescriptions["oHL-unspaced-ellipsis"] = ["Unspaced ellipsis", "Ellipses should have spaces surrounding them. (MOS:ELLIPSES)"]; html = html.replaceAll(" ", "€€"); // guard html = html.replaceAll(/([^ "'€])\.\.\.([^ .?!:;,)\]])/g, '$1 ... $2');   html = html.replaceAll(/(&[^ "'€])\.\.\. /g, '$1 ... ');   html = html.replaceAll(/ \.\.\.([^ .?!:;,)\]])/g, ' ... $1'); html = html.replaceAll("€€", " "); // unguard filterList.push(".mw-references-wrap .oHL-unspaced-ellipsis", ".oHL_wikilink .oHL-unspaced-ellipsis",                   "a.new .oHL-unspaced-ellipsis");

// Non-breaking spaces matchDescriptions["oHL-nbsp-multi"] = ["Multiple non-breaking spaces", "Only a single NBSP should be between words. They should also not be used to force formatting, instead, semantic elements or CSS should be used."]; html = html.replace(/((?: ){2,})/g, ' $1 '); filterList.push(".poem .oHL-nbsp-multi", "table .oHL-nbsp-multi",                   ".quotebox .oHL-nbsp-multi"); matchDescriptions["oHL-bad-nbsp"] = ["Spaced non-breaking-space", "A non-breaking space should not have any spaces around it."]; html = html.replace(/( |  )/g, ' $1 ');

// Note: units, am/pm handled elsewhere matchDescriptions["oHL-nbsp"] = ["Non-breaking-space", "Numbers, ellipses, etc. should be preceded by . (MOS:NBSP)"]; html = html.replace(/([0-9]) (dozen|hundred|thousand|million|billion|trillion)/g, "$1 $2"); html = html.replaceAll(" ...", " ..."); filterList.push(".mw-references-wrap .oHL-nbsp", ".infobox .oHL-nbsp",                   ".navbox .oHL-nbsp", "th .oHL-nbsp", ".nowrap .oHL-nbsp",                    ".texhtml .oHL-nbsp");

// Whitespace matchDescriptions["oHL-unspaced-period"] = ["Unspaced period", "A full stop should be followed by a space."]; html = html.replaceAll('Ph.D.', 'Ph€D.'); // Guard "Ph.D." html = html.replace(/([a-z]\.[A-Z][^A-Z])/g, ' $1 '); html = html.replaceAll('Ph€D.', 'Ph.D.'); // Unguard matchDescriptions["oHL-spaced-punc"] = ["Spaced punctuation", "Punctuation should not be preceded by a space. (MOS:PUNCTSPACE)"]; html = html.replace(/( | )\.\.\./g, '$1€.€€.€€.€'); // Guard ellipses html = html.replaceAll('. . .', '€.€ €.€ €.€'); // Guard spaced ellipses html = html.replace(/\.([0-9]{2,})/g, '€€$1'); // Guard gun calibers html = html.replace(/(( | )[,;:.?!%])/g, ' $1 '); html = html.replaceAll('€.€', '.'); // Unguard html = html.replaceAll('€€', '.'); // Unguard matchDescriptions["oHL-full-space"] = ["Fullwidth space", "Half-width spaces should be used instead of full-width ones. (Exception: inside Japanese text)"]; html = html.replaceAll('　', ' 　 '); matchDescriptions["oHL-spaced-chars"] = ["Spaced characters", "Characters should not have spacing between them."]; html = html.replace(/ (["'\[\]]) /g, ' $1 ');

matchDescriptions["oHL-spaced-ref"] = ["Spaced reference", "References should not be preceded by a space. (MOS:REFSPACE)"]; html = html.replaceAll(' <sup', ' <sup'); matchDescriptions["oHL-unspaced-ref"] = ["Unspaced reference", "References should be followed by a space."]; html = html.replace(/\/sup>(\w)/g, '/sup> $1 '); html = html.replace(/\/sup><i>(\w)/g, '/sup><i> $1 ');

// Inches/feet marks matchDescriptions["oHL-ft-inch"] = ["Height notation", "The symbols for feet and inches should be spelled out. (MOS:NUM)"]; html = html.replace(/(\d' [1-9]\d?")/g, ' $1 ');

// Multiplication matchDescriptions["oHL-mult-sign"] = ["Multiplication sign", "Multiplication should use the proper Unicode character,, instead of the Latin letter x. (MOS:MATH)"]; html = html.replace(/ x(64|86)/g, ' €€$1'); // Guard html = html.replaceAll(' 0x', ' 0€€'); // Guard html = html.replace(/(annotation_+[0-9]+)x/g, '$1€€'); // Guard html = html.replace(/(\d ?)x/g, '$1 x '); html = html.replace(/ x /g, ' x '); html = html.replaceAll('€€', 'x'); // Unguard

// Parenthesis matchDescriptions["oHL-unspaced-paren"] = ["Unspaced parenthesis", "Parenthesis should have spaces around them. (MOS:PAREN; exception: function names in programming)"]; // Trying to match parenthesis hitting words like this(   html = html.replaceAll('', '€€)'); // Guard function names html = html.replace(/([a-z"]\/g, ' $1 ');   html = html.replaceAll('€€)', ''); // Unguard    filterList.push(".Inline-Template .oHL-unspaced-paren", ".infobox-label .oHL-unspaced-paren");    // Trying to match parenthesis hitting words like )this    html = html.replace(/(\)[A-Za-z])/g, ' $1 ');    matchDescriptions["oHL-adj-parens"] = ["Adjacent parentheses", "Avoid adjacent parenthesis. (MOS:PAREN)"];   html = html.replace(/(\) ?\/g, ' $1 ');    filterList.push(".mw-references-wrap .oHL-adj-parens");    matchDescriptions["oHL-nested-parens"] = ["Nested parentheses", "Nested parentheticals should utilize square brackets ."];    html = html.replace(/(\([^)\n]+)\(/g, '$1 ( '); //    html = html.replaceAll('))', ' )) ');    filterList.push(".oHL_img_info .oHL-nested-parens");    // Minus sign    matchDescriptions["oHL-minus-score"] = ["Minus sign", "The proper Unicode minus sign, , should be used instead of dashes. (MOS:MINUS)"];   html = html.replace(/( [A-F])([-–—])([.,;: ])/g, ' $1 $2 $3');    // Note: have never encountered this highlight.    matchDescriptions["oHL-plus-minus"] = ["Plus/minus sign", "The proper Unicode plus-minus sign, , should be used."];    html = html.replace(/(\+\/[-–—−])/g, ' $1 ');    // Exponents and subscripts    matchDescriptions["oHL-subsup"] = ["Precomposed sub/superscript", "Instead of precomposed Unicode characters for subscripts or superscripts, use   tags, or  ; and   tags. (MOS:SUPERSCRIPT)"];   html = html.replace(/([²³¹⁰ⁱ⁴⁵⁶⁷⁸⁹⁺⁻⁼⁽⁾ⁿ₀₁₂₃₄₅₆₇₈₉₊₋₌₍₎ₐₑₒₓₔₕₖₗₘₙₚₛₜᴬᴮᴰᴱᴳᴴᴵᴶᴷᴸᴹᴺᴼᴾᴿᵀᵁⱽᵂᶦᶫᶰᶸᵃᵇᶜᵈᵉᶠᵍʰⁱʲᵏˡᵐⁿᵒᵖʳˢᵗᵘᵛʷˣʸᶻₐₑₕᵢⱼₖₗₘₙₒₚᵣₛₜᵤᵥₓᵝᵞᵟᶿᶥᵠᵡᵦᵧᵨᵩᵪ⏨])/g, ' $1 ');    // Fractions    // html = html.replace(/([½¼¾⅐⅑⅒⅓⅔⅕⅖⅗⅘⅙⅚⅛⅜⅝⅞⅟↉])/g, ' $1 ');    matchDescriptions["oHL-frac-slash"] = ["Fraction slash", "Fractions should use the 1/undefined template. (MOS:FRAC)"];   html = html.replace(/([0-9])\/([0-9])/g, '$1 / $2');    html = html.replaceAll("sup>/<sub", 'sup> / <sub');    filterList.push(".mw-references-wrap .oHL-frac-slash", ".video-game-reviews .oHL-frac-slash",                    ".external .oHL-frac-slash");

// Ordinals matchDescriptions["oHL-ordinal"] = ["Ordinal", "Do not superscript ordinals. (MOS:ORDINAL)"]; html = html.replace(/ (th|st|[nr]d)/g, ' $1 '); html = html.replace(/([ªº])/g, ' $1 ');

// Substed nihongo question mark matchDescriptions["oHL-nihongo-question"] = ["Substed nihongo template", "The template should be be substituted."]; html = html.replaceAll(' ?', ' ? '); // Precomposed units matchDescriptions["oHL-unit-char"] = ["Precomposed unit", "Do not use precomposed unit symbols. (MOS:UNITSYMBOLS)"]; html = html.replace(/([㎚㎛㎜㎝㎞㏌㎟㎠㎡㎢㎣㎤㎥㎦㎕㎖㎗㎘㏄㎰㎱㎲㎳㎍㎎㎏㎅㎆㎇㎐㎑㎒㎓㎔㎴㎵㎶㎷㎸㎹㎺㎻㎼㎽㎾㎿㏀㏁㎀㎁㎂㎃㎄㎧㎨㎭㎮㎯㎩㎪㎫㎬㎈㎉㍷㍸㍹㎙㍱㍲㍳㍴㍵㍶㍺㎊㎋㎌㏃㏅㏆㏇㏈㏉㏊㏋㏍㏎㏏㏐㏑㏒㏓㏔㏕㏖㏗㏚㏛㏜㏝㏞㏟㏿㏂㏘㏙])/g, ' $1 '); // Unspaced unit matchDescriptions["oHL-unit-space"] = ["Unspaced unit", "Unit symbols should usually be preceded by a non-breaking space. (MOS:UNITSYMBOLS)"]; html = html.replace(/([0-9])(in|ft|mi|mph|cm|μm|mm|km|mg|kg|g|m|psi|oz|qt|gal|lb|yr|kcal|cal|hz|sq|cu|W|kW|Ah|mAh|ohm)/g, '$1<span class="oHL oHL-unit-space oHL_added">[ ] $2'); filterList.push(".mw-references-wrap .oHL-unit-space"); // Hyphenated unit matchDescriptions["oHL-unit-hyphen"] = ["Hyphenated unit", "Unit symbols should not be preceded by a hyphen. (MOS:UNITSYMBOLS)"]; html = html.replace(/([0-9])-(in|ft|mi|mph|cm|μm|mm|km|mg|kg|g|m|psi|oz|qt|gal|lb|yr|kcal|cal|hz|sq|cu|W|kW|Ah|mAh|ohm)([ .,;:'"\)])/g, '$1 - $2$3');   // Dotted unit name    matchDescriptions["oHL-dotted-unit"] = ["Dotted unit", "Unit symbols should not have a dot. (MOS:UNITSYMBOLS)"];    html = html.replace(/([0-9])( | )(in|ft|mi|mph|cm|μm|mm|km|mg|kg|g|m|psi|oz|qt|gal|lb|yr|kcal|cal|hz|sq|cu|W|kW|Ah|mAh|ohm)\./g, '$1$2$3 . ');

// Brackets matchDescriptions["oHL-bracket"] = ["Unparsed brackets", "Unparsed brackets likely indicate a broken template or wikilink."]; html = html.replace(/(\{\{|\[\[|\]\]|\}\}|\{\||\|\})/g, ' $1 '); // Malformed tags matchDescriptions["oHL-bad-tag"] = ["Malformed tag", "A tag was not properly closed."]; html = html.replace(/(&lt;\/?(ref|blockquote|poem|math|chem))/g, ' $1 '); // Unspaced comma matchDescriptions["oHL-unspaced-comma"] = ["Unspaced comma", "Commas should usually have a space after them."]; html = html.replace(/([a-z]),([A-Za-z])/g, '$1, $2'); // Commas in money matchDescriptions["oHL-money-comma"] = ["Thousands separator (money)", "In general, digits are grouped by commas. (MOS:DIGITS)"]; html = html.replace(/([$€£¥₣₹])(\d{4})/g, '$1 $2 '); filterList.push(".mw-references-wrap .oHL-money-comma"); // Commas in five digit plus numbers matchDescriptions["oHL-digit-comma"] = ["Thousands separator", "Digits should be grouped by commas. (MOS:DIGITS)"]; html = html.replace(/(\d{2,})(\d{3})/g, '$1€€$2'); html = html.replace(/(\.[0-9]+)€€/g, '$1'); // filter decimals out html = html.replace(/([A-Za-z]\w+)€€/g, '$1'); // filter model numbers out html = html.replace(/(="[0-9]+)€€([0-9]+")/g, '$1$2'); // filter HTML attributes out html = html.replaceAll('€€', '<span class="oHL oHL-digit-comma oHL_added">[,] '); filterList.push(".external .oHL-digit-comma",                   ".mw-references-wrap .oHL-digit-comma",                    ".extiw .oHL-digit-comma",                    ".navbox .oHL-digit-comma",                    ".oHL_img_info_dimensions .oHL-digit-comma"); // 9s at the end of prices, a form of psychological pricing matchDescriptions["oHL-excess-precision"] = ["Excess precision (money)", "In most cases, large monetary figures do not necessitate a lot of precision. (MOS:LARGENUM)"]; html = html.replace(/([$€£¥₣₹][0-9,]+)(9{2,})([^0-9])/g, '$1 $2 $3');

// Double punctuation matchDescriptions["oHL-doublepunc"] = ["Double punctuation", "Punctuation should only be present once."]; html = html.replaceAll(' ', '€nbsp€'); // Guard html = html.replace(/(,,|;;|::)/g, ' $1 '); html = html.replaceAll('€nbsp€', ' '); // Unguard

// Punctation after citations matchDescriptions["oHL-cite-punc"] = ["Citation punctuation", "References should be placed after punctuation. (MOS:CITEPUNCT)"]; html = html.replace(/<\/sup>([.,;:])/g, ' $1 '); filterList.push("sup:not(.reference):not(.Inline-Template) + .oHL-cite-punc"); // Actual exponents

// Extra punctuation after inline quote matchDescriptions["oHL-extra-punc"] = ["Extra punctuation", "Quotations with terminal punctuation shouldn't usually have another punctuation mark after them. (MOS:CONSECUTIVE)"]; // html = html.replace(/(\?["'])\./g, '$1 . ');   html = html.replace(/(\.["'])([,.])/g, '$1 $2 '); filterList.push(".mw-references-wrap .oHL-extra-punc");

// Date comma matchDescriptions["oHL-datecomma"] = ["Date comma", "Dates in MDY forma require a comma after the year. (MOS:DATECOMMA)"]; html = html.replace(/((?:(?:Jan|Febr)uary|March|April|May|June|July|August|(?:Septem|Octo|Novem|Decem)ber) [0-9]{1,2})( [0-9])/g, '$1<span class="oHL oHL-datecomma oHL_added">[,] $2'); html = html.replace(/((?:(?:Jan|Febr)uary|March|April|May|June|July|August|(?:Septem|Octo|Novem|Decem)ber) [0-9]{1,2}, [0-9]{4})( \w|<sup)/g, '$1<span class="oHL oHL-datecomma oHL_added">[,] $2'); filterList.push(".mw-references-wrap .oHL-datecomma", ".wikitable .oHL-datecomma"); // "In $year" comma matchDescriptions["oHL-yearcomma"] = ["Year comma", "This type of clause should probably have a comma after it."]; html = html.replace(/((?:In|As of) ?(?:(?:Jan|Febr)uary|March|April|May|June|July|August|(?:Septem|Octo|Novem|Decem)ber)? [0-9]{4})( \w|<sup)/g, '$1<span class="oHL oHL-yearcomma oHL_added">[,] $2'); // Start of sentence comma matchDescriptions["oHL-word-comma"] = ["Word comma", "This type of clause should probably have a comma after it."]; html = html.replaceAll('Recently ', 'Recently<span class="oHL oHL-word-comma oHL_added">[,] '); html = html.replaceAll(/Originally (?!known)/g, 'Originally<span class="oHL oHL-word-comma oHL_added">[,] '); // html = html.replace(/([a-z]) (but|whereas) /g, '$1<span class="oHL oHL oHL-word-comma oHL_added">[,] $2 '); filterList.push(".mw-references-wrap .oHL-word-comma", ".oHL_wikilink .oHL-word-comma");

// Double conjunction // html = html.replace(/ and ([^"'.,;:![\]—–\n]+) and/g, ' and $1 and ');   // html = html.replace(/ or ([^"'.,;:![\]—–\n]+) or/g, ' or $1 or ');

// Short months matchDescriptions["oHL-month-abbr"] = ["Abbreviated month", "Abbreviations for months should only be used where space is limited. (WP:MOS)"]; html = html.replace(/((?:Jan|Feb|Mar|Apr|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\.?) ([0-9]{1,2})/g, ' $1 $2'); filterList.push(".mw-references-wrap .oHL-month-abbr",                   "table .oHL-month-abbr"); // table includes .infobox // Short days matchDescriptions["oHL-day-abbr"] = ["Abbreviated day", "Abbreviations should only be used where space is limited."]; html = html.replace(/(Mon|Tues|Wed|Thur|Fri|Sat|Sun)([^\w])/g, ' $1 $2'); filterList.push(".mw-references-wrap .oHL-day-abbr", ".external .oHL-day-abbr",                   ".oHL_wikilink .oHL-day-abbr", "a.new .oHL-day-abbr"); // All caps matchDescriptions["oHL-allcaps"] = ["All caps", "Avoid using all capital letters for things other than acronyms. (MOS:ALLCAPS)"]; html = html.replace(/([A-Z]{3,} [ "',;:.?!A-Z]*[A-Z]{3,})/g, ' $1 ');   filterList.push(".smallcaps .oHL-allcaps", ".navbox .oHL-allcaps", ".Inline-Template .oHL-allcaps");    // Hyphen table placeholders    matchDescriptions["oHL-table-hyphen"] = ["Table placeholder hyphen", "Placeholders should use em dashes instead of hyphens."];    html = html.replaceAll(' -', ' - ');    // Empty cells    matchDescriptions["oHL-missing-placeholder"] = ["Table cell w/o placeholder", "Empty cells in a table should probably use em dashes as placeholders."];    html = html.replace(/ \s*<\/td>/g, ' <span class="oHL oHL-missing-placeholder oHL_added">[—]  ');    // Circa    matchDescriptions["oHL-circa"] = ["Circa", "The preferred formatting for circa is the c.  template. (MOS:CIRCA)"];   html = html.replaceAll(' ca.', ' ca. ');    html = html.replaceAll(' ca ', ' ca ');    html = html.replace(/([ \(])c\.(\w)/g, '$1 c. $2');    // Missing dots in abbreviations    matchDescriptions["oHL-abbr-period"] = ["Abbreviation dot", "Abbreviations should end with periods. (MOS:POINTS)"];    html = html.replace(/([ (])(etc|i\.e|e\.g|cf|et al|viz|vs|Inc|Jr|Sr)([ ),:;'"])/g, ' $1$2<span class="oHL oHL-abbr-period oHL_added">[.] $3');    filterList.push(".mw-references-wrap .oHL-abbr-period", ".oHL_wikilink .oHL-abbr-period", "a.new .oHL-abbr-period");   // Inconsistent slash spacing    matchDescriptions["oHL-slash-space"] = ["Slash spacing", "Slashes, when considered proper to use, should have consistent spacing on both sides. (MOS:SLASH)"];    html = html.replaceAll(' / ', ' /€'); // Guard    html = html.replace(/([^\/ ])\/ /g, '$1 / ');    html = html.replace(/ \/([^\/ ])/g, ' / $1');    html = html.replaceAll(' /€', ' / '); // Unguard    // Fullwidth characters    matchDescriptions["oHL-fullwidth"] = ["Fullwidth characters", "Fullwidth characters should be replaced with their halfwidth equivalents."];    html = html.replace(/([：；？！＄％（）＆＋＊＠])/g, ' $1 ');    filterList.push("[lang='ja'] .oHL-fullwidth", ".mw-references-wrap .oHL-fullwidth");   // Time    matchDescriptions["oHL-time-space"] = ["Time space", "Times should have a non-breaking space  before a.m./p.m. (MOS:AMPM)"];    html = html.replace(/([0-9])([ap]\.?m)/gi, '$1<span class="oHL oHL-time-space oHL_added">[ ] $2');    filterList.push(".mw-references-wrap .oHL-time-space");    matchDescriptions["oHL-time-uppercase"] = ["Time uppercase", "a.m./p.m. notation should be lowercase. (MOS:AMPM)"];    html = html.replace(/([0-9]) ([AP](M|\.M\.))/g, '$1 $2 ');    filterList.push(".mw-references-wrap .oHL-time-uppercase");    matchDescriptions["oHL-time-dot"] = ["Time dot", "a.m./p.m. notation should have two dots. (MOS:AMPM)"];    html = html.replace(/(a\.m|p\.m)([ ),:;'"])/g, '$1<span class="oHL oHL-time-dot oHL_added">[.] $2');

// Commas after place names matchDescriptions["oHL-place-comma"] = ["Place name comma", "A comma should follow the last part of a geographic location. (MOS:GEOCOMMA)"]; const stateNamesRe = stateNames.join("|").replaceAll(".", "\\."); const countryNamesRe = countryNames.join("|").replaceAll(".", "\\."); const stateRe = new RegExp(", (" + stateNamesRe + ")([ <])", "g"); const stateRe2 = new RegExp("(, (?:<a[^>\n]*?>))(" + stateNamesRe + ")<", "g"); // wikilinked const countryRe = new RegExp(", (" + countryNamesRe + ")([ <])", "g"); const countryRe2 = new RegExp("(, (?:<a[^>\n]*?>))(" + countryNamesRe + ")<", "g"); // wikilinked html = html.replace(/([0-9]),/g, '$1,₭'); // guard html = html.replace(stateRe, ', $1€€$2'); html = html.replace(stateRe2, '$1$2€€<'); html = html.replace(countryRe, ', $1€€$2'); html = html.replace(countryRe2, '$1$2€€<'); html = html.replaceAll('€€</a>', '</a>€€'); // neater html = html.replaceAll('€€<', '<'); // clean html = html.replaceAll('€€ <a id="sectiontitlecopy', ' <a id="sectiontitlecopy'); // clean html = html.replace(/€€([,.:;)\]])/g, '$1'); // clean   html = html.replaceAll('€€', '<span class="oHL oHL-place-comma oHL_added">[,] ');    html = html.replaceAll(',₭', ','); // unguard    filterList.push(".mw-references-wrap .oHL-place-comma");

// Title italicization matchDescriptions["oHL-title-italics"] = ["Title italics", "Instances of the italicized page title should be consistently italicized in the body."]; const pageTitle = mw.config.get("wgTitle").replace(/ \(.*\)$/, ""); if (pageTitle == $("h1 i").first.text) { const pageTitleCleaned = pageTitle.replace(/:.*/, ""); const pageTitleEscaped = mw.util.escapeRegExp(pageTitleCleaned); const titleRe = new RegExp("([\"' ])(" + pageTitleEscaped + ")", "gi");   	html = html.replace(titleRe, '$1 $2 ');    	filterList.push("i .oHL-title-italics", ".mw-references-wrap .oHL-title-italics",    	                "a .oHL-title-italics, .shortdescription .oHL-title-italics",    	                ".oHL_img_info .oHL-title-italics");    }    matchDescriptions["oHL-court-italics"] = ["Court case italics", "Titles of court cases are usually italicized. (MOS:TEXT)"];   // Court cases italicization    html = html.replace(/ (v\.?) /g, ' $1 ');    filterList.push("i .oHL-court-italics", ".mw-references-wrap .oHL-court-italics",                    ".infobox-above .oHL-court-italics", ".hatnote .oHL-court-italics");    // Full name in biographies    matchDescriptions["oHL-fullname"] = ["Full name", "After the first mention, people should not be referred to by their full name. (MOS:SURNAME)"];   const categories = mw.config.get("wgCategories")?.join(" | ");    if (categories.includes("births") ||  categories.includes("deaths")        || categories.includes("Living people")) {    	const fullNameTitle = $(".mw-page-title-main").text;    	const fullNameLead = $("#bodyContent p b").first.text;    	const subjectNames = [];    	subjectNames.push(fullNameTitle);    	if (fullNameLead != fullNameTitle) {    		subjectNames.push(fullNameLead);    	}    	for (const name of subjectNames) {    		if (name.includes(" ")) {    		    html = html.replaceAll(name, ' ' + name + ' ');    		}    	}    	filterList.push("#bodyContent p:first-of-type .oHL-fullname",    	                "i .oHL-fullname", "a .oHL-fullname", "table .oHL-fullname",    	                "figcaption .oHL-fullname", ".mw-references-wrap .oHL-fullname",    	                "cite .oHL-fullname", ".side-box .oHL-fullname", ".hatnote .oHL-fullname"); }

// Pseudo-references matchDescriptions["oHL-pseudo-ref"] = ["Pseudo ref", "Numbered references should use  tags."]; html = html.replace(/(\[\d{1,2}\])/g, ' $1 '); filterList.push(".reference .oHL-pseudo-ref", ".external .oHL-pseudo-ref", ".mw-references-wrap .oHL-pseudo-ref");

// Degrees symbol matchDescriptions["oHL-bad-degree"] = ["Bad degree symbol", "Degrees should use the proper symbol. (MOS:NUM)"]; html = html.replaceAll('˚', ' ˚ '); matchDescriptions["oHL-missing-degree"] = ["Missing degrees symbol", "Temperatures should have the degrees symbol. (MOS:NUM)"]; html = html.replace(/([0-9]) (C|F)([ ,;:)])/g, '$1 <span class="oHL oHL-missing-degree oHL_added">[°] $2$3');   matchDescriptions["oHL-unspaced-degree"] = ["Unspaced degrees symbol", "The degrees symbol should be spaced. (MOS:NUM)"];    html = html.replace(/([0-9])°(C|F)/g, '$1<span class="oHL oHL-unspaced-degree oHL_added">[ ] °$2');

// Misc matchDescriptions["oHL-corporate"] = ["Corporate symbol", "Avoid using symbols like, etc. (MOS:TMRULES)"]; html = html.replace(/([™©®]|\(TM\)|\(C\)|\(R\))/ig, ' $1 '); filterList.push(".mw-references-wrap .oHL-corporate");

/* html = html.replace(/([0-9]+°) ([0-9]+)′ ([0-9]+)″/g, '$1 $2€€ $3€€€'); // Guard matchDescriptions["oHL-prime"] = ["Prime symbol", "Outside of angles and coordinates, the prime symbols shouldn't be used. (MOS:UNITS)"]; html = html.replaceAll('€€€', '″'); // Unguard html = html.replaceAll('€€', '′'); // Unguard html = html.replace(/([′″])/g, ' $1 ');

matchDescriptions["oHL-unit-symbol"] = ["Inch & feet symbols", "\"in\" and \"ft\" should be used instead of quote marks. (MOS:NUM)"]; html = html.replace(/("\w.*?[0-9])"/g, '$1€€"'); // Guard   html = html.replace(/([0-9])'s/g, "$1€€'s"); // Guard    html = html.replace(/( [0-9]+)(['"])/g, '$1 $2 '); html = html.replaceAll("€€'", "'"); // Unguard html = html.replaceAll('€€"', '"'); // Unguard filterList.push(".mw-references-wrap .oHL-unit-symbol");

matchDescriptions["oHL-bad-bullet"] = ["Bad bullet", "Lists on Wikipedia should use the proper list markup. (MOS:LISTBULLET)"]; html = html.replaceAll('•', ' • '); filterList.push(".infobox-label .oHL-bad-bullet");

matchDescriptions["oHL-spaced-amper"] = ["Ampersand", "Normal text should use \"and\" instead of the ampersand. (MOS:AMP)"]; html = html.replace(/ &amp; ([A-Z])/g, ' €amp€ $1'); // Guard html = html.replaceAll(' &amp; ', ' & '); html = html.replaceAll('€amp€', '&amp;'); // Unguard filterList.push(".mw-references-wrap .oHL-spaced-amper", ".oHL_wikilink .oHL-spaced-amper",                   "a.new .oHL-spaced-amper", "i .oHL-spaced-amper", "table .oHL-spaced-amper",                    ".cite .oHL-spaced-amper");

// Never actually hit this one matchDescriptions["oHL-spaced-el"] = ["Spaced el", "An \"el\" seems to have been typed instead of \"eye\" ."]; html = html.replaceAll(' l ', ' l ');

matchDescriptions["oHL-unspaced-pgnum"] = ["Unspaced page number", "Page number abbreviations in citations should be spaced. (see examples at WP:CITE)"]; html = html.replace(/, p\.([0-9])/g, ', p.<span class="oHL oHL-unspaced-pgnum oHL_added">[ ] $1'); html = html.replace(/pp\.([0-9])/g, 'pp.<span class="oHL oHL-unspaced-pgnum oHL_added">[ ] $1');

matchDescriptions["oHL-unspaced-ordinal"] = ["Unspaced ordinal", "Ordinal abbreviations should be followed by a space."]; html = html.replace(/([Nn]o\.)([0-9])/g, '$1<span class="oHL oHL-unspaced-ordinal oHL_added">[ ] $2'); filterList.push(".mw-references-wrap .oHL-unspaced-ordinal");

matchDescriptions["oHL-arrow"] = ["Arrow symbols", "Arrows should use the proper Unicode characters. "]; html = html.replace(/( |&lt;)-&gt;/g, '$1 -&gt; '); filterList.push(".mw-references-wrap .oHL-arrow");

// Contractions matchDescriptions["oHL-contraction"] = ["Contraction", "Contractions should not be used outside of quoted text. (MOS:CONTRACTIONS)"]; // Try guarding up to four contractions // Guards won't work if HTML tags in between, e.g. `He said that's…`` html = html.replace(/("\w[^"]*?)'([^'"]*)'([^'"]*)'([^'"]*)'/g, "$1'€$2'€$3'€$4'€"); // Guard   html = html.replace(/("\w[^"]*?)'([^'"]*)'([^'"]*)'/g, "$1'€$2'€$3'€"); // Guard    html = html.replace(/("\w[^"]*?)'([^'"]*)'/g, "$1'€$2'€"); // Guard html = html.replace(/("\w[^"]*?)([a-z])'([a-z])/g, "$1$2'€$3"); // Guard; this one also checks for surrounding letters

html = html.replaceAll("n't", " n't "); html = html.replaceAll("'ve", " 've "); html = html.replace(/(\w)'d/g, '$1 \'d '); html = html.replaceAll("'ll", " 'll ");

html = html.replaceAll("they're", " they're "); html = html.replaceAll("might've", " might've "); html = html.replaceAll("that's", " that's "); html = html.replaceAll(/ (t?here's)/g, " $1 "); html = html.replace(/ (s?he's)/g, " $1 "); html = html.replaceAll(" it's", " it's "); html = html.replaceAll("who's", " who's "); html = html.replaceAll("something's", " something's "); html = html.replace(/'€+/g, "'"); // Unguard

filterList.push(".mw-references-wrap .oHL-contraction", ".oHL_wikilink .oHL-contraction",                   "a.new .oHL-contraction", "i .oHL-contraction", "blockquote .oHL-contraction",                    ".poem .oHL-contraction");

// Editorial issues /*html = html.replaceAll('and/or', ' $& ');

html = html.replaceAll('fortun', ' $& '); html = html.replaceAll('sadly', ' $& '); html = html.replaceAll('ill-fated', ' $& '); html = html.replaceAll('fateful', ' $& '); html = html.replaceAll('tragedy', ' $& '); html = html.replaceAll('tragic', ' $& '); html = html.replaceAll('suffer', ' $& '); html = html.replaceAll('mirac', ' $& '); html = html.replaceAll('lucky', ' $& '); html = html.replaceAll('happily', ' $& '); html = html.replaceAll('interesting', ' $& '); html = html.replaceAll('curious', ' $& '); html = html.replaceAll('ironic', ' $& '); html = html.replaceAll('definitely', ' $& '); html = html.replaceAll('exclaimed', ' $& '); // html = html.replaceAll('popular', ' $& '); html = html.replaceAll('famous', ' $& '); html = html.replaceAll(' fame', ' $& '); html = html.replaceAll('infamy', ' $& '); html = html.replaceAll('prestigious', ' $& '); html = html.replaceAll('renowned', ' $& '); html = html.replaceAll('made headlines', ' $& '); html = html.replaceAll('iconic', ' $& '); html = html.replaceAll('acclaimed', ' $& '); html = html.replaceAll('visionary', ' $& '); html = html.replaceAll('outstanding', ' $& '); html = html.replaceAll(' leading', ' $& '); html = html.replaceAll('celebrated', ' $& '); html = html.replaceAll('lauded', ' $& '); html = html.replaceAll('legendary', ' $& '); html = html.replaceAll('exceptional', ' $& '); html = html.replaceAll('spectacular', ' $& '); html = html.replaceAll('remarkable', ' $& '); html = html.replaceAll('amazing', ' $& '); html = html.replaceAll('extraordinar', ' $& ') html = html.replaceAll('world-class', ' $& '); html = html.replaceAll('greatest', ' $& '); html = html.replaceAll('surprising', ' $& '); html = html.replaceAll('unexpect', ' $& '); html = html.replaceAll('a twist', ' $& '); html = html.replaceAll('bizzare', ' $& '); html = html.replaceAll('puzzling', ' $& '); html = html.replaceAll('incredibl', ' $& '); html = html.replaceAll('heroic', ' $& '); html = html.replaceAll('brave', ' $& '); html = html.replaceAll(' courage', ' $& '); html = html.replaceAll('daring', ' $& '); html = html.replaceAll('beautiful', ' $& '); html = html.replaceAll('respected', ' $& '); html = html.replaceAll('forefront', ' $& '); html = html.replaceAll('tasty', ' $& '); html = html.replaceAll('disturbing', ' $& '); html = html.replaceAll('ingenious', ' $& '); html = html.replaceAll('brillian', ' $& '); html = html.replaceAll('a hit', ' $& '); html = html.replaceAll('extraordinary', ' $& '); html = html.replaceAll('phenomenal', ' $& '); html = html.replaceAll('innovative', ' $& '); html = html.replaceAll('pioneer', ' $& '); html = html.replaceAll('state of the art', ' $& '); html = html.replaceAll('state-of-the-art', ' $& '); html = html.replaceAll('cutting-edge', ' $& '); html = html.replaceAll('creatively', ' $& '); html = html.replaceAll('awesome', ' $& '); html = html.replaceAll('amusing', ' $& '); html = html.replaceAll('obvious', ' $& '); html = html.replaceAll('in fact', ' $& '); html = html.replaceAll('contrary', ' $& '); html = html.replaceAll('mere', ' $& '); html = html.replaceAll('so-called', ' $& '); html = html.replaceAll('of course', ' $& '); // html = html.replaceAll(' even ', ' $& '); html = html.replaceAll('spite', ' $& '); html = html.replaceAll('begs', ' $& '); html = html.replaceAll('and yet', ' $& '); html = html.replaceAll('not to mention', ' $& '); html = html.replaceAll('should not', ' $& '); html = html.replaceAll('should be noted', ' $& '); filterList.push("blockquote .oHL-editorializing", "cite .oHL-editorializing",                   ".oHL_wikilink .oHL-editorializing", "a.new .oHL-editorializing");

html = html.replaceAll(/some say/gi, ' $& '); html = html.replaceAll(/it is said/gi, ' $& '); html = html.replaceAll('passed away', ' $& '); html = html.replaceAll('survived by', ' $& '); html = html.replaceAll(/gave (her|his|their) life/g, ' $& '); html = html.replaceAll(/ma(d|k)e love/g, ' $& ');

// Annotate non-visible or hard to distinguish elements html = html.replaceAll('×', '<ruby class="oHL_ruby"><rb>×</rb><rt>mult</rt> '); html = html.replaceAll(' ', '<ruby class="oHL_ruby"><rb> </rb><rt>_nbsp_</rt> '); html = html.replaceAll('&thinsp;', '<ruby class="oHL_ruby"><rb>&thinsp;</rb><rt>_thinsp_</rt> '); html = html.replaceAll('&hairsp;', '<ruby class="oHL_ruby"><rb>&hairsp;</rb><rt>_hairsp_</rt> '); html = html.replaceAll('&ZeroWidthSpace;', '<ruby class="oHL_ruby"><rb>&ZeroWidthSpace;</rb><rt>_ZeroWidthSpace_</rt> '); html = html.replaceAll('&shy;', '<ruby class="oHL_ruby"><rb>&shy;</rb><rt>_shy_</rt> '); html = html.replaceAll('–', '<ruby class="oHL_ruby"><rb>–</rb><rt>en</rt> '); html = html.replaceAll('—', '<ruby class="oHL_ruby"><rb>—</rb><rt>em</rt> '); html = html.replaceAll('−', '<ruby class="oHL_ruby"><rb>−</rb><rt>minus</rt> '); html = html.replaceAll('‐', '<ruby class="oHL_ruby"><rb>‐</rb><rt>hyphen</rt> '); html = html.replaceAll('ʼ', '<ruby class="oHL_ruby"><rb>ʼ</rb><rt>glottal</rt> ');

// Trailing spaces html = html.replace(/ +\n/g, '<span class="oHL_trailingSpace" title="Whitespace">_ \n');

contentElement.innerHTML = html; // Make sure we didn't improperly unguard something const euroCountAfter = (html.match(/€/g) || []).length; if (euroCountBefore != euroCountAfter) { $("#contentSub").after(`<div class='oHL_warning'>highlightStrings.js: Warning: Guard character count changed from ${euroCountBefore} to ${euroCountAfter}. Page text might be formatted incorrectly from original. `); addReportButton; }   const brokenHTML = html.match(/<span[^>]+<span.{5,60}/); if (brokenHTML != null && brokenHTML.length != 0) { const brokenHTMLMessage = mw.html.escape(brokenHTML.toString); $("#contentSub").after(`<div class='oHL_warning'>highlightStrings.js: Warning: Might have broken the page HTML: `); addReportButton; } }

function postClean { // Put back original attributes // We iterate backwards because we target the mangled ids and we don't want // them to change back until the end for (let i = mangled.length-1; i >= 0; i--) { unmangle(mangled[i]); }

// Reattach the elements we removed at the start reattachTemp;

whitelist; // Optionals $(".mw-references-wrap .oHL, .navbox .oHL").each(function markOptionalsSelector {       $(this).addClass("oHL-opt");        $(this).removeClass("oHL");    }); $(refSectionsSelector).parent.nextUntil("h2").find(".oHL").each(function markOptionalsSection {       $(this).addClass("oHL-opt");        $(this).removeClass("oHL");    }); // Handle elements tagged multiple times $(".oHL.oHL-opt").removeClass("oHL-opt"); $(".infobox tr .oHL_ruby").children("rt").remove; $(".oHL_img_info_dimensions .oHL_ruby").children("rt").remove; }

// Get wikitext of current page function getWikitext { // API docs: https://www.mediawiki.org/wiki/API:Revisions const apiUrl = location.origin + "/w/api.php"; $.ajax({       url: apiUrl,        data: {            action: "query",            prop: "revisions",            format: "json",            revids: mw.config.get("wgRevisionId"),            rvprop: "content",            rvslots: "main"        },        success: searchWikitext    }); }

function searchWikitext(response) { const pageId = mw.config.get("wgArticleId"); const wikitext = response.query.pages[pageId].revisions[0].slots.main["*"];

const searches = new Map; const results = new Map;

// Note: need to escape backslashes here searches.set("nowiki", "<nowiki/?>"); searches.set("include tags", "<(no|only)include/?>"); searches.set("Infobox name param", "\\| +name +="); searches.set("Closable named refs", ' '); searches.set("Cite with author param", "\\| ?author1? ?="); searches.set("Reflist", "{{reflist\\|"); searches.set("Anchors (span)", "<span id=[^>]+>"); searches.set("Anchors (template)", "{{anchor ?\\|[^}]+}}"); searches.set("Anchors (visible)", "{{(visible anchor|visanc|va|vanchor) ?\\|[^}]+}}"); searches.set("Inline files", "(?<=.)\\"); searches.set("Hardcoded thumbnail sizes", "\\"); searches.set("Simplifiable wikilinks", "\\[\\[([^|\\n]+)\\|\\1[a-z]+\\]\\]"); searches.set("Overly precise numbers", "(?<!<ref[^<]*)" // not in a DOI                                          + "([0-9][,.][1-9]{3}|[0-9][,.]0[1-9]{2}|[0-9][,.][1-9]0[1-9]|[0-9][,.][1-9]{2}0|[0-9][,.]00[1-9])"); searches.set("Long to short links", "\\[\\[[^|\\]\\n]+ [^|\\]\\n]+ [^|\\]\\n]+\\|[^ \\]\\n]+\\]\\]");   searches.set("Headers with capital letters", "(?<===)[\\w ]+ [A-Z][^=\\n]+(?===)");    searches.set("Table captions with capital letters", "(?<=\\|\\+)[\\w ]+ [A-Z][^|\\n]+");

for (const [type, re] of searches) { let flags = "gd"; if (type != "Hyphenated names" && type != "Headers with capital letters"   	    && type != "Table captions with capital letters") { flags += "i"; }       const searchRe = new RegExp(re, flags); const matches = wikitext.matchAll(searchRe); const matchesList = []; for (const m of matches) { const start = m.indices[0][0]; const end = m.indices[0][1]; const context = 20; const leftContext = wikitext.substring(start-context, start); const matchText = wikitext.substring(start, end); const rightContext = wikitext.substring(end, end+context); matchesList.push([leftContext, matchText, rightContext]); }       if (matchesList.length > 0) { results.set(type, matchesList); }   }    const nestedResults = getNestedQuotes(wikitext); if (nestedResults.length > 0) { results.set("Nested quote marks", nestedResults); }

showWikitextMatches(results); $("#oHL_comma_less_numbers summary").after(" <input type='checkbox' id='oHL_commalessFilter'> Hide Years "); $("#oHL_commalessFilter").change(function filterCommalessResults {   	if ($("#oHL_commalessFilter").is(":checked")) {    		$("#oHL_comma_less_numbers .oHL_wikitext-match").each(function hideYearResults { const yearText = $(this).children(".oHL_wikitext-match-text").text; const year = parseInt(yearText); if (year > 1300 && year < 2500) { // arbritrary date range $(this).hide; }   		});    	} else {    		$("#oHL_comma_less_numbers .oHL_wikitext-match").show;    	}    }); compareDefaultSort(wikitext); showLongQuotes(wikitext); showRedlinks; showFrequency; showRedirects; checkDisambigLink; checkOutlinkAnchors; tabulateReferences(wikitext); }

function getNestedQuotes(wikitext) { wikitext = wikitext.replace(/(=[^>]+?)" /g, '$1₭ '); // guard quotes in    wikitext = wikitext.replaceAll('>"<', '>₭<'); // guard single highlighted quotes wikitext = wikitext.replace(/([ >])"/g, '$1𐑱'); // 𐑱: left quote placeholder   wikitext = wikitext.replace(/"([ .,;:\n]|<ref)/g, '𐑲$1'); // 𐑲: right quote placeholder wikitext = wikitext.replaceAll('₭', '"'); // unguard   // Creating this separately to prevent jshint error due to newer /d flag    const matchRegex = new RegExp("𐑱[^𐑲\n]+𐑱[^𐑱\n]+𐑲[^𐑱\n]+𐑲", "gd");    const matches = wikitext.matchAll(matchRegex);    const matchesList = [];    for (const m of matches) {        const start = m.indices[0][0];        const end = m.indices[0][1];        const context = 20;

const leftContext = wikitext.substring(start - context, start); const matchText = wikitext.substring(start, end); const rightContext = wikitext.substring(end, end + context); const match = [leftContext, matchText, rightContext]; const matchUnguarded = match.map(m => m.replace(/[𐑱𐑲]/gu, '"'));        matchesList.push(matchUnguarded);    }    return matchesList; }

function compareDefaultSort(wikitext) { let title = mw.config.get("wgTitle"); let defaultSort; const match = wikitext.match(/{DEFAULTSORT:([^}\n]+)}/i); if (match != null) { defaultSort = match[1]; } else { defaultSort = title; }   defaultSort = defaultSort.replace(/ \([^)]+\)$/, ""); // Remove " (disambiguation)"

title = title.replace(/ \([^)]+\)$/, ""); // Remove " (disambiguation)"   title = title.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); // https://stackoverflow.com/a/37511463/1995949    title = title.replaceAll("–", "-");    title = title.replaceAll("—", "-");    title = title.replaceAll("×", "x");

let titleCollated = title; const categories = $("#mw-normal-catlinks").text; if (title.startsWith("The ")) { titleCollated = title.substring(4) + ", The"; } else if (title.startsWith("A ")) { titleCollated = title.substring(2) + ", A"; } else if (categories.includes("births") || categories.includes("deaths")              || categories.includes("Living people")) { // people // See: https://en.wikipedia.org/wiki/WP:NAMESORT title = title.replace("Saint ", ""); title = title.replace("O'", "O"); // e.g. O'Neil let suffix = ""; if (title.endsWith(" Jr.")) { suffix = " Jr."; title = title.substring(0, title.length - suffix.length); }

// Assume the final part of the name is surname; not applicable to all cultures const splitName = title.split(" "); if (splitName.length > 1) { const lastPart = splitName.at(-1); const firstPart = splitName.slice(0, -1).join(" "); titleCollated = lastPart + ", " + firstPart + suffix; } else { titleCollated = title; }   }

if (titleCollated != defaultSort) { const mismatchText = `"${defaultSort}" ≠ "${titleCollated}"`; $("#oHL_results").append("<details id='oHL_defaultSort'> DefaultSort mismatch (1) "                                + "<ul><li>" + mismatchText + "</li></ul> "); } }

function showLongQuotes(wikitext) { const quoteRe = / "[A-Z.].*?"/g; const quotes = wikitext.match(quoteRe); if (quotes === null) { return; }

const longQuotes = []; for (const quote of quotes) { const wordCount = quote.split(" ").length; if (wordCount > 40) { longQuotes.push([quote, wordCount]); }   }    if (longQuotes.length == 0) { return; }   let list = "<ul>"; for (const [quote, wordCount] of longQuotes) { list += "<li>" + quote.substring(1) + " [~" + wordCount + " words]</li>"; }   list += "</ul>"; $("#oHL_results").append("<details id='oHL_longQuotes'> Long quotes (" + longQuotes.length + ") " + list + " "); }

function checkOutlinkAnchors { const anchorLinksArray = []; $(".oHL_wikilink").each(function getAnchoredWikilinks {		const anchor = decodeURIComponent(this.hash.slice(1));		if (anchor != "" && !this.href.includes("/wiki/Help:") && !this.href.includes("/wiki/Wikipedia:") && !this.href.includes("/wiki/Talk:")) {			const pageTitle = decodeURIComponent(this.pathname.split("/")[2]);			const linkText = $(this).text.replace("|", " | ");			anchorLinksArray.push({"link": pageTitle, "text": linkText, "anchor": anchor});		}	}); if (anchorLinksArray.length == 0) { return; }	// Deduplicate const anchorLinks = [...new Set(anchorLinksArray)];

// Placeholder $("#oHL_results").append("<details id='oHL_brokenAnchors' style='display: none'> Broken outgoing anchors (0) <ul></ul> "); for (const anchorLink of anchorLinks) { // API docs: https://www.mediawiki.org/wiki/API:Parsing_wikitext const apiUrl = location.origin + "/w/api.php"; $.ajax({           url: apiUrl,            data: {                action: "parse",                page: anchorLink.link,                prop: "text",                format: "json",                redirects: "true",            },            success: function processRevisions(response) {            	checkAnchor(anchorLink, response);            }        }); }

}

function checkAnchor(anchorLink, response) { const pageHtml = response.parse.text["*"]; const pageParsed = $.parseHTML(pageHtml); const ids = $(pageParsed).find("span[id]").toArray.map(e => e.id); if (!ids.includes(anchorLink.anchor)) { const summaryElement = $("#oHL_brokenAnchors"); const summaryCountElement = $(summaryElement).find(".summaryCount").first; const summaryCountString = summaryCountElement.text.replace("(", "").replace(")", ""); const summaryCount = parseInt(summaryCountString) + 1; summaryCountElement.text("(" + summaryCount + ")"); const linkHref = anchorLink.link + "#" + anchorLink.anchor; const linkText = anchorLink.link.replaceAll("_", " ") + " § " + anchorLink.anchor.replaceAll("_", " "); $(summaryElement).find("ul").append("<li>" + anchorLink.text + " → <a href='" + linkHref + "'>" + linkText + "</a></li>"); $(summaryElement).show; } }

function tabulateReferences(wikitext) { wikitext = wikitext.replace(//gs, ''); // delete comments const templateRefRe = /<ref[^>]*>\s*{{(cite |citation)[^<]+<\/ref>/gi; const templateRefs = wikitext.match(templateRefRe) || []; const templateRefcount = templateRefs.length; const plainRefRe = /<ref[^>]*>[^{<]+<\/ref>/gi; const plainRefs = wikitext.match(plainRefRe) || []; const plainRefcount = plainRefs.length; const foundCount = templateRefcount + plainRefcount; if (foundCount == 0) { return; }

let tableHeader = " "; let countString = foundCount; const totalCount = $(".reference-text").last.closest("li").index + 1; if (foundCount != totalCount) { countString += "/" + totalCount; } $("#oHL_results").append("<details id='oHL_refTable'> References (" + countString + ") " + table + " ");

// tabulateMissingRefs; catchFinalOrdinal(foundCount, totalCount);

mw.loader.using("jquery.tablesorter", function makeTableSortable {		$("#oHL_refTable table").tablesorter( { sortList: [ {0: "asc"} ] } );	}); }

function getRefOrdinalFromURL(url, ref, template) { let ordinal = "—"; let refParent; if (url != "—") { // for some reason, MediaWiki upgrades some URLs to https even if they were http in the source const urlStripped = url.replace(/https?:/, ""); const urlEscaped = CSS.escape(urlStripped); refParent = $(".reference-text [href$='" + urlEscaped + "']").closest(".reference-text").closest("li"); }   if (typeof refParent == "undefined" || refParent.length == 0) { const isbn = ref.match(/\|\s*isbn\s*=\s*([^|}][^|}]+)/i)?.[1]; if (isbn) { const isbnTrimmed = isbn.trim; refParent = $("cite [href*='" + isbnTrimmed + "']").closest("cite").closest("li"); }   }

if (refParent) { const ordinalNumber = $(refParent).index + 1; const refId = $(refParent).attr("id"); ordinal = `<a href='#${refId}'>${ordinalNumber}</a>`; }   return ordinal; }

function catchFinalOrdinal(foundCount, totalCount) { if (foundCount != totalCount) { return; } const ordinalColumn = $("#oHL_refTable tbody tr td:first-child"); const ordinalString = $(ordinalColumn).text; const noOrdinalCount = (ordinalString.match(/—/g) || []).length; if (noOrdinalCount != 1) { return; } const ordinals = []; let blankOrdinal; ordinalColumn.each(function getOrdinals {		const ordinal = $(this).text;		if (ordinal != "—") {			ordinals.push(parseInt(ordinal));		} else {			blankOrdinal = this;		}	}); ordinals.sort((a, b) => a - b);

for (let counter = 1; counter <= totalCount; counter++) { if (!ordinals.includes(counter)) { const ordinalId = $(".references > li[id$='-" + counter + "']").attr("id"); const markup = `<a href="#${ordinalId}">${counter}</a>`; $(blankOrdinal).html(markup); break; }	} }

/* function tabulateMissingRefs { // Get all the refs we couldn't match const unmatched = []; const ordinals = []; $(".oHL_refTable tbody tr td:first-child").each(function iterateRefTableOrdinals {		const ordinal = $(this).text;		if (ordinal == "—") {			unmatched.push($(this));		} else {			ordinals.push(ordinal);		}	}); for (const ref of unmatched) { ;	} } */

// Reference: https://en.wikipedia.org/wiki/Snowflake_ID function tweetURLtoDate(tweetID) { if (!tweetID) { return null; } const epoch = 1288834974657; // Example: https://twitter.com/wikipedia/status/1541815603606036480 // e.g. 1541815603606036480 const snowflake = parseInt(tweetID); // e.g. 0b 1 0101 0110 0101 1010 0001 0001 1111 0110 0010 00|01 0111 1010|0000 0000 0000 const offsetBinary = snowflake.toString(2).substring(0, 39); // e.g. 367597485448 const offset = parseInt(offsetBinary, 2); // e.g. 1288834974657 + 367597485448 = 1656432460_105 const timestampMS = epoch + offset; // e.g. June 28, 2022 const date = new Date(timestampMS).toLocaleDateString("en-us", { day:"numeric", year:"numeric", month:"long"});

return date; }

function showRedlinks { const redLinks = $("a.new"); if (redLinks.length == 0) { return; }

const linkText = {}; $(redLinks).each(function getRedlinks {		const link = $(this).attr("href");		const text = $(this).text;		linkText[link] = text;	}); const navLinks = $(".navbox a.new, .sidebar a.new, .ambox a.new"); $(navLinks).each(function getNavlinks {		const link = $(this).attr("href");		delete linkText[link];	}); const linkTextSize = Object.keys(linkText).length; if (linkTextSize == 0) { return; }

let list = "<ul>"; for (const [link, text] of Object.entries(linkText)) { list += "<li><a href='" + link + "' class='new'>" + text + "</a></li>"; }	list += "</ul>"; $("#oHL_results").append("<details id='oHL_redlinks'> Redlinks (" + linkTextSize + ") " + list + " "); }

const wordListURL = "https://" + window.location.hostname + "/w/index.php?title=User:Opencooper/highlightStringsWordlist.js&action=raw&ctype=text/javascript"; function showFrequency { $.ajax({   	url: wordListURL,        success: getFrequencies    }); }

function containsVowel(s) { const vowels = ["a", "e", "i", "o", "u", "y"]; return Array.from(s).filter(c => vowels.includes(c)).length > 0; }

function getSingular(word) { const startLength = word.length; word = word.replace(/('s'|'s)$/, ""); if (word.length != startLength) { return word; } word = word.replace(/'$/, ""); if (word.endsWith("sses") || word.endsWith("xes")) { word = word.slice(0, -2); } else if (word.endsWith("us") || word.endsWith("ss") || word.endsWith("es")) { // do nothing } else if (word.endsWith("s")) { const precedingWordPart = word.slice(0, -2); if (containsVowel(precedingWordPart)) { word = word.slice(0, -1); }	}	return word; }

// Simpler form of the Porter2 algorithm: http://snowball.tartarus.org/algorithms/english/stemmer.html // Attempts to lemmatize better function getStem(word) { function getRegions(s) { // Not implementing gener/commun/arsen exception const regionRe = /[aeiouy][^aeiouy](.*)/; const r1 = s.match(regionRe)?.[1] || ""; const r2 = r1.match(regionRe)?.[1] || ""; return [r1, r2]; }

function endsWithDouble(s) { return s.length >= 2 && s.slice(-1) == s.slice(-2, -1); }   function isShort(s) { const [r1, r2] = getRegions(s); return r1 == "" && /[^aeiouy][aeiouy][^aeiouywxY]$/.test(s); }	word = getSingular(word); if (word.length <= 2) { return word; }   if (word.endsWith("ies")) { word = word.replace(/ies$/, "y"); } else if (word.endsWith("es")) { word = word.replace(/es$/, ""); const finalLetter = word.slice(-1); if (/[bcdefgklmnopqrstuvz]/.test(finalLetter) || word.endsWith("ach")) { word += "e"; }   }

// e.g. painting, rating if (word.endsWith("ting") || word.endsWith("ted")) { word = word.replace(/ing$/, "").replace(/ed$/, ""); if (/[aeiouyt]$/.test(word)) { word += "e"; }	}	// e.g. rising, sized, inviting if (word.endsWith("ising") || word.endsWith("izing") || word.endsWith("iting")) { word = word.replace(/(i[szt])ing$/, "$1e"); } else if (word.endsWith("ised") || word.endsWith("ized") || word.endsWith("ised") || word.endsWith("ited")) { word = word.replace(/(i[szt])ed$/, "$1e"); }	// e.g. ensnaring, snored if (word.endsWith("naring") || word.endsWith("noring")) { word = word.replace(/(n[ao]r)ing$/, "$1e"); } else if (word.endsWith("nared") || word.endsWith("nored")) { word = word.replace(/(n[ao]r)ed$/, "$1e"); }	if (word.endsWith("ing")) { word = word.replace(/ing$/, ""); if (endsWithDouble(word)) { word = word.slice(0, -1); } else if (/[cpvn]$/.test(word)) { word += "e"; }	}   if (word.endsWith("ied")) { word = word.replace(/ied$/, "y"); } else if (word.endsWith("ed")) { const deletedWord = word.replace(/ed$/, ""); if (containsVowel(deletedWord)) { word = deletedWord; if (word.endsWith("at") || word.endsWith("bl") || word.endsWith("iz") || word.endsWith("en") || word.endsWith("ok") || word.endsWith("v") || word.endsWith("in")) { word += "e"; } else if (endsWithDouble(word)) { word = word.slice(0, -1); } else if (isShort(word)) { word += "e"; }       }	}  if (word.endsWith("er") || word.endsWith("est")) { word = word.replace(/er$/, "").replace(/est$/, ""); if (word.endsWith("i")) { word = word.slice(0, -1) + "y"; } else if (word.endsWith("m")) { word += "e"; } }	if (word.endsWith("tche")) { // e.g. blotches word = word.replace(/tche$/, "tch"); } else if (word.endsWith("ttl") || word.endsWith("rul")) { // e.g. unsettled, overruling word += "e"; }	return word; }

function getFrequencies(response) { const wordList = response.split("\n"); const commentEnd = wordList.indexOf("//———") + 1; for (let i = 0; i <= commentEnd; i++) { wordList[i] = ""; }

const wordListDehyphenated = []; const wordListDeperioded = []; for (const word of wordList) { if (word.includes("-")) { const wordDehyphenated = word.replaceAll("-", ""); wordListDehyphenated.push(wordDehyphenated); } else if (word.endsWith(".")) { const periodCount = word.match(/\./g).length; if (periodCount == 1) { const wordDeperioded = word.slice(0, -1); wordListDeperioded.push(wordDeperioded); }		}	}

// Get article text and cleanup text we don't want const bodyContent = $("#mw-content-text .mw-content-ltr").first.clone; bodyContent.find("p, div, tr, .infobox-data, br").before("\n"); bodyContent.find("li, td, sub, sup").before(" "); bodyContent.find("q").before('"'); bodyContent.find("q").after('"'); bodyContent.find("sub, sup, math, style,"                    + " [href='/wiki/Help:Pronunciation_respelling_key'],"                     + " [href*='doi.org'], [href*='arxiv.org'],"                      + " .ambox, .portalbox, .infobox-label, #toc, .texhtml,"                     + " .printfooter, .sidebar, .IPA, .stub, .url,"                     + " .sistersitebox, .mw-hidden-catlinks, .cs1-maint,"                     + " .cs1-prop-foreign-lang-source, .cs1-visible-error,"                     + " .harv-error, .cite-accessibility-label, .mw-editsection,"                     + " .oHL_anchorLink, .oHL_piped, .oHL_added, .oHL_ruby rt,"                     + " .oHL_trailingSpace, #oHL_wd_img, .oHL_shownAnchor,"                     + " .oHL_img_info_dimensions, .oHL_clear,"                     + " .navbox-title .hlist").remove;

String.prototype.cleanText = function { return this.replaceAll("\n", " ") .replaceAll("’", "'") .replaceAll(/http[^ \n]*/g, "").replaceAll(/[\w.-]+\.[\w\/]{2,}/g, "") .replaceAll(/([–—−+×·⋅÷√&\/\\<>{}~$@%_…\|\*=º°^′™©®†‡§←→↔～「」【】（）・])/g, " ") .replaceAll(/(\p{Emoji_Presentation})/ug, "") .replaceAll(/([-‑‐])/g, " ") .replaceAll(/[\s​]/g, " ") .replaceAll("\u2060", "").replaceAll("\u200C", "").replaceAll("\u200D", "").replaceAll("\u200E", "").replaceAll("\u00AD", "") // invisible characters .replaceAll(/(' '|(?<![A-Za-z])'|'(?![A-Za-z]))/g, " ") .replaceAll(/\/g, "") .replaceAll(/([.,:;"‘’“”„`´«»‹›!¡?¿#｜＆％. ．，、：；？！])/g, " ")                  .replaceAll("æ", "ae").replaceAll("œ", "oe");    };    const bodyContentNavboxless = bodyContent.clone;    bodyContentNavboxless.find(".navbox").remove;    const bodyTextRawNavboxless = bodyContentNavboxless.text;    let bodyTextRaw = bodyContent.text;    const bodyText = bodyTextRaw.cleanText;    // Create lists of words for filtering    const refTextArray = bodyContent.find(refSectionsSelector + ", #Further_reading, #Additional_reading")                                    .parent.nextUntil("h2, .navbox, .stub").text.cleanText.split(" ");    const italicTextArray = bodyContent.find("i").append(" ").text.cleanText.split(" ");    const wikilinkTextArray = bodyContent.find(".oHL_wikilink, a.new").append(" ").text.cleanText.split(" ");    const externalTextArray = bodyContent.find(".external").append(" ").text.cleanText.split(" ");

const blockTextArray = bodyContent.find("blockquote, .quotebox, .poem:not(blockquote .poem), .oHL_bad-indent").text.cleanText.split(" "); // TODO: use a different char for single quotes const quoteMarkTextArray = bodyTextRaw.replaceAll(/([ \(\n])['"]/g, "$1𐑱") // 𐑱: left quote placeholder                                          .replaceAll(/[‘“]/g, "𐑱")                                          .replaceAll(/['’"]([ .,;:\)\n])/g, '𐑲$1') // 𐑲: right quote placeholder .replaceAll("”", "𐑲") .match(/(?<=𐑱)[^𐑲\n]+(?=𐑲)/g) ?.join(" ").replaceAll(/[𐑱𐑲]/g, "") .cleanText.split(" "); const quoteTextArray = blockTextArray.concat(quoteMarkTextArray); // Create lists of words for whitelisting const wikilinkPipedTextArray = $("#bodyContent .oHL_piped small").clone.append(" ").text.cleanText.toLowerCase.split(" "); const hatnoteTextArray = bodyContent.find(".hatnote .oHL_wikilink").text.cleanText.toLowerCase.split(" "); const navboxTextArray = bodyContent.find(".navbox").text.cleanText.toLowerCase.split(" "); const foreignTextArray = bodyContent.find("[lang], .extiw").append(" ").text.cleanText.toLowerCase.split(" "); const categoryTextArray = $("#catlinks").clone.find("li").append("|").text.cleanText.toLowerCase.split(" "); const titleTextArray = bodyContent.find(".oHL_title").append(" ").text.cleanText.toLowerCase.split(" "); const codeTextArray = bodyContent.find("pre, code").append(" ").text.cleanText.toLowerCase.split(" "); const sicTextArray = bodyText.match(/\w+(?=\s+sic )/g)?.join(" ").toLowerCase.split(" "); const usernameArray = bodyTextRaw.match(/(?<=@)(\w+)/g)?.join(" ").replaceAll("_", "").toLowerCase.split(" "); const hashtagArray = bodyTextRaw.match(/(?<=#)(\w+)/g)?.join(" ").toLowerCase.split(" "); const gitHubArray = bodyContent.find("[href^='https://github.com/']").append(" ").text.split(" ").filter(s => s.includes("/")).join(" ").cleanText.toLowerCase.split(" ");

bodyTextRaw = bodyTextRaw.replaceAll(/[ \n]*\n+[ \n]*/g, " ¶ ") .replaceAll(/(¶ \^ ){2,}/g, "¶ ^ ");

// Extract names const bodyContentNames = bodyContent.clone; const wikilinkedWhitelistElements = $.map($(".oHL_piped small"), $.text).map(s => s.trim); bodyContentNames.find(".oHL_piped").remove; const wikilinkedWhitelistElements2 = $.map(bodyContentNames.find(".oHL_wikilink, a.new, .oHL_title, #oHL_redirects ul li"), $.text).map(s => s.trim); bodyContentNames.find(".hatnote, b, i, a, caption, th, pre, code, .mw-headline, .mw-references-wrap, .navbox").remove; const bodyContentNamesRaw = bodyContentNames.text; const namesArray = bodyContentNamesRaw.match(/(\p{Upper}\w+\W){2,}/gu).map(s => s.slice(0, -1)).filter(s => !s.includes("[")); const wikilinkedWhitelistCustom = ["In January", "In February", "In March", "In April", "In May", "In June", "In July", "In August", "In September", "In November", "In October", "In December", "Wikimedia Commons"]; const wikilinkedWhitelistArray = wikilinkedWhitelistElements.concat(wikilinkedWhitelistElements2, wikilinkedWhitelistCustom, countryNames, stateNames); const nameCandidates = namesArray.filter(n => !wikilinkedWhitelistArray.includes(n)); const nameCandidatesUnique = [ ...new Set(nameCandidates) ]; checkWikilinkCandidates(nameCandidatesUnique);

// Convert text into frequency list const tokens = bodyText.split(" "); const counts = {}; for (const token of tokens) { if (token.length <= 1 || /\d/.test(token) || /[A-Z]{2}|[a-z][A-Z]/.test(token)   	    || !/\w/.test(token) || token.startsWith("d'") || token.startsWith("l'")) { continue; }   	if (token in counts) { counts[token] += 1; } else { counts[token] = 1; }   }

// Merge together variants, e.g. "chopsticks" and "Chopsticks" for "chopstick" for (const [token, count] of Object.entries(counts)) { const variants = [token]; const tokenNormalized = token.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); // https://stackoverflow.com/a/37511463/1995949 if (tokenNormalized != token) { variants.push(tokenNormalized); }		for (const variant of variants) { const variantSingular = getSingular(variant); if (variantSingular != variant) { variants.push(variantSingular); }		}		for (const variant of variants) { const variantStemmed = getStem(variant); if (variantStemmed != variant) { variants.push(variantStemmed); }		}   	for (const variant of variants) { const tokenLowercased = variant.toLowerCase; if (tokenLowercased != variant) { variants.push(tokenLowercased); }   	}    	for (const variant of variants) { if (variant != token && variant in counts) { counts[variant] += count; delete counts[token]; }	   	}    }    // Only keep words that don't repeat const singleCounts = []; for (const [token, count] of Object.entries(counts)) { if (count == 1) { singleCounts.push(token); }   }

const wordWhiteListArray = wordList.concat(wordListDehyphenated, wordListDeperioded,                                              navboxTextArray, foreignTextArray,                                               categoryTextArray, titleTextArray,                                               sicTextArray, usernameArray,                                               hashtagArray, gitHubArray,                                               codeTextArray, hatnoteTextArray,                                               wikilinkPipedTextArray); const wordWhitelist = new Set(wordWhiteListArray);

// Filter out common words const singleCountsUncommon = []; for (const token of singleCounts) { const tokenLowercase = token.toLowerCase.replace("æ", "ae").replace("œ", "oe"); const tokenSingular = getSingular(tokenLowercase); if (!wordWhitelist.has(tokenLowercase)   	    && !wordWhitelist.has(tokenSingular)    	    && !wordWhitelist.has(getStem(tokenSingular))) { singleCountsUncommon.push(token); }   }

if (singleCountsUncommon.length == 0) { return; }   // Alphabetize and build list const finalIndex = bodyTextRaw.length - 1; singleCountsUncommon.sort((a, b) => a.localeCompare(b, 'en', {'sensitivity': 'base'})); let listMarkup = "<ul>"; for (const token of singleCountsUncommon) { const searchRe = new RegExp("(?<=\\W)" + token + "(?=\\W)"); const contextIndex = bodyTextRaw.match(searchRe)?.index; let context = ""; if (typeof contextIndex != "undefined") { const contextOffset = 35 - (token.length / 2); const contextCenterIndex = contextIndex + (token.length / 2); let contextStartIndex = contextCenterIndex - contextOffset; if (contextStartIndex < 0) { contextStartIndex = 0; } let contextEndIndex = contextCenterIndex + contextOffset; if (contextEndIndex > finalIndex) { contextStartIndex = finalIndex; } const contextText = bodyTextRaw.substring(contextStartIndex, contextEndIndex); context = " – " + contextText.replace(searchRe, "<span class='oHL_wikitext-match-text'>"   		                                       + token + " "); }

let classList = []; if (/[A-Z]/.test(token[0])) { classList.push("oHL_uncommonWordUppercase"); }   	if (/[a-z]/.test(token[0])) { classList.push("oHL_uncommonWordLowercase"); }   	if (!/^[A-Za-z']+$/.test(token)) { classList.push("oHL_uncommonWordNonASCII"); }   	if (refTextArray.includes(token)) { classList.push("oHL_uncommonWordReference"); }   	if (italicTextArray.includes(token)) { classList.push("oHL_uncommonWordItalic"); }   	if (quoteTextArray.includes(token)) { classList.push("oHL_uncommonWordQuote"); }   	if (wikilinkTextArray.includes(token)) { classList.push("oHL_uncommonWordWikilink"); }   	if (externalTextArray.includes(token)) { classList.push("oHL_uncommonWordExternal"); }

listMarkup += "<li class='" + classList.join(" ") + "'><span class='oHL_uncommonWord'>" + token + " " + context + "<span class='oHL_uncommonSymbols'> </li>"; }   listMarkup += "</ul>"; $("#oHL_results").append("<details id='oHL_uncommon'> Uncommon words (" + singleCountsUncommon.length + ") " + listMarkup + " ");

addFrequencyFilters; showUnbalanced(bodyTextRawNavboxless); }

function addFrequencyFilters { const filters = [ {"id": "oHL_lowercaseFilter", "class": "oHL_uncommonWordLowercase", "label": "Lowercase", "symbol": "a"}, {"id": "oHL_uppercaseFilter", "class": "oHL_uncommonWordUppercase", "label": "Uppercase", "symbol": "A"}, {"id": "oHL_UnicodeFilter", "class": "oHL_uncommonWordNonASCII", "label": "Unicode", "symbol": "Ü"}, {"id": "oHL_italicFilter", "class": "oHL_uncommonWordItalic", "label": "Italicized", "symbol": "𝐼"}, {"id": "oHL_quoteFilter", "class": "oHL_uncommonWordQuote", "label": "Quoted", "symbol": "“"}, {"id": "oHL_wikilinkFilter", "class": "oHL_uncommonWordWikilink", "label": "Wikilinked", "symbol": "∞"}, {"id": "oHL_referenceFilter", "class": "oHL_uncommonWordReference", "label": "Reference", "symbol": "^"}, {"id": "oHL_externalFilter", "class": "oHL_uncommonWordExternal", "label": "External", "symbol": "→"} ];	filters.forEach(f => {		if (["a", "A", "Ü"].includes(f.symbol)) { return; }		const symbolMarkup = "<span title='" + f.label + "'>" + f.symbol + " ";		$("." + f.class + " .oHL_uncommonSymbols").append(symbolMarkup);	});

function updateFilterEnabledStatus { if (this.checked) { return; } const filterId = this.parentElement.parentElement.id; const filterClass = filters.find(f => f.id == filterId).class; const isFilterable = $("." + filterClass).not(".oHL_uncommonWordHidden").length > 0; if (isFilterable) { this.disabled = false; $(this).parent.removeClass("oHL_filterDisabled"); } else { this.disabled = true; $(this).parent.addClass("oHL_filterDisabled"); }   }

$("#oHL_uncommon summary").after("<fieldset id='oHL_uncommonFilters'> Hide: "); for (const filter of filters) { $("#oHL_uncommonFilters").append(` <span id='${filter.id}'> <input type='checkbox'> <span class='oHL_filterLabel'>${filter.label} `); }   $("#oHL_uncommonFilters input").each(updateFilterEnabledStatus);

const filterCheckboxSelector = filters.map(f => "#" + f.id + " input").join(", "); $(filterCheckboxSelector).change(function filterUncommonWords {		const hideList = [];		const showList = [];		for (const filter of filters) {			if ($(`#${filter.id} input`).is(":checked")) {   		    hideList.push("." + filter.class);   	    } else {    	    	showList.push("." + filter.class);   	    }		}		const hideSelectors = hideList.join(", ");		const showSelectors = showList.join(", ");		$(hideSelectors).addClass("oHL_uncommonWordHidden");		$(showSelectors).not(hideSelectors).removeClass("oHL_uncommonWordHidden");		$("#oHL_uncommonFilters input").each(updateFilterEnabledStatus);	});

$(filterCheckboxSelector).hover(function showFilteredHighlights {		const filterId = this.parentElement.parentElement.id;		const filterClass = filters.find(f => f.id == filterId).class;		$("." + filterClass + " .oHL_uncommonWord").addClass("oHL_filterableWordHighlighted");	}, function hideFilteredHighlights {		$(".oHL_filterableWordHighlighted").removeClass("oHL_filterableWordHighlighted");	}); }

function checkWikilinkCandidates(nameCandidates) { if (nameCandidates.length == 0) { return; }	$("#oHL_results").append("<details id='oHL_wikilinkCandidates' style='display: none;'> Wikilink candidates <ul id='oHL_wikilinkCandidates_items'></ul> "); // Need to chunk since API has a limit on number of titles for (let i = 0; i < nameCandidates.length; i+= 50) { const nameChunk = nameCandidates.slice(i, i+50); // API docs: https://www.mediawiki.org/w/api.php?action=help&modules=query%2Binfo const apiUrl = location.origin + "/w/api.php"; $.ajax({           url: apiUrl,            data: {                action: "query",                prop: "info",                titles: nameChunk.join("|"),                format: "json",                origin: "*"            },            success: getCandidatesStatus        }); } }

function getCandidatesStatus(response) { const pages = response.query.pages; for (const key in pages) { // Non-existent pages will have the key "missing" if (typeof pages[key].missing == "undefined") { const page = pages[key].title; const link = location.origin + "/wiki/" + page; $("#oHL_wikilinkCandidates_items").append("<li><a href='" + link +"'>" + page + "</a></li>"); $("#oHL_wikilinkCandidates").show; updateSummaryCount("#oHL_wikilinkCandidates"); }	} }

function showUnbalanced(bodyText) { const unbalanced = []; const brackets = { "\"\"": /"/g,		"“”": [/“/g, /”/g],		"": [/\(/g, /\)/g],		"[]": [/\[/g, /\]/g]	};

for (const line of bodyText.split("\n")) { for (const [bracket, bracketRe] of Object.entries(brackets)) { const bracketLeft = bracket[0]; const bracketRight = bracket[1]; let isUnbalanced = false; if (bracketLeft == '"') {				const count = (line.match(bracketRe) || []).length;			   if (count % 2 != 0) {				    isUnbalanced = true;			    }			} else {				const leftCount = (line.match(bracketRe[0]) || []).length;				const rightCount = (line.match(bracketRe[1]) || []).length;				if (leftCount != rightCount) {					isUnbalanced = true;				}			}

if (isUnbalanced) { let lineFormatted = line.replaceAll(bracketLeft, "<span class='oHL_wikitext-match-text'>"				                                                + bracketLeft + " "); if (bracketRight != bracketLeft) { lineFormatted = lineFormatted.replaceAll(bracketRight, "<span class='oHL_wikitext-match-text'>"				                                            + bracketRight + " "); }				unbalanced.push(lineFormatted); }		}	}

if (unbalanced.length == 0) { return; }

let list = "<ul>"; for (const entry of unbalanced) { list += "<li>" + entry + "</li>"; }   list += "</ul>";

$("#oHL_results").append("<details id='oHL_unbalanced'> Unbalanced quotes and brackets (" + unbalanced.length + ") " + list + " "); }

function showRedirects { // Placeholder $("#oHL_results").append("<details id='oHL_redirects'> Incoming redirects (0) "); // API docs: https://www.mediawiki.org/w/api.php?action=help&modules=query%2Bredirects const apiUrl = location.origin + "/w/api.php"; $.ajax({       url: apiUrl,        data: {            action: "query",            prop: "redirects",            rdprop: "title|fragment",            rdnamespace: "0",            rdlimit: "500",            format: "json",            titles: mw.config.get("wgPageName")        },        success: listRedirects    }); }

function listRedirects(response) { const pageId = mw.config.get("wgArticleId"); const redirects = response.query.pages[pageId].redirects;

let redirectText = "No redirects."; let redirectCount = 0;

const redirectCandidatesMarkup = $(".oHL_title, p:first-of-type i[lang], p:first-of-type i [lang]").clone; redirectCandidatesMarkup.find(".oHL_ruby rt, .oHL_piped, .oHL_added").remove; let redirectCandidates = redirectCandidatesMarkup.toArray.map(e => $(e).text	   .replaceAll("\u2060", "").replaceAll("\u200C", "").replaceAll("\u200D", "").replaceAll("\u200E", "").replaceAll("\u00AD", "")); let pageTitle = mw.config.get("wgTitle"); pageTitle = pageTitle.replace(/ \(.*\)/, ""); redirectCandidates = redirectCandidates.filter(c => c.toLowerCase != pageTitle.toLowerCase); if (typeof redirects != "undefined") { redirectText = ""; redirectCount = redirects.length; redirects.sort((a, b) => a.title.localeCompare(b.title));

redirects.forEach(r => {           redirectText += "<li><a href='/w/index.php?title="			                + encodeURIComponent(r.title).replaceAll("'", "%27")			                + "&redirect=no'>" + r.title + "</a>";			const candidateAlreadyInRedirects = redirectCandidates.some(c => c.toLowerCase == r.title.toLowerCase);			if (candidateAlreadyInRedirects) {				redirectCandidates = redirectCandidates.filter(c => c.toLowerCase != r.title.toLowerCase);			}			if (typeof r.fragment != "undefined") {				const fragment = r.fragment.replaceAll(" ", "_");				const fragmentEscaped = CSS.escape(fragment);				if ($("#" + fragmentEscaped).length) {					redirectText += " → <a";				} else {					redirectText += " → ❌ <a class='new'";				}				redirectText += " href='#" + fragment + "'>§" + fragment + "</a>";			}            			redirectText +=  "</li>";        }); }	// TODO: just update the children instead of the whole element $("#oHL_redirects").html(" Incoming redirects (" + redirectCount+") <ul>"	                        + redirectText + "</ul>"); if (redirectCandidates.length > 0) { let redirectCandidatesText = ""; for (const candidate of redirectCandidates) { redirectCandidatesText += "<li><a href='/wiki/" + encodeURIComponent(candidate).replaceAll("'", "%27") + "'>" + candidate + "</a></li>"; }		$("#oHL_redirects").after("<details id='oHL_redirect_candidates'> New redirect candidates (" + redirectCandidates.length +") <ul>"		                         + redirectCandidatesText + "</ul> "); } }

// Check if page is linked to from disambig page function checkDisambigLink { const pageTitle = mw.config.get("wgTitle"); if (pageTitle.slice(-1) != ")") {		return;	}	// API docs: https://www.mediawiki.org/w/api.php?action=help&modules=query%2Blinkshere   const apiUrl = location.origin + "/w/api.php";    $.ajax({ url: apiUrl, data: { action: "query", prop: "linkshere", lhprop: "title", lhnamespace: "0", lhlimit: "500", format: "json", titles: mw.config.get("wgPageName") },       success: searchDisambigLink }); }

function searchDisambigLink(response) { const pageId = mw.config.get("wgArticleId"); const incomingLinks = response.query.pages[pageId].linkshere; if (typeof incomingLinks == "undefined") { return; } const pageTitle = mw.config.get("wgTitle"); const pageTitleWithoutParens = pageTitle.replace(/ \(.*\)$/, ""); const pageTitleDab = pageTitleWithoutParens + " (disambiguation)"; for (const link of incomingLinks) { if (link.title == pageTitleWithoutParens || link.title == pageTitleDab) { return; }	}	const dabMarkup = "<a href='/wiki/" + encodeURIComponent(pageTitleWithoutParens).replaceAll("'", "%27") + "'>" + pageTitleWithoutParens + "</a> or <a href='/wiki/" + encodeURIComponent(pageTitleDab).replaceAll("'", "%27") + "'>" + pageTitleDab + "</a>"; $("#oHL_results").append("<details id='oHL_noDabLink'> Incoming disambiguation link missing (1) "	                        + "<ul><li>Either " + dabMarkup + " need to link to this page.</i></ul> "); }

function showWikitextMatches(results) { if (results.size === 0) { return; }

let resultsHTML = ""; for (const [type, matches] of results) { const cssId = "oHL_" + type.replaceAll(/[^\w]/g, "_").toLowerCase; resultsHTML += "<details id='" + cssId + "'> " + type + " (" + matches.length                      + ") <ul>"; matches.forEach(m => resultsHTML += "<li class='oHL_wikitext-match'>"                                           + mw.html.escape(m[0])                                            + "<span class='oHL_wikitext-match-text'>"                                            + mw.html.escape(m[1]) + " "                                            + mw.html.escape(m[2]) + "</li>"); resultsHTML += "</ul> "; }   $("#oHL_summary").after(resultsHTML); }

// Check italicization of wikilinks function getItalics { const wikilinks = $(".oHL_wikilink").toArray; const whitelist = $(".mw-references-wrap .oHL_wikilink, .navbox .oHL_wikilink,"                       + " .stub .oHL_wikilink, .hatnote .oHL_wikilink").toArray; const filteredWikilinks = wikilinks.filter(wl => !whitelist.includes(wl)); const links = {}; const crossNamespaceRe = /[a-z]:[A-Z]/; filteredWikilinks.forEach(l => {       if (l.title === "" || crossNamespaceRe.test(l.title)) { return; }        links[l.title] = l; // {title: selector}    }); $("#oHL_results").append("<details id='oHL_italicization' style='display: none;'> Italicization <ul id='oHL_italicization_items'></ul> "); const titles = Object.keys(links); console.log("highlightStrings.js: Getting DefaultSort for " + titles.length + " pages"); // Need to chunk since API has a limit on number of titles for (let i = 0; i < titles.length; i+= 50) { const titleChunk = titles.slice(i, i+50); getDisplayTitles(links, titleChunk); } }

function getDisplayTitles(links, titles) { // API docs: https://www.mediawiki.org/w/api.php?action=help&modules=query%2Bpageprops const apiUrl = location.origin + "/w/api.php"; $.ajax({       url: apiUrl,        data: {            action: "query",            prop: "pageprops",            ppprop: "displaytitle",            format: "json",            titles: titles.join("|"),            redirects: "yes",        },        success: response => checkItalics(links, response)    }); }

function checkItalics(links, response) { var redirects = {}; response.query?.redirects?.forEach(r => {       const newTitle = r.to;        const oldTitle = r.from;        redirects[newTitle] = oldTitle;    }); checkSelfRedirects(redirects); Object.values(response.query.pages).forEach(p => {       let title = p.title;        if (title in redirects) {            title = redirects[title];        }        const element = links[title];        const originalItalicized = $(element).parent("i").length;        const displayItalicization = p?.pageprops?.displaytitle || "None";        // Skip Foo (Bar)        if (/\(<i>/.test(displayItalicization)) { return; }

if (!originalItalicized && displayItalicization != "None"           || originalItalicized && displayItalicization == "None") { const originalDisplay = $(element).clone; $(originalDisplay).find("*").each(function cleanOriginalElement {               this.className = "";                this.removeAttribute("lang");            }); $(originalDisplay).find("rt").remove; if (originalItalicized) { originalDisplay.wrapInner(""); } const originalDisplayMarkup = $(originalDisplay).html;

if (originalDisplayMarkup == displayItalicization) { return; }

$("#oHL_italicization_items").append("<li>" + originalDisplayMarkup                                                + " <a href='#"+ element.id + "'>→</a> "                                                 + displayItalicization + "</li>"); $("#oHL_italicization").show; updateSummaryCount("#oHL_italicization"); }   }); }

function updateSummaryCount(selector) { const element = $(selector); const count = element.children("ul").children.length; const countElement = element.find(".summaryCount"); countElement.text("(" + count + ")"); }

// Find any redirects that lead back to article we're on function checkSelfRedirects(redirects) { const selfRedirects = []; const currentPage = mw.config.get("wgTitle"); for (const [target, wikilink] of Object.entries(redirects)) { if (target == currentPage) { selfRedirects.push(wikilink); }   }    if (selfRedirects.length) { let redirectList = ""; for (const r of selfRedirects) { redirectList += "<li>" + r + "</li>"; }       if ($("#oHL_selfRedirects").length == 0) { $("#oHL_results").append("<details id='oHL_selfRedirects'> Self-redirects <ul id='oHL_selfRedirects_items'></ul> "); }       $("#oHL_selfRedirects_items").append(redirectList); updateSummaryCount("#oHL_selfRedirects"); } }

// Find dead interwiki links function getDeadInterwikis { const links = {}; $(".extiw").each(function getInterwikiLinks {		const url = new URL(this.href);		const pageEncoded = url.pathname.replace("/wiki/", "");		const page = decodeURIComponent(pageEncoded);		if (!(url.host in links)) {			links[url.host] = [page];		} else {			links[url.host].push(page);		}	}); if (Object.keys(links).length == 0) { return; }	$("#oHL_results").append("<details id='oHL_interwikiStatus' style='display: none;'> Dead interwiki links <ul id='oHL_interwikiStatus_items'></ul> "); for (const [hostname, pages] of Object.entries(links)) { // API docs: https://www.mediawiki.org/w/api.php?action=help&modules=query%2Binfo const apiUrl = "https://" + hostname + "/w/api.php"; $.ajax({           url: apiUrl,            data: {                action: "query",                prop: "info",                titles: pages.join("|"),                format: "json",                origin: "*"            },            success: function callLinkStatusFunction(response) {            	getLinkStatus(response, hostname);            }        }); } }

function getLinkStatus(response, hostname) { const pages = response.query.pages; const site = hostname.replace(".wikimedia", "").replace(".org", ""); for (const key in pages) { // Non-existent pages will have the key "missing" if (typeof pages[key].missing != "undefined") { const page = pages[key].title; const link = "https://" + hostname + "/wiki/" + page; $("#oHL_interwikiStatus_items").append("<li><a href='" + link +"'>" + site + ": " + page + "</a></li>"); $("#oHL_interwikiStatus").show; updateSummaryCount("#oHL_interwikiStatus"); }	} }

// Cosmetic changes function tweakDisplay { // Italics $("#mw-content-text i, #mw-content-text i a").addClass("oHL_i"); $("h1 i, sup i, sup a, .stub i, .stub a, .ambox i, .ambox a").removeClass("oHL_i");

// Clears $("div[style='clear:both;']").after("<span class='oHL_clear'>[clear] ");

// Anchors $(".anchor").each(function showAnchors {   	const id = $(this).attr("id");    	$(this).closest("h2, h3, h4, h5, h6").after("<a class='oHL_shownAnchor' style='font-size: 85%' href='#" + id + "'>#" + id + "</a>");   }); // Short descriptions $(".shortdescription").first.each(function showShortDescriptions {   	this.style.display = "";    	$(this).prepend("<span class='oHL_added'>[Short description]: ");    });

// Ruby wrapRuby("em", "em"); wrapRuby(".official-website", "official"); wrapRuby("#mw-content-text big", "big"); wrapRuby("#mw-content-text small", "small"); wrapRuby("span[dir=rtl]", "RTL"); wrapRuby("span.plainlinks", "plainlink"); wrapRuby(".external[class*='mw-magiclink']", "magic"); wrapRuby(".vanchor", "#vanchor"); wrapRuby(".smallcaps", "smallcaps");

$("#otherImages-pageImage").after("<p id='oHL_wd_img'>[Wikidata image] ");

// Show piped link targets showPiped; // Show duplicate refs on hover $(".oHL-duplicated-ref.oHL, .oHL-duplicated-ref.oHL-opt").on("mouseover", function highlightDupeLinks {       const href = $(this).attr("href");        const hrefCleaned = href.replace(/https?:\/\//, "");        $("a[href*='" + hrefCleaned + "'].oHL_dupe_ref").addClass("oHL_dupe_ref_active");    }).on("mouseout", _ => $(".oHL_dupe_ref_active").removeClass("oHL_dupe_ref_active"));

// Re-enable ReferenceTooltips since we replaced the HTML mw.loader.load("//en.wikipedia.org/w/index.php?title=MediaWiki:Gadget-ReferenceTooltips.js&action=raw&ctype=text/javascript"); }

function wrapRuby(selector, label) { document.querySelectorAll(selector)?.forEach(e => {       const rubyElement = document.createElement("ruby");        rubyElement.className = "oHL_ruby";        const innerRb = document.createElement("rb");        rubyElement.appendChild(innerRb);        const rtElement = document.createElement("rt");        rtElement.textContent = label;        rubyElement.appendChild(rtElement);        e.parentElement.insertBefore(rubyElement, e);        innerRb.appendChild(e);    }); }

function displayMatches { const matches = getMatchTotal(".oHL"); const optionalMatches = getMatchTotal(".oHL-opt"); const totalMatches = matches + optionalMatches;

let alertMessage; if (totalMatches !== 0) { alertMessage = "<div id='oHL_matches'>Matches: " + matches + " Optional: " + optionalMatches + " "; window.addEventListener("keypress", keyListener, false); $("#mw-content-text").before("<div id='oHL_info'> <sup id='oHL_info_counter'>0 "                                    + "&frasl;<sub id='oHL_info_total'>"                                     + totalMatches + " <span id='oHL_info_arrows'>"                                     + "<span id='oHL_info_left_arrow' title='Previous highlight [shift-n]' class='oHL_arrow_disabled'>← "                                     + "<span id='oHL_info_right_arrow' title='Next highlight [n]'>→ "                                     + "  <div id='oHL_info_class'>  "); $("#oHL_info_left_arrow").click(function previousHighlight {       	advanceHighlight(-1);        }); $("#oHL_info_right_arrow").click(function nextHighlight {       	advanceHighlight(1);        }); } else { alertMessage = "<div id='oHL_results'><div id='oHL_summary'>No matches. "; }

$("#mw-content-text").before(alertMessage);

$("#mw-content-text").before("<div id='oHL_results'> "); getMatchesSummary(totalMatches); const gearIconURL = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3c/OOjs_UI_icon_advanced_apex.svg/20px-OOjs_UI_icon_advanced_apex.svg.png"; $("#oHL_summary").append(" <button id='oHL_configureButton' class='cdx-button'> <img src='"                            + gearIconURL + "'> Configure ");

// Hover display $("#oHL_results").append("<span id='oHL_hover'> "); $(".oHL, .oHL-opt").on("mouseenter", showHighlightName) .on("mouseleave", _ => $("#oHL_hover").hide); }

function getMatchTotal(selector) { let count = 0; $(selector).each(function countTotalMatches {       const hlClasses = getHLClasses([ ...this.classList ]);        count += hlClasses.length;    }); return count; }

function showHighlightName(e) { if ($(e.target).hasClass("oHL_disabled")) { return; } const hlClasses = getHLClasses([ ...e.target.classList ]); let hlTexts = []; for (const hlClass of hlClasses) { let hlText = hlClass; if (hlClass in matchDescriptions) { hlText = matchDescriptions[hlClass][0]; }       hlTexts.push(hlText); }   const hlTextsCombined = hlTexts.join(" · "); $("#oHL_hover").text(hlTextsCombined); $("#oHL_hover").css("top", "calc(" + e.clientY + "px - 2.4em)"); $("#oHL_hover").css("left", e.clientX); $("#oHL_hover").show; }

function getMatchesSummary(totalMatches) { // Wikify highlight descriptions for (const [hlName, hlProps] of Object.entries(matchDescriptions)) { let desc = hlProps[1]; desc = desc.replace(/\[\[([^\[]+)\]\]/g, "<a href='/wiki/$1'>$1</a>"); desc = desc.replace(/>([^<]*?)#([^<]*?)</g, ">$1 § $2<"); // section links desc = desc.replace(/{{([^{]+)}}/g, " "); matchDescriptions[hlName][1] = desc; }

const counts = new Map; $(".oHL, .oHL-opt").each(function incrementCounts {       const hlClasses = getHLClasses([ ...this.classList ]);        if (typeof hlClasses == "undefined") { return true; }        for (const hlClass of hlClasses) {            if (!counts.has(hlClass)) {                counts.set(hlClass, 1);             } else {                const c = counts.get(hlClass);                counts.set(hlClass, c+1);             }        }    }); if (counts.size === 0) { return; } const countsSorted = Array.from(counts.entries).sort((a, b) => b[1] - a[1]); let tableHTML = " "; $("#oHL_results").prepend("<details id='oHL_summary'> Highlights"                             + " (" + totalMatches + ") " + tableHTML+ " "); if (countsTotal != totalMatches) { console.warn("highlightStrings.js: Not all oHL matches have a class."); }

// Allow toggling specific matches $("#oHL_results input").change(toggleResult); // Checkbox to de/select all $("#oHL_results tbody").before(" <input title='(De)select all' id='oHL_checkAll' type='checkbox' checked> "                                   + " Name  Description  Count  "); $("#oHL_checkAll").data("total", counts.size); $("#oHL_checkAll").data("checked", counts.size); $("#oHL_checkAll").change(e => {   	if (!e.target.checked) {    		$("#oHL_results input:checked").not("#oHL_checkAll").click;    	} else {    		$("#oHL_results input:not(:checked)").not("#oHL_checkAll").click;    	}    }); }

function toggleResult(e) { const oHLclass = $(e.target).attr("oHLclass"); const element = $("." + oHLclass); if (!e.target.checked) { // Disable $(element).addClass("oHL_disabled");

if ($(element).hasClass("oHL_added")) { $(element).hide; }       $("input[oHLclass='" + oHLclass + "']:checked").prop("checked", false); updateCheckAllBox(-1); } else { // Enable $(element).removeClass("oHL_disabled");

if ($(element).hasClass("oHL_added")) { $(element).show; }       $("input[oHLclass='" + oHLclass + "']:not(checked)").prop("checked", true); updateCheckAllBox(1); } }

function updateCheckAllBox(change) { const totalBoxes = $("#oHL_checkAll").data("total"); let checkedBoxes = $("#oHL_checkAll").data("checked"); checkedBoxes += change; $("#oHL_checkAll").data("checked", checkedBoxes);

if (checkedBoxes == totalBoxes) { $("#oHL_checkAll").prop("checked", true); $("#oHL_checkAll").prop("indeterminate", false); } else if (checkedBoxes == 0) { $("#oHL_checkAll").prop("checked", false); $("#oHL_checkAll").prop("indeterminate", false); } else { $("#oHL_checkAll").prop("checked", true); $("#oHL_checkAll").prop("indeterminate", true); } }

function getHLClasses(classArray) { return classArray.filter(c => c != "oHL-opt" && c.startsWith("oHL-")); }

function keyListener(event) { let offset; event = event || window.event; const key = event.key || event.which; if (key === "n") { offset=1; } else if (key === "N") { offset=-1; } else { return; }   advanceHighlight(offset); }

function advanceHighlight(offset) { let index; const highlightList = $(".oHL, .oHL-opt").toArray; const currentHighlight = $(".oHL_keyed").first; if (currentHighlight.length == 0) { index = -1; } else { index = highlightList.findIndex(e => e == currentHighlight.get(0)); }

let nextIndex = index; let nextHighlight; let nextIsDisabled = true; // Search highlightList until we find an enabled highlight or reach the end while(nextIsDisabled) { nextIndex += offset; nextHighlight = highlightList[nextIndex]; // No next higlight if (typeof nextHighlight == "undefined") { // Pulse highlight $(".oHL_keyed").animate({"border-width": "4px"}, 150); $(".oHL_keyed").animate({"border-width": "2px"}, 100); return; }       nextIsDisabled = $(nextHighlight).hasClass("oHL_disabled"); }

if (!$(nextHighlight).is(":visible")) { console.info("highlightStrings.js: Highlighted invisible element."); // Walk up DOM and make parents visible let parent = nextHighlight.parentElement; while (parent != null && parent.id != "bodyContent") { $(parent).show; parent = parent.parentElement; }   }

$(".oHL_keyed").removeClass("oHL_keyed"); const hlClasses = getHLClasses([ ...nextHighlight.classList ]); let hlTexts = []; for (const hlClass of hlClasses) { let hlText = hlClass; let hlDescription = ""; if (hlClass in matchDescriptions) { hlText = matchDescriptions[hlClass][0]; hlDescription = matchDescriptions[hlClass][1]; }       hlTexts.push("<div id='oHL_info_class_name'><input oHLclass='" + hlClass                     + "' type='checkbox' checked> " + hlText                     + " <div id='oHL_info_class_desc'>"                     + hlDescription + " "); }   const hlTextsCombined = hlTexts.join(" "); $("#oHL_info_class").html(hlTextsCombined); $("#oHL_info_class input").change(toggleResult);

// Adjust arrows being grayed out const finalIndex = parseInt($("#oHL_info_total").text) - 1; if (offset == -1) { // left if (index == finalIndex) { // we were at the end $("#oHL_info_right_arrow").removeClass("oHL_arrow_disabled"); }       if (nextIndex == 0) { // we hit the beginning $("#oHL_info_left_arrow").addClass("oHL_arrow_disabled"); }   } else { // right if (index == 0) { // we were at the beginning $("#oHL_info_left_arrow").removeClass("oHL_arrow_disabled"); }       if (nextIndex == finalIndex) { // we hit the end $("#oHL_info_right_arrow").addClass("oHL_arrow_disabled"); }	}

$("#oHL_info_counter").text(nextIndex+1); nextHighlight.classList.add("oHL_keyed"); nextHighlight.scrollIntoView; }

var mangleIndex = 0; const mangled = []; var mangleSkipCount = 0; var mangleIdIndex = 0; var mangleIdReuseCount = 0; function mangle(element, attr) { const original = element.getAttribute(attr);

// Empty attribute if (original === "") { return; }

// Don't waste resources on simple attributes // But still do titles since wikilinks duplicate text in them const simpleAttributeRe = /^[\w]+$/; if (attr != "title" && simpleAttributeRe.test(original)) { mangleSkipCount++; return; }

// We add a comma for the index number to avoid hitting a rule later const placeholder = "mangle" + mangleIndex.toLocaleString("en-US"); mangleIndex++; if (attr == "id") { element.setAttribute("id", placeholder); } else { // We change the attribute name so imgs aren't reloaded as 404s element.setAttribute("hs-" + attr, placeholder); element.removeAttribute(attr); }

/*    * Id lookups are fast so let's reuse them or add our own * Ideally we could just cache element references, but we rewrite * the HTML, invalidating them */   let targetId; if (element.id !== "") { targetId = element.id; mangleIdReuseCount++; } else { targetId = "mangleId" + mangleIdIndex++; element.id = targetId; }

mangled.push({"selector": targetId, "attr": attr, "value": original}); }

function unmangle(original) { const element = document.getElementById(original.selector); if (element === null) { console.warn("highlightStrings.js: " + original.selector + " doesn't exist!"); return; }   element.setAttribute(original.attr, original.value); }

var detachIndex = 0; const detached = {}; function detachTemp { const placeholder = "_hsdetach" + detachIndex++;

const newElement = document.createElement("span"); newElement.id = placeholder; this.parentNode.insertBefore(newElement, this);

this.remove; detached[placeholder] = this; }

function reattachTemp { for (const [target, html] of Object.entries(detached)) { const element = document.getElementById(target); if (element == null) { console.warn("highlightStrings.js: Could not reattach " + target			            + " (" + html + "), either because element was broken or because it's"			            + " a child of another detached element."); continue; }       element.parentNode.insertBefore(html, element.nextSibling); // insertAfter } }

function whitelist { for (const selector of filterList) { const element = $(selector); if (element.hasClass("oHL_added")) { $(element).remove; continue; }       $(element).removeClass("oHL oHL-opt"); }

// Bolded letter in Further reading and Sources sections $("#Further_reading, #Sources").parent.nextUntil("h2").find(".oHL-bolded-letter").removeClass("oHL");

// Non-English text $("span[lang] .oHL, bdi[lang] .oHL").removeClass("oHL");

$(".infobox center .oHL").removeClass("oHL"); //, .infobox th .oHL

// Code and syntax highlighting $("pre .oHL, pre .oHL-opt").removeClass("oHL oHL-opt");

$(".latitude .oHL, .longitude .oHL,"     +" .mw-kartographer-attribution .oHL").removeClass("oHL");

// Bibcode $("a[href*='adsabs.harvard.edu']").children(".oHL-two-dots, .oHL-unspaced-ellipsis").removeClass("oHL oHL-opt");

// Infobox book LC Class $(".infobox a[href$='LCC_(identifier)']").parent.next.children(".oHL").removeClass("oHL");

// Breaks before stub templates const breakElement = $(".stub").first.prev("p"); if (breakElement.length && breakElement.find("br").length       && breakElement.find(".oHL").length) { breakElement.remove; }   // Handle cases where dates are followed by refs and then a closing parenthesis $(".oHL-datecomma + .reference").each(function filterDateCommas {   	let finalRefElement = this;    	let sibling = finalRefElement.nextElementSibling;    	if (sibling == null) { return true; }        while (sibling != null && sibling.classList.contains("reference")) {    	    finalRefElement = sibling;    	    sibling = finalRefElement.nextElementSibling;        }    	const textSibling = finalRefElement.nextSibling;    	if (textSibling != null && textSibling.nodeType == 3 && textSibling.textContent.startsWith(")")) { const oHLelement = this.previousElementSibling; $(oHLelement).removeClass("oHL oHL-opt"); }   });    // Editorial terms in reception sections    // $(".mw-headline").each(function filterReceptionEditorializing { //    const header = $(this).text; //    if (/reception/i.test(header)) { //        $(this).parent.nextUntil("h2").find(".oHL-editorializing").removeClass("oHL"); //        return false; //    }    // }); }

function showImageInfo { // Remove styling on multiple images so size shows $(".tmulti").find(".thumbimage").removeAttr("style"); // Ignore templates $(".ambox img, .stub img, .dmbox img, .navbox img, .mwe-math-element img").addClass("noviewer");

const extensions = ["jpg", "jpeg", "webp", "png", "gif", "tif", "tiff", "svg", "webm"]; $("[typeof^='mw:File'] img").not(".noviewer").each(function getImageInfo {       const displayWidth = $(this).attr("width");        const displayHeight = $(this).attr("height");        const originalWidth = $(this).attr("data-file-width");        const originalheight = $(this).attr("data-file-height");

let imgAlt = $(this).attr("alt"); // Don't include autogenerated alt text if (imgAlt?.includes(".")) { const imgAltlower = imgAlt.toLowerCase; for (const ext of extensions) { if (imgAltlower.endsWith(ext)) { imgAlt = null; break; }           }        }

matchDescriptions["oHL-dupe-alt"] = ['Duplicate ALT text', "Alternative text for images should not repeat the caption. (MOS:ALT)"]; const imgCaption = $(this).closest(".mw-file-description").siblings("figcaption").text; let filenameMatch = $(this).parent(".mw-file-description").attr("href")?.match(/(Image|File):(.*)\.(jpe?g|webp|png|gif|tiff?|svg|webm)$/i); let filename; if (filenameMatch && filenameMatch.length >= 3) { filename = filenameMatch[2].replaceAll("_", " "); }       if ((imgAlt && imgCaption && imgAlt.toLowerCase == imgCaption.toLowerCase)            || (imgAlt && filename && imgAlt.toLowerCase == filename.toLowerCase)) { imgAlt = " " + imgAlt + " "; }

let displayMessage = "<span class='oHL_img_info_dimensions'>" + displayWidth + "×" + displayHeight + " ("                            + originalWidth + "×" + originalheight + ") "; if (imgAlt) { displayMessage += " (Alt: `" + imgAlt + "`)"; }       $(this).parent.after("<p class='oHL_img_info'>" + displayMessage + " "); }); }

function showPiped { // Ignore ISBN labels $(".oHL_wikilink[href^='/wiki/ISBN_(identifier)']").addClass("oHL_ISBN_pre");

$(".navbox a, .sidebar a, .infobox th a, .infobox b a, .oHL_ISBN,"      + " .oHL_ISBN_pre, sup a, #disambigbox a, .stub a, .ambox a, .portalbox a,"      + " .cs1-visible-error a, .cs1-maint a").addClass("oHL_no_pipe");

// Note: doesn't handle redlinks $(".oHL_wikilink:not(.oHL_no_pipe)").each(function getPipeInfo {       const text = this.textContent;        const target = this.getAttribute("title");

if (target && this.textContent !== ""           && text.toLowerCase !== target.toLowerCase) { const pipedName = document.createElement("span"); pipedName.classList.add("oHL_piped");

const smallText = document.createElement("small"); smallText.textContent = target; pipedName.appendChild(smallText);

const bigPipe = document.createElement("span"); bigPipe.textContent = "|"; bigPipe.classList.add("oHL_piped-pipe"); pipedName.appendChild(bigPipe);

this.insertAdjacentElement("afterbegin", pipedName); }   }); }

function checkSectionOrder { // First, build a list of all section ids const sections = []; $("h2 > .mw-headline").each(function getSectionIds {       sections.push($(this).attr("id"));    });

const len = sections.length; if (len < 2) { return; } // Too few sections

// Make sure "External links" section is last matchDescriptions["oHL-nonfinal-ext"] = ["Non-final External links section", "The External links section should be the last subsection. (MOS:LAYOUT)"]; if (sections[len-1] !== "External_links") { $("#External_links").after(" <span class='oHL oHL-nonfinal-ext oHL_added'>[Move section last↓] "); }

// "See also" section last matchDescriptions["oHL-misplaced-seeAlso"] = ["Misplaced See also", "The See also section should be in the proper order of sections. (MOS:LAYOUT)"]; if (sections[len-1] === "See_also") { $("#See_also").after(" <span class='oHL oHL-misplaced-seeAlso oHL_added'>[Move section up↑] "); return; }

const endSections = ["References", "Sources", "Notes", "Explanatory_notes", "Footnotes", "Bibliography", "Notes_and_references", "Citations"];

for (let i=0; i<len-1; i++) { if (sections[i] === "See_also") { if (!endSections.includes(sections[i+1])) { $("#See_also").after(" <span class='oHL oHL-misplaced-seeAlso oHL_added'>[Move section↕] "); }

break; }   }

// Further reading not after References matchDescriptions["oHL-misplaced-furtherReading"] = ["Misplaced Further reading", "The Further reading section should go after the References section. (MOS:LAYOUT)"]; for (let i=1; i<len-1; i++) { if (sections[i] === "Further_reading") { if (!endSections.includes(sections[i-1])) { $("#Further_reading").after(" <span class='oHL oHL-misplaced-furtherReading oHL_added'>[Move section↕] "); }

break; }   } }

function checkOverlinking { const allWikilinks = $(".oHL_wikilink").toArray; const ignoreLinks = $(".infobox .oHL_wikilink, .navbox .oHL_wikilink,"                         + " table .oHL_wikilink,"                          + " .sidebar .oHL_wikilink,"                          + " .quotebox .oHL_wikilink,"                          + " .thumbcaption .oHL_wikilink,"                          + " figcaption .oHL_wikilink,"                          + " .gallery .oHL_wikilink,"                          + " .quotebox .oHL_wikilink,"                          + " .hatnote .oHL_wikilink,"                          + " .succession-box .oHL_wikilink,"                          + " .mw-references-wrap .oHL_wikilink,"                          + " .listen .oHL_wikilink,"                          + " .spoken-wikipedia .oHL_wikilink,"                          + " .mw-ext-score .oHL_wikilink,"                          + " .mw-tmh-player .oHL_wikilink").toArray; const seeAlsoLinks = $("#See_also").parent.nextUntil("h2").filter("ul").find(".oHL_wikilink").toArray;

// See also links already in body matchDescriptions["oHL-duplicate-seeAlso"] = ["Duplicate See also", "The See also section should not contain wikilinks already in the body. (MOS:NOTSEEALSO)"]; let whitelist = ignoreLinks.concat(seeAlsoLinks); let filteredLinks; if ($("#See_also").length) { filteredLinks = allWikilinks.filter(link => !whitelist.includes(link)); const allHrefs = filteredLinks.map(l => l.getAttribute("href")); for (const link of seeAlsoLinks) { const href = link.getAttribute("href"); if (allHrefs.includes(href)) { $(link).addClass("oHL oHL-duplicate-seeAlso"); }       }    }    // Any links that occur more than once besides the lead let leadMarker; if ($("#toc").length) { leadMarker = $("#toc"); } else { leadMarker = $("#mw-content-text h2").first; }   const leadLinks = leadMarker.prevAll.find(".oHL_wikilink").toArray; whitelist = whitelist.concat(leadLinks); const ignoreSections = refSectionsSelector + ", #Cast, #Filmography, #Discography," + " #Bibliography, #Further_reading, #External_links," + " #Works, #Selected_works"; for (const section of ignoreSections.split(", ")) { const sectionLinks = $(section).parent.nextUntil("h2").find(".oHL_wikilink").toArray; whitelist = whitelist.concat(sectionLinks); }   filteredLinks = allWikilinks.filter(link => !whitelist.includes(link)); const ignoreHrefs = ["/wiki/ISBN_(identifier)", "/wiki/OCLC_(identifier)"]; const linkCounts = new Map; for (const link of filteredLinks) { const href = link.getAttribute("href"); if (ignoreHrefs.includes(href)) { continue; }

const links = linkCounts.get(href); if (typeof links == "undefined") { linkCounts.set(href, [link]); } else { links.push(link); }   }    matchDescriptions["oHL-overlink"] = ["Overlink", "Aside from the lead, the text of an article should generally only contain a link once. (MOS:LINKONCE)"]; for (const [href, links] of linkCounts) { if (links.length < 2) { continue; } for (let i = 1; i < links.length; i++) { $(links[i]).addClass("oHL-opt oHL-overlink"); }   } }

// TODO: Use a blacklist as well function checkTitleItalicization { matchDescriptions["oHL-category-italics"] = ["Category italicization", "Based on its categorization, the page's title might need to be italicized."]; if ($("#firstHeading > i").length > 0) { return; }

let categories = ""; $("#catlinks li a").each(function getCategories {       categories += $(this).text + " ";    });

const works = ["books", "novels", "films", "anime", "Manga series", " plays", "television series", "albums", "paintings", "magazines", "journals", "graphic novels", "sculptures", "cases", "video games", "ships"];

for (const w of works) { if (categories.includes(w)) { $("#mw-content-text").prepend(" <span class='oHL-opt oHL-category-italics oHL_added'>[Italicize title] (" + w + " category) "); return; }   } }

function checkContrast { matchDescriptions["oHL-color-contrast"] = ["Color contrast", "Text must have a minimum contrast (WCAG AA) for accessibility. (MOS:COLOR)"]; $(".infobox td[style], .infobox th[style], .navbox th[style],"     + " .wikitable td[style], .wikitable th[style]").each(function checkTemplateContrast {    	const style = this.style.cssText;    	if (/(background:|background-color:|color:|#\w)/.test(style)) {    		checkElementContrast(this);    	}    }); $(".quotebox").each(function checkQuoteboxContrast {   	checkElementContrast(this);    }); }

function checkElementContrast(element) { // e.g. "rgb(6, 69, 173)" => [6, 69, 173] // can also have alpha, e.g. "rgba(0, 0, 0, 0)" const parseColorString = s => { const m = s.match(/rgba?\(([0-9]+), ([0-9]+), ([0-9]+)/);		return [m[1], m[2], m[3]];	};

const textColorString = window.getComputedStyle(element).color; const bgColorString = window.getComputedStyle(element)["background-color"]; const textColor = parseColorString(textColorString); const bgColor = parseColorString(bgColorString); // Want at least 4.5:1 ratio for WCAG AA   // Reference: https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html const contrast = calculateContrastRatio(textColor, bgColor); const decToHex = d => Number(d).toString(16).padStart(2, '0'); if (contrast < 4.5) { const textColorHex = textColor.map(decToHex).join(""); const bgColorHex = bgColor.map(decToHex).join(""); $(element).addClass("oHL oHL-color-contrast"); $(element).append(" <a class='oHL_added external' href='https://webaim.org/resources/contrastchecker/?fcolor="   	                  + textColorHex + "&bcolor=" + bgColorHex + "'>[AIM]</a>"); } }

// Reference: https://stackoverflow.com/questions/596216/formula-to-determine-perceived-brightness-of-rgb-color/ function calculateContrastRatio(c1RGB, c2RGB) { let c1Luminance = rgbToLuminance(c1RGB); let c2Luminance = rgbToLuminance(c2RGB);

if (c1Luminance < c2Luminance) { // want lighter first [c1Luminance, c2Luminance] = [c2Luminance, c1Luminance]; }   const ratio = (c1Luminance + 0.05) / (c2Luminance + 0.05); return ratio; }

function rgbToLuminance(RGB) { // Convert integers to decimal const RGBdec = RGB.map(i => i / 255);

// Convert to linear value const RGBtoLinear = RGB => { if (RGB <= 0.04045) { return RGB / 12.92; } else { return Math.pow(((RGB + 0.055) / 1.055), 2.4); }    };    const RGBlinear = RGBdec.map(RGBtoLinear); // Find luminance const luminance = 0.2126 * RGBlinear[0] + 0.7152 * RGBlinear[1] + 0.0722 * RGBlinear[2]; // Convert to perceived lightness let perceivedLuminance; if (luminance <= (216/24389)) { perceivedLuminance = luminance * (24389/27); } else { perceivedLuminance = Math.pow(luminance, (1/3)) * 116 - 16; }

return perceivedLuminance; }

// If we're not reading an article, do nothing if (mw.config.get("wgAction") === "view"     && mw.config.get("wgIsArticle")      && !mw.config.get("wgIsMainPage")) { $(mw.util.addPortletLink("p-tb", "#", "Highlight strings", "hStrings", "Highlight errors", "h")).click(highlightStrings); }

//