MediaWiki:Gadget-ListingEditor2023.js

/** * Listing Editor v3.3.1 * @maintainer Jdlrobson * Please upstream any changes you make here to https://github.com/jdlrobson/Gadget-Workshop/tree/master/GadgetListingEditor * Raise issues at https://github.com/jdlrobson/Gadget-Workshop/issues * to avoid losing them in future updates. *	Source code: https://github.com/jdlrobson/Gadget-Workshop *	Wiki: https://en.wikivoyage.org/wiki/MediaWiki:Gadget-ListingEditor2023Main.js *	Original author: *	- torty3 *	Additional contributors: *	- Andyrom75 *	- ARR8 *	- RolandUnger *	- Wrh2 *	- Jdlrobson *	Changelog: https://en.wikivoyage.org/wiki/Wikivoyage:Listing_editor#Changelog

*	TODO *	- Add support for mobile devices. *	- wrapContent is breaking the expand/collapse logic on the VFD page. *	- populate the input-type select list from LISTING_TEMPLATES *	- Allow syncing Wikipedia link back to Wikidata with wbsetsitelink *	- Allow hierarchy of preferred sources, rather than just one source, for Wikidata sync *		- E.g. get URL with language of work = english before any other language version if exists *		- E.g. get fall back to getting coordinate of headquarters if geographic coordinates unavailable. Prioritize getting coordinates of entrance before any other coordinates if all present *		- E.g. Can use multiple sources to fetch address *		- Figure out how to get this to upload properly */ // window.__WIKIVOYAGE_LISTING_EDITOR_VERSION__ = '3.3.1'

'use strict';

var GadgetListingEditor2023 = {};

/** * Wrap the h2/h3 heading tag and everything up to the next section * (including sub-sections) in a div to make it easier to traverse the DOM. * This change introduces the potential for code incompatibility should the * div cause any CSS or UI conflicts. */

const wrapContent = function { var isNewMarkup = $( '.mw-heading').length > 0; // No need to wrap with ?useparsoid=1&safemode=1 if ( $( 'section .mw-heading3, section .mw-heading2' ).length ) { return; }   // MobileFrontend use-case if ( $( '.mw-parser-output > h2.section-heading' ).length ) { $( '.mw-parser-output > section' ).addClass( 'mw-h2section' ); } else { if ( isNewMarkup ) { $('#bodyContent').find('.mw-heading2').each(function{               $(this).nextUntil(".mw-heading1, .mw-heading2").addBack.wrapAll(' ');            }); } else { $('#bodyContent').find('h2').each(function{               $(this).nextUntil("h1, h2").addBack.wrapAll(' ');            }); }   }    if ( isNewMarkup ) { $('#bodyContent').find('.mw-heading3').each(function{           $(this).nextUntil(".mw-heading1,.mw-heading2,.mw-heading3").addBack.wrapAll(' ');        }); } else { $('#bodyContent').find('h3').each(function{           $(this).nextUntil("h1, h2, h3").addBack.wrapAll(' ');        }); } };

/** * Utility function for appending the "add listing" link text to a heading. */ const insertAddListingPlaceholder = function(parentHeading, addMsg = '' ) { const $pheading = $(parentHeading); const $headline = $(parentHeading).find( '.mw-headline' ); const editSection = $headline.length ? $headline.next('.mw-editsection') : $pheading.next( '.mw-editsection'); editSection.append(` [ ${addMsg} ] `); };

const getHeading = ( sectionId ) => { // do not search using "#id" for two reasons. one, the article might // re-use the same heading elsewhere and thus have two of the same ID. // two, unicode headings are escaped ("è" becomes ".C3.A8") and the dot // is interpreted by JQuery to indicate a child pattern unless it is   // escaped const $nodeWithId = $(`[id="${sectionId}"]`); if ( $nodeWithId.is( 'h2' ) ) { return $nodeWithId; } else { return $nodeWithId.closest( 'h2' ); } };

const getSectionElement = ( $headingElement ) => { if ( $headingElement.is( '.section-heading' ) ) { return $headingElement.next( 'section.mw-h2section' ); } else { return $headingElement.closest( 'div.mw-h2section, section' ); } };

/** * Place an "add listing" link at the top of each section heading next to * the "edit" link in the section heading. */ const addListingButtons = function( SECTION_TO_TEMPLATE_TYPE, addMsg = '' ) { for (let sectionId in SECTION_TO_TEMPLATE_TYPE) { const topHeading = getHeading( sectionId ); if (topHeading.length) { insertAddListingPlaceholder(topHeading, addMsg); const parentHeading = getSectionElement( topHeading ); $('h3', parentHeading).each(function {               insertAddListingPlaceholder(this, addMsg);            }); }   } };

var contentTransform$1 = { addListingButtons, wrapContent, insertAddListingPlaceholder };

// map section heading ID to the listing template to use for that section var sectionToTemplateType$1 = function ( DB_NAME = 'enwikivoyage' ) { switch ( DB_NAME ) { case 'frwikivoyage': return { Aller: 'Aller', Circuler: 'Circuler', Voir: 'Voir', Faire: 'Faire', Acheter: 'Acheter', Manger: 'Manger', Communiquer: 'Listing', 'Boire_un_verre_.2F_Sortir': 'Sortir', Sortir: 'Sortir', Se_loger: 'Se loger', 'S.C3.A9curit.C3.A9': 'Listing', 'G.C3.A9rer_le_quotidien': 'Représentation diplomatique', Villes: 'Ville', Autres_destinations: 'Destination', Aux_environs: 'Destination' };       case 'itwikivoyage': return { 'Cosa_vedere': 'see', 'Cosa_fare': 'do', 'Acquisti': 'buy', 'Dove_mangiare': 'eat', 'Come_divertirsi': 'drink', 'Dove_alloggiare': 'sleep', 'Eventi_e_feste': 'listing', 'Come arrivare': 'listing', 'Come spostarsi': 'listing' };       default: return { 'See': 'see', 'Do': 'do', 'Buy': 'buy', 'Eat': 'eat', 'Drink': 'drink', 'Sleep': 'sleep', 'Connect': 'listing', 'Wait': 'see', 'See_and_do': 'see', 'Eat_and_drink': 'eat', 'Get_in': 'go', 'Get_around': 'go', 'Anreise': 'station', // go               'Mobilität': 'public transport', // go                'Sehenswürdigkeiten': 'monument', // see 'Aktivitäten': 'sports', // do               'Einkaufen': 'shop', // buy 'Küche': 'restaurant', // eat 'Nachtleben': 'bar', // drink // dummy line (es) // drink and night life 'Unterkunft': 'hotel', // sleep 'Lernen': 'education', // education 'Arbeiten': 'administration', // work 'Sicherheit': 'administration', // security 'Gesundheit': 'health', // health 'Praktische_Hinweise': 'office' // practicalities };   } };

const contentTransform = contentTransform$1; const sectionToTemplateType = sectionToTemplateType$1;

$(function {	const USE_LISTING_BETA = window.__USE_LISTING_EDITOR_BETA__;	const GADGET_NAME = USE_LISTING_BETA ? 'ext.gadget.ListingEditorMainBeta' :		'ext.gadget.ListingEditorMain';	const GADGET_CONFIG_NAME = 'ext.gadget.ListingEditorConfig';	var DEV_NAMESPACE = 9000;

// (oldid=4687849)Travellers%27_pub if ( !USE_LISTING_BETA && mw.config.get( 'skin' ) === 'minerva' ) { return; }

// 	// UPDATE THE FOLLOWING TO MATCH WIKIVOYAGE ARTICLE SECTION NAMES //

var DB_NAME = mw.config.get( 'wgDBname' ); const SECTION_TO_TEMPLATE_TYPE = sectionToTemplateType( DB_NAME ); // selector that identifies the HTML elements into which the 'edit' link // for each listing will be placed var EDIT_LINK_CONTAINER_SELECTOR = 'span.listing-metadata-items'; var MODE_EDIT = 'edit';

// List of namespaces where the editor is allowed var ALLOWED_NAMESPACE = [ 0, //Main 2, //User 4 //Wikivoyage ];

// For development purposes, if localhost is detected, support namespace 9000. if ( window.location.host.indexOf( 'localhost' ) > -1 ) { ALLOWED_NAMESPACE.push( DEV_NAMESPACE ); }

// If any of these patterns are present on a page then no 'add listing' // buttons will be added to the page const DISALLOW_ADD_LISTING_IF_PRESENT = ( function {		switch ( DB_NAME ) {			case 'frwikivoyage':				return [ '#R\u00E9gions', '#\u00EEles' ];			case 'itwikivoyage':				return  ['#Centri_urbani', '#Altre_destinazioni'];			default:				return ['#Cities', '#Other_destinations', '#Islands', '#print-districts' ];		}	}  );

/**	 * Determine if the specified DOM element contains only whitespace or * whitespace HTML characters. */	var isElementEmpty = function(element) { var text = $(element).text; if (!text.trim) { return true; }		return (text.trim == ' '); };

var TRANSLATIONS_ALL = { en: { add: 'add listing', addBeta: 'add listing (beta)', edit: 'edit', editBeta: 'edit (beta)' },		de: { add: 'Eintrag hinzufügen', edit: 'bearbeiten', addBeta: 'Eintrag hinzufügen (beta)', editBeta: 'bearbeiten (beta)' },		it: { add: 'aggiungi elemento', edit: 'modifica', addBeta: 'aggiungi elemento (beta)', editBeta: 'modifica (beta)' }	};	var TRANSLATIONS = $.extend( true,		{},		TRANSLATIONS_ALL.en,		TRANSLATIONS_ALL[ mw.config.get( 'wgUserLanguage' ) ]	);

/**	 * 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 listingEditorAllowedForCurrentPage = function { var namespace = mw.config.get( 'wgNamespaceNumber' ); // allow development if ( location.host.includes( 'localhost' ) ) { return true; }		if ( namespace === DEV_NAMESPACE ) { return true; }		if (ALLOWED_NAMESPACE.indexOf(namespace)<0) { return false; }		if ( mw.config.get('wgAction') != 'view' || $('#mw-revision-info').length				|| mw.config.get('wgCurRevisionId') != mw.config.get('wgRevisionId')				|| $('#ca-viewsource').length ) { return false; }		return true; };

const wrapContent = contentTransform.wrapContent;

var isLoaded = false; function importForeignModule { if ( isLoaded ) { return Promise.resolve( mw.loader.require ); } else if ( mw.loader.getState( GADGET_NAME ) !== 'ready' ) { isLoaded = true; if ( mw.loader.getState( GADGET_NAME ) === null ) { return new Promise( function ( resolve ) {					mw.loader.addScriptTag( `https://en.wikivoyage.org/w/load.php?modules=${GADGET_NAME}`, function { setTimeout( function {							resolve( mw.loader.require );						}, 300 ); } );				} );			} else { // use the local gadget return mw.loader.using( `${GADGET_NAME}` ).then( => mw.loader.require ); }		}	}

function loadMain { const localModuleForDebugging = window._listingEditorModule; return Promise.all( [			localModuleForDebugging ? Promise.resolve : importForeignModule,			mw.loader.using( GADGET_CONFIG_NAME )		] ).then( function ( args ) {			var req = args[ 1 ];			var config = req( GADGET_CONFIG_NAME );			var module = localModuleForDebugging || req( GADGET_NAME );			return module( ALLOWED_NAMESPACE, SECTION_TO_TEMPLATE_TYPE, config );		} ); }

/**	 * Place an "edit" link next to all existing listing tags. */	var addEditButtons = function { const editMsg = USE_LISTING_BETA ? TRANSLATIONS.editBeta : TRANSLATIONS.edit; var editButton = $(' ') .html(`${editMsg}` ) .on('click', function {				var $this = $(this);				loadMain.then( function ( core ) { core.initListingEditorDialog(MODE_EDIT, $this); } );			});		// if there is already metadata present add a separator $(EDIT_LINK_CONTAINER_SELECTOR).each(function {			if (!isElementEmpty(this)) {				$(this).append(' | ');			}		}); // append the edit link $(EDIT_LINK_CONTAINER_SELECTOR).append( editButton ); };

/**	 * Called on DOM ready, this method initializes the listing editor and * adds the "add/edit listing" links to sections and existing listings. */	var initListingEditor = function { if (!listingEditorAllowedForCurrentPage) { return; }		wrapContent; mw.hook( 'wikipage.content' ).add(			 => {

if ($(DISALLOW_ADD_LISTING_IF_PRESENT.join(',')).length > 0) { return false; }				contentTransform.addListingButtons(					SECTION_TO_TEMPLATE_TYPE,					USE_LISTING_BETA ? TRANSLATIONS.addBeta : TRANSLATIONS.add				); $('.listingeditor-add').on('click', function {					const $this = $(this);					loadMain.then( function ( core ) { core.initListingEditorDialog(core.MODE_ADD, $this); } );				});			}		);		addEditButtons;	};	initListingEditor; });

module.exports = GadgetListingEditor2023;