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.
// <nowiki>

(function() {
	console.log('Running text-reactions.');

	var $textarea, api;

	/**
	 * List of reactions. Each reaction object contains:
	 * - name
	 * - trigger
	 * 		'keypress' (triggered by keyup in the textarea)
	 * 		'word' 	(triggered by entry of a word)
	 * 		'interval' (triggered at regular interval)
	 * 		'paste' (triggered by text insertion via copy-paste or drag-drop)
	 * 		'ref' (triggered when reference is inserted via RefToolbar
	 * - key: (for keypress trigger) key that was pressed
	 * - word: (for word trigger) word that was written
	 * - interval: (for interval trigger) in milliseconds
	 * - react: function that shows the notice/warning if needed.
	 *
	 */
	var textReactions = [
		{
			name: 'keypress-test',
			trigger: 'keypress',
			key: 'e',
			react: function (text, caretPosition) {
				console.log('Hit key "e"');
			}
		},

		{
			name: 'word-test',
			trigger: 'word',
			word: 'test',
			react: function (text, caretPosition) {
				console.log('Hit word "test"');
			}
		},
		{
			name: 'paste-test',
			trigger: 'paste',
			react: function (pasteContent, wikitext) {
				console.log('You pasted: ' + pasteContent);
			}
		},
		{
			name: 'ref-test',
			trigger: 'ref',
			react: function (citeTemplate) {
				var url = citeTemplate.getParam('url');
				// check if url is blacklisted/deprecated/predatory journal, etc
				console.log('You inserted ref: ' + citeTemplate.wikitext);
			}
		},
		{
			name: 'interval-test',
			trigger: 'interval',
			interval: 5000,
			react: function (wikitext) {
				console.log('This is triggered every 5 secs');
			}
		},
		{
			test: 'dabNotification',
			trigger: 'keypress',
			key: ']',
			react: function (wikitext, cursorPosition) {
				// Adapted from https://gerrit.wikimedia.org/r/c/mediawiki/extensions/Disambiguator/+/716439
				var matches = /.*(\[\[([^[\]|]+)(?:\|.*]]|]]))$/.exec(wikitext.slice(0, cursorPosition));
				if (matches) {
					var pageTitle = matches[matches.length - 1].trim();
					var linkWikitext = matches[matches.length - 2];
					return api.get({
						action: 'query',
						titles: pageTitle,
						prop: 'pageprops',
						ppprop: 'disambiguation',
					}).then(function (json) {
						if (json.query.pages && json.query.pages[0].pageprops &&
							Object.prototype.hasOwnProperty.call(json.query.pages[0].pageprops, 'disambiguation')
						) {
							console.log('You linked the disambiguation page ' + pageTitle);
						}
					});
				}
			},
		}
	];


	$.when(
		$.ready,
		mw.loader.using([
			'mediawiki.util', 'mediawiki.api', 'mediawiki.user',
			'jquery.textSelection', 'ext.gadget.libExtraUtil'
		])
	).then(function () {
		if (!['edit', 'submit'].includes(mw.util.getParamValue('action'))) return;

		$textarea = $('#wpTextbox1');
		if (!$textarea.length) return; // probably VE

		api = new mw.Api({
			parameters: {
				formatversion: 2
			}
		});

		var keyReactionsMap = {};
		var wordReactionsMap = {};
		var pasteReactions = [];

		textReactions.forEach(function (tr) {
			if (tr.trigger === 'keypress') {
				keyReactionsMap[tr.key] = (keyReactionsMap[tr.key] || []).concat(tr);
			} else if (tr.trigger === 'word') {
				wordReactionsMap[tr.word] = (wordReactionsMap[tr.word] || []).concat(tr);
			} else if (tr.trigger === 'paste') {
				pasteReactions.push(tr);
			}
		});

		var WORD_DELIMITERS = [' ', '.', ',', ';'];
		var WORD_MATCH_REGEX = /\s(\S+)[ .,;]$/;

		var keyupHandler = function (e) {
			(keyReactionsMap[e.key] || []).forEach(function (tr) {
				tr.react($textarea.textSelection('getContents'), $textarea.textSelection('getCaretPosition'));
			});

			if (WORD_DELIMITERS.includes(e.key)) {
				var content = $textarea.textSelection('getContents');
				var caretPosition = $textarea.textSelection('getCaretPosition');
				var word = WORD_MATCH_REGEX.exec(content.slice(0, caretPosition))[1];
				if (word) {
					(wordReactionsMap[word] || []).forEach(function (tr) {
						tr.react(content, caretPosition);
					});
				}
			}
		};

		var pasteHandler = function (e) {
			var wikitext = $textarea.textSelection('getContents');
			var pasteData = e.originalEvent.clipboardData || // for paste
				e.originalEvent.dataTransfer; // for drag-drop
			var pasteContent = pasteData.getData('text');
			pasteReactions.forEach(function (tr) {
				tr.react(pasteContent, wikitext);
			});
		};

		// HACK: Hijack WikiEditor method used by RefToolbar to insert ref into the textbox
		var origFunc = $.wikiEditor.modules.toolbar.fn.doAction;
		$.wikiEditor.modules.toolbar.fn.doAction = function() {
			// Call the original function
			var args = Array.prototype.slice.call(arguments);
			origFunc.apply($.wikiEditor.modules.toolbar.fn, args);

			// Now do our thing
			var ref = args[1] && args[1].options && args[1].options.post;
			var refContent;
			try {
				refContent = $($.parseXML(ref)).find('ref').text();
			} catch (e) {}
			if (refContent) {
				var citeTemplate = extraJs.parseTemplates(refContent)[0];
				textReactions.filter(function (tr) {
					return tr.trigger === 'ref';
				}).forEach(function (tr) {
					tr.react(citeTemplate);
				});
			}
		};

		// Bind textarea event handlers, with CodeMirror integration
		$textarea.on('keyup', keyupHandler).on('paste drop', pasteHandler);
		mw.hook('ext.CodeMirror.switch').add(function (_enabled, $editor) {
			$textarea = $editor;
			$textarea.off('keyup', keyupHandler).on('keyup', keyupHandler)
				.off('paste drop', pasteHandler).on('paste drop', pasteHandler);
		});

		var intervalReactions = textReactions.filter(function (tr) {
			return tr.trigger === 'interval';
		});
		intervalReactions.forEach(function (tr) {
			setInterval(function () {
				tr.react($textarea.textSelection('getContents'));
			}, tr.interval);
		});

	});

})();

// </nowiki>