MediaWiki:Gadget-aWa.js: difference between revisions

From Wiktionary, the free dictionary
Jump to navigation Jump to search
Content deleted Content added
hmm
m no need to leave that here then
(2 intermediate revisions by the same user not shown)
Line 205: Line 205:
api.get({
api.get({
action: 'parse',
action: 'parse',
pageid: mw.config.get('wgArticleId'),
oldid: mw.config.get('wgRevisionId'),
oldid: mw.config.get('wgRevisionId'),
prop: '',
prop: 'parsetree',
generatexml: 1
}).then(function (result) {
}).then(function (result) {
setStatus("Slicing up discussions…");
setStatus("Slicing up discussions…");

Revision as of 16:34, 20 December 2016

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

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 ((wgPageName === "Wiktionary:Requests_for_verification") || /^Wiktionary:Requests_for_verification_archive\//.test(wgPageName)) {
	forumId = 'rfv';
	remindersList.push(["Remember to remove the ", el('code', "{{rfv}}"), " and/or ", el('code', "{{rfv-sense}}"), " template from verified entries."]);
} else if ((wgPageName === "Wiktionary:Requests_for_deletion") || /^Wiktionary:Requests_for_deletion\/Archives\//.test(wgPageName)) {
	forumId = 'rfd';
	remindersList.push(["Remember to remove the ", el('code', "{{rfd}}"), " and/or ", el('code', "{{rfd-sense}}"), " template from kept entries."]);
} else if (wgPageName === "Wiktionary:Requests_for_deletion/Others") {
	forumId = 'rfdo';
	remindersList.push(["Remember to remove the ", el('code', "{{rfd}}"), " 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', "{{rfm}}"), " 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', "{{rfc}}"), " 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 '{{{' + children.join('') + '}}}';
					else if (node.tagName === 'template')
						return '{{' + children.join('') + '}}';
					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 = "[[" + prettyPageName + "|RFD]] discussion"             ; break;
					case 'rfdo'    : discussionTitle = "[[" + prettyPageName + "|RFDO]] discussion"            ; break;
					case 'rfv'     : discussionTitle = "[[" + prettyPageName + "|RFV]] discussion"             ; break;
					case 'rfc'     : discussionTitle = "[[" + prettyPageName + "|RFC]] discussion"             ; break;
					case 'rfm'     : discussionTitle = "[[" + prettyPageName + "|RFM]] discussion"             ; break;
					case 'rft'     : discussionTitle = "Archived from the [[" + prettyPageName + "|Tea Room]]" ; break;
					case 'feedback': discussionTitle = "Archived [[" + prettyPageName + "|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|$)/, '{{fake$1|1=$2}}');

					majorQueue.push({
						targetPage: item.talk,
						uiStatus: item.uiStatus,
						sectTitle: discussionTitle,
						summary: '[[MediaWiki:Gadget-aWa.js/documentation|aWa]]: Archiving discussions from [[' + prettyPageName + ']]',
						content: '{{archive-top|' + forumId + '|' + item.result +
							'|link=Special:PermanentLink/' + mw.config.get('wgRevisionId') + '#' + item.sectid + '}}\n' +
							secttext.trimRight() +
							'\n{{archive-bottom}}'
					});

					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: "[[MediaWiki:Gadget-aWa.js/documentation|aWa]]: Adding link to a deletion 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 = '[[MediaWiki:Gadget-aWa.js/documentation|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 = "[[MediaWiki:Gadget-aWa.js/documentation|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('editToken'),
					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('editToken'),
									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';
			if (results.passed) {
				var resultText;

				for (var node = headerNode.parentNode.nextSibling; node; node = node.nextSibling) {
					if (node.tagName === headerNode.parentNode.tagName)
						break;
					if (!node.getElementsByTagName)
						continue;
					if (node.getElementsByTagName(headerNode.parentNode.tagName).length)
						break;
					var bs = node.getElementsByTagName('b');
					
					for (var j = 0; j < bs.length; ++j) {
						var resultText = bs[j].textContent || bs[j].innerText;
						if (/no\s+consensus/i.test(bs[j].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].getElementsByTagName('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].getElementsByTagName('a'), function (item) {
			var title = getPageFromUrl(item.href);
			return title && (title.getNamespaceId() >= 0);
		}));
	}

	var nodes = collectUntilH2(headers[i].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);
}

})();