User:Evad37/Watchlist-openUnread.js

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.
/* Add any or all of these lines after the importScript line to set various options (the default values are shown):

	var openUnread_maxnum = "10";  // Preset value for max number of pages
	var openUnread_oldest = false; // Preset state of checkbox "oldest first". Set to  true  for checked, or  false  for unchecked
	var openUnread_showAbove = false; // If set to  true  the "Open unread pages" button appears above the above the "Watchlist options" box, instead of below it

*/
mw.loader.using( ['mediawiki.util', 'mediawiki.api'], function () {
$(document).ready( function () {
	if( mw.config.get('wgCanonicalSpecialPageName') != 'Watchlist' ) {
	// only operate in Special: namespace, on Special:Watchlist
	return;
	}

	// Set default options for any that haven't been set
	if ( window.openUnread_maxnum === undefined ) { window.openUnread_maxnum = "10"; }
	if ( window.openUnread_oldest === undefined ) { window.openUnread_oldest = true; }
	if ( window.openUnread_showAbove === undefined ) { window.openUnread_showAbove = false; }	

	//Add a form to open multiple unread pages
	var openUnread_form = '<div id="openUnread-input">'+
		'<button id="openUnread-go">Open unread pages</button> '+
		'(Options: <input type="text" name="openUnread-number" id="openUnread-number" style="width:2em;" value=' + openUnread_maxnum + ' /> pages max / '+
		'<span class="mw-input-with-label"><input name="openUnread-order" id="openUnread-order" type="checkbox">&nbsp;<label for="openUnread-order">oldest first</label></span>'+
		')</div>';
	if (openUnread_showAbove) {
		$("form#mw-watchlist-form").before(openUnread_form);
		$("#openUnread-input").css("margin-top","5px");
		$("fieldset#mw-watchlist-options").css("margin-top", "0");
	} else {
		$("form#mw-watchlist-form").after(openUnread_form + '<hr>');
		$("#openUnread-input").css("margin","5px 0");
		$("fieldset#mw-watchlist-options").css("margin-bottom", "0");
	}

	if (openUnread_oldest) {
		$("#openUnread-order").prop( "checked", "true" );
	}

		//Seen is reused for each button click to account for itmes which have already been opened
	var seen = {};
	var count = 0; // var increment on each button click iteration
	
	
	// Start processing on button click
	$("#openUnread-go").click(function() {
		// get max number from input
		var maxnum = parseInt( $('#openUnread-number').val() );
		if (isNaN(maxnum) || maxnum < 1) {
			msg = "\Error: \"" + $('#openUnread-number').val() + "\" is zero, negative, or not a number";
			mw.notify( msg, { autoHide: false, tag: "WatchlistOpenUnread", title: "Watchlist-openUnread says:" } );
			return;		// error - zero, negative, or not a number
		}
		
		count++;
		
		// get unread title links as array
		var unread = $("li.mw-changeslist-line-watched, table.mw-changeslist-line-watched")
			.find("a.mw-changeslist-title")
			.map(function() {
				return $(this).text();
			})
			.get();

		// reverse order if oldest first option selected
		if ( $('#openUnread-order').prop('checked') ) {
			unread.reverse();
		}
		
		//remove duplicates
		var unique_unread = [];
		var len = unread.length;
		var j = 0;
		for (var i = 0; i < len; i++) {
			var item = unread[i];
			if( !(seen[item] >= count) ) { // because (undefined < number) is false
				seen[item] = count;
				unique_unread[j++] = item;
			}
		}

		// reduce to the max number to open
		if (unique_unread.length > maxnum) {
			unique_unread = unique_unread.slice(0, maxnum);
		}

		// Mark each unique unread item as already seen for the next iterations
		for (var u = 0; u < len; u++) {
			seen[unique_unread[u]] = seen[unique_unread[u]]+1000;
		}

		// Callback for api errors
		apiFailedCallback = function(code, result) {
			var msg = "";
			if ( code === "http" ) {
				msg = "HTTP error: " + result.textStatus; // api_result contains the jqXHR object
			} else if ( code === "ok-but-empty" ) {
				msg = "Error: Got an empty response from the server</i></span>";
			} else {
				msg = "API error: " + code;
			}
			mw.notify( msg, { autoHide: false, tag: "WatchlistOpenUnread", title: "Watchlist-openUnread says:" } );
		};
		
		// Callback for getRevId: get old revision id for diff link
		apiCallback_getRevId = function(result) {
			var page_id = result.query.pageids[0];
			var diff_oldid = result.query.pages[page_id].revisions && result.query.pages[page_id].revisions[1] && result.query.pages[page_id].revisions[1].revid;
			var diff_title = mw.util.wikiUrlencode(result.query.pages[page_id].title);

			if (!diff_oldid) {
				window.open("https:" + mw.config.get('wgServer') + mw.config.get('wgScriptPath') +
				"/index.php?title=" + diff_title, "_blank");
				console.log("Could not find old revision for page "+ result.query.pages[page_id].title);
				return;
			}
			
			window.open("https:" + mw.config.get('wgServer') + mw.config.get('wgScriptPath') +
			"/index.php?title=" + diff_title +"&diff=cur&oldid=" + diff_oldid, "_blank");
		};
		
		// Function to get revision id of a page as of a particular timestamp
		getRevId = function(pageid, timestamp) {
			new mw.Api().get( {
				action: 'query',
				pageids: pageid,
				prop: 'revisions',
				rvprop: 'ids',
				rvstart: timestamp,
				rvlimit: '2',
				indexpageids: 1,
				rawcontinue: ''
			} ).done( apiCallback_getRevId )
			.fail( apiFailedCallback );
		};
		
		// Callback for getTimestamps: get timestamps/pageids to pass through to getRevId function
		apiCallback_getTimestamps = function(result) {
			var page_ids = result.query.pageids;
			for (var k=0; k<page_ids.length; k++) {
				var nts = result.query.pages[ page_ids[k] ].notificationtimestamp;
				getRevId(page_ids[k], nts);
			}
		};
		
		// Function to get timestamps from the api
		getTimestamps = function(pagetitles) {
			new mw.Api().get( {
				action: 'query',
				titles: pagetitles,
				prop: 'info',
				inprop: 'notificationtimestamp',
				indexpageids: 1,
				rawcontinue: ''
			} ).done( apiCallback_getTimestamps )
			.fail( apiFailedCallback );
		};
		
		// Split into lists of 50 (max number for api)
		for (var ii=0; ii<unique_unread.length; ii+=50) {
			getTimestamps(unique_unread.slice(ii, ii+49).join("|"));
		}

		// show pages as read on watchlist
		$("li.mw-changeslist-line-watched, table.mw-changeslist-line-watched")
		.find("a.mw-changeslist-title")
		.filter(":contains('" + unique_unread.join("'), :contains('") + "')")
		.closest(".mw-changeslist-line-watched")
		.removeClass("mw-changeslist-line-watched")
		.addClass("mw-changeslist-line-not-watched");
		
	});

});
});