MediaWiki:Gadget-NAC.js

Z Wikipedii, wolnej encyklopedii

Uwaga: aby zobaczyć zmiany po opublikowaniu, może zajść potrzeba wyczyszczenia pamięci podręcznej przeglądarki.

  • Firefox / Safari: Przytrzymaj Shift podczas klikania Odśwież bieżącą stronę, lub naciśnij klawisze Ctrl+F5, lub Ctrl+R (⌘-R na komputerze Mac)
  • Google Chrome: Naciśnij Ctrl-Shift-R (⌘-Shift-R na komputerze Mac)
  • Internet Explorer / Edge: Przytrzymaj Ctrl, jednocześnie klikając Odśwież, lub naciśnij klawisze Ctrl+F5
  • Opera: Naciśnij klawisze Ctrl+F5.
/**
 * Autocorrection features in Visual Editor.
 * 
 * Polska instrukcja:
 * https://pl.wikipedia.org/wiki/WP:NAC
 * 
 * Version history and technical docs:
 * https://github.com/Eccenux/veAutocorrect
 * 
 * Authors:
 * Maciej Nux Jaros, Schnark.
 * 
 * Deployed with love using Wikiploy:
 * [[Wikipedia:Wikiploy]]
 * 
 * <nowiki>
 */
/*global mediaWiki, OO, ve*/
(function (mw) {
	"use strict";

	var version = '2.1.9';

	/**
	 * Helpers for defining replacements.
	 */
	class Helpers {
		/**
		 * p-starter
		 * 
		 * Note! The paragraph is not closed, so that it can be used in `from`.
		 * E.g.: `from: p('=z+'),`
		 * 
		 * Normally paragraphs should be closed (see h2).
		 */
		p(text) {
			var textArray = text.split('');
			return [{type: 'paragraph'}].concat(textArray);
		}
		
		/**
		 * Standard header.
		 * 
		 * E.g.: `to: h2('See also'),`
		 */
		h2(text, skipParagraph) {
			var head = [
				{type: 'heading', attributes: {level: 2}},
				...text.split(''),
				{type: '/heading'},
			];
			var p = [
				{type: 'paragraph'},
				{type: '/paragraph'},
			];
			return skipParagraph ? head : head.concat(p);
		}
		
		/**
		 * Inline or block template.
		 * 
		 * Note! Inline templates should be inside a paragraph.
		 * E.g.:
		 * ```
		 * tpl({
				target: {
					href: 'Szablon:Przypisy',
					wt: 'Przypisy'
				},
				//params: {}
			})
			* ```
			*/
		tpl(template, block) {
			var tplType = block ? 'mwTransclusionBlock' : 'mwTransclusionInline';
			return [
				{
					type: tplType,
					attributes: {
						mw: {
							parts: [ { template: template } ]
						}
					}
				},
				{ type: '/' + tplType },
			];
		}
	}

	/**
	 * Autocorrect class (export).
	 */
	var veNuxAutocorrect = {
		version: version,
		
		helpers: new Helpers(),
		
		_ready: false,
		_configs: [],
		
		/**
		 * Add replacemnt rule.
		 *
		 * Examples in documentation.
		 * See also: `autoCorrectFromTo`.
		 */
		addReplacements: function(config) {
			if (this._ready) {
				this._run(config);
			} else {
				this._configs.push(config);
			}
		},
		/**
		 * Alias `addReplacements`.
		 */
		add: function(config) {
			this.addReplacements(config);
		},
		
		_run: function(config) {
			autoCorrectFromTo(config.from, config.to);
		},
		
		_onReady: function() {
			for (var i = 0; i < this._configs.length; i++) {
				this._run(this._configs[i]);
			}
			this._configs = [];
			this._ready = true;
			mw.hook('userjs.veNuxAutocorrect.ready').fire(veNuxAutocorrect, veNuxAutocorrect.helpers);
		},
	};
	mw.hook('userjs.veNuxAutocorrect').fire(veNuxAutocorrect, veNuxAutocorrect.helpers);

	// shorthand for helpers
	var h = veNuxAutocorrect.helpers;

	// Usage info helper
	// This is only for quick death, expected to be re-checked on page reload e.g. from wiki-code editor.
	var usageInfoDone = false;
	/**
	 * Append gadget usage info.
	 * 
	 * Adds documentation page shortcut in summary.
	 */
	function appendUsageInfo() {
		// quick death
		if (usageInfoDone) {
			//console.log('[NAC] appendUsageInfo: quick death');
			return;
		}

		if (!(ve.init && typeof ve.init.target === 'object')) {
			//console.log('[NAC] appendUsageInfo: no target');
			return;
		}

		var target = ve.init.target;
		var myInfo = "[[WP:NAC]]";
		// append if not already
		if (typeof target.initialEditSummary === 'string' && target.initialEditSummary.length) {
			//console.log('[NAC] appendUsageInfo: append?');
			if (target.initialEditSummary.indexOf(myInfo) < 0) {
				//console.log('[NAC] appendUsageInfo: append');
				target.initialEditSummary += ", " + myInfo;
			}
		// create when empty
		} else {
			//console.log('[NAC] appendUsageInfo: create info');
			target.initialEditSummary = myInfo;
		}
		usageInfoDone = true;
	}

	/**
	 * AutoCorrectCommand.
	 * 
	 * Command to replace selected content and place the cursor after it.
	 * 
	 * inherit from ve.ui.Command, and override execute
	 */
	function AutoCorrectCommand (name, content) {
		AutoCorrectCommand.parent.call(this, name);
		this.content = content;
	}

	/**
	 * ReSequence.
	 * 
	 * like ve.ui.Sequence, with the difference that for regular expressions
	 * of the form /foo(bar)/ only the parentheses is used as Range, not the whole expression
	 */
	function ReSequence () {
		ReSequence.parent.apply(this, arguments);
	}

	var customVeClassesReady = false;

	/**
	 * Init classes when ready ve.ui is ready.
	 * 
	 * @returns true if already there.
	 */
	function initCustomVeClasses() {
		// avoid re-run when VE re-opened without reloading page
		if (customVeClassesReady) {
			return true;
		}
		customVeClassesReady = true;
		
		OO.inheritClass(AutoCorrectCommand, ve.ui.Command);
		AutoCorrectCommand.prototype.execute = function (surface) {
			surface.getModel().getFragment().insertContent(this.content).collapseToEnd().select();
			appendUsageInfo();
			return true;
		};
		
		OO.inheritClass(ReSequence, ve.ui.Sequence);
		ReSequence.prototype.match = function (data, offset, plaintext) {
			var execResult;
			if (this.data instanceof RegExp) {
				execResult = this.data.exec(plaintext);
				return execResult && new ve.Range(offset - execResult[1].length, offset);
			}
			return ReSequence.parent.prototype.match.apply(this, arguments);
		};
	}

	/**
	 * autoCorrectFromTo.
	 * 
	 * when the user enters "from" change it to "to"
	 * @param from can be a string, a regular expression of the form /foo(bar)/ or an array of data
	 * @param to can be a string or an array of data
	 */
	function autoCorrectFromTo (from, to) {
		//get a unique name, we use it for both the command and the sequnce
		var name = 'nuxAutoCorrectCommand-' + (autoCorrectCommandCount++);
		//create and register the command
		ve.ui.commandRegistry.register(
			new AutoCorrectCommand(name, to)
		);
		//let the surface know that there is a new command that can be executed
		ve.init.target.getSurface().commands.push(name);
		//create and register the sequence
		ve.ui.sequenceRegistry.register(
			new ReSequence(/*sequence*/ name, /*command*/ name, from, 0, true)
		);
	}
	var autoCorrectCommandCount = 0;

	/**
	 * Init commands.
	 */
	function initAutoCorrect (lang, wiki) {
		//define what should be autocorrected

		//for all languages and projects
		autoCorrectFromTo('--', '–');
		autoCorrectFromTo('–-', '—');
		autoCorrectFromTo('...', '…');
		autoCorrectFromTo('<<', '«');
		autoCorrectFromTo('>>', '»');
		autoCorrectFromTo('->', '→');
		autoCorrectFromTo(/(?:^|[^\d])(1\/2 )$/, '½ ');
		autoCorrectFromTo(/(?:^|[^\d])(1\/4 )$/, '¼ ');
		autoCorrectFromTo(/(?:^|[^\d])(3\/4 )$/, '¾ ');
		autoCorrectFromTo('+-', '±');
		/*
		autoCorrectFromTo(/\d(')/, '′');
		autoCorrectFromTo(/\D(')/, '’');
		autoCorrectFromTo(/\d(")/, '″');
		*/
		
		//depending on the content language
		switch (lang) {
			case 'de':
				autoCorrectFromTo(/(?:^|[( \n])(")$/, '„');
				autoCorrectFromTo(/[^\d( \n](")$/, '“');
			break;
			// disabled per en.wiki policies [[:en:MOS:PUNCT]]
			/*
			case 'en':
				autoCorrectFromTo(/(?:^|[( \n])(")$/, '“');
				autoCorrectFromTo(/[^\d( \n](")$/, '”');
			break;
			*/
			case 'pl':
				autoCorrectFromTo(/(?:^|[( \n])(")$/, '„');
				autoCorrectFromTo(/[^\d( \n](")$/, '”');
			break;
		}
		
		//depending on the wiki
		/*jshint onecase: true*/
		switch (wiki) {
			case 'dewiki':
				autoCorrectFromTo([{type: 'paragraph'}, '=', 'w'], [
					{type: 'heading', attributes: {level: 2}},
					'W', 'e', 'b', 'l', 'i', 'n', 'k', 's',
					{type: '/heading'},
					{type: 'paragraph'}
				]);
			break;
			case 'plwiki':
				var iso = (new Date()).toISOString();
				var ym = iso.substr(0,7);
				autoCorrectFromTo('{fd',
					h.tpl({
						target: {
							href: 'Szablon:Fakt',
							wt: 'fakt'
						},
						params: {
							'data': {
								wt: ym
							}
						}
					})
				);
			break;
		}
		
		// run custom commands
		veNuxAutocorrect._onReady();
	}

	//we just need to run once the editor is ready
	//don't care about dependencies, they should be fine when activation is complete
	mw.hook('ve.activationComplete').add(function () {
		var alreadyDone = initCustomVeClasses();
		if (!alreadyDone) {
			initAutoCorrect(mw.config.get('wgContentLanguage'), mw.config.get('wgDBname'));
		}
	});

})(mediaWiki);
//</nowiki>