Участник:Putnik/infobox.js

Материал из Википедии — свободной энциклопедии
Перейти к навигации Перейти к поиску
/*
 * В случае, если на странице нет шаблона-карточки, показывает,
 * как она могла бы выглядеть на этой странице.
 */
( function ( mw, $ ) {
	var api;
	var wdApi;
	var loopCnt = 0;
	var blacklist = [];
	var site = mw.config.get( 'wgDBname' );
	var templateNS = mw.config.get( 'wgFormattedNamespaces' )[ 10 ];

	function addIndicator( icon, action, title ) {
		var $indicatorLink = $( '<a>' )
			.append( new OO.ui.IconWidget( {
				icon: icon
			} ).$element );
			
		if ( typeof action === 'string' ) {
			$indicatorLink.attr( 'href', mw.util.getUrl( action ) );
		} else {
			$indicatorLink.attr( 'href', '#' );
			if ( typeof action === 'function' ) {
				$indicatorLink.on( 'click', action );
			}
		}

		if ( title ) {
			$indicatorLink.attr( 'title', title );
		}
		
		var $indicator = $( '<div>' )
			.addClass( 'mw-indicator' )
			.append( $indicatorLink );

		mw.util.$content.find( '.mw-indicators' ).append( $indicator );
	}

	function addTemplate( wikitext ) {
		api.postWithToken( 'csrf', {
			action: 'edit',
			title: mw.config.get( 'wgTitle' ),
			prependtext: wikitext + '\n',
			summary: '+' + wikitext,
		} ).done( function ( data ) {
			window.location.reload();
		} );
	}
	
	function generateTemplate( templateTitle ) {
		var replacement = new RegExp( '^' + templateNS + ':' );
		var wikitext = '{' + '{' + templateTitle.replace( replacement, '' ) + '}}';

		// Генерация HTML-кода шаблона.
		api.get( {
			action: 'parse',
			prop: 'text',
			title: mw.config.get( 'wgTitle' ),
			text: wikitext
		} ).done( function ( data ) {
			if ( data.parse && data.parse.text ) {
				var $infobox = $( '<div>' )
					.append( $( data.parse.text[ '*' ] ) )
					.find( '.infobox' );
				$contentText = mw.util.$content.find( '.mw-body-content .mw-parser-output' )
					.prepend( $infobox );

				// Переход к шаблону.
				addIndicator( 'code', templateTitle, 'Перейти к шаблону ' + wikitext );

				// Вставка в код статьи.
				addIndicator( 'templateAdd', function ( e ) {
					e.preventDefault();
					addTemplate( wikitext );
				}, 'Вставить шаблон ' + wikitext + ' в начало статьи' );
			} else {
				mw.notify( 'Ошибка при генерации HTML-кода карточки', 'error' );
			}
		} );
	}

	/*
	 * Запрос ссылок на шаблоны в проекте для найденных элементов шаблонов.
	 */
	function getMainInfoboxSitelink( templateId )	{
		// Запрос ссылки для элемента.
		wdApi.get( {
			action: 'wbgetentities',
			props: 'sitelinks',
			ids: templateId
		} ).done( function ( data ) {
			if ( data.success ) {
				for ( var i in data.entities ) {
					if ( !i.match( /^Q/ ) ) {
						continue;
					}

					var sitelinks = data.entities[ i ].sitelinks;
					if ( !sitelinks ) {
						continue;
					}
					
					if ( sitelinks[ site ] ) {
						generateTemplate( sitelinks[ site ].title );
						return;
					}
				}
			}

			console.log( 'No infobox: ', templateId );
		} );
	}

	/*
	 * Рекурсивный поиск шаблонов.
	 */
	function getMainInfobox( entityId ) {
		// TODO: Сюда ссылку на запрос
		// TODO: Исключить перечисления (Q12139612)
		var query = 'SELECT DISTINCT ?tpl\
			WHERE {\
				{\
					SELECT DISTINCT ?tpl ?tplClass ?supTpl ?depth (count(?midTpl) as ?specific)\
					WHERE {\
						{\
							SELECT DISTINCT ?tpl ?tplClass (count(?mid) as ?depth)\
							WHERE {\
								?tpl wdt:P31 wd:Q19887878 .\
								?tpl wdt:P1423 ?tplClass .\
								wd:' + entityId + ' wdt:P31|wdt:P106 ?entityClass .\
								?entityClass wdt:P279* ?mid .\
								?mid wdt:P279* ?tplClass .\
							}\
							GROUP BY ?tpl ?tplClass\
						}\
						OPTIONAL{\
						  ?tpl wdt:P279* ?midTpl .\
						  ?midTpl wdt:P279* ?supTpl .\
						}\
					}\
					GROUP BY ?tpl ?tplClass ?supTpl ?depth\
				}\
				FILTER EXISTS {\
					?page schema:about ?tpl .\
					?page schema:isPartOf <https:' + mw.config.get( 'wgServer' ) + '/> .\
				}\
			}\
			ORDER BY ASC(?depth) DESC(?specific)\
			LIMIT 1';
		var url = 'https://query.wikidata.org/sparql?format=json&query=' + mw.util.rawurlencode( query );
		$.post( url, function( data ) {
			var templateUrl = ( ( ( ( ( data || {} ).results || {} ).bindings || [] )[ 0 ] || {} ).tpl || {} ).value;
			if ( !templateUrl ) {
				mw.notify( 'Не найден соответствующий шаблон-карточка', 'error' );
				generateTemplate( templateNS + ':Универсальная карточка' );
				return;
			}
			var templateId = templateUrl.replace( 'http://www.wikidata.org/entity/', '' );
			getMainInfoboxSitelink( templateId );
		});
	}

	/*
	 * Получение данных о шаблоне из Викиданных и вывод индикаторов.
	 */
	function checkExistingInfobox( templateName ) {
		wdApi.get( {
			action: 'wbgetentities',
			props: 'claims',
			sites: site,
			titles: templateNS + ':' + templateName,
		} ).done( function ( data ) {
			var entityId;
			var isInfobox = false;
			var tplEntityId;
			if ( data.success && data.entities ) {
				for ( tplEntityId in data.entities ) {
					var claims = ( data.entities[ tplEntityId ] || {} ).claims || {};
					// Поиск связанного элемента.
					entityId = ( ( ( ( ( claims.P1423 || {} )[ 0 ] || {} ).mainsnak || {} ).datavalue || {} ).value || {} ).id;

					// Проверка типа шаблона.
					var tplType = ( ( ( ( ( claims.P31 || {} )[ 0 ] || {} ).mainsnak || {} ).datavalue || {} ).value || {} ).id;
					isInfobox = ( tplType === 'Q19887878' );

					break;
				}
			}

			// Иконка шаблона.			
			if ( isInfobox ) {
				addIndicator( 'code', templateNS + ':' + templateName, 'Перейти к шаблону {' + '{' + templateName + '}}' );
			} else {
				addIndicator( 'alert', 'd:' + tplEntityId, 'Перейти к элементу шаблона {' + '{' + templateName + '}} в Викиданных для исправления типа (P31)' );
			}
			
			// Иконка типа.
			if ( entityId ) {
				addIndicator( 'wikiText', 'd:' + entityId, 'Перейти к элементу темы шаблона {' + '{' + templateName + '}} в Викиданных' );
				processInfoboxEntityId( entityId );
			} else {
				addIndicator( 'noWikiText', 'd:' + tplEntityId, 'Перейти к элементу шаблона {' + '{' + templateName + '}} в Викиданных для исправления темы (P1423)' );
			}
		} );
	}
	
	function processInfoboxEntityId( entityId ) {
		// Запрос ссылок для элементов.
		wdApi.get( {
			action: 'wbgetentities',
			props: 'claims',
			ids: entityId
		} ).done( function ( data ) {
			var propertyIds = [];
			if ( data.success ) {
				for ( var i in data.entities ) {
					if ( !i.match( /^Q/ ) ) {
						return;
					}

					var claims = ( data.entities[ i ] || {} ).claims;
					if ( !claims || !claims.P1963 ) {
						continue;
					}
					
					for ( var k in claims.P1963 ) {
						var propertyId = ( ( ( ( ( claims.P1963 || {} )[ k ] || {} ).mainsnak || {} ).datavalue || {} ).value || {} ).id;
						if ( propertyId ) {
							propertyIds.push( propertyId );
						}
					}
				}
			}
			if ( propertyIds.length ) {
				requestPropertiesInfo( propertyIds );
			}
		} );
	}

	function requestPropertiesInfo( propertyIds ) {
		// Запрос ссылок для элементов.
		wdApi.get( {
			action: 'wbgetentities',
			props: 'info',
			ids: propertyIds
		} ).done( function ( data ) {
			var fields = [];
			if ( data.success ) {
				for ( var i in data.entities ) {
					if ( !i.match( /^P/ ) ) {
						return;
					}

					var entity = data.entities[ i ];
					fields.push( {
						code: entity.id,
						datatype: entity.datatype
					} );
				}
			}
		} );
	}

	/*
	 * Получение данных о TemplateData для шаблона и вывод индикатора.
	 */
	function checkTemplateData( templateName ) {
		api.get( {
			action: 'templatedata',
			titles: templateNS + ':' + templateName,
		} ).done( function ( data ) {
			if ( data.pages && Object.keys( data.pages ).length === 0 ) {
				addIndicator( 'articleNotFound', templateNS + ':' + templateName, 'Перейти к элементу шаблона {' + '{' + templateName + '}}' );
			}
		} );
	}
	
	/*
	 * Инициализация скрипта.
	 */
	var init = function () {
		api = new mw.Api();
		wdApi = new mw.ForeignApi( '//www.wikidata.org/w/api.php' );

		if ( mw.config.get( 'wgAction' ) !== 'view' ||
			mw.config.get( 'wgNamespaceNumber' ) ||
			mw.config.get( 'wgIsMainPage' ) ||
			mw.config.get( 'wgDiffNewId' ) ||
			mw.config.get( 'wgDiffOldId' ) ||
			[ 'metawiki', 'wikidatawiki' ].includes( mw.config.get( 'wgDBname' ) )
		) {
			return;
		}
		if ( mw.config.get( 'wgWikibaseItemId' ) === null ) {
			mw.notify( 'К странице не привязан элемент Викиданных', 'warn' );
			return;
		}

		// TODO: Проблема с mw.util.$content где-то в mediawiki.page.ready.
		if ( mw.util.$content === null ) {
			mw.util.$content = $( '#mw-content-text' );
		}

		// Если на странице есть карточка, то добавляем иконки:
		// 1. Перехода к шаблону,
		// 2. Удаления шаблона и вставки пустого.
		// С самим шаблоном ничего не делаем.
		// TODO: Показывать по клику, чтобы можно было увидеть,
		//       как она выглядит с пустыми параметрами.
		var $infobox = mw.util.$content.find( '.infobox:not(.vertical-navbox)' );
		if ( $infobox.length ) {
			// Переход к шаблону.
			var templateName = $infobox.first().data('name');
			if ( templateName && templateName !== '{' + '{subst:PAGENAME}}' ) {
				checkExistingInfobox( templateName );
				checkTemplateData( templateName );
			}

			// Удаление и вставка пустого.
			addIndicator( 'cancel', function ( e ) {
				e.preventDefault();
				mw.util.$content.find( '.infobox' ).remove();
				init();
			}, 'Скрыть имеющиеся шаблоны-карточки и показать, как будет выглядеть автоматически заполненная карточка' );
			return;
		}

		getMainInfobox( mw.config.get( 'wgWikibaseItemId' ) );
	};

	$.when(
		$.ready,
		mw.loader.using( [
			'mediawiki.api',
			'mediawiki.ForeignApi',
			'mediawiki.page.ready',
			'mediawiki.util',
			'oojs-ui-widgets',
			'oojs-ui.styles.icons-alerts',
			'oojs-ui.styles.icons-content',
			'oojs-ui.styles.icons-editing-advanced',
			'oojs-ui.styles.icons-interactions',
		] )
	).done( init );
}( mediaWiki, jQuery ) );