Wikisłownikarz:Peter Bowman/add-translations.js

Wersja z dnia 00:28, 4 maj 2015 autorstwa Peter Bowman (dyskusja | edycje) (nowa wersja, mozliwosc dodawania wielu jezykow)

Uwaga: aby zobaczyć zmiany po opublikowaniu, może zajść potrzeba wyczyszczenia pamięci podręcznej przeglądarki.

  • Firefox / Safari: Przytrzymaj Shift podczas klikania Odśwież bieżącą stronę, lub naciśnij klawisze Ctrl+F5, lub Ctrl+R (⌘-R na komputerze Mac)
  • Google Chrome: Naciśnij Ctrl-Shift-R (⌘-Shift-R na komputerze Mac)
  • Internet Explorer / Edge: Przytrzymaj Ctrl, jednocześnie klikając Odśwież, lub naciśnij klawisze Ctrl+F5
  • Opera: Naciśnij klawisze Ctrl+F5.
( function ( mw, $ ) {
	var gadget = window.addTranslations = {};
	
	var pageContent, pageDraft, translations, api;
	var $transl, $defs;
	var starttimestamp, basetimestamp;
	var startIndex, endIndex;
	
	var css = [
		'#transl-buttons { display: inline; margin: 0 5px; }',
		'#transl-buttons > small { cursor: pointer; font-weight: normal; margin: 0 5px; }',
		'#transl-editbox { border: 1px solid black; background-color: #F9F9F9; margin-top: 5px; font-weight: normal; }',
		'#transl-editbox > div { text-align: center; vertical-align: middle; margin: 5px; display: inline-block; }',
		'#transl-textinputs > div input:first-child { margin: 0 5px; }',
		'#transl-preview > ul { text-align: left; margin-right: 1em; }',
		'#transl-preview > ul small { cursor: pointer; }',
		'#transl-preview > ul code { margin: 0 0.5em; }',
		'.transl-active { background-color: #F0F0F0; }'
	];
	
	var messages = {
		'transl-add-translation': '(dodaj)',
		'transl-api-error':       'Prawdopodobnie nastąpił konflikt edycji. Przeładuj stronę i spróbuj ponownie.',
		'transl-edit-error':      'Możliwość edytowania istniejących tlumaczeń zostanie wdrożona w przyszłości.',
		'transl-parsing-error':   'Nie udało się odczytać kodu strony. Przejdź na widok edycji i sprawdź, czy nie ma błędów w formacie.',
		'transl-select-lang':     'Wybierz język',
		'transl-lang-label':      'tłumaczysz na<br><strong>$1</strong>',
		'transl-load-button':     'Załaduj',
		'transl-preview-label':   'Podgląd zmian',
		'transl-apply-button':    'Pokaż zmiany',
		'transl-submit-button':   'Zapisz',
		'transl-watch-article':   'Dodaj do obserwowanych',
		'transl-submitting':      'Zapisywanie...',
		'transl-reloading':       'Przeładowywanie strony...',
		'transl-draft-change':    '(przejdź)',
		'transl-draft-remove':    '(usuń)',
	};
	
	function initialize( $content ) {
		var $buttonHolder;
		
		gadget.activeLang = '';
		gadget.defn = [];
		gadget.langs = [];
		gadget.drafts = {};
		
		$transl = $content.find( 'dt.lang-pl.fldt-tlumaczenia' ).first();
		$defs   = $content.find( 'dd.lang-pl.fldt-znaczenia' );
		
		if ( !$transl.length || !$defs.length ) {
			return;
		}
		
		$transl
			.children( '#transl' )
			.css( 'display', 'inline' );
		
		$buttonHolder = $( '<div>' )
			.attr( 'id', 'transl-buttons' )
			.appendTo( $transl );
		
		if ( window.customLang && typeof hideTranslations === 'function' ) {
			hideTranslations( customLang );
		}
		
		extractDefNums();
		
		if ( gadget.defn.length !== $defs.length ) {
			return;
		}
		
		$( '<small>' )
			.text( mw.msg( 'transl-add-translation' ) )
			.attr( 'id', 'transl-addbutton' )
			.appendTo( $buttonHolder )
			.on( 'click', function () {
				var $spinner;
				
				if ( !pageContent ) {
					$spinner = $.createSpinner( {
						size: 'large',
						type: 'block'
					} ).appendTo( $transl );
					fetchContent( function () {
						$spinner.remove();
						$content.find( '#transl-editbox' ).show();
					} );
				}
				
				$content.find( '#transl-editbox' ).toggle();
			} );
	}
	
	function fetchContent( callback ) {
		$.when(
			api.get( {
				prop:        'revisions',
				rvprop:      [ 'timestamp', 'content' ],
				titles:       mw.config.get( 'wgPageName' ),
				indexpageids: true,
				curtimestamp: true
			} ),
			mw.loader.using( [
				'mediawiki.user',
				'mediawiki.util',
				'mediawiki.api.edit',
				'jquery.suggestions',
				'jquery.mwExtension',
				'jquery.byteLength',
				'ext.gadget.langdata',
				'ext.gadget.section-links'
			] )
		)
		.done( function ( res ) {
			var data = res[ 0 ];
			var id   = data.query.pageids[ 0 ];
			var page = data.query.pages[ id ];
			
			pageContent    = page.revisions[ 0 ][ '*' ];
			starttimestamp = data.curtimestamp;
			basetimestamp  = page.revisions[ 0 ].timestamp;
		} )
		.done( function () {
			if ( analyzePage() ) {
				createMenu();
			} else {
				alert( mw.msg( 'transl-parsing-error' ) );
			}
		} )
		.done( callback );
	}
	
	function extractDefNums() {
		$defs.each( function () {
			var m, def = $( this ).children( 'dfn' ).text();
			
			if ( !def ) {
				def = $( this ).text();
			}
			
			m = def.match( /^(\(.+?\))/ );
			
			if ( m && m[ 0 ] && m[ 1 ] ) {
				gadget.defn.push( m[ 1 ] );
			} else {
				return false;
			}
		} );
	}
	
	function analyzePage() {
		var a2, a3, a4, b, langSection, langs, targetLang, targetRow;
		var a = pageContent.indexOf( ' ({' + '{język polski' );
		
		if ( a === -1 ) {
			a = pageContent.indexOf( ' ({' + '{termin obcy w języku polskim' );
		}
		
		if ( a === -1 ) {
			return false;
		}
		
		b = pageContent.indexOf( '\n== ', a );
		b = ( b !== -1 ) ? b : pageContent.length;
		langSection = pageContent.slice( 0, b );
		a2 = langSection.indexOf( '{' + '{tłumaczenia}}\n', a );
		b = langSection.indexOf( '{' + '{źródła}}', a2 );
		
		if ( a2 === -1 || b === -1 ) {
			return false;
		}
		
		translations = langSection.slice( 0, b );
		a3 = translations.indexOf( '\n*', a2 );
		
		if (
			a3 !== -1 &&
			translations.slice( a3 ).indexOf( '{{zobtłum' ) !== -1
		) {
			a3 = translations.lastIndexOf( '{{zobtłum' );
			a3 = translations.indexOf( '\n*', a3 );
		}
		
		if ( a3 !== -1 ) {
			langs = [];
			
			$.each(
				translations.slice( a3 + 1, b ).split( '\n' ),
				function ( i, line ) {
					var res = line.match( /^\* +([^:]+):/ );
					
					if ( res && res[ 1 ] ) {
						langs.push( res[ 1 ] );
					}
				} );
			
			if ( !langs.length ) {
				return false;
			}
			
			gadget.langs = langs;
			startIndex = a3 + 1;
		} else {
			startIndex = b;
		}
		
		endIndex = b;
		translations = pageContent.substring( startIndex, endIndex );
		
		return true;
	}
	
	function getLangPos( target ) {
		var n, lang, _char, p1, p2;
		var abc = 'aąbcćdeęfghijklłmnńoópqrsśtuvwxyzźż';
		var index = -1;
		
		outer:
		for ( n = 0; n < gadget.langs.length; n++ ) {
			lang = gadget.langs[ n ];
			
			inner:
			for ( _char = 0; _char < lang.length; _char++ ) {
				p1 = abc.indexOf( target[ _char ] );
				p2 = abc.indexOf( lang[ _char ] );
				
				if ( p1 > p2 ) {
					index++;
					continue outer;
				} else if ( p1 === p2 ) {
					continue inner;
				} else {
					break outer;
				}
			}
		}
		
		return gadget.langs[ index ];
	}
	
	function prepareDraft() {
		$.each( gadget.drafts, function ( lang, draft ) {
			var targetLang, targetRow, pos, translation;
			
			if ( gadget.langs.length ) {
				targetLang = getLangPos( lang );
			}
			
			if ( targetLang ) {
				targetRow = mw.format( '\\* +$1:', $.escapeRE( targetLang ) );
				pos = translations.search( targetRow );
				pos = translations.indexOf( '\n', pos ) + 1;
			} else {
				pos = 0;
			}
			
			translation = mw.format( '$1: $2\n', lang, serializeDraft( draft ) );
			
			translations =
				translations.slice( 0, pos ) +
				'* ' + translation +
				translations.slice( pos, translations.length );
			
			gadget.langs.splice( gadget.langs.indexOf( targetLang ) + 1, 0, lang );
		} );
		
		pageDraft =
			pageContent.slice( 0, startIndex ) +
			translations +
			pageContent.slice( endIndex, pageContent.length );
	}
	
	function resetForms( gui ) {
		gadget.activeLang = null;
		gui.$langLabel.html( mw.msg( 'transl-select-lang' ) ),
		gui.$langSelector.val( '' );
		gui.$textInputs.find( 'input' ).val( '' );
		gui.$apply.prop( 'disabled', true );
	}
	
	function onLoadLang( gui, targetLang, evt ) {
		var lang = targetLang || gui.$langSelector.val();
		var draft = gadget.drafts[ lang ];
		evt && evt.preventDefault();
		
		if ( !lang ) {
			return;
		}
		
		resetForms( gui );
		
		if (
			!draft &&
			gadget.langs.indexOf( lang ) !== -1
		) {
			alert( mw.msg( 'transl-edit-error' ) );
			return;
		}
		
		gadget.activeLang = lang;
		gui.$langLabel.html( mw.msg( 'transl-lang-label', lang ) );
		
		if ( draft ) {
			gui.$textInputs.children().each( function () {
				var $this = $( this );
				var defn = $this.data( 'defn' );
				var $inputs = $this.children();
				var $text = $inputs.first();
				var $tmpl = $inputs.last();
				var text = [], tmpl = [];
				
				if ( !draft[ defn ] ) {
					return true;
				}
				
				$.each( draft[ defn ], function ( i, obj ) {
					text.push( obj.base );
					tmpl.push( obj.template || '' );
				} );
				
				$text.val( text.join( ', ' ) );
				$tmpl.val( tmpl.join( ',' ) );
			} );
		}
		
		refreshPreview( gui, lang );
		gui.$apply.prop( 'disabled', false );
	}
	
	function onApplyChanges( gui, evt ) {
		var preview;
		var draft = {};
		evt.preventDefault();
		
		gui.$textInputs.children()
			.each( function () {
				var terms, tmpls, temp;
				var $this = $( this );
				var $inputs = $this.children();
				var text = $inputs.first().val().trim();
				var tmpl = $inputs.last().val().trim();
				
				if ( !text ) {
					return true;
				}
				
				terms = text.split( / *, */ );
				tmpls = tmpl
					? tmpl.split( / *, */ )
					: null;
				arr = [];
				
				$.each( terms, function ( j, term ) {
					var base, template;

					if ( term.indexOf( '[' ) > -1 ) {
						base = term;
					} else {
						base = '[' + '[' + term + ']]';
					}
					
					if ( tmpls && tmpls[ j ] ) {
						template = '{' + '{' + tmpls[ j ] + '}}';
					}
					
					arr.push( {
						base:     base,
						template: template
					} );
				} );
				
				draft[ $this.data( 'defn' ) ] = arr;
			} );
		
		if ( $.isEmpty( draft ) ) {
			return;
		}
		
		gadget.drafts[ gadget.activeLang ] = draft;
		refreshPreview( gui, gadget.activeLang );
		
		gui.$submit.prop( 'disabled', false );
		gui.$watch.prop( 'disabled', false );
	}
	
	function serializeDraft( obj ) {
		return $.map( obj, function ( data, defn ) {
			return defn + ' ' + $.map( data, function ( word ) {
				return ( word.template
					? word.base + ' ' + word.template
					: word.base
				);
			} ).join( ', ' );
		} ).join( '; ' );
	}
	
	function refreshPreview( gui, active ) {
		var $ul = $( '<ul>' );
		
		$.each( gadget.drafts, function ( lang, draft ) {
			var $li, $change, $remove;
			
			$li = $( '<li>' )
				.append(
					$( '<strong>' ).text( mw.format( '$1:', lang ) ),
					$( '<code>' ).text( serializeDraft( draft ) ),
					$change = $( '<small>' ).text( mw.msg( 'transl-draft-change' ) ),
					' ',
					$remove = $( '<small>' ).text( mw.msg( 'transl-draft-remove' ) )
				)
				.appendTo( $ul );
			
			if ( lang === active ) {
				$li.addClass( 'transl-active' );
			}
			
			$change.on( 'click', function () {
				gadget.activeLang = lang;
				onLoadLang( gui, lang );
				$ul.children().removeClass( 'transl-active' );
				$li.addClass( 'transl-active' );
			} );
			
			$remove.on( 'click', function () {
				delete gadget.drafts[ lang ];
				gadget.langs.splice( gadget.langs.indexOf( lang ), 1 );
				
				if ( $li.hasClass( 'transl-active' ) ) {
					resetForms( gui );
				}
				
				$li.remove();
			} );
		} );
		
		gui.$preview.replaceWith( $ul );
		gui.$preview = $ul;
	}
	
	function makeSummary() {
		var s, arr = [];
		
		var buildString = function ( a ) {
			return mw.format( '+$1 na $2',
				( a.length > 1 ) ? 'tłumaczenia' : 'tłumaczenie',
				a.join( ' || ' )
			);
		};
		
		$.each( gadget.drafts, function ( lang, data ) {
			arr.push( mw.format( '$1: $2', lang, serializeDraft( data ) ) );
		} );
		
		arr.sort( function ( a, b ) {
			return a.localeCompare( b, 'pl' );
		} );
		
		s = buildString( arr );
		
		if ( $.byteLength( s ) > 255 ) {
			arr = [];
			
			$.each( gadget.drafts, function ( lang, data ) {
				arr.push( lang );
			} );
			
			arr.sort( function ( a, b ) {
				return a.localeCompare( b, 'pl' );
			} );
			
			s = buildString( arr );
		}
		
		return s;
	}
	
	function onSubmit( gui, evt ) {
		evt.preventDefault();
		prepareDraft();
		
		gui.$submit
			.prop( 'disabled', true )
			.attr( 'value', mw.msg( 'transl-submitting' ) );
		
		api.postWithEditToken( {
			action:        'edit',
			title:          mw.config.get( 'wgPageName' ),
			text:           pageDraft,
			tags:           'script',
			summary:        makeSummary(),
			watchlist:      gui.$watch.prop( 'checked' ) ? 'watch' : 'nochange',
			starttimestamp: starttimestamp,
			basetimestamp:  basetimestamp
		} )
		.done( function () {
			gui.$submit.attr( 'value', mw.msg( 'transl-reloading' ) );
			window.location.reload();
		} )
		.fail( function () {
			alert( mw.msg( 'transl-api-error' ) );
		} );
	}
	
	function createMenu() {
		var gui = {};
		
		gui.$langSelector = $( '<input>' )
			.attr( {
				type: 'text',
				size: 15
			} );
		
		gui.$textInputs = $( '<div>' )
			.attr( 'id', 'transl-textinputs' );
		
		$.each( gadget.defn, function ( i, defn ) {
			$( '<div>' )
				.data( 'defn', defn )
				.append(
					defn,
					$( '<input>' )
						.attr( {
							'type': 'text',
							'size': 70
						} ),
					$( '<input>' )
						.attr( {
							'type': 'text',
							'size': 1
						} )
				)
				.appendTo( gui.$textInputs );
		} );
		
		gui.$loadButton = $( '<input>' )
			.attr( {
				type:  'button',
				value: mw.msg( 'transl-load-button' )
			} ).on( 'click', $.proxy( onLoadLang, this, gui, null ) );
		
		gui.$apply = $( '<input>' )
			.attr( {
				'type':  'submit',
				'value': mw.msg( 'transl-apply-button' )
			} )
			.prop( 'disabled', true )
			.on( 'click', $.proxy( onApplyChanges, this, gui ) );
				
		gui.$submit = $( '<input>' )
			.attr( {
				'type':  'submit',
				'value': mw.msg( 'transl-submit-button' )
			} )
			.prop( 'disabled', true )
			.on( 'click', $.proxy( onSubmit, this, gui ) );
		
		gui.$watch = $( '<input>' )
			.attr( {
				type: 'checkbox',
				id:   'transl-watch'
			} )
			.prop( {
				disabled: true,
				checked:  !!mw.user.options.get( 'watchdefault' )
			} );
		
		gui.$previewbox = $( '<div>' )
			.attr( 'id', 'transl-preview' )
			.append(
				$( '<strong>' ).text( mw.msg( 'transl-preview-label' ) ),
				gui.$preview = $( '<ul>' )
			);
		
		gui.$editbox = $( '<div>' )
			.attr( 'id', 'transl-editbox' )
			.hide()
			.append(
				$( '<div>' ).append(
					gui.$langLabel = $( '<p>' )
						.attr( 'id', 'transl-lang-label' )
						.html( mw.msg( 'transl-select-lang' ) ),
					gui.$langSelector,
					gui.$loadButton
				),
				gui.$textInputs,
				$( '<div>' )
					.append(
						gui.$apply, gui.$submit, '<br>', gui.$watch,
						$( '<label>' )
							.attr( 'for', 'transl-watch' )
							.text( mw.msg( 'transl-watch-article' ) )
					),
				$( '<hr>' ),
				gui.$previewbox
			);
			
		$transl
			.append( gui.$editbox )
			.parent()
			.show();
		
		gui.$langSelector.suggestions( {
			fetch: function ( input, response ) {
				var re = new RegExp( '^' + input );
				var arr = $.map( mw.config.get( 'lang2code' ), function ( code, lang ) {
					return lang;
				} );
				
				response( $.grep( arr, function ( s ) {
					return re.test( s );
				} ) );
			},
			highlightInput: true
		} );
		
		if ( window.customLang ) {
			gadget.activeLang = customLang;
			gui.$langSelector.val( gadget.activeLang );
			onLoadLang( gui, customLang );
		}
	}
	
	if (
		mw.config.get( 'wgNamespaceNumber' ) === 0 &&
		mw.config.get( 'wgAction' ) === 'view'
	) {
		mw.util.addCSS( css.join( ' ' ) );
		mw.messages.set( messages );
		
		api = new mw.Api();
		
		mw.loader.using( 'jquery.spinner' ).done( function () {
			mw.hook( 'wikipage.content' ).add( initialize );
		} );
	}
}( mediaWiki, jQuery ) );