User:Shubinator/DYKcheck.js

// ***************************************************************** ////                          DYKcheck tool                            ////                           Version 1.1                             //// For quick installation, add                                       //// importScript('User:Shubinator/DYKcheck.js');                      //// to your vector.js                                                 //// See [[User:Shubinator/DYKcheck]] for more info, including         //// configurable options and how to use the tool without installation //// or logging in.                                                    //// First version written by Shubinator in February 2009              //// ***************************************************************** //mw.loader.using(['mediawiki.api', 'mediawiki.util'], function () {"use strict";var onTTDYK, nextSection, urlJump, sections, currentTitle, partsProcessing,articleTitles, dates, nom5x;// Configurable optionsvardateFormat = window.dateFormat,unlock = window.unlock,hookLengthYellow = window.hookLengthYellow || 200,hookLengthRed = window.hookLengthRed || 220,check5xNoms = window.check5xNoms || "ifnom5x",fixedSidebar = window.fixedSidebar || "onttydk";var mwConfig = mw.config.get(["wgAction","wgTitle","wgPageName","wgUserName","wgNamespaceNumber","skin"]);var api = new mw.Api();// Polyfill String.prototype.includes so that we don't have to write "!== -1" all over// the codebase.if (!String.prototype.includes) {  String.prototype.includes = function() {    return String.prototype.indexOf.apply(this, arguments) !== -1;  };}function escapeHtml(s) {// Use the browser's built-in ability to escape HTML.var div = document.createElement('div');div.appendChild(document.createTextNode(s));return div.innerHTML;}function scanArticle(title, output, html) {// the meat of the DYKcheck tool// calculates prose size of the given html// checks for inline citations and stub templates in the given html// passes info to checkTalk(), getFirstRevision(), checkMove(), and checkExpansion()if (!onTTDYK || (check5xNoms === "always") || (check5xNoms === "ifnom5x" && nom5x)) {partsProcessing = new Array(4);} else {partsProcessing = new Array(3);}dates = new Array(4);var proseDisp = document.createElement("li");proseDisp.id = "dyk-prose";output.appendChild(proseDisp);// calculate prose sizevar prose = calculateProse(html, true);var pList = html.getElementsByTagName("p");var word_count = 0;for (var iPara = 0;iPara < pList.length; iPara++) {var para = pList[iPara];if (para.parentNode.parentNode === html || para.parentNode.parentNode.parentNode.id === getBodyId()) {word_count += para.innerHTML.replace(/(<([^>]+)>)/ig,"").split(' ').length;}}proseDisp.innerHTML='<b>Prose size (text only): </b>' + escapeHtml(prose) + '&nbsp;characters (' +escapeHtml(word_count) + ' words) "readable prose size"';if (prose < 1500) {proseDisp.style.cssText = "background-color:pink";}// check for inline citationsif (!html.innerHTML.includes('id="cite_ref-') && !html.innerHTML.includes('id=cite_ref-')) {var noref = document.createElement("li");noref.id = "no-ref";output.appendChild(noref);noref.innerHTML = 'No inline citations';noref.style.cssText = "background-color:pink";}// check if article is stub or if it has appeared in DYK or ITNif (html.innerHTML.includes('id="stub"') || html.innerHTML.includes('id=stub') || html.innerHTML.includes('metadata plainlinks stub')) {var stubAlert = document.createElement("li");stubAlert.id = "stub-alert";output.appendChild(stubAlert);stubAlert.innerHTML = 'Article is classified as a stub';stubAlert.style.cssText = "background-color:yellow";}checkTalk(title, output); //check talk page// check for various tagsvar alertColor = "yellow";var imageList = new Array("Text_document_with_red_question_mark.svg", "Question book-new.svg", "Ambox content.png", "Ambox style.png", "Imbox style.png", "Copyright-problem.svg", "Copyright-problem paste.svg", "Ambox globe content.svg", "Unbalanced scales.svg", "Ambox scales.svg", "Ambox_contradict.svg", "Ambox warning orange.svg", "Acap.svg");var tagList = new Array("unverified content", "insufficient citations", "dispute", "cleanup", "cleanup","copyright violations", "copyright violations", "globalization", "neutrality", "neutrality", "contradiction", "dispute", "copyedit");var tagsFound = false;var tagAlert = document.createElement("li");if (html.innerHTML.includes("This article is being considered for deletion in accordance with Wikipedia's")) {tagsFound = true;var afdIndex = html.innerHTML.indexOf('title="Wikipedia:Articles for deletion/') + 7;var afdLink = html.innerHTML.substring(afdIndex, html.innerHTML.indexOf('"', afdIndex));var afdBoldTag = document.createElement("b");var afdLinkTag = document.createElement("a");afdLinkTag.setAttribute("href", "//en.wikipedia.org/wiki/" + afdLink);afdLinkTag.appendChild(document.createTextNode("nominated for deletion"));afdBoldTag.appendChild(afdLinkTag);tagAlert.appendChild(document.createTextNode("Article has been "));tagAlert.appendChild(afdBoldTag);tagAlert.appendChild(document.createTextNode(". "));alertColor = "pink";} else if ((html.innerHTML.toLowerCase().includes('<table class="plainlinks ombox ombox-speedy"')) || (html.innerHTML.toLowerCase().includes('<table class="plainlinks ambox ambox-speedy"')) || (html.innerHTML.toLowerCase().includes('<table class="metadata plainlinks ombox ombox-speedy"')) ||(html.innerHTML.toLowerCase().includes('<table class="metadata plainlinks ambox ambox-speedy"'))) {tagsFound = true;tagAlert.appendChild("Article has been <b>tagged for speedy deletion</b>. ");alertColor = "pink";}for (var iImage = 0; iImage < imageList.length; iImage++) {if (html.innerHTML.includes(imageList[iImage])) {tagsFound = true;tagAlert.appendChild(document.createTextNode("Article has a "));tagAlert.appendChild(document.createTextNode(tagList[iImage]));tagAlert.appendChild(document.createTextNode(" tag. "));}}if (tagsFound) {tagAlert.id = "tag-alert";tagAlert.style["background-color"] = alertColor;output.appendChild(tagAlert);}// find creator of article and dategetFirstRevision(title, output);// check if the article has been moved from userspace within last 100 editsif (mwConfig.wgNamespaceNumber !== 2) {checkMove(title, output);} else {partsProcessing[2] = true;}// check for expansion start date, assuming now expanded to 5x (last 500 edits)if (!onTTDYK || (check5xNoms === "always") || (check5xNoms === "ifnom5x" && nom5x)) {checkExpansion(title, output, prose);}}function checkDocument() {// prepares for scan and passes info to scanArticle()onTTDYK = false;if (document.getElementById("dyk-stats-0")) {clearStats();} else {var output = document.createElement("ul");output.id = "dyk-stats-0";var body = getBody();var dummy = body.getElementsByTagName("div")[0];if (dummy.nextSibling && dummy.nextSibling.id === 'siteNotice') { // if siteNotice is below siteSubdummy = dummy.nextSibling;} else if (dummy.nextSibling.nextSibling && dummy.nextSibling.nextSibling.id === 'siteNotice') {dummy = dummy.nextSibling.nextSibling;}dummy.parentNode.insertBefore(output, dummy.nextSibling);createHeaderAndProcessing(output);currentTitle = 0;var title = mwConfig.wgTitle;if (mwConfig.wgNamespaceNumber === 2) {title = "User:" + title;}scanArticle(title, output, body);}}function checkTTDYK() {// finds the current nomination// can jump to a section if it shows up in the URL// (i.e. http://en.wikipedia.org/wiki/T:TDYK#Older_nominations)// prepares for scan and passes info to checkHooks() and scanArticle() (through pit stop)onTTDYK = true;if (!sections) {sections = document.getElementsByTagName("h4");nextSection = getFirstNom();}// Jumping codeif (window.location.hash) {var sectionAt = window.location.hash;if (sectionAt !== urlJump) {var jump = document.getElementById(sectionAt.substring(1, sectionAt.length));var next = jump.parentNode;while (next.nodeName.toLowerCase() !== "h4") {next = next.nextSibling;}for (var iSection = 0; iSection < sections.length; iSection++) {if (sections[iSection] === next) {nextSection = iSection;urlJump = sectionAt;break;}}}}if (nextSection === sections.length) {alert("Reached end of nominations; looping to beginning");nextSection = getFirstNom();}if (document.getElementById("dyk-stats-0")) {clearStats();}var firstOutput = document.createElement("ul");firstOutput.id = "dyk-stats-0";sections[nextSection].parentNode.insertBefore(firstOutput, sections[nextSection]);var hook = checkHooks(firstOutput);if (!hook) {var hookErrorDisp = document.createElement("div");hookErrorDisp.id = "error-disp";hookErrorDisp.style.cssText = 'color:red; font-weight:bold;';hookErrorDisp.innerHTML = 'Error: Hook is not formatted correctly';firstOutput.parentNode.insertBefore(hookErrorDisp, firstOutput);nextSection++;return;}createHeaderAndProcessing(document.getElementById("dyk-stats-0"));var tempHolder = document.createElement("div");tempHolder.id = "temp-holder";tempHolder.innerHTML = hook;var bolded = tempHolder.getElementsByTagName("b");var articleTitlesTemp = new Array(bolded.length);var titlesCounter = 0;for (var iBolded = 0; iBolded < bolded.length; iBolded++) {var links = bolded[iBolded].getElementsByTagName("a");if (links.length > 0) {for (var iLink = 0; iLink < links.length; iLink++) {var linkTitle = links[iLink].getAttribute("title");if (!linkTitle) linkTitle = links[iLink].innerHTML; // links that aren't pipedarticleTitlesTemp[titlesCounter] = linkTitle;titlesCounter++;}} else {var pointer = bolded[iBolded];while (pointer !== tempHolder) {if (pointer.nodeName.toLowerCase() === "a") {var pointerTitle = pointer.getAttribute("title");if (!pointerTitle) pointerTitle = pointer.innerHTML; // links that aren't pipedarticleTitlesTemp[titlesCounter] = pointerTitle;titlesCounter++;}pointer = pointer.parentNode;}}}currentTitle = 0;articleTitles = new Array(titlesCounter);var hookOutput = document.getElementById("hook-container");for (var i = 0; i < titlesCounter; i++) {var output;if (i === 0) {output = firstOutput;} else {output = document.createElement("ul");output.id = "dyk-stats-" + i;hookOutput.parentNode.insertBefore(output, hookOutput);}var articleDisp = document.createElement("li");articleDisp.id = "article-title" + i;output.appendChild(articleDisp);articleDisp.innerHTML = '<b>Article ' + escapeHtml(i+1) + ':</b> ' + escapeHtml(articleTitlesTemp[i]);articleTitles[i] = articleTitlesTemp[i];}if (titlesCounter === 1) {document.getElementById("article-title0").innerHTML = '<b>Article:</b> ' + escapeHtml(articleTitles[0]);} else if (titlesCounter === 0) {if (document.getElementById("dyk-processing")) {var processing = document.getElementById("dyk-processing");processing.parentNode.removeChild(processing);}var boldErrorDisp = document.createElement("div");boldErrorDisp.id = "error-disp";boldErrorDisp.style.cssText = 'color:red; font-weight:bold;';boldErrorDisp.innerHTML = document.createTextNode('Error: The nominated article must appear in bold');sections[nextSection].parentNode.insertBefore(boldErrorDisp, firstOutput);nextSection++;return;}nextSection++;checkTitle(articleTitles[0], firstOutput, 0);}function checkHooks(output) {// gets the nomination section (complete with comments, etc) and passes this to helper function// returns the last suggested hook so the parent method can find article titlesvar hookOutput = document.createElement("ul");hookOutput.id = "hook-container";output.parentNode.insertBefore(hookOutput, output.nextSibling);var bodyHTML = getBody().innerHTML;var thisSection;if (nextSection !== sections.length - 1) {thisSection = bodyHTML.substring(bodyHTML.indexOf(sections[nextSection].innerHTML) + sections[nextSection].innerHTML.length, bodyHTML.indexOf(sections[nextSection+1].innerHTML));} else {thisSection = bodyHTML.substring(bodyHTML.indexOf(sections[nextSection].innerHTML) + sections[nextSection].innerHTML.length, bodyHTML.indexOf('NewPP limit report'));}thisSection = thisSection.replace('that,', 'that ').replace('...that ', '... that ');if (thisSection.includes("5x expan")) {nom5x = true;} else {nom5x = false;}return checkHooksHelper(hookOutput, thisSection, 0);}function checkHooksHelper(hookOutput, whatsLeft, num) {// recursively finds proposed hooks for a nom// identifies hooks starting with " ... that " and ending with "?"// does not count "... " or "(pictured)" in hook character countvar hook;var questionIndex = whatsLeft.indexOf("?");var whatsLeftLowerCase = whatsLeft.toLowerCase();while ((whatsLeftLowerCase.indexOf("<a ", questionIndex) > whatsLeftLowerCase.indexOf("</a>", questionIndex)) || ((!whatsLeftLowerCase.includes("<a ", questionIndex)) && (whatsLeftLowerCase.includes("</a>", questionIndex))) ||(whatsLeftLowerCase.indexOf("<i>", questionIndex) > whatsLeftLowerCase.indexOf("</i>", questionIndex)) || ((!whatsLeftLowerCase.includes("<i>", questionIndex)) && (whatsLeftLowerCase.includes("</i>", questionIndex)))) {questionIndex = whatsLeft.indexOf("?", questionIndex + 1);}if (whatsLeft.includes("... that ") && questionIndex !== -1) {if (whatsLeft.indexOf("... that ") < questionIndex) {hook = whatsLeft.substring(whatsLeft.indexOf("... that ") + 4, questionIndex + 1);var hookTemp = document.createElement("div");hookTemp.id = "hook-temp";hookTemp.innerHTML = "<p>" + hook + "</p>";var hookLength = calculateProse(hookTemp, false);if (hookTemp.innerHTML.includes("pictured)") || hookTemp.innerHTML.includes("(pictured")) {hookLength = hookLength - 10;}var hookDisp = document.createElement("li");hookDisp.id = "hooks-" + num;if (num === 0) {hookDisp.innerHTML = '<b>Original Hook:</b> ' + escapeHtml(hookLength) + ' characters';} else {hookDisp.innerHTML = '<b>Alternate Hook ' + escapeHtml(num) +'</b>: ' +escapeHtml(hookLength) + ' characters';}if (hookLength > hookLengthRed) {hookDisp.style.cssText = 'background-color:pink';} else if (hookLength > hookLengthYellow) {hookDisp.style.cssText = 'background-color:yellow';}hookOutput.appendChild(hookDisp);num = num + 1;}var parsed = whatsLeft.substring(questionIndex + 1, whatsLeft.length - 1);var lastHook = checkHooksHelper(hookOutput, parsed, num);if (!lastHook && hook) {lastHook = hook;}return lastHook;}return;}function checkTitle(title, output, i) {// gets the given title from Wikipedia's server and passes it to scanArticle(),// resolving any redirects.var promise = api.get({format: 'json',action: 'parse',page: title,redirects: true,prop: 'text'});promise.done(function (obj) {var ttdykTemp = document.createElement("div");ttdykTemp.id = "ttdyk-temp" + i;ttdykTemp.innerHTML = obj.parse.text["*"];title = obj.parse.title; // Get the new title if we were redirectedscanArticle(title, output, ttdykTemp);});promise.fail(function () {alert("API error");});}function clearStats() {// if scan results already exist, turn them off and remove highlightingif (!onTTDYK) {var oldStyle = document.getElementById("dyk-stats-0").className;var mainContent = getBody();var pList = mainContent.getElementsByTagName("p");for (var iPara = 0; iPara < pList.length; iPara++) {if (pList[iPara].parentNode === mainContent || pList[iPara].parentNode.parentNode === mainContent) {pList[iPara].style.cssText = oldStyle;}}}if (document.getElementById("error-disp")) {var errorDisp = document.getElementById("error-disp");errorDisp.parentNode.removeChild(errorDisp);}var iStat = 0;while (document.getElementById("dyk-stats-" + iStat)) {var output = document.getElementById("dyk-stats-" + iStat);output.parentNode.removeChild(output);iStat++;}if (document.getElementById("hook-container")) {var hookOutput = document.getElementById("hook-container");hookOutput.parentNode.removeChild(hookOutput);}if (document.getElementById("dyk-header")) {var header = document.getElementById("dyk-header");header.parentNode.removeChild(header);}if (document.getElementById("dyk-processing")) {var processing = document.getElementById("dyk-processing");processing.parentNode.removeChild(processing);}}function calculateProse(doc, visible) {// calculates the prose of a given document// this function and its helper below are modified versions of// the prosesize tool (http://en.wikipedia.org/wiki/User:Dr_pda/prosesize.js)var pList = doc.getElementsByTagName("p");var prose_size = 0;var i = 0;if (mwConfig.wgAction === 'submit' && visible) i = 1; // Avoid the "Remember that this is only a preview" textfor (; i < pList.length; i++) {if (pList[i].parentNode.parentNode === doc || pList[i].parentNode.parentNode.parentNode.id === getBodyId()) {prose_size += getReadable(pList[i], visible);if (!onTTDYK && visible) {pList[i].style.cssText = 'background-color:yellow';}}}return prose_size;}function getReadable(id, visible) {// helper method for calculateProse()var textReadable = 0;for (var i = 0; i < id.childNodes.length; i++) {if (id.childNodes[i].nodeName === '#text') {textReadable += id.childNodes[i].nodeValue.length;} else if (id.childNodes[i].className !== 'reference' && !(id.childNodes[i].className && id.childNodes[i].className.includes('emplate')) &&id.childNodes[i].id !== 'coordinates') {textReadable += getReadable(id.childNodes[i], visible);} else if (visible) { // if it's an inline maintenance tag (like [citation needed]) or geocoordinatesif (document.getElementById("dyk-stats-0").className) {id.childNodes[i].style.cssText = document.getElementById("dyk-stats-0").className;} else {id.childNodes[i].style.cssText = 'background-color:white';}}}return textReadable;}function checkExpansion(title, output, current) {  // finds the start of expansion date (last 500 edits)// gets the last 500 unique revision ids for past revisions of the article and passes to helper functionvar promise = getRevisions({titles: title,rvlimit: 500,rvprop: ['ids', 'timestamp', 'sha1'],rvdir: 'older'});promise.done(function (revisionsWithDeletedRevs) {var revisions = revisionsWithDeletedRevs.filter(revision => 'sha1' in revision);var expandTemp = document.createElement("div");expandTemp.id = "expand-temp";checkExpansionHelper(title, current, output, expandTemp, revisions, 0, revisions.length-1, -1);});}function checkExpansionHelper(title, current, output, expandTemp, revisions, min, max, expandIndex) {// helper for expansion check, used recursively// searches for start of expansion date using a binary search algorithm// assumes the article has been more or less increasing in size all the time// if the article length has yoyo-ed, this function won't give accurate resultsvar mid = Math.ceil((max + min)/2);if ((((max - min) < 2) && (max !== revisions.length - 1 || expandIndex !== -1)) || expandIndex === -2) {var expandResult = document.createElement("li");expandResult.id = "expand-result";output.appendChild(expandResult);if (expandIndex < 0) {if (revisions.length === 500) {expandResult.innerHTML = escapeHtml('Article has not been expanded 5x in the last 500 edits');} else {expandResult.innerHTML = escapeHtml('Article has not been expanded 5x since it was created');}} else {var date = revisions[expandIndex-1].timestamp;expandResult.innerHTML = 'Assuming article is at 5x now, expansion began ' + escapeHtml(expandIndex) + ' edits ago on ' + escapeHtml(toNormalDate(date.substring(0,10)));dates[2] = toDateObject(date);}partsProcessing[3] = true;doneProcessing();return;} else if ((max - min) < 2 && max === revisions.length - 1) {expandIndex = -2;}var promise = api.get({format: 'json',action: 'parse',oldid: revisions[mid].revid,prop: 'text'});promise.done(function (obj) {expandTemp.innerHTML = obj.parse.text['*'];var prose = calculateProse(expandTemp, false);// alert("Prose: " + prose + " 1x: " + current/5 + " Mid: " + mid + " Expand index: " + expandIndex); // use above line to debug the expansion checkif (prose < (current/5.0)) {if ((expandIndex > mid) || (expandIndex < 0)) {expandIndex = mid;}checkExpansionHelper(title, current, output, expandTemp, revisions, min, mid, expandIndex);} else {checkExpansionHelper(title, current, output, expandTemp, revisions, mid, max, expandIndex);}});promise.fail(function () {alert("API error");partsProcessing[3] = true;doneProcessing();});}function checkTalk(title, output) { // checks the talk page of the article for DYK, ITN, or stub templatesif (mwConfig.wgNamespaceNumber !== 2) {title = "Talk:" + title;} else {title = title.replace("User:", "User talk:");}var promise = getRevisions({titles: title,rvprop: 'content'});promise.done(function (revisions) {if (revisions && revisions[0]) {var talkPage = revisions[0]['*'];var result = '';var color = '';if (talkPage.match(/class\s*=\s*[sS]tub/) && (document.getElementById("stub-alert") === null)) {result += 'Article is classified as a stub ';color = 'yellow';}var dyktalkRegexMatches = talkPage.match(/{{\s*[dD](yk|YK\s?)talk[^}]*}}/g);if (dyktalkRegexMatches) {// if there's a DYK tag, try to find the date of previous appearanceresult += 'Article has appeared on Did You Know before ';var dyktalkTag = dyktalkRegexMatches.pop();var firstPipeIndex = dyktalkTag.indexOf('|');var secondPipeIndex = dyktalkTag.indexOf('|', firstPipeIndex + 1);var thirdPipeIndex = dyktalkTag.indexOf('|', secondPipeIndex + 1);if (firstPipeIndex !== -1 && secondPipeIndex !== -1) {if (thirdPipeIndex === -1) {thirdPipeIndex = dyktalkTag.length - 2; // -2 to get rid of the }}}var monthDate = dyktalkTag.substring(firstPipeIndex + 1, secondPipeIndex);var year = dyktalkTag.substring(secondPipeIndex + 1, thirdPipeIndex);var featuredDate = new Date(monthDate + " " + year);if (featuredDate.toString() !== 'Invalid Date') {var month = featuredDate.getMonth() + 1;if (month < 10) {month = '0' + month;}var date = featuredDate.getDate();if (date < 10) {date = '0' + date;}var dateString = toNormalDate(featuredDate.getFullYear() + '-' + month + '-' + date);result = result.substring(0, result.length - 1) + ', on ' + escapeHtml(dateString);}}color = 'pink';} else if (talkPage.match(/rticle[ ]?[hH]istory[\s\S]*dykdate\s*=.*?\S/)) {result += 'Article has appeared on Did You Know before ';color = 'pink';}if (talkPage.match(/{{\s*[iI]TN(\st|t|T)alk/)) { // {{ITNtalk}}, {{ITN talk}}, {{ITNTalk}}result += 'Article has appeared on In The News before ';color = 'pink';}if (result) {var talkResult = document.createElement("li");talkResult.id = "talk-result";output.appendChild(talkResult);talkResult.innerHTML = result;if (color) {talkResult.style["background-color"] = color;}}checkTalkForGoodArticleStatus(talkPage, output);}partsProcessing[0] = true;doneProcessing();});promise.fail(function () {partsProcessing[0] = true;doneProcessing();});}function checkTalkForGoodArticleStatus(talkPage, output) {// Test cases:// Cathedral of the Immaculate Conception (Moscow) - ArticleHistory, two GANs (last successful), currently a featured article// LoveGame - ArticleHistory, one successful GAN, no GARs// Paparazzi (Lady Gaga song) - ArticleHistory, one successful GAN, unlisted after a GAR, another successful GAN, no-op GAR// Curtis (50 Cent album) - ArticleHistory, one successful GAN, unlisted after a GAR// G.U.Y. - ArticleHistory with unconventional formatting// Arthur Adams (comics) - ArticleHistory with unconventional formatting// Blackburn Firebrand - {{GA}}// Tony Hawk's Underground - {{GA}} with confounding {{Game}} tagvar gaDate = '';var gaRegexMatches = talkPage.match(/{{\s*[gG][aA]\s*[|][^}]*}}/g);if (gaRegexMatches) {// if there's a GA tag, try to find the date of promotion to Good Articlevar gaTag = gaRegexMatches.pop();var firstPipeIndex = gaTag.indexOf('|');var secondPipeIndex = gaTag.indexOf('|', firstPipeIndex + 1);if (firstPipeIndex !== -1) {if (secondPipeIndex === -1) {secondPipeIndex = gaTag.length - 2; // -2 to get rid of the }}}gaDate = gaTag.substring(firstPipeIndex + 1, secondPipeIndex);}} else if (talkPage.match(/rticle[ ]?[hH]istory/)) {// check ArticleHistory tag for Good Article status// grab last GAN action// figure out action number// given action number, was action result "listed"?// if no, stop here - article is not a Good Article// if yes, grab the Good Article promotion date from actionXdatevar ganMatches = talkPage.match(/action[0123456789]+\s*=\s*(gan|GAN)/g);if (ganMatches) {var lastGanAction = ganMatches.pop();var lastGanActionNumber = lastGanAction.substring(6, lastGanAction.indexOf('=')); // remove 'action' and everything at and after the equals signlastGanActionNumber = lastGanActionNumber.trim();var ganResultIndex = talkPage.indexOf('action' + lastGanActionNumber +  'result');var ganResult = talkPage.substring(talkPage.indexOf('=', ganResultIndex) + 1, talkPage.indexOf('|', ganResultIndex)).trim();if (ganResult === 'listed' || ganResult === 'Listed' || ganResult === 'passed' || ganResult === 'Passed') {var ganDateIndex = talkPage.indexOf('action' + lastGanActionNumber +  'date');gaDate = talkPage.substring(talkPage.indexOf('=', ganDateIndex) + 1, talkPage.indexOf('|', ganDateIndex)).trim();}// then grab last GAR action// figure out action number// is GAR action number after GAN action number? if yes, carry on// given action number, was action result "not listed"?// if yes, article is not a Good Articlevar garMatches = talkPage.match(/action[0123456789]+\s*=\s*(gar|GAR)/g);if (garMatches) {var lastGarAction = garMatches.pop();var lastGarActionNumber = lastGarAction.substring(6, lastGarAction.indexOf('=')); // remove 'action' and everything at and after the equals signlastGarActionNumber = lastGarActionNumber.trim();if (parseInt(lastGarActionNumber) > parseInt(lastGanActionNumber)) {var garResultIndex = talkPage.indexOf('action' + lastGarActionNumber +  'result');var garResult = talkPage.substring(talkPage.indexOf('=', garResultIndex) + 1, talkPage.indexOf('|', garResultIndex)).trim();if (garResult === 'delisted' || garResult === 'Delisted') {gaDate = ''; // Not a Good Article, GAR came after GAN and demoted the article}}}}}if (gaDate) {if (gaDate.length > 6 && gaDate.indexOf(' (UTC)') === gaDate.length - 6) {gaDate = gaDate.substring(0, gaDate.length - 6); // The Boat Race 1997 is not parsing correctly if (UTC) is left in}if (gaDate.length > 5 && gaDate[2] === ':' && gaDate[5] === ',') {gaDate = gaDate.substring(6, gaDate.length).trim(); // Chrome not parsing as expected for strings like "17:21, 26 June 2014"}var gaPromotion = document.createElement("li");gaPromotion.id = "ga-promotion";output.appendChild(gaPromotion);var gaPromotedDate = new Date(gaDate);// ensure the Date object is in the right time zonegaPromotedDate = new Date(Date.UTC(gaPromotedDate.getFullYear(), gaPromotedDate.getMonth(), gaPromotedDate.getDate(), gaPromotedDate.getHours(), gaPromotedDate.getMinutes(), gaPromotedDate.getSeconds()));dates[3] = gaPromotedDate;gaPromotion.innerHTML = 'Article was promoted to Good Article status on ' +escapeHtml(toNormalDate(gaPromotedDate.toISOString()));}}function getRevisions(options) {// Returns a jQuery promise with an array of a title's revisions.// The first parameter is an options object accepting the following API fields:// - titles// - rvlimit// - rvprop// - rvdiroptions = options || {};return api.get({format: 'json',action: 'query',prop: 'revisions',titles: options.titles,rvlimit: options.rvlimit,rvprop: options.rvprop,rvdir: options.rvdir,indexpageids: true}).then(// On successfunction (obj) {var pageId = obj.query.pageids[0];return obj.query.pages[pageId].revisions;},// On failurefunction (err) {alert("API error");return err;});}function getFirstRevision(title, output) {// finds the creator of the article, and the date created// also checks if the article was created as a redirect, finds non-redirect date if sovar created = document.createElement("li");created.id = "creation-info";output.appendChild(created);var promise = getRevisions({titles: title,rvlimit: 4,rvprop: ['timestamp', 'user', 'content'],rvdir: 'newer'});promise.done(function (revisions) {var user = revisions[0].user;var timestamp = revisions[0].timestamp;created.innerHTML='<b>Article created </b> by ' + escapeHtml(user) + ' on ' + escapeHtml(toNormalDate(timestamp.substring(0,10)));dates[0] = toDateObject(timestamp);for (var i = 0; i < revisions.length; i++) {var content = revisions[i]['*'];var isRedirect = content.replace(' ', '').replace(':', '').toUpperCase().includes('#REDIRECT[[');if (isRedirect && i === 0) {created.innerHTML = created.innerHTML + ' as a redirect';} else if (!isRedirect) {if (i !== 0) {var unRedirect = document.createElement("li");unRedirect.id = "expanded-from-redirect";output.appendChild(unRedirect);var urUser = revisions[i].user;var urTimestamp = revisions[i].timestamp;unRedirect.innerHTML = 'Article became a non-redirect on ' + escapeHtml(toNormalDate(urTimestamp.substring(0,10))) +' by ' + escapeHtml(urUser);dates[0] = toDateObject(urTimestamp);}break;}}partsProcessing[1] = true;doneProcessing();});promise.fail(function () {partsProcessing[1] = true;doneProcessing();});}function checkMove(title, output) { //checks the last 100 edits of an article for a move from userspace or AfC to current locationvar promise = getRevisions({titles: title,rvlimit: 100,rvprop: ['flags', 'user', 'timestamp', 'comment'],rvdir: 'older',});promise.done(function (revisions) {for (var i = 0; i < revisions.length; i++) {var comment = revisions[i].comment;var userName = revisions[i].user;if ((revisions[i].minor === "") && comment.match(/moved (page )?((\[\[User:)|(\[\[Draft:)|(\[\[Wikipedia talk:Articles for creation\/))[\s\S]*to \[\[/)) {var movedFrom = comment.substring(comment.indexOf("[[") + 2, comment.indexOf("to [[") - 3);var date = revisions[i].timestamp;var moved = document.createElement("li");moved.id = "moved-userspace";output.appendChild(moved);moved.innerHTML = '<b>Article moved</b> from ' + escapeHtml(movedFrom) + ' on ' + escapeHtml(toNormalDate(date.substring(0,10)));dates[1] = toDateObject(date);break;}}partsProcessing[2] = true;doneProcessing();});promise.fail(function () {partsProcessing[2] = true;doneProcessing();});}function doneProcessing() {// checks if all parts are done processing// if they are, the dates of creation and expansion are checked for within 10 days (rounded down, in nominator's favor)// then the next title (for multiple article noms) is processed (required to combat asynchronous threads)// if there are no more titles left (or not on T:TDYK), the processing message is removedvar titleComplete = true;for (var i = 0; i < partsProcessing.length; i++) {if (!partsProcessing[i]) {titleComplete = false;break;}}if (document.getElementById("dyk-processing") && titleComplete) {var curDate = new Date();var winner = new Date();if (dates[1]) {winner = dates[1]; } else {winner = dates[0];}if (dates[2] > winner) {winner = dates[2];}if (dates[3] > winner) {winner = dates[3];}var dateDifference = Math.floor((curDate.getTime() - winner.getTime())/(1000*60*60*24));if (dateDifference > 10) {var output = document.getElementById("dyk-stats-" + currentTitle);var notRecent = document.createElement("li");notRecent.id = "not-recent";output.appendChild(notRecent);if (!onTTDYK || (check5xNoms === "always") || (check5xNoms === "ifnom5x" && nom5x)) {notRecent.innerHTML = "Article has not been created or expanded 5x or promoted to Good Article within the past 10 days (" + escapeHtml(dateDifference) + " days)" + " <small>DYKcheck does not account for previous versions with " +'<a href="//en.wikipedia.org/wiki/Wikipedia:Split">splits</a> or ' +'<a href="//en.wikipedia.org/wiki/Wikipedia:Copyright_violations">copyright violations</a>.</small>';} else {notRecent.innerHTML = "Article was not created within the past 10 days (" + escapeHtml(dateDifference) + " days)";}notRecent.style.cssText = 'background-color:pink';}if (onTTDYK && currentTitle < (articleTitles.length - 1)) {currentTitle++;checkTitle(articleTitles[currentTitle], document.getElementById("dyk-stats-" + (currentTitle)), currentTitle);} else {var processing = document.getElementById("dyk-processing");processing.parentNode.removeChild(processing);}}}// taken from the prosesize tool (http://en.wikipedia.org/wiki/User:Dr_pda/prosesize.js)function getBodyId() {var contentName;if (mwConfig.skin === 'monobook' || mwConfig.skin === 'chick' || mwConfig.skin === 'mymwConfig.skin' || mwConfig.skin === 'simple') {contentName = 'bodyContent';} else if (mwConfig.skin === 'modern') {contentName = 'mw_contentholder';} else if (mwConfig.skin === 'standard' || mwConfig.skin === 'cologneblue' || mwConfig.skin === 'nostalgia') {contentName = 'article';} else {// fallback case; the above covers all currently existing skinscontentName = 'bodyContent';}// Same for all skins if previewing pageif (mwConfig.wgAction === 'submit') contentName = 'wikiPreview';return contentName;}function getBody() { // gets the HTML body of the page// taken from the prosesize tool (http://en.wikipedia.org/wiki/User:Dr_pda/prosesize.js)return document.getElementById(getBodyId());}function getFirstNom() {var firstNom = 0;// Find the first "Articles created/expanded on ..." h3 sectionvar elementIterator = sections[firstNom].previousSibling;while (elementIterator.nodeName.toLowerCase() !== "h3") {elementIterator = elementIterator.previousSibling;}while (!(elementIterator.nodeName.toLowerCase() === "h3" && elementIterator.innerHTML.includes("Articles created/expanded on"))) {if (elementIterator.nodeName.toLowerCase() === "h4") {firstNom++;}elementIterator = elementIterator.nextSibling;}return firstNom;}function createHeaderAndProcessing(output) { // makes the header above the scan resultsvar header = document.createElement("span");header.id = "dyk-header";header.innerHTML = '<br /><b>DYK eligibility scan results: <small><i>(See <a href="//en.wikipedia.org/wiki/' + 'User:Shubinator/DYKcheck">here</a> for details.)</i></small></b>';output.parentNode.insertBefore(header,output);var processing = document.createElement("span");processing.id = "dyk-processing";processing.innerHTML = '<br /><b><font color="crimson"> Processing... </font></b>';output.parentNode.insertBefore(processing, header);}function toNormalDate(utc) { // converts the date part of a Wikipedia timestamp to a readable datevar months = new Array("blank","January","February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December");if (dateFormat === "british") {return (utc.substring(8,10) * 1) + ' ' + months[utc.substring(5,7) * 1] + ' ' + utc.substring(0,4);} else {return months[utc.substring(5,7) * 1] + ' ' + (utc.substring(8,10) * 1) + ', ' + utc.substring(0,4);}}function toDateObject(timestamp) { // converts a Wikipedia timestamp to a Javascript Date objectvar date = new Date();date.setUTCFullYear(timestamp.substring(0,4), timestamp.substring(5,7) - 1, timestamp.substring(8,10));date.setUTCHours(timestamp.substring(11,13), timestamp.substring(14,16), timestamp.substring(17,19));return date;}function fixSidebar() { // part of the code to fix the sidebar; the rest is below the addToolbarPortletLink function// this function is only necessary for the monobook skinvar content = document.getElementById("column-content");    // Find the main content columnvar footer = document.getElementById("footer");  // Find the footerfooter.parentNode.removeChild(footer);    // Remove the footer from the global wrappercontent.appendChild(footer);    // Place footer at the end of the content column;var tabs = document.getElementById("p-cactions");   // Find the top tab listtabs.parentNode.removeChild(tabs);    // Remove the tab list from the side columncontent.insertBefore(tabs, content.lastChild);    // Place tab list in the content columnvar personal = document.getElementById("p-personal");   // Find the personal links listpersonal.parentNode.removeChild(personal);    // Remove the personal links list from the side columncontent.insertBefore(personal, content.lastChild);    // Place personal links list in the content column}window.dykCheck = function () { // this function for casual use and anonsif (((mwConfig.wgAction === 'view' || mwConfig.wgAction === 'submit' || mwConfig.wgAction === 'purge') && (mwConfig.wgNamespaceNumber === 0 || mwConfig.wgNamespaceNumber === 2)) || unlock) {checkDocument();} else if (mwConfig.wgPageName === 'Template_talk:Did_you_know') {checkTTDYK();}};function addToolbarPortletLink(func, tooltip) {var link = mw.util.addPortletLink('p-tb','#','DYK check','t-dyk-check',tooltip);$( link ).click( function (e) {e.preventDefault();func();});}// Add toolbar portlet linksif (unlock || ((mwConfig.wgAction === 'view' || mwConfig.wgAction === 'submit' || mwConfig.wgAction === 'purge') && (mwConfig.wgNamespaceNumber === 0 || mwConfig.wgNamespaceNumber === 2))) {addToolbarPortletLink(checkDocument, 'Check if this article qualifies for DYK');} else if (mwConfig.wgPageName === 'Template_talk:Did_you_know') {addToolbarPortletLink(checkTTDYK, 'Check DYK nominations for eligibility');}// Fix the sidebarif (mwConfig.wgUserName && mwConfig.skin === 'monobook' && (fixedSidebar === "always" ||fixedSidebar === "onttydk" && mwConfig.wgPageName === 'Template_talk:Did_you_know')) {fixSidebar();}// The code below for the fixed sidebar is a blend of two sources://http://meta.wikimedia.org/wiki/Help:User_style/floating_quickbar // http://en.wikipedia.org/wiki/User:Omegatron/monobook.js/floatingSidebar.js// Very little of the code below was written by me (Shubinator)// This CSS should be hidden from older versions of IE using javascript instead of the attribute selector?// Include style sheet inline so that script is self-contained:if (mwConfig.wgUserName && (fixedSidebar === "always" ||fixedSidebar === "onttydk" && mwConfig.wgPageName === 'Template_talk:Did_you_know')) {var head = document.getElementsByTagName("head")[0];var style = document.createElement('style');style.type = 'text/css';var sidebarDivHidden;var sidebarDiv;var langBody;if (mwConfig.skin === 'vector') { // default skin  (as of May 2010)sidebarDivHidden = 'div#mw-panel';sidebarDiv = 'div#mw-panel';langBody = '#p-lang .body';} else { // monobook, modern, and simple skinsif (mwConfig.skin === 'modern') {sidebarDivHidden = 'div[id=mw_portlets]';} else if (mwConfig.skin === 'simple') {sidebarDivHidden = '#column-one';} else { // monobook skinsidebarDivHidden = 'div[id=column-one]'; /* Using the attribute selector hides this from IE */}sidebarDiv = '#column-one';langBody = '#p-lang .pBody';}var cssText = "   /* Fix the sidebar's position while you scroll */             "+    sidebarDivHidden + ' {                                                      '+'       position: fixed;                                                        ';if (mwConfig.skin === 'vector') { // force the sidebar to the upper left; only necessary in some skins    cssText += 'left: 0px;                                                      '+    '   top: 0px;                                                               ';} else if (mwConfig.skin === 'monobook') {    cssText += 'left: 0px;                                                      '+    '   top: -160px;                                                            ';}cssText += 'height: 100%;   /* If you shrink the browser too small, the     */  '+'       overflow: auto;     /* side column will become scrollable, so stuff */  '+'       z-index: 2;         /* is always accessible, albeit ugly            */  '+'   }                                                                           '+'                                                                               '+'   #p-logo {               /* Make logo inline with other divs             */  '+'       position:static;                                                        '+'   }                                                                           '+'                                                                               '+    sidebarDiv       + ' {  /* Sidebar column start at the top screen edge  */  '+'       padding-top: 0;                                                         '+'   }                                                                           '+'                                                                               '+    langBody +   ' ul{      /* Sets the language box to a fixed height and  */ '+'       height: 6em;        /* scrollable if too long to fit on screen      */  '+'       overflow: auto;                                                         '+'   }                                                                           '+'                                                                               '+'   /* Fix the background image, too, so it looks nice as you scroll */         '+'   body {                                                                      '+'       background-attachment: fixed;                                           '+'   }                                                                           '+'                                                                               '+"   /* Fix the footer so it looks nice and doesn't overlap the sidebar */       "+'   #footer {                                                                   '+'       margin-left: 13.6em;                                                    '+'       border-left: solid 1px rgb(250, 189, 35);                               '+'       -moz-border-radius-topleft: 1em;                                        '+'       -moz-border-radius-bottomleft: 1em;                                     '+'   }                                                                           ';if (mwConfig.skin === 'monobook') {    cssText += '   /* Keep personal links at the top right */                  '+'   #p-personal {                                                               '+'       width:100%;                                                             '+'       white-space:nowrap;                                                     '+'       padding:0 0 0 0;                                                        '+'       margin:0;                                                               '+'       position:absolute;                                                      '+'       left:0px;                                                               '+'       top:0px;                                                                '+'       z-index: 0;                                                             '+'       border: none;                                                           '+'       background: none;                                                       '+'       overflow: visible;                                                      '+'       line-height: 1.2em;                                                     '+'   }                                                                           '+'                                                                               '+'   #p-personal h5 {                                                            '+'       display:none;                                                           '+'   }                                                                           '+'   #p-personal .portlet,                                                       '+'   #p-personal .pBody {                                                        '+'       padding:0;                                                              '+'       margin:0;                                                               '+'       border: none;                                                           '+'       z-index:0;                                                              '+'       overflow: visible;                                                      '+'       background: none;                                                       '+'   }                                                                           '+'   /* this is the ul contained in the portlet */                               '+'   #p-personal ul {                                                            '+'       border: none;                                                           '+'       line-height: 1.4em;                                                     '+'       color: #2f6fab;                                                         '+'       padding: 0em 2em 0 3em;                                                 '+'       margin: 0;                                                              '+'       text-align: right;                                                      '+'       text-transform: lowercase;                                              '+'       list-style: none;                                                       '+'       z-index:0;                                                              '+'       background: none;                                                       '+'   }                                                                           '+'   #p-personal li {                                                            '+'       z-index:0;                                                              '+'       border:none;                                                            '+'       padding:0;                                                              '+'       display: inline;                                                        '+'       color: #2f6fab;                                                         '+'       margin-left: 1em;                                                       '+'       line-height: 1.2em;                                                     '+'       background: none;                                                       '+'   }                                                                           '+'   #p-personal li a {                                                          '+'       text-decoration: none;                                                  '+'       color: #005896;                                                         '+'       padding-bottom: 0.2em;                                                  '+'       background: none;                                                       '+'   }                                                                           '+'   #p-personal li a:hover {                                                    '+'       background-color: White;                                                '+'       padding-bottom: 0.2em;                                                  '+'       text-decoration: none;                                                  '+'   }                                                                           '+'   /* Keep the small user figure left of your user name */                     '+'   li#pt-userpage,                                                             '+'   li#pt-anonuserpage,                                                         '+'   li#pt-login {                                                               '+'       background: url(/skins-1.5/monobook/user.gif) top left no-repeat;       '+'       padding-left: 20px;                                                     '+'       text-transform: none;                                                   '+'   }                                                                           '+'                                                                               ';}var rules = document.createTextNode(cssText);if (style.styleSheet) {style.styleSheet.cssText = rules.nodeValue;} else {style.appendChild(rules);}head.appendChild(style);}});