User:Nardog/ExpandContractions.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.
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$.ajax(
	'//tools-static.wmflabs.org/meta/scripts/pathoschild.templatescript.js',
	{ dataType: 'script', cache: true }
).then(function expandContractions() {
	let re;
	let edit = editor => {
		let escaped = [];
		let escape = $0 => `\x7f\x7f\x7f${escaped.push($0) - 1}\x7f\x7f\x7f`;
		let unescapeRe = /\x7f{3}(\d+)\x7f{3}/g;
		let unescape = ($0, $1) => escaped[$1];
		let reps = new Set();
		let makeCallback = (w, c) => ($0, $1) => {
			reps.add(c);
			return $1 + ' ' + w;
		};
		let orig = editor.get();
		let repl = orig
			.replace(re, escape)
			.replace(
				/<!--[^]+?-->|<ref\s[^>]*\/>|<(blockquote|ref|poem)(?:\s?[^>]*)?>[^]+?<\/\1>|\[\[[^\[\]]+?\]\]|["“].+?["”]|(''+).+?\2|^===?\s*(?:References|Sources|Bibliography|Further reading|External links)\s*==[^]+|^=+.+=/gim,
				escape
			)
			.replace(
				/\|\s*(?:explain|filename|image|name|reason|song|title)\d*\s*=[^|}]+?(?=[|}])/gi,
				escape
			)
			.replace(
				/\b([Hh]e|[Ss]he|[Ii]t|[TWtw]hat|[Tt]here)['’]s(?= (?:(?:not|n?ever|n?either|again|all|almost|also|always|just|often|perhaps|quite|rather|sometimes|soon|still|too|(?!(?:ally|anomaly|apply|assembly|belly|bully|butterfly|chilly|comply|costly|cuddly|curly|disassembly|family|firefly|folly|gainly|gnarly|gully|holy|hurly|imply|jelly|lily|lonely|measly|melancholy|monopoly|multiply|oily|panoply|rally|reassembly|rely|reply|sally|scholarly|silly|sully|supply|surly|tally|ugly|unholy|unruly) )[a-z]{2,}ly) )*(?:appeared|become|been|begun|belonged|come|consisted|existed|got|gotten|had|happened|lasted|occurred|remained|seemed)\b)/g,
				makeCallback('has', 's')
			)
			.replace(
				/\b([Hh]e|[Ss]he|[Ii]t|[TWtw]hat|[Tt]here)['’]s\b/g,
				makeCallback('is', 's')
			)
			.replace(/\b([Cc])an['’]t\b/g, '$1annot')
			.replace(/\b([Ww])on['’]t\b/g, '$1ill not')
			.replace(/\b((?![Aa]i)[A-Z]?[a-z]+)n['’]t\b/g, '$1 not')
			.replace(
				/\b([Ww]e|[Hh]e|[Ss]he|[Ii]t|[Tt]hey|[TWtw]hat|[Tt]here)['’]d(?= (?:(?:not|n?ever|n?either|again|all|almost|also|always|just|often|perhaps|quite|rather|sometimes|soon|still|too|(?!(?:ally|anomaly|apply|assembly|belly|bully|butterfly|chilly|comply|costly|cuddly|curly|disassembly|family|firefly|folly|gainly|gnarly|gully|holy|hurly|imply|jelly|lily|lonely|measly|melancholy|monopoly|multiply|oily|panoply|rally|reassembly|rely|reply|sally|scholarly|silly|sully|supply|surly|tally|ugly|unholy|unruly) )[a-z]{2,}ly) )*(?:agreed|arisen|awoken|been|begun|blown|bought|broken|brought|built|caught|chosen|dealt|decreed|disagreed|done|drawn|driven|drunk|eaten|fallen|fed|felt|fled|foreseen|forgiven|forgot|forgotten|fought|found|freed|given|gone|got|gotten|grown|guaranteed|had|heard|held|hidden|hurt|kept|known|learnt|led|left|lost|made|meant|met|misheard|mistaken|misunderstood|overheard|overseen|overtaken|paid|rebuilt|rewritten|ridden|risen|said|sat|seen|sent|shot|shown|shrunk|slept|sold|spent|spoken|stolen|stood|struck|stuck|sung|sworn|taken|taught|thought|thrown|told|torn|undergone|understood|undone|woken|won|worn|written|(?![^aeiou]+ed\b|embed\b)[a-z]+[^e]ed)\b)/g,
				makeCallback('had', 'd')
			)
			.replace(
				/\b([Ww]e|[Hh]e|[Ss]he|[Ii]t|[Tt]hey|[TWtw]hat|[Tt]here)['’]d\b/g,
				makeCallback('would', 'd')
			)
			.replace(/([a-z])['’]re\b/g, '$1 are')
			.replace(/([a-z])['’]ve\b/g, '$1 have')
			.replace(/([a-z])['’]ll\b/g, '$1 will')
			.replace(unescapeRe, unescape)
			.replace(unescapeRe, unescape)
			.replace(unescapeRe, unescape);
		if (repl === orig) {
			mw.notify('No change.');
			return;
		}
		let iw = mw.config.get('wgWikiID') === 'enwiki' ? '' : 'w:en:';
		editor
			.set(repl)
			.appendEditSummary(`expanded contractions using [[${iw}User:Nardog/ExpandContractions|script]]`)
			.options({ minor: true })
			.clickDiff();
		if (reps.size) {
			mw.notify(
				`Replacements include "'${[...reps].join(`" and "'`)}". Review them before saving.`,
				{ type: 'warn' }
			);
		}
	};
	let clicked;
	window.pathoschild.TemplateScript.add([{
		name: 'Expand contractions',
		script: editor => {
			if (clicked) {
				if (re) edit(editor);
				return;
			}
			clicked = true;
			let templates = [
				'Blockquote', 'Cquote', 'Quote frame', 'Rquote', 'Quote box',
				'Poem', 'Poem quote', 'About', 'For', 'Other uses', 'Redirect',
				'Distinguish', 'Main', 'Further', 'See also', 'Lang',
				'Interlanguage link'
			];
			let prefix = mw.config.get('wgFormattedNamespaces')[10] + ':';
			new mw.Api().get({
				action: 'query',
				titles: templates.map(n => prefix + n),
				redirects: 1,
				prop: 'redirects',
				rdprop: 'title',
				rdnamespace: 10,
				rdshow: '!fragment',
				rdlimit: 'max',
				formatversion: 2
			}).always(response => {
				let set = new Set(templates);
				(((response || {}).query || {}).pages || []).forEach(page => {
					[page, ...(page.redirects || [])].forEach(o => {
						set.add(o.title.slice(prefix.length));
					});
				});
				re = new RegExp(
					`\\{\\{\\s*(?:${
						[...set].map(n => {
							n = mw.util.escapeRegExp(n);
							return n[0].toLowerCase() === n[0]
								? n
								: '[' + n[0] + n[0].toLowerCase() + ']' + n.slice(1);
						}).join('|').replace(/ /g, '[ _]')
					})\\s*\\|(?:\\{\\{[^]+?\\}\\}|[^])*?\\}\\}`,
					'g'
				);
				edit(editor);
			});
		}
	}]);
});