Медијавики:Gadget-Shortdesc-helper.js — разлика између измена

С Википедије, слободне енциклопедије
Садржај обрисан Садржај додат
+
+
Ред 75: Ред 75:
'sdh-override-title': 'Замените тренутан кратак опис',
'sdh-override-title': 'Замените тренутан кратак опис',
'sdh-import-label': 'Увези',
'sdh-import-label': 'Увези',
'sdh-import-title': 'Увези кратак опис с Википодатака',
'sdh-import-title': 'Увезите кратак опис с Википодатака',
'sdh-editimport-label': 'Уреди и увези',
'sdh-editimport-label': 'Увези и уреди',
'sdh-editimport-title': 'Уредите и увезите кратак опис с Википодатака',
'sdh-editimport-title': 'Увезите и уредите кратак опис с Википодатака',
'sdh-export-label': 'Извези',
'sdh-export-label': 'Извези',
'sdh-export-title': 'Извезите локалан кратак опис на Википодатке',
'sdh-export-title': 'Извезите локалан кратак опис на Википодатке',

Верзија на датум 25. јул 2019. у 17:13

/*  _____________________________________________________________________________
 * |                                                                             |
 * |                    === WARNING: GLOBAL GADGET FILE ===                      |
 * |                  Changes to this page affect many users.                    |
 * | Please discuss changes on the talk page or on [[WT:Gadget]] before editing. |
 * |_____________________________________________________________________________|
 *
 */
/**
 * Shortdesc helper: v3.4.4
 * Documentation at en.wikipedia.org/wiki/User:Galobtter/Shortdesc_helper
 * The documentation includes instructions for using this gadget on other wikis.
 * Shows short descriptions, and allows importing wikidata descriptions, adding descriptions,
 * and easier editing of them by giving buttons and inputbox for doing so.
 * Forked from [[MediaWiki:Gadget-Page descriptions.js]] written by the TheDJ.
*/
'use strict';
window.sdh = window.sdh || {};

/**
 * Set messages using mw.message.
 * window.sdh.messages can be used to override these messages (for e.g translations).
*/
window.sdh.initMessages = function () {
	/* These messages are used on all wikis and so need translation. */
	var messages = {
		/* Settings messages */
		'sdh-settingsDialog-title': 'Подешавања помоћника',
		'sdh-header-general': 'Опште',
		'sdh-header-appearance': 'Изгед',
		'sdh-AddToRedirect-label': 'Дозвољавај додавање кратких описа преусмерењима',
		'sdh-AddToRedirect-help': 'Када је означено, преусмерења ће имати дугме „додај” за додавање кратких описа. (подразумевано искључено)',
		'sdh-InputWidth-label': 'Ширина уноса уређивања у јединици ем (подразумевано 35)',
		'sdh-FontSize-label': 'Величина фонта, у процентима (подразумевано 100%)',
		/* Initial view messages */
		'sdh-missing-description': 'Недостаје кратак опис $1',
		/* Initial view buttons */
		'sdh-add-label': 'Додај',
		'sdh-add-title': 'Додај кратак опис',
		'sdh-edit-label': 'Уреди',
		'sdh-edit-title': 'Уреди кратак опис',
		/* Editing messages */
		'sdh-save-label': 'Сачувај',
		'sdh-save-title': 'Сачувај кратак опис',
		'sdh-cancel-label': 'Откажи',
		'sdh-cancel-title': 'Откажи уређивање',
		'sdh-settings-title': 'Подешавања',
		/* Wikidata summary messages */
		'sdh-wd-summary': '([[w:en:User:Galobtter/Shortdesc helper|Помоћник кратких описа (Shortdesc helper)]])',
		'sdh-wd-edit-failed': 'Чување измена на Википодацима није успело.',
		'sdh-wd-edit-failed-prefix': '\n\nИнформације које дају Википодаци гласе:\n\n'
	};

	/**
	 * These messages don't need translation as they are only used on enwiki
	 * because enwiki has the {{SHORTDESC:}} magic word.
	 */
	var srwikiMessages = {
		/* Settings messages */
		'sdh-MarkAsMinor-label': 'Означавај измене мањим',
		'sdh-header-Wikidata': 'Википодаци',
		'sdh-SaveWikidata-label': 'Сачувај промене на Википодацима',
		'sdh-SaveWikidata-help': 'Да ли да се ажурира опис на Википодацима када се користи справица?',
		'sdh-SaveWikidata-add-label': 'Само када не постоји опис на Википодацима (подразумевано)',
		'sdh-SaveWikidata-all-label': 'При свакој измени',
		'sdh-SaveWikidata-never-label': 'Никад',
		'sdh-ExportButton-label': 'Додаје дугме („извези”) за ажурирање описа на Википодацима како би се подударао с локалним.',
		/* Initial view messages */
		'sdh-wikidata-link-label': 'Википодаци',
		'sdh-no-description': 'Ова страница с намером нема опис.',
		/* Initial view buttons */
		'sdh-infoClicky-label': '?',
		'sdh-infoClicky-title': 'Кликните за информације',
		'sdh-override-label': 'Замени',
		'sdh-override-title': 'Замените тренутан кратак опис',
		'sdh-import-label': 'Увези',
		'sdh-import-title': 'Увезите кратак опис с Википодатака',
		'sdh-editimport-label': 'Увези и уреди',
		'sdh-editimport-title': 'Увезите и уредите кратак опис с Википодатака',
		'sdh-export-label': 'Извези',
		'sdh-export-title': 'Извезите локалан кратак опис на Википодатке',
		/* Popup text */
		'sdh-no-description-popup': 'Страница с намером нема кратак опис, што је урађено коришћењем кода {{крата​к опис|нема}}. Међутим, имајте на уму да се за сада кратак опис с Википодатака још увек приказује (ако је доступан).',
		'sdh-override-popup': '<p>Иако се овај опис може заменити другим локалним, он се не може директно уређивати. То је највероватније из разлога што се аутоматски генерише инфокутијом чланка или неким другим шаблоном.</p>',
		/* Summary messages */
		'sdh-summary-append': ' ([[:en:User:Galobtter/Shortdesc helper|Помоћник за кратке описе]])',
		'sdh-summary-changing': 'Мењам кратак опис из $1 у $2',
		'sdh-summary-adding-custom': 'Додајем прилагођен кратак опис: $2',
		'sdh-summary-importing-wikidata': 'Увозим кратак опис с Википодатака: $2',
		'sdh-summary-adding-local': 'Додајем локалан кратак опис: $2',
		'sdh-summary-adding': 'Додајем кратак опис: $2',
		/* Failure message */
		'sdh-edit-failed': 'Чување додавања или измене кратког описа није успело.',
		'sdh-edit-failed-no-template': 'Измена није успела, јер није пронађен шаблон за кратке описе у викитексту странице. Ово је вероватно због сукоба измена.'
	};

	/**
	 * Setting window.sdh.messages last means it overrides previous messages
	 * Thus allowing translations to override previous messages.
	 */
	mw.messages.set( messages );
	mw.messages.set( srwikiMessages );
	mw.messages.set( window.sdh.messages );
};

window.sdh.main = function () {
	/**
	 * What section the short description is in, to be determined later
	 * by searching the DOM. Used so that if the short description is in the lead
	 * only the wikitext of section 0 needs to be downloaded.
	 * @type {number}
	 */
	var section;

	/**
	 * Selector to find the short description in the DOM.
	 * @type {string}
	*/
	var sdelement = '.shortdescription';

	// Config variables
	var title = mw.config.get( 'wgPageName' );
	var namespace = mw.config.get( 'wgNamespaceNumber' );
	var wgQid = mw.config.get( 'wgWikibaseItemId' );
	var language = mw.config.get( 'wgContentLanguage' );
	var canEdit = mw.config.get( 'wgIsProbablyEditable' );
	var isRedirect = mw.config.get( 'wgIsRedirect' );
	var DBName = mw.config.get( 'wgDBname' );

	/**
	 * onlyEditWikidata is a site-wide flag.
	 * If it is true, then the only descriptions for the wiki are assumed to be on Wikidata.
	 * If it is false, then that means descriptions can also be added through {{SHORTDESC:}}
	 * (currently, this is only the case on enwiki).
	 * This flag modifies the behaviour of various methods to display the appropriate buttons and
	 * settings, and make the description saved to the right place.
	 * @type {boolean}
	*/
	var onlyEditWikidata = ( DBName !== 'srwiki' );

	/**
	 * Check if the user can edit the page,
	 * and disallow editing of templates and categories to prevent accidental addition.
	 * @type {boolean}
	 */
	var allowEditing = (
		(
			canEdit &&
			[ 10, 14, 710, 828, 2300, 2302 ].indexOf( namespace ) === -1
		)
	);

	// Define user agent when accessing the API
	var APIoptions = {
		ajax: {
			headers: {
				'Api-User-Agent': 'Short description editer/viewer (User:Galobtter/Shortdesc helper)'
			}
		}
	};

	var API = new mw.Api( APIoptions );

	/**
	 * Get the wikitext of the page.
	 * @return {Promise}
	 */
	var getText = function () {
		return API.get( {
			action: 'query',
			prop: 'revisions',
			titles: title,
			rvprop: 'content',
			rvsection: section,
			rvslots: 'main',
			formatversion: 2
		} );
	};

	/**
	 * Download wikitext if it is a local description.
	 * Whether it is a local description is determined through searching the DOM
	 * since waiting for the description API query to complete would delay
	 * showing the short description. Also, whether to download the whole wikitext,
	 * or only the lead section wikitext is determined.
	 * @type {Promise}
	 */
	var callPromiseText = ( function () {
		var elements;
		if ( onlyEditWikidata ) {
			return;
		}
		if ( $( sdelement ).length > 0 ) {
		/**
		 * Find whether the short description is in the first section, to determine
		 * if we need to download the wikitext of the entire page.
		 * Do this by searching elements above the first heading for ".shortdescription"
		 */
			elements = $( '.mw-parser-output > h2' ).first().prevAll();
			/**
			 * Need to check sibling elements with filter and their children
			 * with find to find short description. If length > 0 then found
			 * short description before the first heading, so get wikitext of section 0.
			 */
			if ( elements.filter( sdelement ).add( elements.find( sdelement ) ).length > 0 ) {
				section = 0;
			}

			// Get the wikitext
			return getText();
		}
	}() );

	/**
	 * Get the short description
	 * @type {Promise}
	 */
	var callPromiseDescription = API.get( {
		action: 'query',
		titles: title,
		prop: 'description',
		formatversion: 2
	} );

	/**
	 * Load settings using libSettings if it exists
	 * Otherwise gracefully fallback to defaults.
	 */
	var usinglibSettings = !!mw.libs.libSettings;
	var ls, optionsConfig, settings, options;

	if ( usinglibSettings ) {
		ls = mw.libs.libSettings;

		optionsConfig = new ls.OptionsConfig( [
			new ls.Page( {
				title: mw.msg( 'sdh-header-general' ),
				preferences: [
					new ls.CheckboxOption( {
						name: 'MarkAsMinor',
						label: mw.msg( 'sdh-MarkAsMinor-label' ),
						defaultValue: false,
						hide: onlyEditWikidata
					} ),
					new ls.CheckboxOption( {
						name: 'AddToRedirect',
						label: mw.msg( 'sdh-AddToRedirect-label' ),
						help: mw.msg( 'sdh-AddToRedirect-help' ),
						defaultValue: false
					} ),
					new ls.CheckboxOption( {
						name: 'ExportButton',
						label: mw.msg( 'sdh-ExportButton-label' ),
						defaultValue: false,
						hide: onlyEditWikidata
					} ),
					new ls.DropdownOption( {
						name: 'SaveWikidata',
						label: mw.msg( 'sdh-SaveWikidata-label' ),
						help: mw.msg( 'sdh-SaveWikidata-help' ),
						defaultValue: 'add',
						values: [
							{ data: 'add', label: mw.msg( 'sdh-SaveWikidata-add-label' ) },
							{ data: 'all', label: mw.msg( 'sdh-SaveWikidata-all-label' ) },
							{ data: 'never', label: mw.msg( 'sdh-SaveWikidata-never-label' ) }
						],
						hide: onlyEditWikidata
					} )
				]
			} ),
			new ls.Page( {
				title: mw.msg( 'sdh-header-appearance' ),
				preferences: [
					new ls.NumberOption( {
						name: 'InputWidth',
						label: mw.msg( 'sdh-InputWidth-label' ),
						defaultValue: 35,
						UIconfig: {
							min: 10,
							max: 999
						}
					} ),
					new ls.NumberOption( {
						name: 'FontSize',
						label: mw.msg( 'sdh-FontSize-label' ),
						defaultValue: 100,
						UIconfig: {
							min: 10,
							max: 500
						}
					} )
				]
			} )
		] );

		settings = new mw.libs.libSettings.Settings( {
			title: mw.msg( 'sdh-settingsDialog-title' ),
			scriptName: 'Shortdesc-helper',
			helpInline: true,
			size: 'large',
			height: 300,
			optionsConfig: optionsConfig
		} );

		options = settings.get();
	} else {
		// Use defaults
		options = {
			MarkAsMinor: false,
			AddToRedirect: false,
			InputWidth: 35,
			FontSize: 100,
			ExportButton: false,
			SaveWikidata: 'add'
		};
	}

	// Dynamic CSS based on options
	mw.util.addCSS(
		'#sdh { font-size:' + options.FontSize + '%}' +
		'#sdh-editbox, #sdh-inputbox { max-width:' + options.InputWidth + 'em };'
	);

	/* Execute main code once the short description is gotten */
	callPromiseDescription.then( function ( response ) {
		/**
		 * These two variables are UI elements that need to be closed and reopened,
		 * and so need to be accessed outside the scope of the functions
		 * that define them.
		 */

		/**
		 * Used in InfoClickyPopup
		 * @type {OO.ui.PopupWidget}
		 */
		var infoPopup;

		/**
		 * Used in textInput
		 * @type {OO.ui.ActionFieldLayout}
		 */
		var actionField;

		/**
		 * These three variables are defined by the button being clicked
		 */

		/**
		 * The message to be used for the summary
		 * @type {string}
		 */
		var summaryMsg;

		/**
		 * Is the action a change to an existing local description
		 * or an addition, importation etc.
		 * @type {boolean}
		 */
		var change;

		/**
		 * True when there is no description anywhere, and so
		 * description should be added to Wikidata when options.SaveWikidata is 'add'.
		 * @type {boolean}
		 */
		var addWikidata;

		var pages = response.query.pages[ 0 ];

		/**
		 * The page short description.
		 * @type {string}
		 */
		var pageDescription = pages.description;

		/**
		 * Is the description from Wikidata (non local) or the {{SHORTDESC:}} magic word?
		 * @type {boolean}
		 */
		var isLocal = ( pages.descriptionsource === 'local' );

		/**
		 * Search pattern for finding short description in wikitext.
 		 * Group 1 is the short description.
		 */
		var pattern = /\{\{[Кк]ратак опис\|(.*?)\}\}/;

		/**
		 * Creates "clickies", simple link buttons.
		 * Things are made nice per https://stackoverflow.com/a/10510353
		 * @param {string} msgName
		 * @param {Function} func
		 * @return {Object}
		 */
		var Clicky = function ( msgName, func ) {
			return $( '<span>' )
				.addClass( 'sdh-clicky' )
				.append( $( '<a>' )
					.attr( {
						title: mw.msg( msgName + '-title' ),
						role: 'button',
						tabindex: '0'
					} )
					.text( mw.msg( msgName + '-label' ) )
					.on( 'click', func )
					.on( 'keydown', function ( e ) {
						if ( [ 13, 32 ].indexOf( event.which ) !== -1 ) { // Space and enter
							e.preventDefault();
							return func();
						}
					} )
				);
		};

		/**
		 * Create a Clicky that opens a OOui PopupWidget.
		 * @param {string} text
		 * @return {Clicky}
		 */
		var InfoClickyPopup = function ( text ) {
			var self = this;
			self.text = text;

			self.infoClicky = new Clicky(
				'sdh-infoClicky',
				function () {
					if ( !infoPopup ) {
						mw.loader.using( [ 'oojs-ui-core', 'oojs-ui-widgets' ] ).then( function () {
							infoPopup = new OO.ui.PopupWidget( {
								$content: $( '<span>' ).append( self.text ),
								$autoCloseIgnore: self.infoClicky,
								padded: true,
								autoClose: true,
								width: 300,
								position: 'after'
							} );
							$( '.sdh-clickies' ).append( infoPopup.$element );
							infoPopup.toggle();
						} );
					} else {
						infoPopup.toggle();
					}
				}
			);

			return self.infoClicky;
		};

		/**
		 * Creates OOui buttons, which are used for save and cancel.
		 * @param {string} msgName
		 * @param {Function} func
		 * @param {Array<string>} flags
		 * @param {string} icon
		 * @return {OO.ui.ButtonWidget}
		 */
		var OOuiClicky = function ( msgName, func, flags, icon ) {
			return new OO.ui.ButtonWidget( {
				label: mw.msg( msgName + '-label' ),
				icon: icon,
				title: mw.msg( msgName + '-title' ),
				flags: flags,
				classes: [ 'sdh-ooui-clicky' ]
			} ).on( 'click', func );
		};

		/**
		 * Function to check if the short description is in the wikitext.
 		 * If it is, return the wikitext and short description as defined in the text
		 * @param {Object} wikitextResult
		 * @return {Array}
		 */
		var shortdescInText = function ( wikitextResult ) {
			var wikitext = wikitextResult.query.pages[ 0 ].revisions[ 0 ].slots.main.content;
			var match = wikitext && wikitext.match( pattern );
			if ( match ) {
				return [ wikitext, match[ 1 ] ];
			} else {
				return [ wikitext, false ];
			}
		};

		/**
		 * Notify the user that the edit failed and log any debug info.
		 * @param {string} msgName
		 * @param {*} debug
		 * @param {string} extraMsg
		 */
		var editFailed = function ( msgName, debug, extraMsg ) {
			var message = mw.msg( msgName ) + extraMsg;
			mw.notify(
				message,
				{
					autoHide: false
				}
			);
			if ( debug ) {
				mw.log.warn( debug );
			}
		};

		/**
		 * Set the Wikidata description using the API.
		 * @param {string} newDescription
		 * @return {Promise}
		 */
		var setWikidataDescription = function ( newDescription ) {
			return mw.loader.using( 'mediawiki.ForeignApi' ).then( function () {
				var wikidataAPI = new mw.ForeignApi( 'https://www.wikidata.org/w/api.php', APIoptions );
				return wikidataAPI.postWithToken( 'csrf', {
					action: 'wbsetdescription',
					id: wgQid,
					language: language,
					summary: mw.message( 'sdh-wd-summary', language ).plain(),
					value: newDescription
				} );
			} );
		};

		/**
		 * This function edits Wikidata descriptions and is used on wikis that aren't enwiki.
		 * Beyond what setWikidataDescription does, it reloads the page on success
		 * and gives an informative error notification.
		 * @param {string} newDescription
		 */
		var editWikidataDescription = function ( newDescription ) {
			setWikidataDescription( newDescription ).then(
				function () {
					window.location.reload();
				},
				function () {
					editFailed(
						'sdh-wd-edit-failed',
						arguments,
						arguments[ 1 ].error.info ? (
							mw.msg( 'sdh-wd-edit-failed-prefix' ) +
							arguments[ 1 ].error.info
						) : ''
					);
				}
			);
		};

		/**
		 * This function adds or replaces short descriptions.
		 * @param {string} newDescription
		 */
		var editDescription = function ( newDescription ) {
			var replacement, prependText, appendText, text;

			/**
			 * Helper function to add quotes around text,
			 * used when generating the summary.
			 * @param {string} text
			 * @return {string}
			 */
			var quotify = function ( text ) {
				if ( text === '' || text === 'none' ) {
					return 'none';
				} else {
					return '„' + text + '”';
				}
			};

			/**
			 * Appends, prepends, or replaces the wikitext.
			 * depending on which of text, prependText, and appendText exists.
			 */
			var makeEdit = function () {
				var summary = mw.message(
					summaryMsg,
					quotify( pageDescription ),
					quotify( newDescription )
				).plain() +
				mw.message( 'sdh-summary-append' ).plain();
				API.postWithToken( 'csrf', {
					action: 'edit',
					section: section,
					text: text,
					title: title,
					prependtext: prependText,
					appendtext: appendText,
					summary: summary,
					minor: options.MarkAsMinor
				} ).then( function () {
					// Reload the page
					window.location.reload();
				} ).fail( function () {
					editFailed( 'sdh-edit-failed', arguments );
				} );
			};

			/**
			 * Replaces the current local short description with the new one.
			 * If the short description doesn't exist in the text, return false.
			 * @param {string} wikitextResult Result of getText()
			 * @return {boolean} Whether there was a description in the wikitext
			 * and so whether makeEdit could be called.
			 */
			var replaceAndEdit = function ( wikitextResult ) {
				var output = shortdescInText( wikitextResult );
				var oldtext = output[ 0 ];
				var descriptionFromText = output[ 1 ];
				if ( descriptionFromText ) {
					text = oldtext.replace( pattern, replacement );
					makeEdit();
					return true;
				} else {
					return false;
				}
			};

			// Make edits to Wikidata as appropiate
			if (
				wgQid &&
				( options.SaveWikidata === 'all' || options.SaveWikidata === 'add' && addWikidata ) &&
				newDescription !== ''
			) {
				setWikidataDescription( newDescription );
			}

			// Capitalize first letter by default unless editing local description
			if ( !isLocal ) {
				newDescription = (
					newDescription.charAt( 0 ).toUpperCase() +
					newDescription.slice( 1 )
				);
			}

			if ( newDescription === '' ) {
				newDescription = 'none';
			}

			// eslint-disable-next-line no-useless-concat
			replacement = '{' + '{кратак опис|' + newDescription + '}}';

			/**
			 * change = true means there was a previous short description in the wikitext
			 * that needs to be replaced.
			 */
			if ( change ) {
				/**
				 * Get the wikitext again right before making the edit
				 * to avoid issues with edit conflicts, and make the edit.
				 */
				getText().then( function ( result ) {
					if ( !replaceAndEdit( result ) ) {
						editFailed( 'sdh-edit-failed' );
					}
				} );
			} else {
				if ( isRedirect ) {
					appendText = '\n' + replacement;
				} else {
					prependText = replacement + '\n';
				}
				makeEdit();
			}
		};

		/**
		 * Creates input box with save and cancel buttons.
		 * If input box was created before, show it again.
	 	 * Otherwise, create the input box using OOui.
		*/
		var textInput = function () {
			if ( actionField ) {
				$( '#sdh-showdescrip' ).hide( 0 );
				actionField.toggle();
			} else {
				mw.loader.using( [ 'oojs-ui-core', 'oojs-ui-widgets' ] ).then( function () {
					var length, saveInput, buttons;
					// Define the input box and buttons.
					var descriptionInput = new OO.ui.TextInputWidget( {
						autocomplete: false,
						autofocus: true,
						id: [ 'sdh-inputbox' ],
						label: '0',
						value: pageDescription,
						placeholder: 'Кратак опис'
					} );

					var saveButton = new OOuiClicky(
						'sdh-save',
						function () {
							saveInput();
						},
						[ 'primary', 'progressive' ]
					);

					var cancelButton = new OOuiClicky(
						'sdh-cancel',
						function () {
							actionField.toggle();
							$( '#sdh-showdescrip' ).show( 0 );
						},
						[ 'safe', 'destructive' ]
					);

					var settingsButton = new OO.ui.ButtonWidget( {
						icon: 'settings',
						framed: false,
						title: mw.msg( 'sdh-settings-title' ),
						flags: [ 'safe' ],
						classes: [ 'sdh-ooui-clicky' ]
					} ).on( 'click', function () {
						settings.display();
					} );

					// On change, update character count label.
					var updateOnChange = function () {
						length = descriptionInput.getInputLength();
						descriptionInput.setLabel( String( length ) );
					};

					var items = [ saveButton, cancelButton ];

					if ( usinglibSettings ) {
						items.push( settingsButton );
					}

					buttons = new OO.ui.ButtonGroupWidget( {
						items: items
					} );

					/**
					 * This is bound to the save button.
					 * Disables all the elements and calls the relevant function
					 * responsible for saving the the entered short description.
					*/
					saveInput = function () {
						var description = descriptionInput.getValue().trim();
						descriptionInput
							.setDisabled( true )
							.pushPending( true );
						items.forEach( function ( item ) {
							item.setDisabled( true );
						} );
						if ( onlyEditWikidata ) {
							editWikidataDescription( description );
						} else {
							editDescription( description );
						}
					};

					actionField = new OO.ui.ActionFieldLayout(
						descriptionInput,
						buttons, {
							label: '', // For some dumb reason, the buttons won't align with the inputbox unless a dummy label is put
							align: 'top',
							id: [ 'sdh-editbox' ]
						}
					);

					// Initial character count
					updateOnChange();

					descriptionInput.on( 'change', updateOnChange );
					descriptionInput.on( 'enter', saveInput );

					// Hide previous displayed clickies and add to DOM
					$( '#sdh-showdescrip' ).hide( 0 );
					$( '#sdh' ).append( actionField.$element );
				} );
			}
		};

		/**
		 * Create the html and append it to the DOM
		 * @param {Object} textElement
		 * @param {Array<Clicky>} clickyElements
		 * @param {InfoClickyPopup} popupElement
		 */
		var updateSDH = function ( textElement, clickyElements, popupElement ) {
			var $sdh = $( '<div>' ).prop( 'id', 'sdh' );
			var $description = $( '<div>' ).prop( 'id', 'sdh-showdescrip' );
			var $clickies = $( '<span>' ).addClass( 'sdh-clickies' );

			if ( popupElement ) {
				clickyElements.push( popupElement );
			}

			$description.append( textElement );

			if ( clickyElements.length > 0 ) {
				$clickies.append( clickyElements );
				$description.append( $clickies );
			}

			$sdh.append( $description );

			$.ready.then( function () {
				// Undo padding used to fix content jump
				mw.util.addCSS( '.skin-vector.ns-0 #contentSub::after {content: none;}' );
				// Create and attach the main div to #contentSub
				$( '#contentSub' ).append( $sdh );
			} );
		};

		/**
		 * Disable all buttons and create processing (...) animation
		 * Used by export and import buttons.
		*/
		var setProcessing = function () {
			var x;
			// Disable all clicky buttons
			$( '.sdh-clicky a' )
				.css( 'pointer-events', 'none' )
				.off();

			// Add processing ... animation
			$( '#sdh-showdescrip ' ).append(
				$( '<div>' )
					.addClass( 'sdh-processing' )
					.css( 'margin-left', '0.5em' )
			);

			for ( x = 0; x < 3; x++ ) {
				$( '.sdh-processing' ).append(
					$( '<div>' )
						.addClass( [
							'sdh-processing-dot',
							'sdh-processing-dot-' + x
						] )
						.text( '.' )
				);
			}
		};

		/**
		 * Texts, clickies, and popups contain
		 * elements that could make up the initial display.
		*/
		var texts = {
			noDescription: $( '<span>' )
				.addClass( 'sdh-no-description' )
				.text( mw.msg( 'sdh-no-description' ) ),
			missingDescription: $( '<span>' )
				.addClass( 'sdh-missing-description' )
				.html( mw.msg( 'sdh-missing-description', ( isRedirect ? 'преусмерења' : 'чланка' ) ) ),
			pageDescription: $( '<span>' )
				.addClass( 'mw-page-description ' )
				.text( pageDescription )
		};

		var clickies = {
			add: new Clicky(
				'sdh-add',
				function () {
					summaryMsg = 'sdh-summary-adding';
					addWikidata = true; // Description should be added to wikidata in this case
					textInput();
				}
			),
			addNone: new Clicky(
				'sdh-add',
				function () {
					summaryMsg = 'sdh-summary-changing';
					change = true;
					pageDescription = '';
					textInput();
				}
			),
			edit: new Clicky(
				'sdh-edit',
				function () {
					summaryMsg = 'sdh-summary-changing';
					change = true;
					textInput();
				}
			),
			editimport: new Clicky(
				'sdh-editimport',
				function () {
					summaryMsg = 'sdh-summary-adding-local';
					textInput();
				}
			),
			export: new Clicky(
				'sdh-export',
				function () {
					setProcessing();
					editWikidataDescription( pageDescription );
				}
			),
			import: new Clicky(
				'sdh-import',
				function () {
					setProcessing();
					summaryMsg = 'sdh-summary-importing-wikidata';
					editDescription( pageDescription );
				}
			),
			override: new Clicky(
				'sdh-override',
				function () {
					summaryMsg = 'sdh-summary-adding-custom';
					textInput();
				}
			),
			wikidataLink: $( '<span>' )
				.addClass( 'sdh-clicky' )
				.append( $( '<a>' )
					.attr( 'href', 'https://www.wikidata.org/wiki/Special:SetLabelDescriptionAliases/' + wgQid + '/' + language )
					.addClass( 'sdh-wikidata-description' )
					.text( mw.msg( 'sdh-wikidata-link-label' ) )
				)
		};

		var popups = {
			noDescription: new InfoClickyPopup(
				mw.message( 'sdh-no-description-popup' ).plain()
			),
			override: new InfoClickyPopup(
				mw.message( 'sdh-override-popup' ).plain()
			)
		};

		/**
		 * Depending on various factors, such as
		 * whether the description exists,
		 * whether the description is on wikidata or not,
		 * and whether the page is in mainspace,
		 * this code determines what elements should make up the initial display.
		 * updateSDH() is then called to generate the html
		 * and add that to the DOM.
		 * @param {Object} wikitextResult
		*/
		var determineElements = function ( wikitextResult ) {
			/**
			 * The description as determined from the wikitext.
			 * @type {string}
			 */
			var descriptionFromText;

			/**
			 * The short description or a message saying no description exists etc.
			 * @type {Object}
			 */
			var textElement;

			/**
			 * What the relevant buttons ("clickies") are.
			 * @type {Array<Clicky>}
			 */
			var clickyElements = [];

			/**
			 * what clickable popup explanation is there if any
			 * @type {InfoClickyPopup}
			 */
			var popupElement;

			// Whether to show "Missing article description" if applicable
			var showMissing = (
				namespace === 0 &&
				( !isRedirect || ( isRedirect && options.AddToRedirect ) )
			);

			// If not enwiki, complete logic for non-enwiki case and exit.
			if ( onlyEditWikidata ) {
				if ( pageDescription ) {
					textElement = pageDescription;
					clickyElements.push( clickies.edit );
				} else if ( showMissing ) {
					textElement = texts.missingDescription;
					clickyElements.push( clickies.add );
				}
				updateSDH( textElement, clickyElements, popupElement );
				return;
			}

			/**
			 * Determine if the short description is in the wikitext
			 * or if it is on Wikidata/generated by an infobox.
			 */
			if ( isLocal ) {
				descriptionFromText = shortdescInText( wikitextResult )[ 1 ];
			} else {
				descriptionFromText = false;
			}

			// Show wikidata link at beginning if displaying non-local description.
			if ( pageDescription && !isLocal ) {
				clickyElements.push( clickies.wikidataLink );
			}

			if ( descriptionFromText === 'none' ) {
				// eslint-disable-next-line no-irregular-whitespace
				// Handle {{Shor​t description|none}}
				textElement = texts.noDescription;
				clickyElements.push( clickies.addNone );
				popupElement = popups.noDescription;
			} else {
				// Handle remaining cases
				if ( pageDescription ) {
					textElement = texts.pageDescription;
					if ( isLocal ) {
						if ( descriptionFromText ) {
							clickyElements.push( clickies.edit );
						} else {
							clickyElements.push( clickies.override );
							popupElement = popups.override;
						}
					} else {
						clickyElements.push(
							clickies.import,
							clickies.editimport
						);
					}
				} else if ( showMissing ) {
					textElement = texts.missingDescription;
					clickyElements.push( clickies.add );
				}
			}

			// Don't show clickies for editing if not allowing editing
			if ( !allowEditing ) {
				clickyElements = [];
			}

			if ( isLocal && options.ExportButton ) {
				clickyElements.push( clickies.export );
			}

			updateSDH( textElement, clickyElements, popupElement );
		};

		if ( callPromiseText ) {
			callPromiseText.then( function ( wikitextResult ) {
				determineElements( wikitextResult );
			} );
		} else {
			determineElements();
		}
	} );
};

/* Load if viewing a page normally (not in diff view) */
if (
	mw.config.get( 'wgIsArticle' ) &&
	!mw.config.get( 'wgDiffOldId' ) &&
	mw.config.get( 'wgArticleId' ) !== 0
) {
	window.sdh.initMessages();
	window.sdh.main();
}