// Add listing buttons in edit toolbar var customizeToolbar2 = function( $textarea ) { $textarea.wikiEditor( 'removeFromToolbar', { section: 'main', group: 'insert', tool: 'reference' }); $textarea.wikiEditor( 'addToToolbar', { section: 'main', groups: { 'transform': { label: 'Tranform' } } } ); /** * Parses a single line of raw wikitext into listing template components. * "When in doubt, put it in content." */ function parseListingLine(line) { var result = { name: '', address: '', phone: '', content: '' }; // 1. Remove leading spaces and the bullet point var cleanedLine = line.replace(/^\s*\*?\s*/, ''); // 2. Extract Name var boldMatch = cleanedLine.match(/^'''(.*?)'''\s*(?:[-–—,]\s*)?(?:is\s+)?(.*)$/i); if (boldMatch) { result.name = boldMatch[1].trim(); result.content = boldMatch[2].trim(); } else { var dashMatch = cleanedLine.match(/^([^-–—\n]+?)\s*[-–—]\s*(?:is\s+)?(.*)$/i); if (dashMatch) { result.name = dashMatch[1].trim(); result.content = dashMatch[2].trim(); } else { result.content = cleanedLine; } } // 3. Extract Italicized Address var addressMatch = result.content.match(/^''([^']+)''\s*(.*)$/); if (addressMatch) { result.address = addressMatch[1].trim(); result.content = addressMatch[2].trim(); } // 4. Extract International Phone Number var phoneMatch = result.content.match(/(\+[\d\-\s\(\)]{8,})/); if (phoneMatch) { result.phone = phoneMatch[1].trim(); result.content = result.content.replace(phoneMatch[1], '').replace(/^[,\.\s]+|[,\.\s]+$/g, '').trim(); } return result; } /** * Builds the final Wikivoyage listing template string. */ function buildListingTemplate(type, parsedData, isNew) { // Only use the current date for newly created templates var lastEdit = (isNew && typeof CURRENT_LAST_EDIT_DATE !== 'undefined') ? CURRENT_LAST_EDIT_DATE : ''; var template = '* {{' + type + '\n' + '| name=' + (parsedData.name || '') + ' | alt= | url= | email=\n' + '| address=' + (parsedData.address || '') + ' | lat= | long= | directions=\n' + '| phone=' + (parsedData.phone || '') + ' | tollfree=\n'; // Add specific fields for 'sleep' listings if (type === 'sleep') { template += '| fax= | checkin= | checkout=\n'; } template += '| hours= | price=\n' + '| wikidata=\n' + '| lastedit=' + lastEdit + '\n' + '| content=' + (parsedData.content || '') + '\n}}'; return template; } // --------------------------------------------------------- // Toolbar Configuration & Integration // --------------------------------------------------------- var LISTING_TOOLBAR_ITEMS = { 'see': { label: 'See listing', icon: '//upload.wikimedia.org/wikipedia/commons/thumb/b/b7/Italian_traffic_signs_-_icona_museo.svg/20px-Italian_traffic_signs_-_icona_museo.svg.png' }, 'do': { label: 'Do listing', // or use labelMsg for a localized label, see above icon: '//upload.wikimedia.org/wikipedia/commons/thumb/3/30/Pictograms-nps-bicycle_trail-2.svg/20px-Pictograms-nps-bicycle_trail-2.svg.png' }, 'buy': { label: 'Buy listing', // or use labelMsg for a localized label, see above icon: '//upload.wikimedia.org/wikipedia/commons/thumb/a/a1/Italian_traffic_signs_-_icona_supermercato.svg/20px-Italian_traffic_signs_-_icona_supermercato.svg.png' }, 'eat': { label: 'Eat listing', // or use labelMsg for a localized label, see above icon: '//upload.wikimedia.org/wikipedia/commons/thumb/2/2c/Italian_traffic_signs_-_icona_ristorante.svg/20px-Italian_traffic_signs_-_icona_ristorante.svg.png' }, 'drink': { label: 'Drink listing', // or use labelMsg for a localized label, see above icon: '//upload.wikimedia.org/wikipedia/commons/thumb/a/a5/Verre_cocktail.svg/20px-Verre_cocktail.svg.png' }, 'sleep': { label: 'Sleep listing', // or use labelMsg for a localized label, see above icon: '//upload.wikimedia.org/wikipedia/commons/thumb/2/25/Pictograms-nps-lodging.svg/20px-Pictograms-nps-lodging.svg.png' }, 'go': { label: 'Go listing', // or use labelMsg for a localized label, see above icon: '//upload.wikimedia.org/wikipedia/commons/thumb/a/ae/Pictograms-nps-airport.svg/20px-Pictograms-nps-airport.svg.png' }, 'listing': { label: 'Other listing', // or use labelMsg for a localized label, see above icon: '//upload.wikimedia.org/wikipedia/commons/thumb/f/f5/Italian_traffic_signs_-_icona_informazioni_%28figura_II_108%29.svg/20px-Italian_traffic_signs_-_icona_informazioni_%28figura_II_108%29.svg.png' } }; $.each(LISTING_TOOLBAR_ITEMS, function(index, element) { var toolConfig = {}; toolConfig[index] = { label: element.label, type: 'button', icon: element.icon, action: { type: 'callback', execute: function(context) { var selection = context.$textarea.textSelection('getSelection'); // Case 1: No selection (Create empty new item and focus cursor on 'name') if (!selection) { var pre = '* {{' + index + '\n| name='; var post = ' | alt= | url= | email=\n' + '| address= | lat= | long= | directions=\n' + '| phone= | tollfree=\n'; if (index === 'sleep') { post += '| fax= | checkin= | checkout=\n'; } post += '| hours= | price=\n' + '| wikidata=\n' + '| lastedit=' + (typeof CURRENT_LAST_EDIT_DATE !== 'undefined' ? CURRENT_LAST_EDIT_DATE : '') + '\n' + '| content=\n}}'; context.$textarea.textSelection('encapsulateSelection', { pre: pre, peri: '', post: post, replace: true }); return; } // Case 2: Transform selection (handles one or multiple lines) var lines = selection.split('\n'); var replacements = []; for (var i = 0; i < lines.length; i++) { var line = lines[i]; // Preserve purely empty lines between selections if (line.trim() === '') { replacements.push(line); continue; } var parsed = parseListingLine(line); var replacement = buildListingTemplate(index, parsed, false); replacements.push(replacement); } context.$textarea.textSelection('encapsulateSelection', { replace: true, peri: replacements.join('\n') }); } } }; // Add the button to the wiki editor $textarea.wikiEditor('addToToolbar', { section: 'main', group: 'listings', tools: toolConfig }); }); }; mw.hook( 'wikiEditor.toolbarReady' ).add( customizeToolbar2 );