Jump to content

User:SD0001/T-Watch.js

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by SD0001 (talk | contribs) at 12:22, 20 October 2019 (use wikiUrlencode while putting in href). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
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.
/**
 * Script for enabling temporary watchlisting of pages
 *
 */

/* jshint maxerr: 999 */

// <nowiki>
var api;

$.when(mw.loader.using(['mediawiki.util', 'mediawiki.user', 'mediawiki.api', 'mediawiki.Title', 'mediawiki.notify']), $.ready).then(function() {

	api = new mw.Api();

	// Menu interface
	if (mw.config.get('wgNamespaceNumber') >= 0) {
		var menuItems = [
			['1 week', 1000*60*60*24*7],
			['1 month', 1000*60*60*24*30]
		];
		if (window.TWatch_Durations_viewing) {
			$.each(window.TWatch_Durations_viewing, function(label, value) {
				menuItems.push([label, value]);
			});
		}

		// For vector skin, make a submenu within the "more" dropdown, inspired by [[meta:MoreMenu]]
		var hasMenu = false;
		if (mw.config.get('skin') === 'vector') {
			hasMenu = true;
			$(mw.util.addPortletLink('p-cactions', '#', 'T-Watch...', 'ca-twatch')).css({
				'position': 'relative'
			}).append(
				$('<ul>').addClass('menu').css({
					'display': 'none',
					'background-color': '#fff',
					'border': '1px solid #aaa'
				})
			).click(function(e) {
				e.preventDefault();
			}).on('mouseenter', function() {
				$(this).find('.menu').css({
					'left': $(this).outerWidth(),
					'top': '-1px',
					'position': 'absolute'
				}).show();
			}).on('mouseleave', function() {
				$(this).find('.menu').hide();
			});

		} else if (mw.config.get('skin') === 'monobook') {
			hasMenu = true;
			$(mw.util.addPortletLink('p-cactions', '#', 'T-Watch...', 'ca-twatch')).css({
				'position': 'relative',
				'padding-bottom': '0'
			}).append(
				$('<ul>').addClass('menu').css({
					'display': 'none',
					'z-index': '1000',
					'list-style': 'none',
					'background-color': '#fff',
					'border': '1px solid #aaa',
					'margin': '0'
				})
			).click(function(e) {
				e.preventDefault();
			}).on('mouseenter', function() {
				$(this).find('.menu').css({
					'left': '0px',
					'top': $(this).outerHeight(),
					'position': 'absolute'
				}).show();
			}).on('mouseleave', function() {
				$(this).find('.menu').hide();
			});
			// mw.util.addCSS(`
			// 	#ca-twatch .menu li {
			// 		display: list-item;
			// 		border: none;
			// 	}
			// 	#ca-twatch .menu li a {
			// 		background: none !important; <!-- !important needed for IE/firefox -->
			// 	}
			// 	#ca-twatch .menu li:hover {
			// 		text-decoration: underline;
			// 	}
			// `);
			mw.util.addCSS(
				'#ca-twatch .menu li { display: list-item; border: none; }' +
				'#ca-twatch .menu li a { background: none !important; }' +
				'#ca-twatch .menu li:hover { text-decoration: underline; }'
			);
		}

		menuItems.sort(function(a, b) {
			return a[1] - b[1];
		}).forEach(function(item) {
			var li = mw.util.addPortletLink(hasMenu ? 'ca-twatch' : 'p-cactions',
			'#', 'Watch – ' + item[0], '', 'Watchlist this page for a duration of ' + item[0]);
			li.addEventListener('click', function(e) {
				e.preventDefault();
				watchPage(mw.config.get('wgPageName'), item[1]);
			});
		});

	}

	// Edit page interface
	if (mw.config.get('wgAction') === 'edit' || mw.config.get('wgAction') === 'submit') {

		var $select = $('<select>').attr('id', 'watchduration').css({
			'margin-left': '5px'
		}).change(function() {
			$('#wpWatchthis')[0].checked = true;
		}).insertAfter($('#wpWatchthisWidget').parent().next());

		var options = [
			['1 week', 604800000],
			['2 weeks', 1209600000],
			['1 month', 2592000000],
			['2 months', 5184000000],
		];
		if (window.TWatch_Durations_editing) {
			$.each(window.TWatch_Durations_editing, function(label, value) {
				options.push([label, value]);
			});
		}
		options.sort(function(a, b) {
			return a[1] - b[1];
		}).forEach(function(item) {
			$select.append(
				$('<option>').text(item[0]).val(item[1])
			);
		});
		$select.append(
			$('<option>').text('Indefinitely').val('inf').prop('selected', true)
		);


		// record in pages object that the page is to be unwatched for said duration
		// watching of the page is done by mediawiki
		$('#wpSave').click(function() {
			if ($('#wpWatchthis')[0].checked) {
				var dur = $select.val();
				if (dur === 'inf') {
					return;
				}
				recordAsWatching(mw.config.get('wgPageName'), parseInt(dur));
			}
		});
	}

	// Integration with user scripts that edit pages (probably unnecessary)
	hookEventListener();

	// Special page to see list of temporarily watched pages
	if (mw.config.get('wgPageName') === 'Special:BlankPage/TempWatched' ||
		mw.config.get('wgPageName') === 'Special:BlankPage/T-Watch' || 
		mw.config.get('wgPageName') === 'Special:TempWatched') {
			buildSpecialPage();
	}

	// Unwatch expired pages every hour
	var nextCheckTime = mw.user.options.get('userjs-twl-nextcheck');
	if (!nextCheckTime) {  // for first-time users
		api.saveOption('userjs-twl-nextcheck', (new Date().getTime() + 1000*60*60).toString());
	}
	else if (new Date().getTime() > parseInt(nextCheckTime)) {
		removeExpiredPages();
	}

});

/**
 * @param {string} page
 * @param {number} duration
 */
function watchPage(page, duration) {
	api.watch(page).done(function() {
		recordAsWatching(page, duration);
		var d = new Date(new Date().getTime() + duration);
		mw.notify('"' + page.replace(/_/g, ' ') + '" and its ' + (mw.Title.newFromText(page).isTalkPage() ? 'associated subject' : 'talk') + ' page have been added to your watchlist till ' + getString(d) + '.');
	});
}

/**
 * @param {string} page
 * @param {number} duration
 */
function recordAsWatching(page, duration) {
	page = new mw.Title(page).getSubjectPage().getPrefixedText(); // normalize talk page to subject page
	var opt = JSON.parse(mw.user.options.get('userjs-twl-pages'));
	if (!opt) opt = {};
	opt[page] = new Date().getTime() + duration; // expiry timestamp
	api.saveOption('userjs-twl-pages', JSON.stringify(opt));
}

function removeExpiredPages() {
	var opt = JSON.parse(mw.user.options.get('userjs-twl-pages'));
	if (!opt) return;
	var pagesToUnwatch = [];
	$.each(opt, function(page, expiry) {
		if (new Date().getTime() > expiry) {
			pagesToUnwatch.push(page);
		}
	});
	api.unwatch(pagesToUnwatch).done(function() {
		// check again for expired pages after an hour
		api.saveOption('userjs-twl-nextcheck', (new Date().getTime() + 1000*60*60).toString());

		// update pages object
		pagesToUnwatch.forEach(function(page) {
			delete opt[page];
		});
		api.saveOption('userjs-twl-pages', JSON.stringify(opt));
	});
}

function hookEventListener() {
	mw.hook('record_watch').add(function(arg) {
		if (!arg) arg = {};
		arg.page = arg.page || mw.config.get('wgPageName');
		arg.setting = arg.setting || 'preferences'; // allows input like window.ScriptNameWatchPref
		arg.duration = arg.duration || window.tempWatchlistDefaultDuration || 'inf';
		arg.action; // 'edit', 'create', 'upload', 'move', 'delete', 'rollback'

		if (arg.setting === 'watch' || arg.setting === true) {
			if (arg.duration !== 'inf')
				recordAsWatching(arg.page, arg.duration);
			api.watch(arg.page); // client script should do this ideally, but just in case...
		} else if (arg.setting === 'preferences') {  // consult user's site preferences
			var pref;
			switch (arg.action) {
				case 'create': pref = 'watchcreations'; break;
				case 'move': pref = 'watchmoves'; break;
				case 'delete': pref = 'watchdeletions'; break;
				case 'upload': pref = 'watchuploads'; break;
				case 'rollback': pref = 'watchrollbacks'; break;
				default: pref = 'watchdefault';
			}
			if (mw.user.options.get(pref) == 1) { // dunno whether its string or number
				if (arg.duration !== 'inf')
					recordAsWatching(arg.page, arg.duration);
				api.watch(arg.page);
			}
		}
	});
}

function buildSpecialPage() {
	$('#firstHeading').text('Temporarily watched pages');
	document.title = 'Temporarily watched pages';
	$('#mw-content-text').empty();

	var opt = JSON.parse(mw.user.options.get('userjs-twl-pages'));

	var $ul = $('<ul>');
	$.each(opt, function(page, expiry) {
		$ul.append(
			$('<li>').html('<a href=//en.wikipedia.org/wiki/' + mw.util.wikiUrlencode(page) + ' title=' + page + '>'+ page + '</a>: ' + getString(new Date(expiry)))
		);
	});
	$('#mw-content-text').append(
		$('<p>').text('The following pages are set to be automatically unwatched after the given time in UTC:'),
		$('<p>').html('This list may include any pages that you may have subsequently unwatched manually, <a id="purgeunwatchedpages">click here to purge such pages</a>.'),
		$ul
	);
	$('#purgeunwatchedpages').click(function() {

		$ul.replaceWith('Purging...');
		var arrayOfPages = Object.keys(opt); // ASSUME < 50 for now
		if (arrayOfPages.length > 50) {
			alert('You have more than 50 pages here: purge feature coming soon');
			return;
		}
		api.get({
			"action": "query",
			"format": "json",
			"prop": "info",
			"titles": arrayOfPages,
			"inprop": "watched"
		}).then(function(json) {
			Object.values(json.query.pages).forEach(function(info) {
				if (info.watched === undefined) {
					delete opt[info.title];
				}
			});
			opt = JSON.stringify(opt);
			api.saveOption('userjs-twl-pages', opt).then(function() {
				mw.user.options.set('userjs-twl-pages', opt);
				buildSpecialPage();
			});
		});


		// var arrayOfArrays = arrayChunk(arrayOfPages, 50);
		// arrayOfArrays.forEach(function(array) {
		// 	api.get({
		// 		"action": "query",
		// 		"format": "json",
		// 		"prop": "info",
		// 		"titles": array,
		// 		"inprop": "watched"
		// 	}).then(function(json) {
		// 		Object.values(json.query.pages).forEach(function(info) {
		// 			if (info.watched === undefined) {
		// 				delete opt[info.title.replace(/ /g, '_')];
		// 			}
		// 		});
		// 	});
		// });

	});
}

// HELPER FUNCTIONS:

/**
 * @param {Date} date
 */
function getString(date) {
	var hours = date.getUTCHours().toString();
	if (hours.length === 1) {
		hours = '0' + hours;
	}
	var minutes = date.getUTCMinutes().toString();
	if (minutes.length === 1) {
		minutes = '0' + minutes;
	}
	return hours + ':' + minutes + ', ' + date.getUTCDate() + ' ' +
	mw.config.get('wgMonthNames')[ date.getUTCMonth() + 1 ] + ' ' + date.getUTCFullYear() + ' (UTC)';
}

// function arrayChunk(arr, size) {
// 	var result = [];
// 	var current;
// 	for (var i = 0; i < arr.length; ++i) {
// 		if (i % size === 0) { // when 'i' is 0, this is always true, so we start by creating one.
// 			current = [];
// 			result.push(current);
// 		}
// 		current.push(arr[i]);
// 	}
// 	return result;
// }

// </nowiki>