User:SD0001/W-Ping.js
Appearance
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump. This code will be executed when previewing this page. |
This user script seems to have a documentation page at User:SD0001/W-Ping. |
/* jshint maxerr: 999 */
// <nowiki>
$.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 = $('<span>');
durations.forEach(function(e) {
$('<a>').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 = $('<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 '<a href="' + mw.util.getUrl(target) + '" title="' + target + '">' + text + '</a>';
});
var histlink = mw.Title.newFromText(page).namespace < 0 ? 'hist' :
('<a href="' + mw.util.getUrl(page, { action: 'history' }) + '">hist</a>');
$('<li>').addClass('wping-line').attr('data-page', page).html(
'(' + histlink + ') ' +
'<span class="mw-changeslist-separator"></span> ' +
'<a href="' + mw.util.getUrl(page) + '" title="' + page + '">' + page + '</a> ' +
'<span class="mw-changeslist-separator"></span> ' +
(reason ? '<i>(' + reason + ')</i> <span class="mw-changeslist-separator"></span> ' : '') +
'[ <a href=# class="wping-snooze">snooze</a> | <a href=# class="wping-dismiss">dismiss</a> ]'
).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(
$('<div>').attr('id', 'wping').append(
$('<h4>').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 = $('<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 '<a href="' + mw.util.getUrl(target) + '" title="' + target + '">' + text + '</a>';
});
$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(
$('<p>').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);
});
// </nowiki>