Jump to content

User:Xoak/common.js

From Wikipedia, the free encyclopedia
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
importScript('User:Evad37/OneClickArchiver.js'); // Backlink: [[User:Evad37/OneClickArchiver.js]]
importScript('User:Shubinator/DYKcheck.js'); //DYKcheck tool
/**
 * DYK-nomination-wizard
 * 
 * Wizard to easily create DYK nominations
 * 
 * Loaded on [[Wikipedia:Did you know/Create new nomination]]
 * using [[mw:Snippets/Load JS and CSS by URL]]
 * 
 * Author: [[User:SD0001]]
 *
 */

/* jshint maxerr: 999 */
// <nowiki>

var dyk = {};
window.dyk = dyk;

$.when(
	mw.loader.using('ext.gadget.morebits'),
	$.ready
).then(function() {
	if (mw.config.get('wgPageName') !== 'Wikipedia:Did_you_know/Create_new_nomination' ||
		mw.config.get('wgAction') !== 'view') return;
	dyk.callback();
});

var NOMPAGE_PREFIX = 'Template:Did you know nominations/';
var NOMINATIONS_PAGE = 'Template talk:Did you know';

dyk.advert = ' ([[Wikipedia:Did you know/Nomination wizard|DYK-wizard]])';

// Calculating prose character count, code based on [[User:Shubinator/DYKcheck.js]] and [[User:Dr pda/prosesize.js]]
dyk.proseCharCountInNode = function(el) {
	var charCount = 0;
	for (var i = 0; i < el.childNodes.length; i++) {
		if (el.childNodes[i].nodeName === '#text') {
			charCount += el.childNodes[i].nodeValue.length;
		} else if (el.childNodes[i].className !== 'reference' && // exclude references [1], [2], etc
			el.childNodes[i].className.indexOf('emplate') === -1 && // exclude inline templates
			el.childNodes[i].id !== 'coordinates' //exclude geocoords
		) {
			charCount += dyk.proseCharCountInNode(el.childNodes[i]);
		}
	}
	return charCount;
};

dyk.proseCharCount = function ($html) {
	var charCount = 0, readable_text = '';
	$html.find('> p').each(function(i, el) {
		charCount += dyk.proseCharCountInNode(el);
		readable_text += el.textContent.trim();
	});
	var wordCount = readable_text.split(/\s/).length;

	var prosesizediv = document.getElementById('dyk-prosesize');
	prosesizediv.textContent = 'Prose size: ' + wordCount + ' words, ' + charCount + ' characters';
	if (charCount < 1500) {
		prosesizediv.style.color = 'red';
		Morebits.quickForm.element.generateTooltip(prosesizediv, {
			tooltip: 'Article must have at least 1500 characters of prose to be eligible for DYK'
		});
	} else {
		prosesizediv.style.color = 'green';
	}
};

dyk.updateProseSize = function (form, article) {
	if (!article) return;
	// Speedy prose size calculation faster without API call if we were invoked from the article
	try { // Just in case of issues with window.opener use
		if (window.opener && opener.document.title === article + ' - Wikipedia') { // ideally should compare with MediaWiki:Pagetitle
			var $html = $(opener.document.body).find('.mw-parser-output');
			dyk.proseCharCount($html);
			return;
		}
	} catch(e) { console.log(e); }
	$('#dyk-prosesize').text('Prose size: calculating...').css('color', 'black');
	new mw.Api().get({
		action: 'parse',
		page: article,
		prop: 'text',
		formatversion: 2
	}).then(function (json) {
		if (article !== form.article.value) {
			$('#dyk-prosesize').text('');
			return; // input was changed, ignore this response
		}
		var $html = $(json.parse.text);
		dyk.proseCharCount($html);
	}).catch(function (code, error) {
		if (code === 'missingtitle') {
			$('#dyk-prosesize').text('Article does not exist!').css('color', 'red');
		} else {
			$('#dyk-prosesize').text(''); // empty it
			console.error(error);
		}
	});
};

dyk.callback = function dykMainCallback() {

	var form = new Morebits.quickForm( dyk.evaluate );

	form.append({
		type: 'div',
		style: 'float: right; font-style: italic;',
		id: 'dyk-prosesize'
	});

	form.append({
		type: 'input',
		name: 'article',
		label: 'Article:',
		value: mw.util.getParamValue('article') || '',
	});

	form.append({
		type: 'select',
		name: 'status',
		label: 'Status: ',
		list: [
			{ type: 'option', label: 'Created', value: 'new', selected: true },
			{ type: 'option', label: '5x expanded', value: 'expanded' },
			{ type: 'option', label: 'Converted from redirect', value: 'redirect' },
			{ type: 'option', label: 'Moved to mainspace', value: 'mainspace' },
			{ type: 'option', label: 'Improved to GA', value: 'GA' }
		],
		style: 'margin-right: 10px'
	});

	form.append({
		type: 'input', // converted to date input below
		label: 'on date: ',
		name: 'date',
		tooltip: 'The date on which creation/expansion began. Must be within the past week. ',
		value: new Date().toISOString().slice(0, 10),  // YYYY-MM-DD format
		event: dyk.dateCheck // for the benefit of browsers that don't support a datepicker for date fields
	});

	form.append({
		type: 'div',
		id: 'dyk-date-check',
		style: 'color: red'
	});

	form.append({
		type: 'checkbox',
		list: [ {
			name: 'multiarticle',
			label: 'Multi-article nomination',
			subgroup: {
				type: 'input',
				label: 'Article 2: ',
				name: 'article2'
			},
			event: function addPlusbuttonArticle() {
				if (document.getElementById('dyk-plusarticle') === null) { // happens only the first time
					var plusbutton = dyk.createPlusButton('dyk-plusarticle');
					plusbutton.addEventListener('click', function(e) {
						var anchor = e.target.parentElement;
						var num = parseInt(anchor.previousElementSibling.previousElementSibling.textContent.slice('Article '.length)) + 1;

						var newDiv = new Morebits.quickForm.element({
							type: 'input',
							label: 'Article ' + num + ': ',
							name: 'multiarticle.article' + num
						}).render();
						$(anchor).parent().after(newDiv);
						newDiv.append(plusbutton);
						newDiv.querySelector('input').focus();
					});
					$(result).find('[name="multiarticle.article2"]').after(plusbutton);
				}
			},
		} ]
	});


	form.append({
		type: 'textarea',
		name: 'hook',
		label: 'Hook: ',
		tooltip: 'Should be concise, not more than 200 characters. See WP:DYKHOOK for guidelines. Do wikilink words in the hook and bold the link to the DYK article(s).',
		value: '... that ',
		className: 'dyk-hook'
	});
	form.append({
		type: 'textarea',
		name: 'source',
		label: 'Source: ',
		className: 'dyk-source',
		tooltip: 'Source for the hook. You are strongly encouraged to quote the source text supporting the hook" (and [link] the source, or cite it briefly without using citation templates)'
	});

	form.append({
		type: 'button',
		label: 'Add ALT hook',
		name: 'anotherhook',
		event: function addAnotherHook(e) {
			var span = e.target.parentElement;
			var prevnum = parseInt($(span).prev().find('textarea').attr('name').slice('source'.length));
			var num = isNaN(prevnum) ? 1 : prevnum + 1;
			var xh = new Morebits.quickForm.element({
				type: 'textarea',
				name: 'ALT' + num,
				label: 'ALT' + num + ' hook: ',
				value: '... that ',
				className: 'dyk-hook'
			});
			var xs = new Morebits.quickForm.element({
				type: 'textarea',
				name: 'source' + num,
				label: 'Source: ',
				className: 'dyk-source'
			});
			$(span).before(xh.render(), xs.render());

			dyk.txtareaModifications(result['ALT' + num], 'hook');
			dyk.txtareaModifications(result['source' + num], 'source');
		}
	});

	form.append({
		type: 'input',
		name: 'author',
		label: 'Author: ',
		value: mw.config.get('wgUserName'),
		tooltip: 'If nominating an article created by another editor, change this value',
		style: 'margin-top: 8px'
	});
	form.append({
		type: 'checkbox',
		list: [ {
			name: 'multiauthor',
			label: 'Add additional authors',
			subgroup: {
				type: 'input',
				label: 'Author 2: ',
				name: 'author2'
			},
			event: function addPlusbuttonAuthor() {
				if (document.getElementById('dyk-plusauthor') === null) {
					var plusbutton = dyk.createPlusButton('dyk-plusauthor');
					plusbutton.addEventListener('click', function(e) {
						var anchor = e.target.parentElement;
						var num = parseInt(anchor.previousElementSibling.previousElementSibling.textContent.slice('Author '.length)) + 1;

						var newDiv = new Morebits.quickForm.element({
							type: 'input',
							label: 'Author ' + num + ': ',
							name: 'multiauthor.author' + num
						}).render();
						$(anchor).parent().after(newDiv);
						newDiv.append(plusbutton);
						newDiv.querySelector('input').focus();
					});
					$(result).find('[name="multiauthor.author2"]').after(plusbutton);
				}
			}
		} ]
	});

	form.append({
		type: 'checkbox',
		list: [ {
			name: 'img',
			label: 'Include image',
			tooltip: 'Images must be free, and used in the article. See WP:DYKIMG',
			subgroup: [ {
				type: 'input',
				label: 'Image name: ',
				name: 'imgname',
				size: '50px'
			}, {
				type: 'input',
				label: 'Image caption: ',
				name: 'imgcaption',
				size: '60px'
			} ]
		} ],
		event: function(e) {
			if (e.target.checked) {
				// Add a datalist for image field populated with images used in article
				$(result['img.imgname']).attr('list', 'dyk-img-list').after(
					$('<datalist>').attr('id', 'dyk-img-list')
				);
				new mw.Api().get({
					"action": "query",
					"format": "json",
					"prop": "images",
					"titles": result.article.value,
					"formatversion": "2"
				}).then(function(data) {
					if (data.query.pages[0].images) {
						data.query.pages[0].images.forEach(function(img) {
							$('#dyk-img-list').append($('<option>').attr('value', img.title));
						});
					}
				});
			}
		}
	});

	form.append({
		type: 'input',
		name: 'qpq',
		label: 'Reviewed: ',
		tooltip: 'DYK nomination you reviewed. This is compulsory for editors with 5+ DYK credits (QPQ requirement). You can fill this after you make the nomination as well.',
		size: '50px',
		value: NOMPAGE_PREFIX
	});
	form.append({
		type: 'button',
		label: 'find articles to review',
		event: function() {
			window.open('//en.wikipedia.org/wiki/' + mw.util.wikiUrlencode(NOMINATIONS_PAGE) + '#Nominations');
		}
	});

	form.append({
		type: 'textarea',
		name: 'comments',
		label: 'Comments ',
		tooltip: 'Any additional comments (optional). Do not include signature.',
		className: 'dyk-comments',
		style: 'margin-bottom: 5px'
	});

	form.append( { type: 'div', id: 'dyk-previewbox', style: 'display: none' } );
	form.append( { type: 'button', label: 'Submit', className: 'dyk-submit mw-ui-button mw-ui-progressive' } );
	form.append( { type: 'button', label: 'Preview', className: 'dyk-preview mw-ui-button', style: 'margin-left: 5px' } );

	// footer links:
	form.append({
		type: 'div',
		style: 'float: right; font-size: smaller;', //text-decoration: italic;',
		label: $('<span>')
			.append(
				Morebits.createHtml([
					'[[w:WP:DYKRULES|DYK rules]]',
					'[[w:Wikipedia talk:Did you know/Nomination wizard|Give feedback]]'
				].join(' &bull; '))
			).get()
	});

	var result = form.render();

	// Attach to the page, #dyk-wizard-container is provided by the wikitext
	$('#dyk-wizard-container').empty().append(result);

	dyk.updateProseSize(result, mw.util.getParamValue('article'));

	$(result).find('.dyk-preview').on('click', function() {
		// |result| is defined below
		result.previewer.beginRender(
			'{{hatnote|This is only a preview. Your nomination has not yet been saved!}}\n' +
			dyk.getDiscussionWikitext(result),
			NOMPAGE_PREFIX + result.article.value
		);
	});
	$(result).find('.dyk-submit').on('click', function () {
		dyk.evaluate(result);
	});

	result.previewer = new Morebits.wiki.preview(document.getElementById('dyk-previewbox'));

	dyk.txtareaModifications(result.hook, 'hook');
	dyk.txtareaModifications(result.source, 'source');
	dyk.txtareaModifications(result.comments, 'comments');

	// morebits should really allow a postRender hook for quickform elements ...

	// Style the div (instead of the label)
	Morebits.quickForm.getElementContainer(result.img).style.margin = '10px 0';

	// remove the awkward lack of alignment of text input fields with other fields
	Morebits.quickForm.getElementContainer(result.author).style.marginLeft = '4px';
	Morebits.quickForm.getElementContainer(result.qpq).style.marginLeft = '4px';

	mw.util.addCSS(
		'form.quickform div textarea.dyk-hook { font-size: 125%; height: 38px; }' +
		'form.quickform div textarea.dyk-source { font-size: 110%; height: 35px; }' +
		'form.quickform div textarea.dyk-comments { font-size: 125%; height: 35px; }'
		//'form.quickform div.dyk-source { display: table-row; }' +
		//'form.quickform div.dyk-source label { display: table-cell; vertical-align: middle; }' +
		//'div.dyk-source > textarea { font-size: 110%; height: 19px; }' +
		//'html form.quickform div textarea.dyk-source { font-size: 110%; height: 35px; }'
	);

	// Automatically scale up textarea to fit all text
	$('textarea').on('keyup keypress', function() {
		$(this).height(0);
		$(this).height(Math.max(this.scrollHeight, 40));
	});

	window.onbeforeunload = function(event) {
		event.preventDefault();
		return event.returnValue = "Are you sure you want to leave without submitting? (Click \"Submit\" to post)";
	};

	mw.loader.using('mediawiki.widgets').then(function () {
		var $input = $('.quickform input[name=article]');
		var $div = $(Morebits.quickForm.getElementContainer($input[0]));
		var $label = $div.find('label');
		var widget = new mw.widgets.TitleInputWidget({
			namespace: 0,
			name: $input.attr('name'),
			value: $input.val(),
		});

		// OOUI widgets are always <div>, to get a div to display
		// in the same line as the label, we have to use a table
		$div.replaceWith(
			$('<table>').append(
				$('<tbody>').append(
					$('<tr>').append(
						$('<td>').append($label),
						$('<td>').append(widget.$element)
					)
				)
			)
		);

		// update $input reference
		$input = $('.quickform input[name=article]');
		$input.on('blur', function () {
			dyk.updateProseSize(result, $input.val());
		});
	});

	// Use date input type
	$('.quickform [name=date]').attr('type', 'date').on('change', dyk.dateCheck);

	// Merge status and date div to one line
	$(Morebits.quickForm.getElementContainer(result.status))
		.append($(Morebits.quickForm.getElementContainer(result.date)).children());

	$(Morebits.quickForm.getElementContainer(result.multiarticle)).css('margin-top', '10px');

};

dyk.dateCheck = function dykDateCheck(e) {
	var checkElem = document.getElementById('dyk-date-check');
	var date = new Date(e.target.value);
	var curDate = new Date();
	var diff = curDate.getTime() - date.getTime();
	if (date.toString() === 'Invalid date' || diff < 0)  {
		checkElem.textContent = 'Invalid date' + (diff < 0 ? '. Back from the future, are you?' : '');
		checkElem.style.color = 'red';
		return;
	}
	var diffdays = diff/(1000*60*60*24);
	if (diffdays >= 12) {
		$(checkElem).html(Morebits.createHtml('Date must be within the past week, see [[WP:DYK#New]]'));
		checkElem.style.color = 'red';
	} else if (diffdays >= 8) {
		checkElem.textContent = 'Possibly ineligible as date is not within the past week';
		checkElem.style.color = '#8f8946';
	} else {
		checkElem.textContent = '';
	}
};

dyk.txtareaModifications = function dykTxtareaModifications(txtarea, type) {
	var $txtarea = $(txtarea);

	if (type === 'source') {
		// var width = txtarea.parentElement.previousElementSibling.offsetWidth - txtarea.previousElementSibling.offsetWidth;
		// txtarea.style.width = width + 'px';
		txtarea.previousElementSibling.style.borderTop = 'none';
		txtarea.previousElementSibling.style.marginTop = '0';
	}

	else if (type === 'comments') {
		txtarea.previousElementSibling.style.borderTop = 'none';
	}

	else if (type === 'hook') {
		// Add character counter
		var stdiv = document.createElement('div');
		stdiv.style.float = 'right';
		stdiv.style.fontWeight = 'normal';
		$txtarea.prev().append(stdiv);

		$txtarea.on('keyup', function updateCharCount() {
			var len = this.value
				.replace(/^\.\.\. ?/, '') // remove ... in the beginning
				.replace(/'''/g, '') // remove bold syntax
				.replace(/''(\(.*?\))'' /, '') // remove italic text in brackets
				.replace(/''/g, '') // remove any remaining italic syntax
				.replace(/\[/g, '\1').replace(/\]/g, '\2') // remove link syntax and piped part - step 1
				.replace(/\2\2/g, '') // step 2
				.replace(/\1\1(?:[^\1\2]*?\|)?/g, '') // step 3
				.length;
			stdiv.textContent = len + ' characters';
			if (len > 200) {
				stdiv.style.color = 'red';
			} else {
				stdiv.style.color = '#222222'; // default morebits color
			}
		});
		$txtarea.trigger('keyup');
	}

};

dyk.createPlusButton = function(id) {

	var a = document.createElement('a');
	a.id = id;
	a.style.paddingLeft = '5px';
	var img = document.createElement('img');
	img.setAttribute('src', '//upload.wikimedia.org/wikipedia/commons/thumb/b/b9/Nuvola_action_edit_add.svg/20px-Nuvola_action_edit_add.svg.png');
	img.setAttribute('alt', 'add another');
	img.setAttribute('title', 'Add another');
	a.append(img);
	return a;
};


dyk.getDiscussionWikitext = function dykGetDiscussionWikitext(form) {
	var params = Morebits.quickForm.getInputData(form);

	var templatetext = '{{subst:NewDYKnomination';
	var addTemplateParam = function (key, value) {
		templatetext += '\n| ' + key + ' = ' + value;
	};

	dyk.articles = Object.keys(params).filter(function (field) {
		return field.indexOf('article') === 0 && !!params[field];
	}).map(function (field) {
		addTemplateParam(field, params[field]);
		return params[field];
	});

	addTemplateParam('status', params.status);
	addTemplateParam('hook', params.hook + (params.source ? (' <small>Source: ' + params.source + '</small>') : ''));

	Object.keys(params).filter(function (field) {
		return field.indexOf('ALT') === 0 && params[field] !== '' && !/^\.\.\. ?that ?$/.test(params[field]);
	}).forEach(function (field) {
		var n = field.slice('ALT'.length); // string form
		addTemplateParam(field, params[field] +
			(params['source' + n] ? (' <small>Source: ' + params['source' + n] + '</small>') : ''));
	});

	Object.keys(params).filter(function (field) {
		if (field.indexOf('author') === 0 && params[field]) {
			addTemplateParam(field, params[field]);
		}
	});

	addTemplateParam('image', params.imgname || '');
	addTemplateParam('caption', params.imgcaption || '');
	addTemplateParam('comment', params.comments);
	addTemplateParam('reviewed', (params.qpq !== NOMPAGE_PREFIX ? '[[' + params.qpq + ']]' : ''));
	templatetext += '\n}}';

	return templatetext;
};

dyk.evaluate = function dykEvaluate(form) {
	var article = form.article.value;
	var date = form.date.value;

	// Validation
	if (!date) {
		alert('Please specify the date as of which creation/expansion has been completed');
		return;
	}
	if (form.hook.value === '') {
		alert('Please specify the hook for DYK nomination');
		return;
	}

	var broken = false; // flag
	var problem; // start or end
	var problemhook;
	var sourcewarning = false;

	$(form).find('.dyk-hook').each(function(i, e) {
		var isGiven = e.value !== '' && !/^\.\.\. ?that ?$/.test(e.value);
		if (!isGiven) return true; // continue
		e.value = e.value.trim();
		if (e.value.indexOf('... that ') !== 0) {
			problem = 'start';
			problemhook = e.name;
			broken = true;
			return false; // break
		}
		if (e.value.slice(-1) !== '?') {
			problem = 'end';
			problemhook = e.name;
			broken = true;
			return false;
		}
		if ($(e).parent().next().find('textarea')[0].value === '') {
			sourcewarning = true;
		}
	});

	if (broken) {
		alert('Hook ' + (problemhook === 'hook' ? '' : problemhook) + ' must ' + (problem === 'start' ? 'start with "... that "' : 'end with a question mark (?)'));
		return;
	}

	// Date error handling, also done above for on keyup in input field
	date = new Morebits.date(date);
	var curDate = new Morebits.date();
	var diff = curDate.getTime() - date.getTime();
	if (date.toString() === 'Invalid date' || diff < 0)  {
		alert("Please specify a valid date");
		return;
	}
	var diffdays = diff/(1000*60*60*24);
	if (diffdays >= 12) {
		alert('The date specified is well outside the past week, and hence the article is ineligible for DYK, see WP:DYK#New');
		return;
	}

	var prosesizewarn = $('#dyk-prosesize').css('color') === "rgb(255, 0, 0)";
	if (prosesizewarn && !confirm('This article has a readable prose size of less than 1500 characters. \n\nWhile you may still nominate it for DYK, it may be rejected unless you expand it to more than 1500 characters after the nomination. \n\nClick OK to continue with the nomination.' )) {
		return;
	}

	if (sourcewarning && !confirm('You have not specified the source for each hook. Are you sure you want to continue?')) {
		return;
	}

	var templatetext = dyk.getDiscussionWikitext(form);

	window.onbeforeunload = function () {};
	Morebits.status.init(form);

	Morebits.wiki.actionCompleted.redirect = NOMINATIONS_PAGE + '#' + dyk.articles.join(', ');
	Morebits.wiki.actionCompleted.notice = 'Completed';
	Morebits.wiki.api.setApiUserAgent('[[w:en:MediaWiki:DYK-nomination-wizard.js]]');

	var nompage = new Morebits.wiki.page(NOMPAGE_PREFIX + article, 'Creating nomination page');
	nompage.setAppendText(templatetext);
	nompage.setCreateOption('createonly');
	nompage.setWatchlist(true);
	nompage.setEditSummary('Creating DYK nomination for [[' + dyk.articles.join(']], [[') + ']]' + dyk.advert);
	nompage.append(function onNominationSuccess() {

		var dykpage = new Morebits.wiki.page(NOMINATIONS_PAGE, 'Adding nomination to ' + NOMINATIONS_PAGE);
		dykpage.load(function addNomToTTDYK(dykpage) {
			var pageText = dykpage.getPageText();
			var todaysHeader = 'Articles created/expanded on ' + date.getUTCMonthName() + ' ' + date.getUTCDate();
			var re = new RegExp('===' + todaysHeader + '===\n<!--.*?-->');
			var newPageText = pageText.replace(re, '$&\n{{' + NOMPAGE_PREFIX + article + '}}');
			if (pageText === newPageText) {
				var linknode = document.createElement('a');
				linknode.setAttribute("href", mw.util.getUrl("Wikipedia:Did you know/Nomination wizard/Fixing nomination"));
				linknode.appendChild(document.createTextNode('Repair nomination'));
				dykpage.getStatusElement().error(['Could not find the target spot for the nomination. Please see: ', linknode, '.']);
				return;
			}
			dykpage.setPageText(newPageText);
			dykpage.setEditSummary('/* ' + todaysHeader + ' */ ' + 'Adding [[' + NOMPAGE_PREFIX + article + ']]' + dyk.advert);
			dykpage.setMaxConflictRetries(3);
			dykpage.save();
		});

		dyk.articles.forEach(function transcludeOnTalk(page) {
			var talkpagename = 'Talk:' + page;
			var talkpage = new Morebits.wiki.page(talkpagename, 'Transcluding nomination on ' + talkpagename);
			talkpage.setAppendText('\n\n==Did you know nomination==\n{{' + NOMPAGE_PREFIX + article + '}}\n');
			talkpage.setEditSummary('Nominated for DYK, see [[' + NOMPAGE_PREFIX + article + ']]' + dyk.advert);
			talkpage.setCreateOption('recreate');
			talkpage.setWatchlist(window.DYKH_watchlistTalkPage || 'preferences');
			talkpage.append();
		});

	}, function onNominationFailure() {
		Morebits.status.printUserText(templatetext, 'Arrgh :( Something bad happened. Your DYK template wikitext is provided below, which you can copy and use to create [[' + nompage.getPageName() + ']] manually.');
	});

};

// </nowiki>
/**
 * DYK-nomination-wizard
 * 
 * Wizard to easily create DYK nominations
 * 
 * Loaded on [[Wikipedia:Did you know/Create new nomination]]
 * using [[mw:Snippets/Load JS and CSS by URL]]
 * 
 * Author: [[User:SD0001]]
 *
 */

/* jshint maxerr: 999 */
// <nowiki>

var dyk = {};
window.dyk = dyk;

$.when(
	mw.loader.using('ext.gadget.morebits'),
	$.ready
).then(function() {
	if (mw.config.get('wgPageName') !== 'Wikipedia:Did_you_know/Create_new_nomination' ||
		mw.config.get('wgAction') !== 'view') return;
	dyk.callback();
});

var NOMPAGE_PREFIX = 'Template:Did you know nominations/';
var NOMINATIONS_PAGE = 'Template talk:Did you know';

dyk.advert = ' ([[Wikipedia:Did you know/Nomination wizard|DYK-wizard]])';

// Calculating prose character count, code based on [[User:Shubinator/DYKcheck.js]] and [[User:Dr pda/prosesize.js]]
dyk.proseCharCountInNode = function(el) {
	var charCount = 0;
	for (var i = 0; i < el.childNodes.length; i++) {
		if (el.childNodes[i].nodeName === '#text') {
			charCount += el.childNodes[i].nodeValue.length;
		} else if (el.childNodes[i].className !== 'reference' && // exclude references [1], [2], etc
			el.childNodes[i].className.indexOf('emplate') === -1 && // exclude inline templates
			el.childNodes[i].id !== 'coordinates' //exclude geocoords
		) {
			charCount += dyk.proseCharCountInNode(el.childNodes[i]);
		}
	}
	return charCount;
};

dyk.proseCharCount = function ($html) {
	var charCount = 0, readable_text = '';
	$html.find('> p').each(function(i, el) {
		charCount += dyk.proseCharCountInNode(el);
		readable_text += el.textContent.trim();
	});
	var wordCount = readable_text.split(/\s/).length;

	var prosesizediv = document.getElementById('dyk-prosesize');
	prosesizediv.textContent = 'Prose size: ' + wordCount + ' words, ' + charCount + ' characters';
	if (charCount < 1500) {
		prosesizediv.style.color = 'red';
		Morebits.quickForm.element.generateTooltip(prosesizediv, {
			tooltip: 'Article must have at least 1500 characters of prose to be eligible for DYK'
		});
	} else {
		prosesizediv.style.color = 'green';
	}
};

dyk.updateProseSize = function (form, article) {
	if (!article) return;
	// Speedy prose size calculation faster without API call if we were invoked from the article
	try { // Just in case of issues with window.opener use
		if (window.opener && opener.document.title === article + ' - Wikipedia') { // ideally should compare with MediaWiki:Pagetitle
			var $html = $(opener.document.body).find('.mw-parser-output');
			dyk.proseCharCount($html);
			return;
		}
	} catch(e) { console.log(e); }
	$('#dyk-prosesize').text('Prose size: calculating...').css('color', 'black');
	new mw.Api().get({
		action: 'parse',
		page: article,
		prop: 'text',
		formatversion: 2
	}).then(function (json) {
		if (article !== form.article.value) {
			$('#dyk-prosesize').text('');
			return; // input was changed, ignore this response
		}
		var $html = $(json.parse.text);
		dyk.proseCharCount($html);
	}).catch(function (code, error) {
		if (code === 'missingtitle') {
			$('#dyk-prosesize').text('Article does not exist!').css('color', 'red');
		} else {
			$('#dyk-prosesize').text(''); // empty it
			console.error(error);
		}
	});
};

dyk.callback = function dykMainCallback() {

	var form = new Morebits.quickForm( dyk.evaluate );

	form.append({
		type: 'div',
		style: 'float: right; font-style: italic;',
		id: 'dyk-prosesize'
	});

	form.append({
		type: 'input',
		name: 'article',
		label: 'Article:',
		value: mw.util.getParamValue('article') || '',
	});

	form.append({
		type: 'select',
		name: 'status',
		label: 'Status: ',
		list: [
			{ type: 'option', label: 'Created', value: 'new', selected: true },
			{ type: 'option', label: '5x expanded', value: 'expanded' },
			{ type: 'option', label: 'Converted from redirect', value: 'redirect' },
			{ type: 'option', label: 'Moved to mainspace', value: 'mainspace' },
			{ type: 'option', label: 'Improved to GA', value: 'GA' }
		],
		style: 'margin-right: 10px'
	});

	form.append({
		type: 'input', // converted to date input below
		label: 'on date: ',
		name: 'date',
		tooltip: 'The date on which creation/expansion began. Must be within the past week. ',
		value: new Date().toISOString().slice(0, 10),  // YYYY-MM-DD format
		event: dyk.dateCheck // for the benefit of browsers that don't support a datepicker for date fields
	});

	form.append({
		type: 'div',
		id: 'dyk-date-check',
		style: 'color: red'
	});

	form.append({
		type: 'checkbox',
		list: [ {
			name: 'multiarticle',
			label: 'Multi-article nomination',
			subgroup: {
				type: 'input',
				label: 'Article 2: ',
				name: 'article2'
			},
			event: function addPlusbuttonArticle() {
				if (document.getElementById('dyk-plusarticle') === null) { // happens only the first time
					var plusbutton = dyk.createPlusButton('dyk-plusarticle');
					plusbutton.addEventListener('click', function(e) {
						var anchor = e.target.parentElement;
						var num = parseInt(anchor.previousElementSibling.previousElementSibling.textContent.slice('Article '.length)) + 1;

						var newDiv = new Morebits.quickForm.element({
							type: 'input',
							label: 'Article ' + num + ': ',
							name: 'multiarticle.article' + num
						}).render();
						$(anchor).parent().after(newDiv);
						newDiv.append(plusbutton);
						newDiv.querySelector('input').focus();
					});
					$(result).find('[name="multiarticle.article2"]').after(plusbutton);
				}
			},
		} ]
	});


	form.append({
		type: 'textarea',
		name: 'hook',
		label: 'Hook: ',
		tooltip: 'Should be concise, not more than 200 characters. See WP:DYKHOOK for guidelines. Do wikilink words in the hook and bold the link to the DYK article(s).',
		value: '... that ',
		className: 'dyk-hook'
	});
	form.append({
		type: 'textarea',
		name: 'source',
		label: 'Source: ',
		className: 'dyk-source',
		tooltip: 'Source for the hook. You are strongly encouraged to quote the source text supporting the hook" (and [link] the source, or cite it briefly without using citation templates)'
	});

	form.append({
		type: 'button',
		label: 'Add ALT hook',
		name: 'anotherhook',
		event: function addAnotherHook(e) {
			var span = e.target.parentElement;
			var prevnum = parseInt($(span).prev().find('textarea').attr('name').slice('source'.length));
			var num = isNaN(prevnum) ? 1 : prevnum + 1;
			var xh = new Morebits.quickForm.element({
				type: 'textarea',
				name: 'ALT' + num,
				label: 'ALT' + num + ' hook: ',
				value: '... that ',
				className: 'dyk-hook'
			});
			var xs = new Morebits.quickForm.element({
				type: 'textarea',
				name: 'source' + num,
				label: 'Source: ',
				className: 'dyk-source'
			});
			$(span).before(xh.render(), xs.render());

			dyk.txtareaModifications(result['ALT' + num], 'hook');
			dyk.txtareaModifications(result['source' + num], 'source');
		}
	});

	form.append({
		type: 'input',
		name: 'author',
		label: 'Author: ',
		value: mw.config.get('wgUserName'),
		tooltip: 'If nominating an article created by another editor, change this value',
		style: 'margin-top: 8px'
	});
	form.append({
		type: 'checkbox',
		list: [ {
			name: 'multiauthor',
			label: 'Add additional authors',
			subgroup: {
				type: 'input',
				label: 'Author 2: ',
				name: 'author2'
			},
			event: function addPlusbuttonAuthor() {
				if (document.getElementById('dyk-plusauthor') === null) {
					var plusbutton = dyk.createPlusButton('dyk-plusauthor');
					plusbutton.addEventListener('click', function(e) {
						var anchor = e.target.parentElement;
						var num = parseInt(anchor.previousElementSibling.previousElementSibling.textContent.slice('Author '.length)) + 1;

						var newDiv = new Morebits.quickForm.element({
							type: 'input',
							label: 'Author ' + num + ': ',
							name: 'multiauthor.author' + num
						}).render();
						$(anchor).parent().after(newDiv);
						newDiv.append(plusbutton);
						newDiv.querySelector('input').focus();
					});
					$(result).find('[name="multiauthor.author2"]').after(plusbutton);
				}
			}
		} ]
	});

	form.append({
		type: 'checkbox',
		list: [ {
			name: 'img',
			label: 'Include image',
			tooltip: 'Images must be free, and used in the article. See WP:DYKIMG',
			subgroup: [ {
				type: 'input',
				label: 'Image name: ',
				name: 'imgname',
				size: '50px'
			}, {
				type: 'input',
				label: 'Image caption: ',
				name: 'imgcaption',
				size: '60px'
			} ]
		} ],
		event: function(e) {
			if (e.target.checked) {
				// Add a datalist for image field populated with images used in article
				$(result['img.imgname']).attr('list', 'dyk-img-list').after(
					$('<datalist>').attr('id', 'dyk-img-list')
				);
				new mw.Api().get({
					"action": "query",
					"format": "json",
					"prop": "images",
					"titles": result.article.value,
					"formatversion": "2"
				}).then(function(data) {
					if (data.query.pages[0].images) {
						data.query.pages[0].images.forEach(function(img) {
							$('#dyk-img-list').append($('<option>').attr('value', img.title));
						});
					}
				});
			}
		}
	});

	form.append({
		type: 'input',
		name: 'qpq',
		label: 'Reviewed: ',
		tooltip: 'DYK nomination you reviewed. This is compulsory for editors with 5+ DYK credits (QPQ requirement). You can fill this after you make the nomination as well.',
		size: '50px',
		value: NOMPAGE_PREFIX
	});
	form.append({
		type: 'button',
		label: 'find articles to review',
		event: function() {
			window.open('//en.wikipedia.org/wiki/' + mw.util.wikiUrlencode(NOMINATIONS_PAGE) + '#Nominations');
		}
	});

	form.append({
		type: 'textarea',
		name: 'comments',
		label: 'Comments ',
		tooltip: 'Any additional comments (optional). Do not include signature.',
		className: 'dyk-comments',
		style: 'margin-bottom: 5px'
	});

	form.append( { type: 'div', id: 'dyk-previewbox', style: 'display: none' } );
	form.append( { type: 'button', label: 'Submit', className: 'dyk-submit mw-ui-button mw-ui-progressive' } );
	form.append( { type: 'button', label: 'Preview', className: 'dyk-preview mw-ui-button', style: 'margin-left: 5px' } );

	// footer links:
	form.append({
		type: 'div',
		style: 'float: right; font-size: smaller;', //text-decoration: italic;',
		label: $('<span>')
			.append(
				Morebits.createHtml([
					'[[w:WP:DYKRULES|DYK rules]]',
					'[[w:Wikipedia talk:Did you know/Nomination wizard|Give feedback]]'
				].join(' &bull; '))
			).get()
	});

	var result = form.render();

	// Attach to the page, #dyk-wizard-container is provided by the wikitext
	$('#dyk-wizard-container').empty().append(result);

	dyk.updateProseSize(result, mw.util.getParamValue('article'));

	$(result).find('.dyk-preview').on('click', function() {
		// |result| is defined below
		result.previewer.beginRender(
			'{{hatnote|This is only a preview. Your nomination has not yet been saved!}}\n' +
			dyk.getDiscussionWikitext(result),
			NOMPAGE_PREFIX + result.article.value
		);
	});
	$(result).find('.dyk-submit').on('click', function () {
		dyk.evaluate(result);
	});

	result.previewer = new Morebits.wiki.preview(document.getElementById('dyk-previewbox'));

	dyk.txtareaModifications(result.hook, 'hook');
	dyk.txtareaModifications(result.source, 'source');
	dyk.txtareaModifications(result.comments, 'comments');

	// morebits should really allow a postRender hook for quickform elements ...

	// Style the div (instead of the label)
	Morebits.quickForm.getElementContainer(result.img).style.margin = '10px 0';

	// remove the awkward lack of alignment of text input fields with other fields
	Morebits.quickForm.getElementContainer(result.author).style.marginLeft = '4px';
	Morebits.quickForm.getElementContainer(result.qpq).style.marginLeft = '4px';

	mw.util.addCSS(
		'form.quickform div textarea.dyk-hook { font-size: 125%; height: 38px; }' +
		'form.quickform div textarea.dyk-source { font-size: 110%; height: 35px; }' +
		'form.quickform div textarea.dyk-comments { font-size: 125%; height: 35px; }'
		//'form.quickform div.dyk-source { display: table-row; }' +
		//'form.quickform div.dyk-source label { display: table-cell; vertical-align: middle; }' +
		//'div.dyk-source > textarea { font-size: 110%; height: 19px; }' +
		//'html form.quickform div textarea.dyk-source { font-size: 110%; height: 35px; }'
	);

	// Automatically scale up textarea to fit all text
	$('textarea').on('keyup keypress', function() {
		$(this).height(0);
		$(this).height(Math.max(this.scrollHeight, 40));
	});

	window.onbeforeunload = function(event) {
		event.preventDefault();
		return event.returnValue = "Are you sure you want to leave without submitting? (Click \"Submit\" to post)";
	};

	mw.loader.using('mediawiki.widgets').then(function () {
		var $input = $('.quickform input[name=article]');
		var $div = $(Morebits.quickForm.getElementContainer($input[0]));
		var $label = $div.find('label');
		var widget = new mw.widgets.TitleInputWidget({
			namespace: 0,
			name: $input.attr('name'),
			value: $input.val(),
		});

		// OOUI widgets are always <div>, to get a div to display
		// in the same line as the label, we have to use a table
		$div.replaceWith(
			$('<table>').append(
				$('<tbody>').append(
					$('<tr>').append(
						$('<td>').append($label),
						$('<td>').append(widget.$element)
					)
				)
			)
		);

		// update $input reference
		$input = $('.quickform input[name=article]');
		$input.on('blur', function () {
			dyk.updateProseSize(result, $input.val());
		});
	});

	// Use date input type
	$('.quickform [name=date]').attr('type', 'date').on('change', dyk.dateCheck);

	// Merge status and date div to one line
	$(Morebits.quickForm.getElementContainer(result.status))
		.append($(Morebits.quickForm.getElementContainer(result.date)).children());

	$(Morebits.quickForm.getElementContainer(result.multiarticle)).css('margin-top', '10px');

};

dyk.dateCheck = function dykDateCheck(e) {
	var checkElem = document.getElementById('dyk-date-check');
	var date = new Date(e.target.value);
	var curDate = new Date();
	var diff = curDate.getTime() - date.getTime();
	if (date.toString() === 'Invalid date' || diff < 0)  {
		checkElem.textContent = 'Invalid date' + (diff < 0 ? '. Back from the future, are you?' : '');
		checkElem.style.color = 'red';
		return;
	}
	var diffdays = diff/(1000*60*60*24);
	if (diffdays >= 12) {
		$(checkElem).html(Morebits.createHtml('Date must be within the past week, see [[WP:DYK#New]]'));
		checkElem.style.color = 'red';
	} else if (diffdays >= 8) {
		checkElem.textContent = 'Possibly ineligible as date is not within the past week';
		checkElem.style.color = '#8f8946';
	} else {
		checkElem.textContent = '';
	}
};

dyk.txtareaModifications = function dykTxtareaModifications(txtarea, type) {
	var $txtarea = $(txtarea);

	if (type === 'source') {
		// var width = txtarea.parentElement.previousElementSibling.offsetWidth - txtarea.previousElementSibling.offsetWidth;
		// txtarea.style.width = width + 'px';
		txtarea.previousElementSibling.style.borderTop = 'none';
		txtarea.previousElementSibling.style.marginTop = '0';
	}

	else if (type === 'comments') {
		txtarea.previousElementSibling.style.borderTop = 'none';
	}

	else if (type === 'hook') {
		// Add character counter
		var stdiv = document.createElement('div');
		stdiv.style.float = 'right';
		stdiv.style.fontWeight = 'normal';
		$txtarea.prev().append(stdiv);

		$txtarea.on('keyup', function updateCharCount() {
			var len = this.value
				.replace(/^\.\.\. ?/, '') // remove ... in the beginning
				.replace(/'''/g, '') // remove bold syntax
				.replace(/''(\(.*?\))'' /, '') // remove italic text in brackets
				.replace(/''/g, '') // remove any remaining italic syntax
				.replace(/\[/g, '\1').replace(/\]/g, '\2') // remove link syntax and piped part - step 1
				.replace(/\2\2/g, '') // step 2
				.replace(/\1\1(?:[^\1\2]*?\|)?/g, '') // step 3
				.length;
			stdiv.textContent = len + ' characters';
			if (len > 200) {
				stdiv.style.color = 'red';
			} else {
				stdiv.style.color = '#222222'; // default morebits color
			}
		});
		$txtarea.trigger('keyup');
	}

};

dyk.createPlusButton = function(id) {

	var a = document.createElement('a');
	a.id = id;
	a.style.paddingLeft = '5px';
	var img = document.createElement('img');
	img.setAttribute('src', '//upload.wikimedia.org/wikipedia/commons/thumb/b/b9/Nuvola_action_edit_add.svg/20px-Nuvola_action_edit_add.svg.png');
	img.setAttribute('alt', 'add another');
	img.setAttribute('title', 'Add another');
	a.append(img);
	return a;
};


dyk.getDiscussionWikitext = function dykGetDiscussionWikitext(form) {
	var params = Morebits.quickForm.getInputData(form);

	var templatetext = '{{subst:NewDYKnomination';
	var addTemplateParam = function (key, value) {
		templatetext += '\n| ' + key + ' = ' + value;
	};

	dyk.articles = Object.keys(params).filter(function (field) {
		return field.indexOf('article') === 0 && !!params[field];
	}).map(function (field) {
		addTemplateParam(field, params[field]);
		return params[field];
	});

	addTemplateParam('status', params.status);
	addTemplateParam('hook', params.hook + (params.source ? (' <small>Source: ' + params.source + '</small>') : ''));

	Object.keys(params).filter(function (field) {
		return field.indexOf('ALT') === 0 && params[field] !== '' && !/^\.\.\. ?that ?$/.test(params[field]);
	}).forEach(function (field) {
		var n = field.slice('ALT'.length); // string form
		addTemplateParam(field, params[field] +
			(params['source' + n] ? (' <small>Source: ' + params['source' + n] + '</small>') : ''));
	});

	Object.keys(params).filter(function (field) {
		if (field.indexOf('author') === 0 && params[field]) {
			addTemplateParam(field, params[field]);
		}
	});

	addTemplateParam('image', params.imgname || '');
	addTemplateParam('caption', params.imgcaption || '');
	addTemplateParam('comment', params.comments);
	addTemplateParam('reviewed', (params.qpq !== NOMPAGE_PREFIX ? '[[' + params.qpq + ']]' : ''));
	templatetext += '\n}}';

	return templatetext;
};

dyk.evaluate = function dykEvaluate(form) {
	var article = form.article.value;
	var date = form.date.value;

	// Validation
	if (!date) {
		alert('Please specify the date as of which creation/expansion has been completed');
		return;
	}
	if (form.hook.value === '') {
		alert('Please specify the hook for DYK nomination');
		return;
	}

	var broken = false; // flag
	var problem; // start or end
	var problemhook;
	var sourcewarning = false;

	$(form).find('.dyk-hook').each(function(i, e) {
		var isGiven = e.value !== '' && !/^\.\.\. ?that ?$/.test(e.value);
		if (!isGiven) return true; // continue
		e.value = e.value.trim();
		if (e.value.indexOf('... that ') !== 0) {
			problem = 'start';
			problemhook = e.name;
			broken = true;
			return false; // break
		}
		if (e.value.slice(-1) !== '?') {
			problem = 'end';
			problemhook = e.name;
			broken = true;
			return false;
		}
		if ($(e).parent().next().find('textarea')[0].value === '') {
			sourcewarning = true;
		}
	});

	if (broken) {
		alert('Hook ' + (problemhook === 'hook' ? '' : problemhook) + ' must ' + (problem === 'start' ? 'start with "... that "' : 'end with a question mark (?)'));
		return;
	}

	// Date error handling, also done above for on keyup in input field
	date = new Morebits.date(date);
	var curDate = new Morebits.date();
	var diff = curDate.getTime() - date.getTime();
	if (date.toString() === 'Invalid date' || diff < 0)  {
		alert("Please specify a valid date");
		return;
	}
	var diffdays = diff/(1000*60*60*24);
	if (diffdays >= 12) {
		alert('The date specified is well outside the past week, and hence the article is ineligible for DYK, see WP:DYK#New');
		return;
	}

	var prosesizewarn = $('#dyk-prosesize').css('color') === "rgb(255, 0, 0)";
	if (prosesizewarn && !confirm('This article has a readable prose size of less than 1500 characters. \n\nWhile you may still nominate it for DYK, it may be rejected unless you expand it to more than 1500 characters after the nomination. \n\nClick OK to continue with the nomination.' )) {
		return;
	}

	if (sourcewarning && !confirm('You have not specified the source for each hook. Are you sure you want to continue?')) {
		return;
	}

	var templatetext = dyk.getDiscussionWikitext(form);

	window.onbeforeunload = function () {};
	Morebits.status.init(form);

	Morebits.wiki.actionCompleted.redirect = NOMINATIONS_PAGE + '#' + dyk.articles.join(', ');
	Morebits.wiki.actionCompleted.notice = 'Completed';
	Morebits.wiki.api.setApiUserAgent('[[w:en:MediaWiki:DYK-nomination-wizard.js]]');

	var nompage = new Morebits.wiki.page(NOMPAGE_PREFIX + article, 'Creating nomination page');
	nompage.setAppendText(templatetext);
	nompage.setCreateOption('createonly');
	nompage.setWatchlist(true);
	nompage.setEditSummary('Creating DYK nomination for [[' + dyk.articles.join(']], [[') + ']]' + dyk.advert);
	nompage.append(function onNominationSuccess() {

		var dykpage = new Morebits.wiki.page(NOMINATIONS_PAGE, 'Adding nomination to ' + NOMINATIONS_PAGE);
		dykpage.load(function addNomToTTDYK(dykpage) {
			var pageText = dykpage.getPageText();
			var todaysHeader = 'Articles created/expanded on ' + date.getUTCMonthName() + ' ' + date.getUTCDate();
			var re = new RegExp('===' + todaysHeader + '===\n<!--.*?-->');
			var newPageText = pageText.replace(re, '$&\n{{' + NOMPAGE_PREFIX + article + '}}');
			if (pageText === newPageText) {
				var linknode = document.createElement('a');
				linknode.setAttribute("href", mw.util.getUrl("Wikipedia:Did you know/Nomination wizard/Fixing nomination"));
				linknode.appendChild(document.createTextNode('Repair nomination'));
				dykpage.getStatusElement().error(['Could not find the target spot for the nomination. Please see: ', linknode, '.']);
				return;
			}
			dykpage.setPageText(newPageText);
			dykpage.setEditSummary('/* ' + todaysHeader + ' */ ' + 'Adding [[' + NOMPAGE_PREFIX + article + ']]' + dyk.advert);
			dykpage.setMaxConflictRetries(3);
			dykpage.save();
		});

		dyk.articles.forEach(function transcludeOnTalk(page) {
			var talkpagename = 'Talk:' + page;
			var talkpage = new Morebits.wiki.page(talkpagename, 'Transcluding nomination on ' + talkpagename);
			talkpage.setAppendText('\n\n==Did you know nomination==\n{{' + NOMPAGE_PREFIX + article + '}}\n');
			talkpage.setEditSummary('Nominated for DYK, see [[' + NOMPAGE_PREFIX + article + ']]' + dyk.advert);
			talkpage.setCreateOption('recreate');
			talkpage.setWatchlist(window.DYKH_watchlistTalkPage || 'preferences');
			talkpage.append();
		});

	}, function onNominationFailure() {
		Morebits.status.printUserText(templatetext, 'Arrgh :( Something bad happened. Your DYK template wikitext is provided below, which you can copy and use to create [[' + nompage.getPageName() + ']] manually.');
	});

};

// </nowiki>
importScript('User:SD0001/GAN-helper.js'); // Backlink: [[User:SD0001/GAN-helper.js]]
importScript('User:SD0001/GAR-helper.js'); // Backlink: [[User:SD0001/GAR-helper.js]]
mw.loader.load( "https://meta.wikimedia.org/w/index.php?title=User:Zhaofeng_Li/Reflinks.js&action=raw&ctype=text/javascript" );
importScript('User:Ohconfucius/dashes.js'); // Backlink: [[User:Ohconfucius/dashes.js]]
importScript('User:BrandonXLF/HotDefaultSort.js'); // Backlink: [[User:BrandonXLF/HotDefaultSort.js]]
importScript('User:Evad37/rater.js'); // Backlink: [[User:Evad37/rater.js]]
importScript('User:SD0001/StubSorter.js'); // Backlink: [[User:SD0001/StubSorter.js]]
importScript("User:Writ Keeper/Scripts/teahouseUtility.js"); // Gives one-click option to add Teahouse invitation or talkback to a user

importScript("User:Writ Keeper/Scripts/teahouseTalkback.js"); // Adds Talkback reminder when you save an edit at the Teahouse

importScript("User:Writ Keeper/Scripts/teahouseTalkbackLink.js"); // Adds a talkback link to signatures on the Teahouse

importScript("User:Ocaasi/WikiLoveinstallscript.js");// Adds Teahouse Badges to WikiLove
importScript('User:MPGuy2824/MoveToDraft.js'); // Backlink: [[User:MPGuy2824/MoveToDraft.js]]