Jump to content

User:PleaseStand/highlight-comments.js

From Wikipedia, the free encyclopedia
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
/**
 * Highlights specific users' posts to discussion pages using a CSS class.
 *
 * Originally written by PleaseStand in 2010, updated for MediaWiki 1.17 in 2011
 * Rewrite completed in 2012
 *
 * Released to the public domain; see http://en.wikipedia.org/wiki/Template:PD-self
 */

( function ( mw, $ ) {

	"use strict";

	// Default settings
	var settings = {
		highlighterFunction: function ( hc ) {
			// Default highlighter function
			hc.addColorForUsers( '#ff7', [mw.config.get( 'wgUserName' )] );
			hc.wrapComments();
			hc.addMenuItem();
		}
	};

	/**
	 * Messages displayed by this script (in English).
	 * Any translations (see below) replace these at runtime.
	 */
	var msg = {
		highlightText: 'Highlight',
		highlightTooltip: 'Enable highlighting of your own comments on this page',
		unhighlightText: 'Unhighlight',
		unhighlightTooltip: 'Disable highlighting of your own comments on this page'
	};

	/**
	 * Translations for messages displayed by this script.
	 * To have your translations added, please contact this script's maintainer.
	 */
	var translations = {
	};

	// Load translations.
	$.extend( msg, translations[document.documentElement.lang] );

	// Initialize other enclosed variables.
	var linkMap = Object.create( null ), classNumber = 0, pageRE = null, commentsAreHighlighted = false;

	/**
	 * Build pageRE, a regexp for the use of findPageNameFromHref().
	*/
	function buildPageRE() {
		var articlePathParts = mw.config.get( 'wgArticlePath' ).split( '$1' );
		var articlePathStartRE = mw.util.escapeRegExp( articlePathParts[0] );
		var articlePathEndRE = mw.util.escapeRegExp( articlePathParts[1] );
		var indexPathRE = mw.util.escapeRegExp( mw.util.wikiScript( 'index' ) );

		return new RegExp(
			'^(?:' + articlePathStartRE + '([^?#]+)' + articlePathEndRE + '|' +
			indexPathRE + '\\?(?:[^&#]*&)*title=([^&#]+))'
		);
	}

	/**
	 * Find a linked page's name (with underscores, not spaces) given a relative URL.
	 * This assumes the page is linked using a normal, intra-wiki link.
	 */
	function findPageNameFromHref( href ) {
		var m = pageRE.exec( href );
		return m ? decodeURIComponent(
			( m[1] || m[2] ).replace( /\+/g, '%20' )
		).replace( / /g, '_' ) : null;
	}

	/**
	 * Give comments linking to any given page a specific CSS class.
	 * @see unwrapComments
	 */
	function wrapComments() {
		wrapElementComments( $( '.mw-highlight-comments' ) );
		commentsAreHighlighted = true;
		addMenuItem( true );
	}

	/**
	 * Mechanics of wrapComments(), excluding the flag and menu item updates.
	 *
	 * Essentially, we need to find the comment's container and wrap (except where unnecessary)
	 * everything inside except replies to that comment. We can filter the replies out in that
	 * they are inside other element types that have the effect of indenting the text.
	 *
	 * @param content The DOM element(s) containing the content. This may be a jQuery object.
	 */
	function wrapElementComments( content ) {
		// Elements containing comments or indented text (replies to those comments)
		var commentTags = 'dd, li, p', indentTags = 'dl, ol, ul';

		$( 'a', content ).each( function () {
			var pageName = findPageNameFromHref( this.getAttribute( 'href' ) );
			// linkMap is from linked page names to CSS class names.
			if ( pageName && pageName in linkMap ) {
				var className = linkMap[pageName];
				$( this ).closest( commentTags ).contents().each( function () {
					if ( this.nodeType === 1 ) {
						var $elem = $( this );
						if ( !$elem.is( indentTags ) && !$elem.hasClass( className ) ) {
							$elem
								.addClass( className )
								.attr( 'data-mw-highlighted-comment-class', className );
						}
					} else {
						$( this ).wrap( $( '<span>', {
							'class': className,
							'data-mw-highlighted-comment-wrapper': ''
						} ) );
					}
				} );
			}
		} );
	}

	/**
	 * Undo the actions performed by wrapComments().
	 */
	function unwrapComments() {
		// Remove added wrappers
		$( '[data-mw-highlighted-comment-wrapper]' ).replaceWith( function () {
			return this.childNodes;
		} );

		// Remove added classes
		$( '[data-mw-highlighted-comment-class]' ).removeClass( function () {
			var klass = $( this ).attr( 'data-mw-highlighted-comment-class' );
			$( this ).removeAttr( 'data-mw-highlighted-comment-class' );
			return klass;
		} );

		commentsAreHighlighted = false;
		addMenuItem( true );
	}

	/**
	 * Add a group of users whose comments should be given the same CSS class.
	 * @param className The CSS class name to use
	 * @param users An array of usernames
	 */
	function addClassForUsers( className, users ) {
		var ns = mw.config.get( 'wgFormattedNamespaces' );
		for ( var i = 0; i < users.length; ++i ) {
			var userName = users[i].replace( / /g, '_' );
			var userPage = ns[2] + ':' + userName, userTalkPage = ns[3] + ':' + userName;
			linkMap[userPage] = className;
			linkMap[userTalkPage] = className;
		}
	}

	/**
	 * Add a group of users whose comments should be highlighted in the same color.
	 * @param color The CSS background-color to use
	 * @param users An array of usernames
	 * @return The resulting CSSStyleSheet object
	 */
	function addColorForUsers( color, users ) {
		var className = 'highlighted-comment-' + classNumber++;
		addClassForUsers( className, users );
		return mw.util.addCSS( '.' + className + ' { background-color: ' + color + '; }' );
	}

	/**
	 * Adds or updates a "Highlight" or "Unhighlight" option in the content action menu.
	 * @param updateOnly Do nothing if the menu item does not already exist?
	 */
	function addMenuItem( updateOnly ) {
		var text, tooltip, $oldItem = $( '#ca-highlightcomments' );

		if ( updateOnly && !$oldItem.length ) {
			return;
		}

		if ( commentsAreHighlighted ) {
			text = msg.unhighlightText;
			tooltip = msg.unhighlightTooltip;
		} else {
			text = msg.highlightText;
			tooltip = msg.highlightTooltip;
		}

		var link = mw.util.addPortletLink(
			'p-cactions', '#', text, 'ca-highlightcomments', tooltip, null, $oldItem[0]
		);
		$oldItem.remove();

		$( link ).click(function () {
			if ( commentsAreHighlighted ) {
				unwrapComments();
			} else {
				wrapComments();
			}
		});
	}

	// Members exposed to custom highlighter functions
	var hc = {
		addClassForUsers: addClassForUsers,
		addColorForUsers: addColorForUsers,
		addMenuItem: addMenuItem,
		wrapComments: wrapComments
	};

	mw.loader.using( [ 'mediawiki.util' ], function () {
		// Cache pageRE for performance.
		pageRE = buildPageRE();

		// Run either the user's highlighter function or the default one.
		$( function () {
			$.extend( settings, window.highlightCommentsSettings );
			settings.highlighterFunction( hc );
		} );

		// Now that all settings have been processed, get the elements
		// in which comments should be highlighted. Of course, actually
		// highlight them if that is desired.
		mw.hook( 'wikipage.content' ).add( function ( $content ) {
			$content.addClass( 'mw-highlight-comments' );
			if ( commentsAreHighlighted ) {
				wrapElementComments( $content );
			}
		} );
	} );

}( mediaWiki, jQuery ) );