Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/**  * Listing Editor v3.7.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  */  //<nowiki> window.__WIKIVOYAGE_LISTING_EDITOR_VERSION__ = '3.7.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('<div class="mw-h2section" />');             });         } else {             $('#bodyContent').find('h2').each(function(){                 $(this).nextUntil("h1, h2").addBack().wrapAll('<div class="mw-h2section" />');             });         }     }     if ( isNewMarkup ) {         $('#bodyContent').find('.mw-heading3').each(function(){             $(this).nextUntil(".mw-heading1,.mw-heading2,.mw-heading3").addBack().wrapAll('<div class="mw-h3section" />');         });     } else {         $('#bodyContent').find('h3').each(function(){             $(this).nextUntil("h1, h2, h3").addBack().wrapAll('<div class="mw-h3section" />');         });     } };  const insertAddListingBracketedLink = ( addMsg ) => {     return $( `<a role="button" href="javascript:" class="listingeditor-add listingeditor-add-brackets">${addMsg}</a>` ); };  const insertAddListingIconButton = ( addMsg ) => { return $( `<button class="listingeditor-add cdx-button cdx-button--size-large cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--icon-only cdx-button--weight-quiet">     <span class="minerva-icon minerva-icon--addListing"></span>     <span>${addMsg}</span> </button>` ); };  /**  * Utility function for appending the "add listing" link text to a heading.  */ const insertAddListingPlaceholder = function(parentHeading, addMsg = '', useButton = false ) {     const $pheading =  $(parentHeading);     const $headline = $(parentHeading).find( '.mw-headline' );     const editSection = $headline.length ? $headline.next('.mw-editsection') : $pheading.next( '.mw-editsection');     const btn = useButton ?         insertAddListingIconButton( addMsg ) :         insertAddListingBracketedLink( addMsg );     editSection.append( btn ); };  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 = '' ) {     const useButton = mw.config.get( 'skin' ) === 'minerva';     for (let sectionId in SECTION_TO_TEMPLATE_TYPE) {         const topHeading = getHeading( sectionId );         if (topHeading.length) {             insertAddListingPlaceholder(topHeading, addMsg, useButton );             const parentHeading = getSectionElement( topHeading );             $('h3', parentHeading).each(function() {                 insertAddListingPlaceholder(this, addMsg, useButton );             });         }     } };  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)[[Wikivoyage:Travellers%27_pub#c-WhatamIdoing-20230630083400-FredTC-20230630053700]] 	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 (&nbsp;). 	 */ 	var isElementEmpty = function(element) { 		var text = $(element).text(); 		if (!text.trim()) { 			return true; 		} 		return (text.trim() == '&nbsp;'); 	};  	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 = $('<span class="vcard-edit-button noprint">') 			.html(`<a href="javascript:" class="listingeditor-edit">${editMsg}</a>` ) 			.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('&nbsp;|&nbsp;'); 			} 		}); 		// append the edit link 		$(EDIT_LINK_CONTAINER_SELECTOR).append( editButton ); 	};  	let setup = false; 	/** 	 * 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(); 		const init = () => { 			setup = true; 			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); 				} ); 			}); 		}; 		mw.hook( 'wikipage.content' ).add( 			init 		); 		setTimeout(() => { 			if ( !setup ) { 				init(); 			} 		}, 1000); 		addEditButtons(); 	}; 	initListingEditor(); });  module.exports = GadgetListingEditor2023;