Benutzer:Nw520/vCaTA.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
$.when( mw.loader.using( 'mediawiki.util' ), $.ready ).then( function () {
	let dialog = null;

	function getTypes() {
		return new Promise( function ( resolve, reject ) {
			Promise.all( [
				getTypesFromWikivoyage(),
				getGroupsFromWikivoyage()
			] ).then( function ( [ types, groups ] ) {
				resolve( Object.keys( types ).sort( function ( a, b ) {
					return types[ a ].label.localeCompare( types[ b ].label );
				} ).map( function ( typeName ) {
					const type = types[ typeName ];
					return {
						color: groups[ type.group ].color || null,
						data: typeName,
						group: type.group,
						groupLabel: groups[ type.group ].label || null,
						label: type.label || typeName
					};
				} ) );
			} ).catch( function ( e ) {
				reject( e );
			} );
		} );
	}

	function getGroupsFromWikivoyage() {
		return new Promise( function ( resolve, reject ) {
			$.ajax( {
				data: {
					title: 'Modul:Marker utilities/Groups',
					action: 'raw',
					ctype: 'text/plain'
				},
				method: 'GET',
				url: mw.util.wikiScript( '' )
			} ).done( function ( e ) {
				// FIXME: Use same approach as subtype/type
				const lineRegexp = /[ \t]+(([a-zA-Z0-9 _]+?)|'([a-zA-Z0-9 _]+)'|"([a-zA-Z0-9 _]+)"|\['([a-zA-Z0-9 _]+)'\]|\["([a-zA-Z0-9 _]+)"\]) *= *{(.+?)}/g;
				let match;
				const jsonLines = [];
				do {
					match = lineRegexp.exec( e );
					if ( match ) {
						const parameterName = match[ 2 ] || match[ 3 ] || match[ 4 ] || match[ 5 ] || match[ 6 ];
						let definition = match[ 7 ];
						definition = definition.replace( /(^|,) ([^ ]+) = /g, '$1"$2":' );
						jsonLines.push( `"${parameterName.replace( /_/g, ' ' )}": {${definition}}` );
					}
				} while ( match );
				resolve( JSON.parse( `{${jsonLines.join( ',' )}}` ) );
			} ).fail( function ( e ) {
				reject( e );
			} );
		} );
	}

	function getTypesFromWikivoyage() {
		return new Promise( function ( resolve, reject ) {
			$.ajax( {
				data: {
					title: 'Modul:Marker utilities/Types',
					action: 'raw',
					ctype: 'text/plain'
				},
				method: 'GET',
				url: mw.util.wikiScript( '' )
			} ).done(
				/**
				 * @param {string} e
				 */
				function ( e ) {
					const lines = e.split( '\n' );
					const declarationRegex = /(?:\["(?<nameBracketed>[^"]+)"\]|(?<nameNonBracketed>[\w]+))[\s\t]*=[\s\t]*\{(?<body>[^\n]+)\}/; // Extracts type name and "body" of declaration. We're assuming that there's at most one declaration per line and that each declaration takes up exactly one line
					const groupRegex = /group[\s\t]*=[\s\t]*"(?<group>[^"]+)"/; // Extracts group, we're using several regexes to prevent requiring a specific order
					const labelRegex = /label[\s\t]*=[\s\t]*"(?<label>[^"]+)"/; // Extracts label
					/**
					 * @type {Object.<string, { group: string, label: string }>}
					 */
					const groups = {};
					lines.forEach( function ( line ) {
						const declarationEval = declarationRegex.exec( line );
						if ( declarationEval ) {
							const name = declarationEval.groups.nameBracketed || declarationEval.groups.nameNonBracketed;
							const body = declarationEval.groups.body;
							// Group
							const groupEval = groupRegex.exec( body );
							const group = groupEval ? groupEval.groups.group : null;
							const labelEval = labelRegex.exec( body );
							const label = labelEval ? labelEval.groups.label : null;
							if ( group !== null && label !== null ) {
								groups[ name ] = {
									group: group,
									label: label
								};
							}
						}
					} );
					resolve( groups );
				} ).fail( function ( e ) {
				reject( e );
			} );
		} );
	}

	function getTypesTable() {
		return new Promise( function ( resolve, reject ) {
			getTypes().then( function ( types ) {
				let out = `<table>
					<thead>
						<th>Typ</th>
						<th>Bezeichnung</th>
						<th>Gruppe</th>
						<th>Gruppenbezeichnung</th>
					</thead>`;
				console.log( types );
				$( types ).each( function ( _i, type ) {
					out += `
						<tr class="vcata-item" style="${type.color !== null ? `background-color:${type.color};color:#fff` : ''}">
							<td class="vcata-item-type"><input class="vcata-pseudo-input" type="text" value="${type.data.replace( /_/g, ' ' )}" readonly /></td>
							<td class="vcata-item-label">${type.label}</td>
							<td class="vcata-item-group">${type.group || '&mdash;'}</td>
							<td class="vcata-item-group-label">${type.groupLabel || '&mdash;'}</td>
						</tr>`;
				} );
				out += '</table>';
				resolve( out );
			} ).catch( function ( e ) {
				reject( e );
			} );
		} );
	}

	function search( q ) {
		q = q.toLowerCase();
		if ( q === '' ) {
			dialog.getContent().find( '.vcata-item' ).each( function ( i, row ) {
				$( row ).show();
			} );
		} else {
			dialog.getContent().find( '.vcata-item' ).each( function ( i, row ) {
				const $i = $( row );
				if ( $i.find( '.vcata-item-type input' ).val().toLowerCase().includes( q ) || $i.find( '.vcata-item-label' ).text().toLowerCase().includes( q ) || $i.find( '.vcata-item-group' ).text().toLowerCase().includes( q ) || $i.find( '.vcata-item-group-label' ).text().toLowerCase().includes( q ) ) {
					$i.show();
				} else {
					$i.hide();
				}
			} );
		}
	}

	function setup() {
		getTypesTable().then( function ( typesTable ) {
			mw.util.addCSS(`.vcata-abbr {
					color: #006699;
				}
				.vcata-pseudo-input {
					background: transparent;
					border: none;
					color: inherit;
					font: inherit;
					padding: unset;
					width: auto;
				}
				#dialog-vcata table {
					border-collapse: collapse;
					min-width: 100%;
				}
				#dialog-vcata td, #dialog-vcata th {
					border-left: 1px solid #888;
					border-right: 1px solid #888;
					padding: .2em .1em;
				}
				#dialog-vcata th {
					border-bottom: 1px solid #888;
				}
				#dialog-vcata .nw520-dialog-inner {
					height: 45vh;
					width: 50vw;
				}
			` );
			dialog = new nw520.CornerDialog( 'vcata', `<input placeholder="Suche" type="text" style="width:100%" />
				<div style="margin-top:1em">
					${typesTable}
				</div>`, '<span class="vcata-abbr">vC</span>ard-<span class="vcata-abbr">T</span>ypen-<span class="vcata-abbr">A</span>ssistent', 'se', true );
			dialog.getContent().find( 'input[placeholder=Suche][type=text]' ).focus();
			dialog.getContent().find( 'input[placeholder=Suche][type=text]' ).keyup( function ( e ) {
				e.preventDefault();
				search( $( this ).val() );
			} );
			dialog.getContent().find( '.vcata-pseudo-input' ).click( function ( e ) {
				e.preventDefault();
				$( this ).trigger( 'select' );
				try {
					document.execCommand( 'copy' );
					mw.notify( $( '<span>Typ in Zwischenablage kopiert. Einfügen mit <kbd>STRG</kbd>+<kbd>V</kbd></span>' ), {
						tag: 'vcata'
					} );
				} catch ( err ) {
					mw.notify( $( '<span>Fehler beim Kopieren in die Zwischenablage. Bitte <kbd>STRG</kbd>+<kbd>C</kbd> drücken</span>' ), {
						tag: 'vcata'
					} );
				}
			} );
		} ).catch( function ( e ) {
			console.warn( e );
			mw.notify( 'Fehler beim Laden von vCaTA', {
				tag: 'vcata',
				title: 'vCaTA',
				type: 'error'
			} );
		} );
	}

	function addPortlet() {
		function main() {
			if ( dialog === null || !dialog.isAttached() ) {
				mw.notify( 'vCaTA wird vorbereitet. Augenblick bitte...', {
					tag: 'vcata',
					title: 'vCaTA'
				} );
				setup();
			} else {
				dialog.kill();
			}
		}
		const portlet = mw.util.addPortletLink( 'p-tb', '#vcata', 'vCard-Typen', 'p-vcata', 'vCard-Typen-Assistenten aufrufen' );
		$( portlet ).on( 'click', function ( e ) {
			e.preventDefault();
			if ( window.nw520?.CornerDialog ?? null === null ) {
				mw.loader.getScript( '//de.wikivoyage.org/w/index.php?title=User:Nw520/Util.js&action=raw&ctype=text/javascript' ).then( function () {
					main();
				}, function ( e ) {
					mw.log.error( e.message );
				} );
			} else {
				main();
			}
		} );
	}
	if ( mw.config.get( 'wgNamespaceNumber' ) === 0 ) {
		addPortlet();
	}
} );