User:SD0001/W-Ping.js

/* jshint maxerr: 999 */

// $.when(	mw.loader.using(['mediawiki.util', 'mediawiki.api', 'mediawiki.user', 'ext.gadget.morebits', 'mediawiki.widgets.DateInputWidget', 'moment']),	$.ready ).then(function {

var WPing = {}; window.WPing = WPing;

WPing.api = new mw.Api({	ajax: { headers: { 'Api-User-Agent': 'w:en:User:SD0001/W-Ping.js' } } });

WPing.pingDialog = function pingDialog(page) { var Window = new Morebits.simpleWindow(800, 500); Window.setScriptName('W-Ping'); Window.setTitle("Schedule a watchlist ping for " + page); Window.addFooterLink('Upcoming pings', 'Special:BlankPage/W-Ping'); Window.addFooterLink('W-Ping', 'User:SD0001/W-Ping');

var form = new Morebits.quickForm(WPing.evaluate);

var reason = ''; var date = moment.utcOffset(WPing.getUserTimeZone).format('YYYY-MM-DD');

// See if there's already a scheduled ping for this page, if so override the above defaults var opt = JSON.parse(mw.user.options.get('userjs-wping-list')); if (opt && opt[page]) { reason = opt[page][1]; date = moment(opt[page][0] * 60000).utcOffset(WPing.getUserTimeZone).format('YYYY-MM-DD'); }

form.append({		type: 'input',		label: 'Reason: ',		name: 'reason',		value: reason,		size: '100px'	});

// input field replaced by datepicker after render form.append({		type: 'input',		name: 'date',		label: 'Ping on: ',	});

form.append({		type: 'hidden',		name: 'page',		value: page	});

if (opt && opt[page] && mw.config.get('wgCanonicalSpecialPageName') !== 'Watchlist') { form.append({			type: 'button',			label: 'Cancel ping',			style: 'margin-top: 5px',			event: function cancelPing {				Morebits.status.init(result);				Morebits.simpleWindow.setButtonsEnabled(false);				var status = new Morebits.status('Ping', 'Cancelling', 'status');

delete opt[page]; WPing.updatePingList(opt).then(function {					status.info('Done');					mw.track('counter.gadget_WPing.ping_cancelled');					window.setTimeout(function { Window.close; // close dialog }, 300);				}).catch(function(err) {					status.error('Failed to cancel: ' + JSON.stringify(err));				}); }		});	}

form.append({ type: 'submit', label: 'Submit' });

var result = form.render; Window.setContent(result); Window.display; mw.track('counter.gadget_WPing.dialog_opened');

var datepicker = new mw.widgets.DateInputWidget({		name: 'date',		value: date	}); datepicker.setRequired(true); $(result.date).replaceWith(datepicker.$element);

// prevent datepicker from getting hidden into the dialog $(Window.content).parent.css('overflow', 'visible'); $(Window.content).css('overflow', 'visible');

datepicker.$element.find('label').css({		'display': 'block',		'font-size': '110%'	});

// prevent enter in date field from submitting // leads to surprises if date was invalid, as datepicker takes in a close valid date anyway datepicker.$element.find('input[type=text]').keypress(function(e) {		if (e.keyCode === 13) {			e.preventDefault;			return false;		}	});

var durations = Array.isArray(window.WPing_Quick_Durations) ? window.WPing_Quick_Durations : [ '1 day', '3 days', '1 week', '2 weeks', '1 month' ];

var $quickSelect = $(' '); durations.forEach(function(e) {		$('').addClass('wping-prompt').text(e).appendTo($quickSelect);	}); datepicker.$element.after($quickSelect);

$quickSelect.find('a').css({		'padding': '0 5px 0 5px'	}).click(function(e) {		e.preventDefault;

// moment doesn't natively parse durations such as "3 weeks", so we manually separate // the number and the unit, and give it as moment.duration(3, "weeks") var s = e.target.textContent; var i;		for (i = 0; i < s.length; i++) { if (s[i] < '0' || s[i] > '9') { break; }		}		var num = parseInt(s); var text = s.slice(i).trim; var duration = moment.duration(num, text);

var targetdate = moment.add(duration); datepicker.setValue(targetdate.utcOffset(WPing.getUserTimeZone).format('YYYY-MM-DD')); });

};

WPing.evaluate = function evaluate(e) { var form = e.target;

var page = form.page.value; var reason = form.reason.value;

// moment reads the date as if it is in the system time zone, we apply an offset correction // to account for the case when it's differnt from the time zone in user preferences var userzone = WPing.getUserTimeZone; var syszone = -new Date.getTimezoneOffset; var enteredDate = moment(form.date.value, 'YYYY-MM-DD').add(syszone - userzone, 'minutes');

// add hours and minutes to entered date: var now = moment.utcOffset(WPing.getUserTimeZone); var pingAt = enteredDate.add(now.hours, 'hours').add(now.minutes, 'minutes');

// store pingtime as number of minutes past unix epoch (to optimise storage) var pingtime = parseInt(pingAt.unix / 60);

Morebits.status.init(form); Morebits.simpleWindow.setButtonsEnabled(false);

var status = new Morebits.status('Ping', 'Scheduling', 'status');

var opt = JSON.parse(mw.user.options.get('userjs-wping-list')); if (!opt) { // for the first-time user opt = {}; }	opt[page] = [ pingtime, reason ];

WPing.updatePingList(opt).then(function {		status.info('Done');		mw.track('counter.gadget_WPing.ping_saved');

// automatically close window in a short while window.setTimeout(function {			$(form).parent.prev.find('.ui-dialog-titlebar-close').click;		}, 300);

// while snoozing, remove the ping entry if (mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist') { WPing.removePingDisplayLine(page); if (page !== Morebits.pageNameNorm) { mw.track('counter.gadget_WPing.ping_snoozed'); }		}	}).catch(function(err) { status.error('Failed ' + JSON.stringify(err)); }); };

WPing.attachPings = function attachPings { var opt = JSON.parse(mw.user.options.get('userjs-wping-list')); if (!opt) return;

var $ul = $('').css({		'margin-left': 'calc((6px + 3px) * 5 + 0.35714286em)' // to match that of .mw-changeslist ul	});

var pingPages = []; $.each(opt, function(page, tr) {		var pingtime = tr[0] * 60000;		if (new Date.getTime > pingtime) {			pingPages.push(page);

// render wikilinks in reason text, though all links will appear blue var reason = tr[1].replace(/\[\[:?(?:([^\|\]]+?)\|)?([^\]\|]+?)\]\]/g, function(_, target, text) {				if (!target) {					target = text;				}				return '' + text + '';			}); var histlink = mw.Title.newFromText(page).namespace < 0 ? 'hist' : ('hist');

$('').addClass('wping-line').attr('data-page', page).html(				'(' + histlink + ') ' +				' ' +				'' + page + ' ' +				' ' +				(reason ? '(' + reason + ') ' : '') +				'[ snooze | dismiss ]'			).appendTo($ul); }	});

if (!pingPages.length) { return; }

var $element = $('.mw-rcfilters-ui-changesListWrapperWidget').length ? $('.mw-rcfilters-ui-changesListWrapperWidget') : ( $('.mw-changeslist').length ? // for users of non-AJAX watchlist			$('.mw-changeslist') :			$('.mw-changeslist-empty') );

$element.before(		$(' ').attr('id', 'wping').append( $(' ').text('Pings'), $ul )	);

// check if pinged pages exists, if not turn the links red, occurs lazily // XXX: only works if there are <50 pages WPing.api.get({ titles: pingPages }).then(function(json) {		$.each(Object.values(json.query.pages), function(pageid, data) { if (data.missing === '') { $ul.find('a[href="' + mw.util.getUrl(data.title) + '"]').addClass('new'); }		});	});

$ul.find('.wping-snooze').click(function(e) {		e.preventDefault;		var page = $(e.target).parent.data('page');		WPing.pingDialog(page);	});

$ul.find('.wping-dismiss').click(function(e) {		e.preventDefault;		var page = $(e.target).parent.data('page');

delete opt[page]; WPing.updatePingList(opt); WPing.removePingDisplayLine(page); mw.track('counter.gadget_WPing.ping_dismissed'); });

};

WPing.updatePingList = function(opt) { var optString = JSON.stringify(opt);

// update object locally too, so that it can be retrieved in case user wants to change reason/date // again (before page is reloaded) mw.user.options.set('userjs-wping-list', optString);

return WPing.api.saveOption('userjs-wping-list', optString); };

WPing.removePingDisplayLine = function removePingDisplayLine(page) { $('#wping ul li[data-page="' + $.escapeSelector(page) + '"]').remove; if ($('#wping ul').children.length === 0) { $('#wping').remove; } };

WPing.buildSpecialPage = function buildSpecialPage { $('#firstHeading').text('Upcoming watchlist pings'); document.title = 'Upcoming watchlist pings'; $('#mw-content-text').empty;

var opt = JSON.parse(mw.user.options.get('userjs-wping-list')); if (!opt) { opt = {}; }	var timezone = WPing.getUserTimeZone;

var $ul = $(''); $.each(opt, function(page, tr) {		var time = new Date(tr[0] * 60000);

// render wikilinks in reason text, though all links will appear blue var reason = tr[1].replace(/\[\[:?(?:([^\|\]]+?)\|)?([^\]\|]+?)\]\]/g, function(_, target, text) {			if (!target) {				target = text;			}			return '' + text + '';		}); $ul.append(			$('<li>').html( '<a href="' + mw.util.getUrl(page) + '" title="' + page + '">' + page + '</a>: ' + (reason ? '(' + reason + ') ' : '') + moment(time).utcOffset(timezone).format('HH:mm, D MMMM YYYY') )		);	});

$('#mw-content-text').append(		$(' ').text('A ping shall be delivered to your watchlist for the following pages, at the specified time in ' + WPing.getTimeZoneString(timezone) + ' time zone:'),		$ul	);

WPing.api.get({ titles: Object.keys(opt) }).then(function(json) {		$.each(Object.values(json.query.pages), function(pageid, data) { if (data.missing === '') { $ul.find('a[href="' + mw.util.getUrl(data.title) + '"]').addClass('new'); }		});	});

};

WPing.getUserTimeZone = function { if (WPing.userTimeZone) { // cache it		return WPing.userTimeZone; }	switch (window.WPing_timezone || 'preferences') { case 'utc': WPing.userTimeZone = 0; break; case 'system': WPing.userTimeZone = -new Date.getTimezoneOffset; break; case 'preferences': WPing.userTimeZone = parseInt(mw.user.options.get('timecorrection').split('|')[1]); break; }	return WPing.userTimeZone; };

WPing.getTimeZoneString = function(timecorrection) { var negative = false; if (timecorrection < 0) { timecorrection = -timecorrection; negative = true; }	var hourCorrection = parseInt(timecorrection/60); hourCorrection = (hourCorrection < 10 ? '0' : '') + hourCorrection.toString;

var minuteCorrection = timecorrection % 60; minuteCorrection = (minuteCorrection < 10 ? '0' : '') + minuteCorrection.toString;

return 'UTC' + (negative ? '–' : '+') + hourCorrection + minuteCorrection; };

// SET UP if (mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist') { WPing.attachPings; } else if (mw.config.get('wgPageName') === 'Special:BlankPage/W-Ping') { WPing.buildSpecialPage; } else { var pageName = Morebits.pageNameNorm; // for Special:Log views where the form at the top of the page was used: if (pageName === 'Special:Log') { var user = mw.util.getParamValue('user'); var type = mw.util.getParamValue('type'); if (type) { pageName += '/' + type; }		if (user) { pageName += '/' + Morebits.string.toUpperCaseFirstChar(user); }	} else if (pageName === 'Special:Contributions') { var user = mw.util.getParamValue('user'); if (user) { pageName += '/' + Morebits.string.toUpperCaseFirstChar(user); }	}	if (pageName) { var li = mw.util.addPortletLink('p-cactions', '#', 'W-Ping', 'ca-wping', 'Schedule a watchlist ping for this page'); li.addEventListener('click', function(e) {			e.preventDefault;			WPing.pingDialog(pageName);		}); } }

}).catch(function(err) { console.error('[W-Ping]:', err); });

//