MediaWiki:Gadget-aWa.js

"use strict"; /*** [aWa]: A Wonderfool Archiver ***/ // /*jshint shadow:true, scripturl:true, undef:true, latedef:true, unused:true */ /*global mw, jQuery */ (function {

// temporarily disable aWa due to a bug where it archives to the talk page of the discussion page on which it is used if (mw.config.get('wgAction') !== 'view') return;

var monthNames = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ]; var revMonthNames = {}; for (var j = 0; j < monthNames.length; ++j) revMonthNames[monthNames[j]] = j;

function el(tag, child, attr, events) { var node = document.createElement(tag);

if (child) { if (typeof child !== 'object') child = [child]; for (var i = 0; i < child.length; ++i) { var ch = child[i]; if ((ch === void(null)) || (ch === null)) continue; else if (typeof ch !== 'object') ch = document.createTextNode(String(ch)); node.appendChild(ch); }	}

if (attr) for (var key in attr) { node.setAttribute(key, String(attr[key])); }

if (events) for (var key in events) { node.addEventListener(key, events[key], false); }

return node; }

var forumId, remindersList = []; var wgPageName = mw.config.get('wgPageName'); if (/^Wiktionary:Requests_for_verification/.test(wgPageName)) { forumId = 'rfv'; remindersList.push(["Remember to remove the ", el('code', ""), " and/or ", el('code', ""), " template from verified entries."]); } else if ((wgPageName === "Wiktionary:Requests_for_deletion") || /^Wiktionary:Requests_for_deletion/.test(wgPageName)) { forumId = 'rfd'; remindersList.push(["Remember to remove the ", el('code', ""), " and/or ", el('code', ""), " template from kept entries."]); } else if (wgPageName === "Wiktionary:Requests_for_deletion/Others") { forumId = 'rfdo'; remindersList.push(["Remember to remove the ", el('code', ""), " template from kept pages."]); } else if ((wgPageName === "Wiktionary:Requests_for_moves,_mergers_and_splits") || /^Wiktionary:Requests_for_moves,_mergers_and_splits\/Unresolved_requests\//.test(wgPageName)) { forumId = 'rfm'; remindersList.push(["Remember to remove the ", el('code', ""), " template from moved pages."]); } else if ((wgPageName === "Wiktionary:Requests_for_cleanup") || /^Wiktionary:Requests_for_cleanup\/archive\/\d+\/Unresolved_requests$/.test(wgPageName)) { forumId = 'rfc'; remindersList.push(["Remember to remove the ", el('code', ""), " template from pages which have been dealt with."]); } else if (wgPageName === "Wiktionary:Feedback") { forumId = 'feedback'; remindersList.push(["Feedback is rarely archived at all; old feedback is usually just removed."]); } else if (wgPageName === "Wiktionary:Sandbox/aWa") { forumId = 'SANDBOX'; remindersList.push(["NO DANGER: Nothing in this room will kill you."]); } else return;

if (forumId !== 'feedback') remindersList.push(["Keep closed discussions unarchived for at least 7 days."]);

function link(child, href, attr, ev) { attr = attr || {}; ev = ev || {}; if (typeof href === 'string') attr.href = href; else { attr.href = 'javascript:void(null);'; ev.click = href; }	return el('a', child, attr, ev); }

var rxPath = new RegExp('^' + mw.config.get('wgArticlePath').replace('$1', '(.*)') + '$'); // XXX: ugly hack function getPageFromUrl(url) { var m, target; try { target = new mw.Uri(url); } catch (e) { return null; }	if (target.host !== location.host) return null; if (target.path === mw.config.get('wgScript')) return new mw.Title(target.query.title); else if ((m = rxPath.exec(target.path))) return new mw.Title(decodeURIComponent(m[1])); return null; }

function findSectionNumber(header) { var links = header.getElementsByTagName('a'); for (var j = 0; j < links.length; ++j) { if (links[j].parentNode.className !== 'mw-editsection') continue; var uri = new mw.Uri(links[j].href); if ((uri.path === mw.config.get('wgScript')) && (uri.query.action === 'edit') && (uri.query.title === wgPageName)) return Number(uri.query.section); }	return null; }

var api = new mw.Api;

// XXX: seriously, action=parse should return this. var starttimestamp, basetimestamp; api.get({	action: 'query',	pageids: mw.config.get('wgArticleId'),	prop: 'revisions',	curtimestamp: 1,	rvstartid: mw.config.get('wgRevisionId'),	rvprop: 'timestamp',	rvlimit: 1 }).done(function (result) {	starttimestamp = result.curtimestamp;	basetimestamp = result.query.pages[mw.config.get('wgArticleId')].revisions[0].timestamp; });

var toArchive = {}; var uiQueue, uiStatus; function setStatus(child, node) { node = node || uiStatus; if (!node.preceder) node.parentNode.insertBefore(node.preceder = document.createTextNode(''), node); while (node.firstChild) node.removeChild(node.firstChild); if (!child) { if (node.preceder !== true) node.preceder.data = ""; return; } else { if (node.preceder !== true) node.preceder.data = ": "; }

if (typeof child === 'string') { node.appendChild(document.createTextNode(child)); return; }

for (var i = 0; i < child.length; ++i) { var ch = child[i]; if ((ch === void(null)) || (ch === null)) continue; else if (typeof ch !== 'object') ch = document.createTextNode(String(ch)); node.appendChild(ch); } }

var uiProceed, uiWrapper; var uiArchiver = el('div', [	el('form', [ uiWrapper = el('fieldset', [			// ↓↓↓↓↓↓ how fitting.			el('legend', [ "A ", link(["Wonderfool"], mw.util.getUrl("wonderfool")), " Archiver ", el('small', ["[", link('collapse', function { this.style.fontWeight = uiWrapper.classList.toggle('collapsed') ? 'bold' : ''; }), "]"])			]),			uiQueue = el('ul', null, { 'class': 'queue' }),			el('ul', remindersList.map(function (nodes) {				return el('li', nodes);			}), { 'class': 'reminders' }),			uiProceed = el('input', null, { type: 'submit', value: "Proceed" }), ' ',			el('input', null, { type: 'button', value: "Close" }, { click: function { uiArchiver.style.display = 'none'; }			}),			uiStatus = el('p', null, { 'class': 'main-statusbar' })		]) ], { action: 'javascript:void(0);' }, { submit: function { if (!uiQueue.hasChildNodes) { // XXX: should probably check the toArchive object instead setStatus("The archiving queue is empty."); return; }

if (forumId !== 'SANDBOX') { uiProceed.disabled = true; uiArchiver.classList.add('archiving-in-progress'); }			var complete = false; window.addEventListener('beforeunload', function (ev) {				if (!complete) {					return (ev.returnValue = "Discussions are still being archived.");				}			}, false); setStatus("Grabbing page text…"); api.get({				action: 'parse',				oldid: mw.config.get('wgRevisionId'),				prop: 'parsetree',			}).then(function (result) {				setStatus("Slicing up discussions…");				var prettyPageName = (new mw.Title(wgPageName)).getPrefixedText;				function serialiseMarkup(node) {					if (node.nodeType === node.TEXT_NODE)						return node.data;

var children = []; for (var child = node.firstChild; child; child = child.nextSibling) children.push(serialiseMarkup(child)); if (node.tagName === 'part') return '|' + children.join(''); else if (node.tagName === 'tplarg') return ''; else if (node.tagName === 'template') return ''; else if (node.tagName === 'ext') return '<' + children.join(''); else if (node.tagName === 'attr') return children.join() + (node.parentNode.lastChild === node ? '/>' : ); else if (node.tagName === 'inner') return '>' + children.join(''); else return children.join(''); }				var parseTree = jQuery.parseXML(result.parse.parsetree['*']);

var headerMap = {}; var headers = parseTree.getElementsByTagName('h'); for (var i = 0; i < headers.length; ++i) { headerMap[headers[i].getAttribute('i')] = headers[i]; }

var majorQueue = [], minorQueue = [], obliterated = []; var keys = Object.keys(toArchive); for (var j = 0; j < keys.length; ++j) { var item = toArchive[keys[j]]; var sectHead = headerMap[item.sect]; var sectContents = []; for (						var node = sectHead, next = sectHead.nextSibling;						node && (!node.getAttribute || !node.getAttribute('level') || (+node.getAttribute('level') > +sectHead.getAttribute('level')) || (node === sectHead));						(next = node.nextSibling, node.parentNode.removeChild(node), node = next)					) { sectContents.push(serialiseMarkup(node)); }					var secttext = sectContents.join('');

var discussionTitle;

switch (forumId) { case 'rfd'    : discussionTitle = "RFD discussion"             ; break; case 'rfdo'   : discussionTitle = "RFDO discussion"            ; break; case 'rfv'    : discussionTitle = "RFV discussion"             ; break; case 'rfc'    : discussionTitle = "RFC discussion"             ; break; case 'rfm'    : discussionTitle = "RFM discussion"             ; break; case 'rft'    : discussionTitle = "Archived from the Tea Room" ; break; case 'feedback': discussionTitle = "Archived feedback"         ; break; default       : discussionTitle = "Archived from " + prettyPageName + ""              ; break; }

var rxStamp = /(\d\d?):(\d\d),\s*(\d+)\s*([A-Z][a-z]+)\s*(\d\d\d\d+)\s*\(UTC\)/g; var m, timestamps = []; while ((m = rxStamp.exec(secttext))) timestamps.push(Date.UTC(parseInt(m[5], 10), revMonthNames[m[4]], parseInt(m[3], 10), parseInt(m[1], 10), parseInt(m[2], 10))); timestamps.sort;

if (timestamps.length > 0) { var first = new Date(timestamps[0]), last = new Date(timestamps[timestamps.length - 1]); if (first.getUTCFullYear === last.getUTCFullYear) { if (first.getUTCMonth === last.getUTCMonth) discussionTitle += ": " + monthNames[first.getUTCMonth] + " " + first.getUTCFullYear; else discussionTitle += ": " + monthNames[first.getUTCMonth] + "–" + monthNames[last.getUTCMonth] + " " + first.getUTCFullYear; } else { discussionTitle += ": " + monthNames[first.getUTCMonth] + " " + first.getUTCFullYear + '–' + monthNames[last.getUTCMonth] + " " + last.getUTCFullYear; }					}

if (!item.talk) { obliterated.push(item.secttitle); continue; // requested not to archive. }					secttext = secttext.replace(/^(=+)(.*?)\1(?=\n|$)/, '');

majorQueue.push({						targetPage: item.talk,						uiStatus: item.uiStatus,						sectTitle: discussionTitle,						summary: 'aWa: Archiving discussions from ' + prettyPageName + '',						content: '\n' +							secttext.trimRight +							'\n'					});

var backlinks = item.backlinks; for (var k = 0; k < backlinks.length; ++k) { if (!backlinks[k]) continue;

var saneDiscussionTitle = discussionTitle.replace(/\[\[(?:.*?\|)?(.*?)\]\]/g, '$1'); // XXX var linkTarget = item.talk.getPrefixedText + "#" + saneDiscussionTitle;

minorQueue.push({							targetPage: backlinks[k].page,							uiStatus: backlinks[k].uiStatus,							sectTitle: discussionTitle,							summary: "aWa: Adding link to a discussion at " + linkTarget + ", archived from " + prettyPageName + "",							content: ": See " + linkTarget + "."						}); }				}				var headers = Array.prototype.slice.call(parseTree.getElementsByTagName('h')); for (var i = 0; i < headers.length; ++i) { if (headers[i].getAttribute('level') !== '1') continue; if (!/^=\s*(January|February|March|April|May|June|July|August|September|October|November|December)\s+\d\d\d\d+\s*=$/.test(headers[i].textContent)) continue; out: for (var node = headers[i].nextSibling;; node = node.nextSibling) { switch (node ? node.nodeType : null) { case parseTree.ELEMENT_NODE: if ((node.tagName !== 'h') || (node.getAttribute('level') !== '1')) break out; /* fall through */ case null: while ((headers[i].nextSibling) !== node) headers[i].parentNode.removeChild(headers[i].nextSibling); headers[i].parentNode.removeChild(headers[i]); break out; case parseTree.TEXT_NODE: if (!/^\s*$/.test(node.data)) break out; break; default: break out; }					}				}

var wikitext = serialiseMarkup(parseTree.documentElement);

setStatus("Removing discussions from this page…");

var summary; if (majorQueue.length) summary = 'aWa: Archiving ' + majorQueue.length + ' discussion(s) to ' + majorQueue.map(function (item) { return  + item.targetPage.getPrefixedText + ; }).join(", ") + (obliterated.length ? " and removing " + obliterated.length + " other(s): \"" + obliterated.join("\", \"") + "\"": ""); else summary = "aWa: Removing " + obliterated.length + " discussion(s): \"" + obliterated.join("\", \"") + "\""; if (forumId === 'SANDBOX') { setStatus; alert("Summary: " + summary + "\nFinal wikitext:\n\n" + wikitext); majorQueue.forEach(function (item) {						alert("" + item.targetPage.getPrefixedText + ": " + item.sectTitle + "\n\n" + item.content);					}); minorQueue.forEach(function (item) {						alert("" + item.targetPage.getPrefixedText + ": " + item.sectTitle + "\n\n" + item.content);					}); return; }

api.post({					action: 'edit',					pageid: mw.config.get('wgArticleId'),					token: mw.user.tokens.get('csrfToken'),					summary: summary,					notminor: 1,					text: wikitext,					basetimestamp: basetimestamp,					starttimestamp: starttimestamp				}).then(function (result) {					function processQueue(queue) {						return jQuery.when.apply(jQuery, queue.map(function (item) {							setStatus("Saving…", item.uiStatus);							return jQuery.Deferred(function (d) { api.post({									action: 'edit',									token: mw.user.tokens.get('csrfToken'),									notminor: 1,									section: 'new',									sectiontitle: item.sectTitle,									title: item.targetPage.getPrefixedDb,									text: item.content,									summary: item.summary,									redirect: 1 // XXX								}).then(function (result) {									if (result.edit.result !== 'Success') {										setStatus("Failed, see console", item.uiStatus);										d.reject;										console.error(result);										return;									}									// XXX: preferably check if a page is a redirect BEFORE archiving.									if (result.redirects) {										var target = result.redirects.pop.to;										setStatus(["Redirected to ", link(target, mw.util.getUrl(target))], item.uiStatus);										d.resolve;										return;									}									setStatus("OK", item.uiStatus);									d.resolve;								}, function (code, result) { setStatus(["Error: [", el('code', [code]), "]"], item.uiStatus); console.error('Request error', result); d.reject; });							}).promise; }));					}					if (result.edit.result !== 'Success') { setStatus("Unexpected failure. See console for details."); console.error(result); complete = true; return; }					setStatus("Putting archived discussions on talk pages…"); processQueue(majorQueue).then(function {						setStatus("Adding backlinks…");						processQueue(minorQueue).then(function  { setStatus("Done. Reloading…"); complete = true; location.reload; }, function { complete = true; setStatus("Archiving was successful, but there was an error while adding backlinks. You can visit the talk pages to add backlinks manually."); });					}, function {						complete = true;						setStatus([ "Oops… there was an error while archiving discussions. Go to your ", link("contributions list", mw.util.getUrl('Special:Contributions/' + mw.config.get('wgUserName'))), " to clean things up and try again." ]);					});				}, function (code, result) { complete = true; if (code === 'editconflict') { setStatus([							'Edit conflict detected. Please ',							link('refresh the page', function { location.reload; }),							' and try again.'						]); return; }					if (code === 'http') { setStatus(['Request error: ' + result.textStatus]); console.error('API request error: ', result); return; }					if (code === 'ok-but-empty') { setStatus(['Returned empty response from server.']); return; }					setStatus([						'Error while saving the main discussion page: ',						result.error.info,						' [code ', el('code', result.error.code), ']'					]); console.error(arguments); });			}, function (code, result) {				if (code === 'http') {					setStatus('Request error: ' + result.textStatus);					console.error('API request error: ', result);					return;				}				if (code === 'ok-but-empty') {					setStatus('Received empty response from server.');					return;				}				setStatus(['Error: ', result.error.info, ' [code ', el('code', [result.error.code]), ']']);				return;			}); }	}) ], {	'class': 'kephir-awa-dialog',	'style': 'display: none;' }); document.body.appendChild(uiArchiver); uiStatus.preceder = true;

var headers = document.getElementsByClassName('mw-headline');

var dupTracker = {};

function makeLinkFor(headerNode, links, age) { var section = findSectionNumber(headerNode.parentNode); var targetChecked = false, targetCheckRequest; if (section === null) return;

headerNode.parentNode.appendChild(el('span', [ '[', link('archive', function {			var that = this, config = {				secttitle: headerNode.textContent,				sect: section,				sectid: headerNode.id,				temp: 'archived',				backlinks: []			};			setStatus;			uiArchiver.style.display = '';			if (this.item)				return;			toArchive[section] = config;

var uiArchiveTarget, uiArchiveTargetLink, uiArchiveNowhereLink; var uiResult, results = {}; var uiBacklinks, uiAddBacklink, uiLinksFromSel;

function setTarget(page, noCheck) { if (!page) { uiArchiveTarget.style.display = 'none'; uiArchiveNowhereLink.classList.add('active-mode'); uiBacklinks.classList.add('irrelevant'); uiAddBacklink.classList.add('irrelevant'); uiLinksFromSel.classList.add('irrelevant'); if (uiResult) uiResult.classList.add('irrelevant'); if (!config.talk) return; delete dupTracker[config.talk.getPrefixedDb]; config.talk = null; return; }

config.talk = page; dupTracker[page.getPrefixedDb] = true; uiArchiveTarget.style.display = ''; uiArchiveTargetLink.textContent = page.getPrefixedText; uiArchiveTargetLink.href = mw.util.getUrl(page.getPrefixedDb); uiArchiveNowhereLink.classList.remove('active-mode'); uiBacklinks.classList.remove('irrelevant'); uiAddBacklink.classList.remove('irrelevant'); uiLinksFromSel.classList.remove('irrelevant'); if (uiResult) uiResult.classList.remove('irrelevant');

if (!noCheck) { targetCheckRequest = api.get({						action: 'query',						titles: page.getPrefixedDb,						redirects: 1					}).then(function (result) {						if (result.query.redirects) {							setStatus([ "Archive destination ",								link(page.getPrefixedText, mw.util.getUrl(page.getPrefixedDb, { 'redirect': 'no' })),								" is a redirect; corrected." ]);							setTarget(new mw.Title(result.query.redirects.pop.to), true);						}						targetChecked = true;						targetCheckRequest = null;					}); } else targetChecked = true; }			function addBacklink(page, noCheck) { if (!page) return;

var uiItem, blConfig = {}; var i = config.backlinks.length; config.backlinks.push(blConfig);

var uiLink;

function changePage(newPage, noCheck) { blConfig.page = newPage; blConfig.checked = noCheck; uiLink.textContent = newPage.getPrefixedText; dupTracker[newPage.getPrefixedDb] = true;

if (!noCheck) { blConfig.checkRequest = api.get({							action: 'query',							titles: newPage.getPrefixedDb,							redirects: 1						}).then(function (result) {							if (result.query.redirects) {								setStatus([ "Backlink destination ",									link(newPage.getPrefixedText, mw.util.getUrl(newPage.getPrefixedDb, { 'redirect': 'no' })),									" is a redirect; corrected." ]);								changePage(new mw.Title(result.query.redirects.pop.to), true);								page = blConfig.page;							}							blConfig.checked = true;							blConfig.checkRequest = null;						}); }				}

uiBacklinks.appendChild(uiItem = el('li', [ uiLink = link(page.getPrefixedText, mw.util.getUrl(page.getPrefixedDb), {						'title': "A link back to the discussion will be put at this page"					}), el('span', [						" (", link('cancel', function {							uiItem.parentNode.removeChild(uiItem);							delete config.backlinks[i];							delete dupTracker[page.getPrefixedDb];						}, { 'title': "Do not put a backlink on that page" }), " \u2022 ", link("swap", function {							if (!config.talk) {								uiItem.parentNode.removeChild(uiItem);								delete config.backlinks[i];								if (blConfig.checkRequest) blConfig.checkRequest.abort;								setTarget(page, blConfig.checked);								return;							}							if (blConfig.checkRequest) blConfig.checkRequest.abort;							if (targetCheckRequest) targetCheckRequest.abort;							changePage(config.talk, targetChecked);							setTarget(page, blConfig.checked);							page = blConfig.page;						}, { 'title': "Swap this page with the archive destination" }), ")"					], { 'class': 'hide-when-archiving' }), blConfig.uiStatus = el('span') ]));

changePage(page, noCheck);

return i;			} function setResult(result) { for (var key in results) results[key].classList.remove('active-mode'); if (results[result]) results[result].classList.add('active-mode'); config.result = result; }

uiQueue.appendChild(this.item = el('li', [ '"', link(headerNode.textContent, '#' + headerNode.id, {					'title': "Click to view this discussion"				}), '"', uiArchiveTarget = el('span', [					"→ ", uiArchiveTargetLink = link('?', 'javascript:void(window.warranty);', {						'title': "The discussion will be archived at this page"					}), ""				]), config.uiStatus = el('span'), el('br'), el('small', [					link("retarget", function { var newTarget = prompt('New archive target?', config.talk ? config.talk.getPrefixedText : ""); if (!newTarget) return; try { newTarget = new mw.Title(newTarget); } catch (e) { setStatus("Not a valid page title"); return; }						if (newTarget.getNamespaceId < 0)	{ setStatus("Cannot archive into a special namespace"); return; }						newTarget.namespace |= 1;

setTarget(newTarget); setStatus; }, { 'title': "Change the page where the discussion is to be archived" }),					" \u2022 ", uiArchiveNowhereLink = link("memory hole", function { setTarget(null); }, { 'title': "Do not archive this discussion anywhere, just remove" }),					" \u2022 ", uiAddBacklink = link("link back", function { var newTarget = prompt("Add it where?", ''); if (!newTarget) return;

try { newTarget = new mw.Title(newTarget); } catch (e) { setStatus("Not a valid page title"); return; }						if (newTarget.getNamespaceId < 0)	{ setStatus("Cannot archive into a special namespace"); return; }						newTarget.namespace |= 1;

addBacklink(newTarget); setStatus; }, { 'title': "Add a page where a link back to this discussion is to be put" }),					" \u2022 ", uiLinksFromSel = link("from selection", function { var sel = window.getSelection, taken = {}; var intersectsNode = function (node) { var tmp = document.createRange; tmp.selectNode(node); return (this.compareBoundaryPoints(this.START_TO_START, tmp) >= 0) && (this.compareBoundaryPoints(this.END_TO_END, tmp) <= 0); };						for (var i = 0; i < sel.rangeCount; ++i) { var range = sel.getRangeAt(i); var links = range.commonAncestorContainer.getElementsByTagName('a'); if (!range.intersectsNode) range.intersectsNode = intersectsNode; for (var j = 0; j < links.length; ++j) { if (!range.intersectsNode(links[j])) continue; var title = getPageFromUrl(links[j].href);

if (!title || (title.getNamespaceId < 0)) continue; if (title) { title.namespace |= 1; if (taken[title.getPrefixedDb]) continue; addBacklink(title); taken[title.getPrefixedDb] = true; }							}							range.detach; }					}, { 'title': "Add backlinks at pages linked in current selection" }),					" \u2022 ", link("cancel", function { that.item.parentNode.removeChild(that.item); delete toArchive[section]; delete that.item; }, { 'title': "Do not archive this discussion" }),					((forumId === 'rfd') || (forumId === 'rfdo') || (forumId === 'rfv') || (forumId === 'SANDBOX')) ? uiResult = el('span', [ " \u2022 result: ", results.passed = link("pass", function {							setResult('passed');						}, { 'title': "Mark this discussion as resolved successfully (verified, consensus to keep)" }), " \u2022 ", results.failed = link("fail", function {							setResult('failed');						}, { 'title': "Mark this discussion as having failed the process (deleted)" }), " \u2022 ", results.archived = link("other", function {							setResult('archived');						}, { 'title': "Do not mark discussion outcome (mixed, no consensus, etc.)" } ) ]) : null				], { 'class': 'hide-when-archiving' }), uiBacklinks = el('ul', [], { 'class': 'kephir-awa-backlinks' }) ]));

var result = 'archived'; // Expected HTML elements: // 			var mwHeading = headerNode.parentNode.parentNode; if (!mwHeading.classList.contains("mw-heading")) { console.error("Expected to find element with class mw-heading as parent of %o", headerNode); }			if (results.passed) { var resultText;

for (var node = mwHeading.nextSibling; node; node = node.nextSibling) { if (!node.getElementsByClassName) continue; if (node.classList.contains("mw-heading")							|| node.getElementsByClassName("mw-heading").length > 0) break; var bs = node.getElementsByTagName('b'); for (var j = 0; j < bs.length; ++j) { var b = bs[j]; resultText = b.textContent || b.innerText; if (/no\s+consensus/i.test(b.parentNode.textContent)) result = 'archived'; else { if (/\b(failed|deleted|speedied|not\s+restored)\b/i.test(resultText)) { result = 'failed'; } else if (/\b(passed|kept|cited|restored|undeleted)\b/i.test(resultText)) { result = 'passed'; }						}					}				}			}			setResult(result); if (links[0]) { var title = getPageFromUrl(links[0].href); if (title.getNamespaceId >= 0) { title.namespace |= 1; setTarget(title); } else setTarget(null); } else { setTarget(null); }

for (var i = 1; i < links.length; ++i) { var title = getPageFromUrl(links[i].href); title.namespace |= 1; addBacklink(title); }		}),		age ? el('small', [' (age: ' + age + ')']) : void(0),		']'	], { "class": "mw-editsection" })); }

var levels = {}; for (var i = 0; i < headers.length; ++i) { var level = parseInt(headers[i].parentNode.tagName.substr(1), 10); for (var k = 1; k <= 6; ++k) { if (k < level) { if (levels[k]) levels[k].subHeaders.push(headers[i]); } else delete levels[k]; }	levels[level] = headers[i]; headers[i].subHeaders = []; }

function collectUntilH2(node) { var nodes = []; while (node && (node.tagName !== 'H2')) { if (node.getElementsByTagName && node.getElementsByTagName('h2').length) { return nodes.concat(collectUntilH2(node.firstChild)); }		nodes.push(node); node = node.nextSibling; }	return nodes; }

for (var i = 0; i < headers.length; ++i) { /*jshint loopfunc:true */ if (headers[i].parentNode.tagName !== 'H2') continue;

var links = Array.prototype.filter.call(		headers[i].querySelectorAll('a:not(.ext-discussiontools-init-section-subscribe a)'), function (item) {			var title = getPageFromUrl(item.href);			return title && (title.getNamespaceId >= 0);		}	);

if (!links.length) links.unshift(null);

for (var j = 0; j < headers[i].subHeaders.length; ++j) { links = links.concat(Array.prototype.filter.call(headers[i].subHeaders[j].querySelectorAll('a:not(.ext-discussiontools-init-section-subscribe a)'), function (item) { var title = getPageFromUrl(item.href); return title && (title.getNamespaceId >= 0); }));	}

var nodes = collectUntilH2(headers[i].parentNode.parentNode.nextSibling); var timestamps = Array.prototype.concat.call(nodes.map(function (node) { return node.textContent || ''; }).join('').match(/(\d\d?):(\d\d),\s*(\d+)\s*([A-Z][a-z]+)\s*(\d\d\d\d+)\s*\(UTC\)/g) || [], nodes.map(function(node) { // Comments in Local Time if (!node.getElementsByClassName) return; var nodes = Array.prototype.slice.call(node.getElementsByClassName('localcomments')); if (/(^|\s)localcomments(\s|$)/.test(node.className)) nodes.push(node); return nodes.map(function (node) {			return node.title;		}); }).reduce(function (accum, item) { return item ? accum.concat(item) : accum; }, [])).map(function (ts) {		var m = /(\d\d?):(\d\d),\s*(\d+)\s*([A-Z][a-z]+)\s*(\d\d\d\d+)\s*\(UTC\)/.exec(ts);		return Date.UTC(parseInt(m[5], 10), revMonthNames[m[4]], parseInt(m[3], 10), parseInt(m[1], 10), parseInt(m[2], 10));	}).sort;

var age = null; if (timestamps.length) { age = Math.floor((Date.now - timestamps[timestamps.length - 1]) / 86400000) + ' days'; }

makeLinkFor(headers[i], links, age); }

}); //