Benutzer:TMg/cleanDiff.js

aus Wikipedia, der freien Enzyklopädie
Zur Navigation springen Zur Suche springen

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
  • Edge: Strg+F5 drücken oder Strg drücken und gleichzeitig Aktualisieren anklicken
/**
 * Säubert die Versionsvergleiche und entfernt Markierungen, wo sich nichts geändert hat. Siehe Diskussionsseite.
 */
( function( $, mw ) {
	/* Quit as fast as possible if there is nothing to do. Doesn't use jQuery for performance reasons */
	if ( !document.getElementsByClassName ) {
		return;
	}

	var action = mw.config.get( 'wgAction' ),
		live = mw.user.options.get( 'uselivepreview' ) && ( action === 'edit' || action === 'submit' );
	if ( !live && !document.getElementsByClassName( 'diff' ).length ) {
		return;
	}

	/* Borders around single characters and sequences of whitespace, punctuation and other non-letters */
	var wsRegex = /[\wÀ-ÖØ-öø-\u02AF\u0370-\u1FFE\u2C00-\uFEFC]/,
		wsClass = 'diffchange-extra',
		wsCss = mw.user.options.get( 'gadget-old-diff-style' )
			? 'td.diff-deletedline .diffchange.' + wsClass + ','
				+ 'td.diff-addedline .diffchange.' + wsClass
				+ '{ background: rgba(255, 0, 0, .1); border: 1px solid red;'
				+ 'border-color: rgba(255, 0, 0, .5); border-radius: 2px; padding: 0 1px; }'
			: '.' + wsClass + '{ border: 0 solid #FFD366; border-width: 0 2px; }'
				+ '.diff-addedline .' + wsClass + ' { border-color: #99CFFF; }';

	mw.loader.using( 'mediawiki.util', function () {
		mw.util.addCSS( wsCss );
	} );

	mw.hook( 'wikipage.diff' ).add( function ( $diffs ) {
		if ( !$diffs.length ) {
			return;
		}

		var lines = $diffs[0].getElementsByTagName( 'TR' );
		if ( !lines || lines.length < 3 ) {
			return;
		}

		cleanDisconnectedLines( lines );
		cleanChangedLines( lines );
		createLinks( lines );
	} );

	function cleanDisconnectedLines( lines ) {
		var moveUps = [],
			moveDowns = [],
			lastDels = [];

		/* Warning, don't pre-calculate the length. It changes due to deleted nodes */
		for ( var i = 0; i < lines.length; i++ ) {
			var tr = lines[i],
				tds,
				dels = tr.getElementsByClassName( 'diff-deletedline' ),
				adds = tr.getElementsByClassName( 'diff-addedline' ),
				emps = tr.getElementsByClassName( 'diff-empty' ),
				isDel = dels.length === 1 && !adds.length && emps.length === 1,
				isAdd = !dels.length && adds.length === 1 && emps.length === 1,
				isChange = dels.length === 1 && adds.length === 1 && !emps.length;

			/* Sonderfall, wenn angeblich eine Leerzeile durch eine gefüllte ersetzt
			   und danach eine gefüllte Zeile gelöscht wurde */
			if ( moveUps.length > 0 && isDel && dels[0].firstChild ) {
				/* Markierung der Löschung nachholen, die Ersetzung ist schon markiert */
				markAllChanged( dels[0] );
				var moveUp = moveUps.shift() ;
				tds = moveUp.getElementsByTagName( 'TD' );
				tr.appendChild( tds[2] );
				tr.appendChild( tds[2] );
				moveUp.appendChild( emps[0] );
				continue;
			} else if ( isChange && dels[0].firstChild.nodeType === 1 && !dels[0].firstChild.firstChild ) {
				/* Angebliche Änderungen sammeln, wenn die linke Seite leer ist */
				moveUps.push( tr );
			} else if ( !isDel ) {
				moveUps = [];
			}

			/* Sonderfall, wenn angeblich eine gefüllte durch eine Leerzeile ersetzt
			   und danach eine gefüllte Zeile eingefügt wurde */
			if ( moveDowns.length > 0 && isAdd && adds[0].firstChild ) {
				/* Markierung der Einfügung nachholen, die Ersetzung ist schon markiert */
				markAllChanged( adds[0] );
				var moveDown = moveDowns.shift();
				tds = moveDown.getElementsByTagName( 'TD' );
				tr.insertBefore( tds[1], tr.firstChild );
				tr.insertBefore( tds[0], tr.firstChild );
				moveDown.insertBefore( emps[0], moveDown.firstChild );
				continue;
			} else if ( isChange && adds[0].firstChild.nodeType === 1 && !adds[0].firstChild.firstChild ) {
				/* Angebliche Änderungen sammeln, wenn die rechte Seite leer ist */
				moveDowns.push( tr );
			} else if ( !isAdd ) {
				moveDowns = [];
			}

			var cons = tr.getElementsByClassName( 'diff-context' );
			/* Einfügungen sowie leere Kontextzeilen nach oben schieben,
			   wenn zuvor passende Löschungen gefunden wurden */
			if ( lastDels.length > 0 && ( isAdd || ( cons.length === 2 && !cons[0].firstChild && !cons[1].firstChild ) ) ) {
				var lastDel = lastDels.shift();

				/* Leere rechte Seite der Löschung wegwerfen */
				tds = lastDel.getElementsByTagName( 'TD' );
				while ( tds.length > 2 ) {
					lastDel.removeChild( tds[2] );
				}

				tds = tr.getElementsByTagName( 'TD' );
				if ( tds.length === 4 ) {
					/* Die beiden Hälften der Kontextzeile als Löschung und Einfügung markieren */
					tds[0].firstChild.data = '−';
					tds[1].className = 'diff-deletedline';
					tds[2].firstChild.data = '+';
					tds[3].className = 'diff-addedline';
					/* Rechte Hälfte der Kontextzeile nach oben zur vorher gefundenen Löschung schieben */
					lastDel.appendChild( tds[2] );
					lastDel.appendChild( tds[2] );
					/* Die unten verbliebene linke Hälfte der Kontextzeile vervollständigen */
					var td = document.createElement( 'TD' );
					td.colSpan = 2;
					td.className = 'diff-empty';
					tr.appendChild( td );
				} else {
					/* Einfügung nach oben zur Löschung schieben */
					lastDel.appendChild( tds[1] );
					lastDel.appendChild( tds[1] );
					/* Leeren Rest der Einfügung wegwerfen */
					tr.parentNode.removeChild( tr );
					/* Korrektur, weil die iterierte NodeList soeben ihre Länge verändert hat */
					i--;
				}
				markAllChanged( lastDel );
			} else if ( isDel ) {
				lastDels.push( tr );
			} else {
				lastDels = [];
			}
		}
	}

	function markAllChanged( e ) {
		/* Alles als Änderung markieren. Schrumpfung der Markierung passiert losgelöst davon sowieso noch */
		var divs = e.getElementsByTagName( 'DIV' );
		for ( var i = divs.length; i--; ) {
			var div = divs[i];
			if ( div.getElementsByTagName( 'SPAN' ).length > 0 ) {
				continue;
			}

			var span = document.createElement( 'SPAN' );
			span.className = 'diffchange diffchange-inline';
			/* Include possible <a> elements, e.g. because of [[User:Schnark/js/linkUnlinked.js]] */
			while ( div.firstChild ) span.appendChild( div.firstChild );
			div.appendChild( span );
		}
	}

	function cleanChangedLines( lines ) {
		for ( var i = 0, length = lines.length; i < length; i++ ) {
			var del = lines[i].getElementsByClassName( 'diff-deletedline' ),
				add = lines[i].getElementsByClassName( 'diff-addedline' );

			if ( del && add && del.length > 0 && add.length > 0 ) {
				var dels = del[0].getElementsByClassName( 'diffchange' ),
					adds = add[0].getElementsByClassName( 'diffchange' );
				/*
				 * Vor der Fehlerbehebung vom 20. Januar 2012 wurden solche
				 * Phantom-Änderungen durch Leerzeichen am Zeilenende ausgelöst.
				 */
				if ( !dels.length && !adds.length ) {
					cleanChangedLineEnding( del[0], add[0] );
				} else {
					cleanChangedLine( dels, adds );
				}
				cleanWhitespaceChanges( dels );
				cleanWhitespaceChanges( adds );
			}
		}
	}

	function cleanChangedLine( dels, adds ) {
		var l1 = Math.min( dels.length, adds.length ),
			l2 = l1 + 1;

		for ( var i = 0; i < l1; i++ ) {
			if ( cleanChange( dels[i], adds[i] ) ) {
				l2 = l1 - i;
			}
		}
		/* Zweite Suche nach zueinander passenden Änderungen vom Zeilenende */
		for ( i = 1; i < l2; i++ ) {
			cleanChange( dels[dels.length - i], adds[adds.length - i] );
		}
	}

	function cleanChange( d, a ) {
		var e1, e2, t1, t2, l,
			p1 = 0,
			p2 = 0;

		/* Keine Optimierung, wenn sich der Text davor/dahinter geändert hat, dann ist es eine Verschiebung */
		if ( d.previousSibling && d.previousSibling.nodeType === 3 && a.previousSibling && a.previousSibling.nodeType === 3 ) {
			t1 = d.previousSibling.data;
			t2 = a.previousSibling.data;
			l = Math.min( t1.length, t2.length );
			if ( t1.slice( t1.length - l ) !== t2.slice( t2.length - l ) ) {
				return false;
			}
		}
		if ( d.nextSibling && d.nextSibling.nodeType === 3 && a.nextSibling && a.nextSibling.nodeType === 3 ) {
			t1 = d.nextSibling.data;
			t2 = a.nextSibling.data;
			l = Math.min( t1.length, t2.length );
			if ( t1.slice( 0, l ) !== t2.slice( 0, l ) ) {
				return false;
			}
		}

		e1 = d;
		e2 = a;
		while ( e1.firstChild ) e1 = e1.firstChild;
		while ( e2.firstChild ) e2 = e2.firstChild;
		/* Keine Optimierung versuchen, wenn beides gleich ist und nichts mehr übrig bleiben würde */
		if ( !e1 || !e2 || !e1.data || !e2.data || e1.data === e2.data ) {
			return true;
		}
		t1 = e1.data;
		t2 = e2.data;

		l = Math.min( t1.length, t2.length );
		while ( p1 < l && t1.charAt( p1 ) === t2.charAt( p1 ) ) {
			p1++;
		}
		l -= p1;
		while ( p2 < l && t1.charAt( t1.length - 1 - p2 ) === t2.charAt( t2.length - 1 - p2 ) ) {
			p2++;
		}
		/* Vorzeitig abbrechen, wenn keine Übereinstimmung gefunden wurde */
		if ( !p1 && !p2 ) {
			return false;
		}

		if ( p1 > 0 ) {
			d.parentNode.insertBefore( document.createTextNode( t1.substr( 0, p1 ) ), d );
			a.parentNode.insertBefore( document.createTextNode( t2.substr( 0, p1 ) ), a );
		}
		if ( p2 > 0 ) {
			d.parentNode.insertBefore( document.createTextNode( t1.substr( t1.length - p2 ) ), d.nextSibling );
			a.parentNode.insertBefore( document.createTextNode( t2.substr( t2.length - p2 ) ), a.nextSibling );
		}
		e1.data = t1.substring( p1, t1.length - p2 );
		e2.data = t2.substring( p1, t2.length - p2 );
		return true;
	}

	function cleanWhitespaceChanges( a ) {
		for ( var i = 0, length = a.length; i < length; i++ ) {
			var e = a[i].firstChild;
			if ( e.nodeType === 3 && ( e.data.length < 2 || !wsRegex.test( e.data ) ) ) {
				a[i].className += ' ' + wsClass;
			}
		}
	}

	function cleanChangedLineEnding( del, add ) {
		while ( del && del.nodeType === 1 ) del = del.lastChild;
		while ( add && add.nodeType === 1 ) add = add.lastChild;
		if ( !del || !add || del.nodeType !== 3 || add.nodeType !== 3 || del.data === add.data ) {
			return;
		}

		var p = 0,
			t1 = del.data,
			t2 = add.data,
			l = Math.min( t1.length, t2.length );
		while ( p < l && t1.charAt( p ) === t2.charAt( p ) ) {
			p++;
		}
		if ( p > 0 ) {
			del.data = t1.substr( 0, p );
			add.data = t2.substr( 0, p );
			var span = document.createElement( 'SPAN' );
			span.className = 'diffchange diffchange-inline ' + wsClass;
			span.appendChild( document.createTextNode( t1.substr( p ).replace( / /g, '\xA0' ) ) );
			if ( span.firstChild.data.length > 0 ) {
				del.parentNode.appendChild( span );
			}
			span = span.cloneNode( true );
			span.firstChild.data = t2.substr( p ).replace( / /g, '\xA0' );
			if ( span.firstChild.data.length > 0 ) {
				add.parentNode.appendChild( span );
			}
		}
	}

	function createLinks( lines ) {
		/* Off by default because of a Firefox bug */
		var limit = typeof window.cleanDiffLinkLimit !== 'undefined' ? window.cleanDiffLinkLimit : -1;
		if ( lines.length > limit ) {
			return;
		}

		for ( var i = lines.length; --i; ) {
			$( 'DIV', lines[i] ).html( function( i, html ) {
				return html.replace(
					/<[^>]*|(\[\[(?:<\/span>)?)((?:[:\w\xC0-\uD7FF]|<span[^>]*>[:\w\xC0-\uD7FF][^&<=>[\]{|}]*<\/span>)(?:[^&<=>[\]{|}]+|<span[^>]*>[^&<=>[\]{|}]*<\/span>)*)(?=(?:<span[^>]*>)?[\]|])/g,
					function( $0, $1, $2 ) {
						return $2
							? $1 + '<a href="' + mw.util.getUrl( $2.replace( /<[^>]*>*/g, '' ) )
							+ '">' + $2 + '<\/a>'
							: $0;
					} );
			} );
		}
	}
} )( jQuery, mediaWiki );