MediaWiki:Gadget-InitListingTools.js

Aus Wikivoyage

Hinweis: Leere nach dem Veröffentlichen den Browser-Cache, um die Änderungen sehen zu können.

  • Firefox/Safari: Umschalttaste drücken und gleichzeitig Aktualisieren anklicken oder entweder Strg+F5 oder Strg+R (⌘+R auf dem Mac) drücken
  • Google Chrome: Umschalttaste+Strg+R (⌘+Umschalttaste+R auf dem Mac) drücken
  • Internet Explorer/Edge: Strg+F5 drücken oder Strg drücken und gleichzeitig Aktualisieren anklicken
  • Opera: Strg+F5
//<nowiki>
/**	initListingTools v1.0, 2023-02-26
	Initialization of listing editor and listing info
	Original author: Roland Unger
	Support of desktop and mobile views
	Documentation: https://de.wikivoyage.org/wiki/Wikivoyage:initListingTools.js
	License: GPL-2.0+, CC-by-sa 3.0
*/

( function( $, mw ) {
	'use strict';

	var initListingTools = function() {

		// scripts' page names
		var scripts = {
			listingEditor: 'MediaWiki:Gadget-ListingEditor.js'
		};

		// allowed namespaces for Listing Editor
		var allowedNamespaces = [
			0, // Main
			2, // User
		];

		// options for module import
		var options = [
			{
				page: 'Module:Marker utilities/Types', // name of module to import
				index: 'type',                         // name of key field
				start: /^.*types *= *{/g,              // to remove from start
				end: /,? *},? *} *$/g,                 // to remove at the end
				label: 'label',                        // second sort key
				alias: 'alias',                        // alias for index
				arrayName: 'types',                    // name of the new array
				defaultArray: [
					{ 'type': 'area', group: 'area', label: 'Gebiet' },
					{ 'type': 'buy', group: 'buy', label: 'Einkaufen' },
					{ 'type': 'do', group: 'do', label: 'Aktivität' },
					{ 'type': 'drink', group: 'drink', label: 'Trinken' },
					{ 'type': 'eat', group: 'eat', label: 'Küche, Essen' },
					{ 'type': 'go', group: 'go', label: 'Anreise' },
					{ 'type': 'other', group: 'other', label: 'Anderes' },
					{ 'type': 'populated', group: 'populated', label: 'Besiedeltes Gebiet' },
					{ 'type': 'see', group: 'see', label: 'Sehenswürdigkeit' },
					{ 'type': 'sleep', group: 'sleep', label: 'Unterkunft' },
					{ 'type': 'view', group: 'view', label: 'Aussicht' },
				]
			},
			{
				page: 'Module:Marker utilities/Groups',
				index: 'group',
				start: /^.*groups *= *{/g,
				end: /,? *},? *} *$/g,
				label: 'label',
				alias: 'alias',
				arrayName: 'groups',
				defaultArray: [
					{ group: 'area', label: 'Gebiet', color: '#0000FF' },
					{ group: 'buy', label: 'Einkaufen', color: '#008080' },
					{ group: 'do', label: 'Aktivität', color: '#808080' },
					{ group: 'drink', label: 'Trinken', color: '#000000' },
					{ group: 'eat', label: 'Essen', color: '#D2691E' },
					{ group: 'go', label: 'Anreise', color: '#A52A2A' },
					{ group: 'other', label: 'Anderes', color: '#228B22' },
					{ group: 'populated', label: 'Besiedeltes Gebiet', color: '#0000FF' },
					{ group: 'see', label: 'Sehenswürdigkeit', color: '#4682B4' },
					{ group: 'sleep', label: 'Unterkunft', color: '#000080' },
					{ group: 'view', label: 'Aussicht', color: '#4169E1' },
				]
			},
			{
				page: 'Module:VCard/Subtypes',
				index: 'type',
				start: /^.* f *= *{/g,
				end: /,? *} *, *g *=.*$/g,
				sortKey: 'sortkey', // first sort key
				label: 'n',         // second sort key
				arrayName: 'subtypes',
				defaultArray: [
					{ 'type': 'budget',   g: 1, w: '', n: '', f: '' },
					{ 'type': 'midrange', g: 1, w: '', n: '', f: '' },
					{ 'type': 'upmarket', g: 1, w: '', n: '', f: '' },
				]
			},
			{
				page: 'Module:VCard/Cards',
				index: '',                  // only import, no rearranging
				start: /^.*cards *= *{/g,
				end: /,? *},? *} *$/g,
				arrayName: 'payments',
				defaultArray: {}
			},
			{
				page: 'Module:Hours/i18n',
				index: '',
				start: /^.*dateIds *= *{/g,
				end: /,? *},? *} *$/g,
				arrayName: 'hours',
				defaultArray: {}
			},
			{
				page: 'Module:VCard/Qualifiers',
				index: '',
				start: /^.*labels *= *{/g,
				end: /,? *},? *} *$/g,
				arrayName: 'qualifiers',
				defaultArray: {}
			},
			{
				page: 'Module:CountryData/Currencies',
				index: '',
				start: /^.*currencies *= *{/g,
				end: /,? *} *, *isoToQid *=.*$/g,
				arrayName: 'currencies',
				defaultArray: {}
			}
		];
	
		// data: data array from module
		// item: single item from options array
		// isDefault: data are defaults from options array
		var analyzeAndCopyData = function( data, item, isDefault ) {
			var i, dataItem;

			// adding missing label from index
			for ( i = 0; i < data.length; i++ ) {
				dataItem = data[ i ];
				dataItem[ item.label ] = dataItem[ item.label ] || '';
				if ( dataItem[ item.label ] === '' ) {
					if ( typeof dataItem[ item.alias ] === 'undefined' )
						dataItem[ item.label ] = dataItem[ item.index ].replace( /_/g, ' ' );
					else
						if ( typeof( dataItem[ item.alias ] ) === 'string' )
							dataItem[ item.label ] = dataItem[ item.alias ].replace( /_/g, ' ' );
						else if ( dataItem[ item.alias ][ 0 ] )
							dataItem[ item.label ] = dataItem[ item.alias ][ 0 ].replace( /_/g, ' ' );
				}
			}
			// sorting by label in alphabetic order
			data.sort( function( a, b ) {
				if ( item.sortKey ) {
					a = a[ item.sortKey ] || a[ item.label ];
					b = b[ item.sortKey ] || b[ item.label ];
				} else {
					a = a[ item.label ];
					b = b[ item.label ];
				}
				return a.localeCompare( b );
			} );

			// copying
			if ( isDefault ) {
				// copy only if window.ListingEditor.array is empty
				if ( typeof window.ListingEditor[ item.arrayName ] === 'undefined' ||
					window.ListingEditor[ item.arrayName ].length < 1 ) {
					window.ListingEditor[ item.arrayName ] = [].concat( data );
				}
			} else {
				window.ListingEditor[ item.arrayName ] = [].concat( data );
			}
		};

		// item: item from options array
		var getDataFromSingleModule = function( item ) {
			return $.ajax( {
				type: 'GET',
				url: mw.util.wikiScript( '' ),
				data: { title: item.page, action: 'raw', ctype: 'text/plain' },
				timeout: 3000,
				dataType: 'text'
			} ).done( function( data ) {
				data = data.replace( /\-\-.*\n/g, '' )      // remove comments
					.replace( /[\s+\t+]/gm, ' ' );          // remove line breaks and tabs

				if ( item.index !== '' )
					// convert to (sortable) array
					data = data.replace( item.start, '[' )  // delete beginning
						.replace( item.end, ']' )           // delete end
						.replace( /([,{]) *(wd|alias) *= *\{([^}]*)\}/g, '$1 "$2": [$3]' )
						.replace( /( *\[ *")([\w\-]+)(" *\] *= *\{)/g, '{ "' + item.index + '": "$2", ' )
						.replace( /( *)([\w\-]+)( *= *\{)/g, '{ "' + item.index + '": "$2", ' )
						.replace( /(, *)([\w\-]+)( *=)/g, ', "$2":' );
				else
					// keep as object
					data = data.replace( item.start, '{' )  // delete beginning
						.replace( item.end, '}' )           // delete end
						.replace( /( *\[ *")([\w\-]+)(" *\] *= *)/g, '"$2":' )
						.replace( /([,\{]) *([\w\-]+)( *=)/g, '$1 "$2":' );

				// check if data string is valid JSON
				var isDefault = false, json;
				try {
					json = JSON.parse( data );
				} catch ( e ) {
					// invalid JSON
					json = item.defaultArray;
					isDefault = true;
					var pos = e.message.match( /column (\d+) of/i )[ 1 ];
					pos = data.substring( pos - 10, pos + 10 );
					console.log( e.message + ', data: ' + item.page + ', text: ' + pos );
				}
				if ( item.index !== '' )
					analyzeAndCopyData( json, item, isDefault );
				else
					window.ListingEditor[ item.arrayName ] = json;
			} ).fail( function() {
				var json = item.defaultArray;
				if ( item.index !== '' )
					analyzeAndCopyData( json, item, true );
				else
					window.ListingEditor[ item.arrayName ] = json;
			} );
		};

		var loadEditor = function() {
			mw.loader.using( [ 'mediawiki.util', 'mediawiki.api', 'jquery.ui', 'jquery.chosen' ] ).then( function() {
				mw.loader.load( '/w/index.php?title=' + scripts.listingEditor + '&action=raw&ctype=text/javascript' );
			});
		};

		var getDataFromModules = function() {
			var promiseArray = [], i;

			// mw already exists but maybe not the ListingEditor object
			if ( typeof window.ListingEditor === 'undefined' )
				window.ListingEditor = {};

			for ( i = 0; i < options.length; i++ )
				promiseArray.push( getDataFromSingleModule( options[ i ] ) );

			// wait for getting all external data
			var isIE11 = !!window.MSInputMethodContext && !!document.documentMode;
			if ( isIE11 )
				$.when.apply( $, promiseArray ).then( function() {
					loadEditor();
				} );
			else
				if ( typeof Promise !== 'undefined' )
					Promise.all( promiseArray )
						.then( function() {
							loadEditor();
						} )
						.catch( function() {
							loadEditor();
							// error warning
						} );
			return;
		};

		// *********************************************************************
		// getting JSON object from Wikidata search
		var ajaxSearch = function( url, data, success ) {
			data.format = 'json';
			$.ajax( {
				url: url,
				data: data,
				dataType: 'jsonp',
				success: success,
				cache: false, // it will force requested pages not to be cached by
				              // the browser in case of script and jsonp data types
				timeout: 3000
			} );
		};

		// parse jsonObj for suboject with index id
		var parseJsonEntitiesObj = function( jsonObj, id ) {
			if ( !jsonObj || !jsonObj.entities || !jsonObj.entities[ id ] )
				return null;
			else
				return jsonObj.entities[ id ];
		};

		// getting first value of a set of Wikidata statements
		var getWikidataValue = function( jsonObj, id, property ) {
			var entity = parseJsonEntitiesObj( jsonObj, id );
			if ( !entity || !entity.claims || !entity.claims[ property ] )
				return null;

			var statements = entity.claims[ property ];
			if ( !statements || statements.length < 1 || !statements[ 0 ].mainsnak ||
				!statements[ 0 ].mainsnak.datavalue )
				return null;
			else
				return statements[ 0 ].mainsnak.datavalue.value;
		};

		// *********************************************************************
		/**	Return false if the current page should not enable the listing editor.
			Examples where the listing editor should not be enabled include talk
			pages, edit pages, history pages, etc.
		*/
		var checkIfAllowed = function() {
			var namespace = mw.config.get( 'wgNamespaceNumber' );
			if ( !allowedNamespaces.includes( namespace ) ||
				mw.config.get( 'wgAction' ) != 'view' || $( '#mw-revision-info' ).length ||
				mw.config.get( 'wgCurRevisionId' ) != mw.config.get( 'wgRevisionId' ) ||
				!mw.config.get( 'wgRelevantPageIsProbablyEditable' ) ||
				$( '#ca-viewsource' ).length )
				return false;
			else
				return true;
		};

		// adding currency, country calling code and local calling code to
		// body-tag data attributes for use in listing editor
		var addDataToBodyTag = function() {
			var body = $( 'body' ),
				data, i, id, success, url, value;

			// copying data-currency data-country-calling-code, etc. from
			// indicator or listings to body tag for use in listing editor
			var dataTags = $( '.voy-coord-indicator' );
			if ( !dataTags.length || dataTags.attr( 'data-country' ) === undefined )
				dataTags = $( '.vCard' );
			var list = [ 'data-currency', 'data-country-calling-code', 'data-lang', 'data-lang-name', 'data-dir', 'data-trunk-prefix' ];
			for ( i = 0; i < list.length; i++ ) {
				data = dataTags.attr( list [ i ] ) || '';
				if ( data !== '' )
					body.attr( list [ i ], data );
			}

			// copying local calling code from Wikidata to body tag
			// if Wikidata id exists
			id = mw.config.get( 'wgWikibaseItemId' );
			if ( id ) {
				url = '//www.wikidata.org' + '/w/api.php';
				data = {
					action: 'wbgetentities',
					ids: id,
					languages: mw.config.get( 'wgPageContentLanguage' ),
				};
				success = function( jsonObj ) {
					value = getWikidataValue( jsonObj, id, 'P473' );
					if ( value )
						body.attr( 'data-local-calling-code', value );
				};
				ajaxSearch( url, data, success );			
			}
		};
		
		var initEditor = function() {
			var suppressLE = ( window.suppressListingEditor ) ?
				window.suppressListingEditor : false;
    		if ( !suppressLE )
				getDataFromModules();
		};

		var init = function() {
			if ( checkIfAllowed() ) {
				addDataToBodyTag();
				initEditor();
			}
		};

		return { init: init };
	} ();

	$( initListingTools.init );

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