MediaWiki:Gadget-wikibugs-core.js

Материал из Википедии — свободной энциклопедии
Перейти к навигации Перейти к поиску
JS-код ниже относится к скрытому гаджету wikibugs-core. Связанный CSS-файл: MediaWiki:Gadget-wikibugs.css. Связанный JSON-файл: MediaWiki:Gadget-wikibugs-ignoreList.json.

После сохранения или недавних изменений очистите кэш браузера.

/*
 * Original version: https://pl.wikipedia.org/wiki/MediaWiki:Wikibugs.js (2008-12-17)
 * Russian version by [[User:Александр Сигачёв]], [[User:Putnik]], [[User:LEMeZza]]
 * Converted to OOjs (2017-05-20) by [[User:Putnik]]
 * Rewritten to be lazyloaded from [[MediaWiki:Gadget-wikibugs.js]] (2024-02-24) by [[User:Stjn]]
 */
mw.loader.using( [
	'mediawiki.api',
	'mediawiki.Title',
	'mediawiki.htmlform.codex.styles',
	'mediawiki.util',
	'oojs-ui-core',
	'oojs-ui.styles.icons-editing-core',
	'oojs-ui-widgets',
	'oojs-ui-windows',
], function () {
	//'use strict';

	window.wb$bugsPage = window.wb$bugsPage || 'Википедия:Сообщения об ошибках';
	
	// Pages where the script does not run are in [[MediaWiki:Gadget-wikibugs.js]]
	var wb$badPageData = require( './wikibugs-ignoreList.json' );
	var wb$badPages = Object.keys( wb$badPageData );

	var wb$i18n = {
		title: 'Сообщить об ошибке',
		btnFix: 'Исправить самостоятельно',
		btnReport: 'Сообщить…',
		btnCancel: 'Закрыть',
		btnSend: 'Отправить',
		fldPage: 'Название страницы',
		fldText: 'Текст сообщения',
		fldTextInfo: `Пожалуйста, опишите ошибку как можно точнее. При сообщении
			о\u00A0фактической ошибке не забудьте указать источник, подтверждающий
			вашу информацию.`.replace( /[\n\t]+/g, ' ' ),
		fldCaptcha: 'Проверочный код',
		fldSign: 'Подпись',
		fldSignIP: 'будет указан ваш IP-адрес',
		noAltText: 'Если вы не можете увидеть код, уберите протокол (HTTP) у ссылок и отправьте сообщение заново.',
		alertShort: 'Описание ошибки слишком коротко. Пожалуйста, расширьте его.',
		alertNoPage: 'Введите корректное имя страницы.',
		alertCaptcha: `В вашем тексте содержатся внешние ссылки. Пожалуйста, 
			введите код с изображения и отправьте сообщение ещё раз.`,
		alertError: 'Попробуйте ещё раз, так как при отправке произошла ошибка:',
		sentFromMobile: 'Отправлено с мобильной версии',
		protectionLevel: 'Уровень защиты страницы: ',
		protections: {
			autoconfirmed: 'до автоподтверждённых',
			editautoreviewprotected: 'до автопатрулируемых',
			sysop: 'до администраторов',
		},
		msgSign: 'Автор сообщения: ',
		htmlIpWarn: `<strong>Внимание.</strong> При\u00A0отправке сообщения ваш IP-адрес 
			будет публично доступен на\u00A0странице сообщений об\u00A0ошибках.`,
		htmlInfo: `<div class="wikibugs-social-warning">
				<p><strong>Не\u00A0сообщайте</strong> об\u00A0ошибках на\u00A0других
					сайтах (например, <strong>«ВКонтакте»</strong> или
					<strong>«Одноклассники»</strong>), они будут проигнорированы.</p>
				<p>Отсутствие статьи в\u00A0Википедии\u00A0— не\u00A0ошибка, вы можете оставить
					<a href="${ mw.util.getUrl( 'Википедия:К созданию' ) }">запрос на её создание</a>.</p>
			</div>
			<p>Если вы заметили ошибку в\u00A0Википедии,
				пожалуйста, исправьте её самостоятельно, используемая на\u00A0этом
				сайте технология <a href="${ mw.util.getUrl( 'вики' ) }">вики</a>
				позволяет это сделать.
				Не\u00A0смущайтесь, одно из\u00A0правил Википедии гласит:
				«<a href="${ mw.util.getUrl( 'Википедия:Правьте смело' ) }">Правьте смело</a>»!
				Если вы не\u00A0можете исправить ошибку самостоятельно, сообщите
				о\u00A0ней с\u00A0помощью данной формы.</p><p><strong>Если ошибка
				уже исправлена\u00A0— не\u00A0сообщайте о\u00A0ней.</strong></p>
			<p>Не\u00A0оставляйте свой телефон и/или электронный адрес, ответ
				на\u00A0сообщение будет дан только на\u00A0странице
				с\u00A0сообщениями и нигде больше.</p>
			<p><a href="${ mw.util.getUrl( wb$bugsPage ) }">Текущий
				список сообщений об ошибках</a></p>`,
	};

	// Support namespace aliases
	const nsIds = mw.config.get( 'wgNamespaceIds' );
	let nsAliases = Object.keys( nsIds ).filter( el => {
		return [ 6, 14 ].includes( nsIds[ el ] );
	} ).map( el => el.replace( /_/g, ' ' ) );
	const wb$nsRegExp = new RegExp( `^(${ nsAliases.join( ':|' ) }:|\\/)`, 'i' );
	
	function getWarning( str, isHtml ) {
		var $html = $( '<div class="cdx-message cdx-message--block cdx-message--warning">' );
		$html.append( '<span class="cdx-message-icon"></span>' );
		
		var $content = $( '<div class="cdx-message__content">' );
		if ( isHtml ) {
			$content.html( str );
		} else {
			$content.text( str );
		}
		$html.append( $content );
		
		return $html;
	}

	var wb$isValidPageName = function( name ) {
		if ( !name || mw.config.get( 'wgNamespaceNumber' ) === -1 ) {
			return false;
		}
		name = name.replace( /_/g, ' ' );
		
		return !wb$badPages.includes( name );
	};
	
	var wb$isTalkPage = mw.config.get( 'wgCanonicalNamespace' ).toLowerCase().includes( 'talk' );
	var wb$getPageName = function() {
		var title = mw.config.get( 'wgTitle' );
		if ( wb$isTalkPage ) {
			var subjectPageNs = mw.config.get( 'wgNamespaceNumber' ) - 1;
			if ( subjectPageNs === 0 ) {
				return title;
			}
			
			return String( mw.Title.makeTitle( subjectPageNs, title ) ).replace( /_/g, ' ' );
		}
		
		return mw.config.get( 'wgPageName' ).replace( /_/g, ' ' );
	};

	// Code from [[:c:user:JWBTH/CD]], author: [[user:Jack who built the house]]
	function es6ClassToOoJsClass( targetClass ) {
		const originClass = Object.getPrototypeOf( targetClass );
		targetClass.parent = targetClass.super = originClass;
	
		OO.initClass( originClass );
		targetClass.static = Object.create( originClass.static );
		Object.getOwnPropertyNames( targetClass )
			.filter( key => key !== 'static' )
			.forEach( key => {
				const descriptor = Object.getOwnPropertyDescriptor( targetClass, key );
				if ( descriptor.enumerable || descriptor.get ) {
					targetClass.static[ key ] = targetClass[ key ];
				}
			} );
	
		targetClass.parent = targetClass.super = originClass;
		return targetClass;
	}

	var wb$api = null;
	var wb$windowManager = null;
	var wb$pageWarn = null;

	var wb$ReportDialog = es6ClassToOoJsClass( class wb$ProcessReportDialog extends OO.ui.ProcessDialog {
		static get name() { return 'wbReportDialog' }
		static get title() { return wb$i18n.title }
		static get actions() {
			return [
				{ label: wb$i18n.btnSend, action: 'send', flags: [ 'primary', 'progressive' ] },
				{ label: wb$i18n.btnCancel, action: 'cancel', flags: [ 'safe', 'close' ] },
			];
		}
		static get size() { return 'medium' }

		constructor () {
			super();
		}

		initialize( ...args ) {
			super.initialize( ...args );

			// Prepare form
			this.inputPage = new OO.ui.TextInputWidget( {
				placeholder: wb$i18n.fldPage,
				value: wb$getPageName(),
				disabled: wb$isValidPageName( mw.config.get( 'wgPageName' ) ) && !mw.config.get( 'wgNamespaceNumber' ),
			} );

			this.inputText = new OO.ui.MultilineTextInputWidget( {
				placeholder: wb$i18n.fldTextInfo,
				rows: 5
			} );

			this.inputCaptcha = new OO.ui.TextInputWidget( {
				placeholder: wb$i18n.fldCaptcha,
			} );

			this.panelCaptcha = new OO.ui.FieldLayout( this.inputCaptcha, {
				label: wb$i18n.fldCaptcha,
			} );
			this.panelCaptcha.toggle( false );

			this.imageCaptcha = new OO.ui.ToggleWidget();
			this.imageCaptcha.$element.css( 'text-align', 'right' );
			this.imageCaptchaImg = $( '<img>' ).attr( 'alt', wb$i18n.noAltText );
			this.imageCaptcha.$element.append( this.imageCaptchaImg );
			this.imageCaptcha.toggle( false );

			this.inputSign = new OO.ui.TextInputWidget( {
				placeholder: wb$i18n.fldSign,
				value: mw.config.get( 'wgUserName' ) || wb$i18n.fldSignIP,
				disabled: true
			} );

			var fieldset = new OO.ui.FieldsetLayout( {
				classes: [ 'container' ],
			} );

			fieldset.addItems( [
				new OO.ui.FieldLayout( this.inputPage, {
					label: wb$i18n.fldPage,
					align: 'top'
				} ),
				new OO.ui.FieldLayout( this.inputText, {
					label: wb$i18n.fldText,
					align: 'top'
				} ),
				this.panelCaptcha,
				this.imageCaptcha,
				new OO.ui.FieldLayout( this.inputSign, {
					label: wb$i18n.fldSign,
				} ),
			] );

			this.panel = new OO.ui.PanelLayout( {
				padded: true,
				expanded: false,
			} );
			
			this.panel.$element.append( wb$pageWarn );
			if ( mw.config.get( 'wgUserName' ) === null ) {
				this.panel.$element.append( getWarning( wb$i18n.htmlIpWarn, true ) );
			}
			
			this.panel.$element.append( fieldset.$element );

			this.$body.append( this.panel.$element );
		}

		getSetupProcess( data ) {
			return super.getSetupProcess( data );
		}

		getReadyProcess( data ) {
			return super.getReadyProcess( data );
		}

		getActionProcess( action ) {
			const dialog = this;
			const superAction = () => {
				return super.getActionProcess( action );
			};

			if ( action !== 'send' ) {
				dialog.close();
				return superAction();
			}

			dialog.getActions().setAbilities( { send: false } );

			// Send message
			var content = dialog.inputText.getValue().trim();
			if ( content === '' || content.length < 20 || !content.match( ' ' ) ) {
				mw.notify( wb$i18n.alertShort, { tag: 'wb$NotifySubmit' } );
				dialog.inputText.focus();
				dialog.getActions().setAbilities({ send: true });

				return superAction();
			}

			var page = dialog.inputPage.getValue()
					.replace( /^https?:\/\/ru\.wikipedia\.org\/wiki\/(.+)$/, '$1' )
					.replace( /_/g, ' ' );
			page = decodeURIComponent( page );

			var section;
			var signature = [];
			if ( location.hostname.includes( '.m.wikipedia.org' ) ) {
				signature.push( wb$i18n.sentFromMobile );
			}

			if ( page === mw.config.get( 'wgPageName' ).replace( /_/g, ' ' ) &&
				wb$isValidPageName( mw.config.get( 'wgPageName' ) )
			) {
				section = page.replace( wb$nsRegExp, ':$1' );
				if ( mw.config.get( 'wgNamespaceNumber' ) === 6 ) {
					content = `[[File:${
						mw.config.get( 'wgTitle' )
					}|thumb|left|100px]]\n* ${content}\n{{clear}}`;
				}
				
				var editRestrictions = mw.config.get( 'wgRestrictionEdit' );
				if ( editRestrictions.length > 0 ) {
					signature.push( wb$i18n.protectionLevel + editRestrictions.map( l => wb$i18n.protections[ l ] || l ).join( ', ' ) );
				}
			} else {
				page = page
					.replace( /\[\[([^\[\]\|]+)\|[^\[\]\|]+\]\]/g, '$1' )
					.replace( /[\[\]\|]/g, '' )
					.replace( /^\s+/g, '' )
					.replace( /\s+$/g, '' );

				if ( !wb$isValidPageName( page ) ) {
					mw.notify( wb$i18n.alertNoPage, { tag: 'wb$NotifySubmit' } );
					if ( wb$isValidPageName( mw.config.get( 'wgPageName' ) ) ) {
						dialog.inputPage.setValue( mw.config.get( 'wgPageName' ) );
					} else {
						dialog.inputPage.setValue( '' );
						dialog.inputPage.focus();
					}
					dialog.getActions().setAbilities({ send: true });

					return superAction();
				}
				section = page.replace( wb$nsRegExp, ':$1' );
			}
			section = `[[${section}]]`;

			signature.push( wb$i18n.msgSign + '~~' + '~~' );
			content += '\n\n';
			content += signature.join( '. ' );

			dialog.getActions().get()[ 0 ].pushPending();

			wb$api = wb$api || new mw.Api();
			var data = {
				errorformat: 'html',
				errorlang: mw.config.get( 'wgUserLanguage' ),
				errorsuselocal: true,
			};
			var captchaId = dialog.imageCaptchaImg.data( 'id' );
			if ( captchaId ) {
				data.captchaid = captchaId;
				data.captchaword = dialog.inputCaptcha.getValue().trim();
			}

			wb$api.newSection(
				wb$bugsPage,
				section,
				content,
				data
			).catch( function( code, err ) {
				dialog.getActions().get()[ 0 ].popPending();
				var $error = $( '<div>' ).text( wb$i18n.alertError );
				$error.append( wb$api.getErrorMessage( err ) );
				mw.notify( $error, { type: 'error', tag: 'wb$NotifySubmit' } );
				dialog.getActions().setAbilities({ send: true });
			} ).then( function( xhr ) {
				if ( OO.getProp( xhr, 'edit', 'result' ) === 'Success' ) {
					// Success, go to DiscussionTools heading ID
					var url = mw.config.get( 'wgServer') + mw.util.getUrl( wb$bugsPage );
					var timestamp = xhr.edit.newtimestamp.replace( /[T:-]/g, '' ).replace( /\d{2}Z/, '00' );
					var anchor = `h-${page.replace( / /g, '_' )}-${timestamp}`;
					window.location.href = `${url}#${anchor}`;
				} else if ( OO.getProp( xhr, 'edit', 'captcha', 'type' ) === 'image' ) {
					// Captcha
					dialog.imageCaptchaImg
						.attr( 'src', xhr.edit.captcha.url )
						.data( 'id', xhr.edit.captcha.id )
						.on( 'load', () => {
							dialog.updateSize();
						} );

					dialog.imageCaptcha.toggle( true );
					dialog.inputCaptcha.setValue( '' );
					dialog.panelCaptcha.toggle( true );
					dialog.updateSize();

					dialog.getActions().get()[ 0 ].popPending();
					mw.notify( wb$i18n.alertCaptcha, { type: 'warning', tag: 'wb$NotifySubmit' } );
					dialog.getActions().setAbilities( { send: true } );
				} else {
					// Error
					dialog.getActions().get()[ 0 ].popPending();
					var $error = $( '<div>' ).text( wb$i18n.alertError );
					$error.append( wb$api.getErrorMessage( xhr ) );
					mw.notify( $error, { type: 'error', tag: 'wb$NotifySubmit' } );
					dialog.getActions().setAbilities( { send: true } );
				}
			} );

			return superAction();
		}
	} );

	var wb$InfoDialog = es6ClassToOoJsClass( class wb$ProcessInfoDialog extends OO.ui.ProcessDialog {
		static get name() { return 'wbInfoDialog' }
		static get title() { return wb$i18n.title }
		static get actions() {
			return [
				{
					label: wb$i18n.btnReport,
					action: 'report',
					flags: [ 'primary', 'progressive' ],
					modes: [ 'all', 'editable' ],
				},
				{
					label: wb$i18n.btnFix,
					action: 'edit',
					icon: 'edit',
					flags: 'progressive',
					modes: 'editable',
				},
				{
					label: wb$i18n.btnCancel,
					action: 'cancel',
					flags: [ 'safe', 'close' ],
					modes: [ 'all', 'editable' ],
				},
			];
		}
		static get size() { return 'large' }

		constructor () {
			super();
		}

		initialize( ...args ) {
			super.initialize( ...args );

			const $infoHtml = $( '<div class="wikibugs-info">' ).html( $.parseHTML( wb$i18n.htmlInfo ) );
			this.panel = new OO.ui.PanelLayout( {
				padded: true,
				expanded: false,
			} );
			this.panel.$element.append( $infoHtml );
			
			var pageName = wb$getPageName();
			if ( !wb$isValidPageName( pageName ) ) {
				var pageWarn = wb$badPageData[ pageName ];
				if ( pageWarn !== true ) {
					wb$pageWarn = getWarning( pageWarn );
					this.panel.$element.append( wb$pageWarn );
				}
			}

			this.$body.append( this.panel.$element );
		}

		getSetupProcess( data ) {
			const dialog = this;
			return super.getSetupProcess( data )
				.next( function() {
					// Disable edit button here
					if ( !mw.config.get( 'wgIsProbablyEditable' ) ) {
						dialog.actions.setMode( 'all' );
					}
				} );
		}

		getReadyProcess( data ) {
			return super.getReadyProcess( data );
		}

		getActionProcess( action ) {
			if ( action === 'edit' ) {
				// Go to edit page
				var $editLink = $( mw.config.get( 'skin' ) === 'minerva' ? '#ca-edit' : '#ca-edit a' );
				var editUrl = mw.util.getUrl( wb$bugsPage );
				if ( $editLink.length ) {
					editUrl = $editLink.attr( 'href' );
				}
				window.location.assign( editUrl );

			} else if ( action === 'report' ) {
				this.close();
				wb$windowManager.openWindow( 'wbReportDialog' );
			} else {
				this.close();
			}

			return super.getActionProcess( action );
		}
	});
	
	// Expose this function for [[MediaWiki:Gadget-wikibugs.js]]
	window.wb$runWikibug = function () {
		const hadNoManager = wb$windowManager === null;
		wb$windowManager = wb$windowManager || new OO.ui.WindowManager();
		if ( hadNoManager ) {
			$( 'body' ).append( wb$windowManager.$element );

			const infoDialog = new wb$InfoDialog();
			const reportDialog = new wb$ReportDialog();
			wb$windowManager.addWindows( [ infoDialog, reportDialog ] );
		}

		wb$windowManager.openWindow( 'wbInfoDialog' );
	};
} );