MediaWiki:Gadget-twinkleblock.js: Difference between revisions

Content deleted Content added
Repo at 4a07910: Add dark mode compatibility to Twinkle (#2023)
Repo at 3da72fd: apply autofixes 1 (#2058)
Line 4:
(function($) {
 
varlet api = new mw.Api(), relevantUserName, blockedUserName;
varconst menuFormattedNamespaces = $.extend({}, mw.config.get('wgFormattedNamespaces'));
menuFormattedNamespaces[0] = '(Article)';
 
Line 35:
Twinkle.block.field_template_options = {};
 
varconst Window = new Morebits.simpleWindow(650, 530);
// need to be verbose about who we're blocking
Window.setTitle('Block or issue block template to ' + relevantUserName);
Line 48:
Window.addFooterLink('Unblock this user', 'Special:Unblock/' + relevantUserName, true);
 
varconst form = new Morebits.quickForm(Twinkle.block.callback.evaluate);
varconst actionfield = form.append({
type: 'field',
label: 'Type of action'
Line 93:
using Morebits.ip.get64 provides a modicum of relief in thise case.
*/
varconst sixtyFour = Morebits.ip.get64(mw.config.get('wgRelevantUserName'));
if (sixtyFour && sixtyFour !== mw.config.get('wgRelevantUserName')) {
varconst block64field = form.append({
type: 'field',
label: 'Convert to /64 rangeblock',
Line 125:
form.append({ type: 'submit' });
 
varconst result = form.render();
Window.setContent(result);
Window.display();
result.root = result;
 
Twinkle.block.fetchUserInfo(function() => {
// Toggle initial partial state depending on prior block type,
// will override the defaultToPartialBlocks pref
Line 141:
 
// init the controls after user and block info have been fetched
varconst evt = document.createEvent('Event');
evt.initEvent('change', true, true);
 
Line 158:
// Twinkle.block.fetchUserInfo to allow reprocessing of already-fetched data
Twinkle.block.processUserInfo = function twinkleblockProcessUserInfo(data, fn) {
varlet blockinfo = data.query.blocks[0],
userinfo = data.query.users[0];
// If an IP is blocked *and* rangeblocked, the above finds
Line 171:
Twinkle.block.isRegistered = !!userinfo.userid;
if (Twinkle.block.isRegistered) {
Twinkle.block.userIsBot = !!userinfo.groupmemberships && userinfo.groupmemberships.map(function(e) {=> e.group).indexOf('bot') !== -1;
return e.group;
}).indexOf('bot') !== -1;
} else {
Twinkle.block.userIsBot = false;
Line 188 ⟶ 186:
 
// Toggle unblock link if not the user in question; always first
varconst unblockLink = document.querySelector('.morebits-dialog-footerlinks a');
if (blockedUserName !== relevantUserName) {
unblockLink.hidden = true, unblockLink.nextSibling.hidden = true; // link+trailing bullet
Line 213 ⟶ 211:
 
Twinkle.block.fetchUserInfo = function twinkleblockFetchUserInfo(fn) {
varconst query = {
format: 'json',
action: 'query',
Line 233 ⟶ 231:
}
 
api.get(query).then(function(data) => {
Twinkle.block.processUserInfo(data, fn);
}, function(msg) => {
Morebits.status.init($('div[name="currentblock"] span').last()[0]);
Morebits.status.warn('Error fetching user info', msg);
Line 243 ⟶ 241:
Twinkle.block.callback.saveFieldset = function twinkleblockCallbacksaveFieldset(fieldset) {
Twinkle.block[$(fieldset).prop('name')] = {};
$(fieldset).serializeArray().forEach(function(el) => {
// namespaces and pages for partial blocks are overwritten
// here, but we're handling them elsewhere so that's fine
Line 251 ⟶ 249:
 
Twinkle.block.callback.change_block64 = function twinkleblockCallbackChangeBlock64(e) {
varconst $form = $(e.target.form), $block64 = $form.find('[name=block64]');
 
// Show/hide block64 button
// Single IPv6, or IPv6 range smaller than a /64
varconst priorName = relevantUserName;
if ($block64.is(':checked')) {
relevantUserName = Morebits.ip.get64(mw.config.get('wgRelevantUserName'));
Line 263 ⟶ 261:
// No templates for ranges, but if the original user is a single IP, offer the option
// (done separately in Twinkle.block.callback.issue_template)
varconst originalIsRange = Morebits.ip.isRange(mw.config.get('wgRelevantUserName'));
$form.find('[name=actiontype][value=template]').prop('disabled', originalIsRange).prop('checked', !originalIsRange);
 
// Refetch/reprocess user info then regenerate the main content
varconst regenerateForm = function() {
// Tweak titlebar text. In theory, we could save the dialog
// at initialization and then use `.setTitle` or
Line 273 ⟶ 271:
// the scriptName and requires `.display`ing, which jumps the
// window. It's just a line of text, so this is fine.
varconst titleBar = document.querySelector('.ui-dialog-title').firstChild.nextSibling;
titleBar.nodeValue = titleBar.nodeValue.replace(priorName, relevantUserName);
// Tweak unblock link
varconst unblockLink = document.querySelector('.morebits-dialog-footerlinks a');
unblockLink.href = unblockLink.href.replace(priorName, relevantUserName);
unblockLink.title = unblockLink.title.replace(priorName, relevantUserName);
Line 298 ⟶ 296:
 
Twinkle.block.callback.change_action = function twinkleblockCallbackChangeAction(e) {
varlet field_preset, field_template_options, field_block_options, $form = $(e.target.form);
// Make ifs shorter
varconst blockBox = $form.find('[name=actiontype][value=block]').is(':checked');
varconst templateBox = $form.find('[name=actiontype][value=template]').is(':checked');
varconst $partial = $form.find('[name=actiontype][value=partial]');
varconst partialBox = $partial.is(':checked');
varlet blockGroup = partialBox ? Twinkle.block.blockGroupsPartial : Twinkle.block.blockGroups;
 
$partial.prop('disabled', !blockBox && !templateBox);
 
// Add current block parameters as default preset
varconst prior = { label: 'Prior block' };
if (blockedUserName === relevantUserName) {
Twinkle.block.blockPresetsInfo.prior = Twinkle.block.currentBlockInfo;
Line 316 ⟶ 314:
 
// Arrays of objects are annoying to check
if (!blockGroup.some(function(bg) => bg.label === prior.label)) {
return bg.label === prior.label;
})) {
blockGroup.push(prior);
}
Line 334 ⟶ 330:
} else {
// But first remove any prior prior
blockGroup = blockGroup.filter(function(bg) {=> bg.label !== prior.label);
return bg.label !== prior.label;
});
}
 
Line 403 ⟶ 397:
tooltip: '10 page max.'
});
varconst ns = field_block_options.append({
type: 'select',
multiple: true,
Line 411 ⟶ 405:
tooltip: 'Block from editing these namespaces.'
});
$.each(menuFormattedNamespaces, function(number, name) => {
// Ignore -1: Special; -2: Media; and 2300-2303: Gadget (talk) and Gadget definition (talk)
if (number >= 0 && number < 830) {
Line 419 ⟶ 413:
}
 
varconst blockoptions = [
{
checked: Twinkle.block.field_block_options.nocreate,
Line 520 ⟶ 514:
Twinkle.block.dsinfo = Morebits.wiki.getCachedJson('Template:Ds/topics.json');
 
Twinkle.block.dsinfo.then(function(dsinfo) => {
varconst $select = $('[name="dstopic"]');
varconst $options = $.map(dsinfo, function (value, key) {=> $('<option>').val(value.code).text(key).prop('label', key));
return $('<option>').val(value.code).text(key).prop('label', key);
});
$select.append($options);
});
Line 530 ⟶ 522:
// DS selection visible in either the template field set or preset,
// joint settings saved here
varconst dsSelectSettings = {
type: 'select',
name: 'dstopic',
Line 624 ⟶ 616:
}
 
varconst $previewlink = $('<a id="twinkleblock-preview-link">Preview</a>');
$previewlink.off('click').on('click', function() => {
Twinkle.block.callback.preview($form[0]);
});
Line 636 ⟶ 628:
}
 
varlet oldfield;
if (field_preset) {
oldfield = $form.find('fieldset[name="field_preset"]')[0];
Line 665 ⟶ 657:
delay: 100,
data: function(params) {
varconst title = mw.Title.newFromText(params.term);
if (!title) {
return;
Line 680 ⟶ 672:
processResults: function(data) {
return {
results: data.query.allpages.map(function(page) => {
varconst title = mw.Title.newFromText(page.title, page.ns).toText();
return {
id: title,
Line 740 ⟶ 732:
// false for an ip covered by a range or a smaller range within a larger range;
// true for a user, single ip block, or the exact range for a range block
varconst sameUser = blockedUserName === relevantUserName;
 
Morebits.status.init($('div[name="currentblock"] span').last()[0]);
varlet statusStr = relevantUserName + ' is ' + (Twinkle.block.currentBlockInfo.partial === '' ? 'partially blocked' : 'blocked sitewide');
 
// Range blocked
Line 752 ⟶ 744:
statusStr += ' within a' + (Morebits.ip.get64(relevantUserName) === blockedUserName ? ' /64' : '') + ' rangeblock';
// Link to the full range
varconst $rangeblockloglink = $('<span>').append($('<a target="_blank" href="' + mw.util.getUrl('Special:Log', {action: 'view', page: blockedUserName, type: 'block'}) + '">' + blockedUserName + '</a>)'));
statusStr += ' (' + $rangeblockloglink.html() + ')';
}
Line 764 ⟶ 756:
 
 
varlet infoStr = 'This form will';
if (sameUser) {
infoStr += ' change that block';
Line 787 ⟶ 779:
// exact range, not merely a funtional equivalent
if (Twinkle.block.hasBlockLog) {
varconst $blockloglink = $('<span>').append($('<a target="_blank" href="' + mw.util.getUrl('Special:Log', {action: 'view', page: relevantUserName, type: 'block'}) + '">block log</a>)'));
if (!Twinkle.block.currentBlockInfo) {
varconst lastBlockAction = Twinkle.block.blockLog[0];
if (lastBlockAction.action === 'unblock') {
$blockloglink.append(' (unblocked ' + new Morebits.date(lastBlockAction.timestamp).calendar('utc') + ')');
Line 1,334 ⟶ 1,326:
Twinkle.block.transformBlockPresets = function twinkleblockTransformBlockPresets() {
// supply sensible defaults
$.each(Twinkle.block.blockPresetsInfo, function(preset, settings) => {
settings.summary = settings.summary || settings.reason;
settings.sig = settings.sig !== undefined ? settings.sig : 'yes';
Line 1,453 ⟶ 1,445:
 
Twinkle.block.callback.filtered_block_groups = function twinkleblockCallbackFilteredBlockGroups(group, show_template) {
return $.map(group, function(blockGroup) => {
varconst list = $.map(blockGroup.list, function(blockPreset) => {
switch (blockPreset.value) {
case 'uw-talkrevoked':
Line 1,483 ⟶ 1,475:
}
 
varconst blockSettings = Twinkle.block.blockPresetsInfo[blockPreset.value];
 
varlet registrationRestrict;
if (blockSettings.forRegisteredOnly) {
registrationRestrict = Twinkle.block.isRegistered;
Line 1,495 ⟶ 1,487:
 
if (!(blockSettings.templateName && show_template) && registrationRestrict) {
varconst templateName = blockSettings.templateName || blockPreset.value;
return {
label: (show_template ? '{{' + templateName + '}}: ' : '') + blockPreset.label,
Line 1,518 ⟶ 1,510:
 
Twinkle.block.callback.change_preset = function twinkleblockCallbackChangePreset(e) {
varconst form = e.target.form, key = form.preset.value;
if (!key) {
return;
Line 1,533 ⟶ 1,525:
 
Twinkle.block.callback.change_expiry = function twinkleblockCallbackChangeExpiry(e) {
varconst expiry = e.target.form.expiry;
if (e.target.value === 'custom') {
Morebits.quickForm.setElementVisibility(expiry.parentNode, true);
Line 1,544 ⟶ 1,536:
Twinkle.block.seeAlsos = [];
Twinkle.block.callback.toggle_see_alsos = function twinkleblockCallbackToggleSeeAlso() {
varconst reason = this.form.reason.value.replace(
new RegExp('( <!--|;) ' + 'see also ' + Twinkle.block.seeAlsos.join(' and ') + '( -->)?'), ''
);
 
Twinkle.block.seeAlsos = Twinkle.block.seeAlsos.filter(function(el) {=> el !== this.value);
return el !== this.value;
}.bind(this));
 
if (this.checked) {
Twinkle.block.seeAlsos.push(this.value);
}
varconst seeAlsoMessage = Twinkle.block.seeAlsos.join(' and ');
 
if (!Twinkle.block.seeAlsos.length) {
Line 1,568 ⟶ 1,558:
Twinkle.block.dsReason = '';
Twinkle.block.callback.toggle_ds_reason = function twinkleblockCallbackToggleDSReason() {
varconst reason = this.form.reason.value.replace(
new RegExp(' ?\\(\\[\\[' + Twinkle.block.dsReason + '\\]\\]\\)'), ''
);
 
Twinkle.block.dsinfo.then(function(dsinfo) => {
varconst sanctionCode = this.selectedIndex;
varconst sanctionName = this.options[sanctionCode].label;
Twinkle.block.dsReason = dsinfo[sanctionName].page;
if (!this.value) {
Line 1,581 ⟶ 1,571:
this.form.reason.value = reason + ' ([[' + Twinkle.block.dsReason + ']])';
}
}.bind(this));
};
 
Twinkle.block.callback.update_form = function twinkleblockCallbackUpdateForm(e, data) {
varlet form = e.target.form, expiry = data.expiry;
 
// don't override original expiry if useInitialOptions is set
Line 1,613 ⟶ 1,603:
}
 
$(form).find('[name=field_block_options]').find(':checkbox').each(function(i, el) => {
// don't override original options if useInitialOptions is set
if (data.useInitialOptions && data[el.name] === undefined) {
Line 1,619 ⟶ 1,609:
}
 
varconst check = data[el.name] === '' || !!data[el.name];
$(el).prop('checked', check);
});
Line 1,631 ⟶ 1,621:
// Clear and/or set any partial page or namespace restrictions
if (form.pagerestrictions) {
varconst $pageSelect = $(form).find('[name=pagerestrictions]');
varconst $namespaceSelect = $(form).find('[name=namespacerestrictions]');
 
// Respect useInitialOptions by clearing data when switching presets
Line 1,644 ⟶ 1,634:
if (data.restrictions) {
if (data.restrictions.pages && !$pageSelect.val().length) {
varconst pages = data.restrictions.pages.map(function(pr) {=> pr.title);
return pr.title;
});
// since page restrictions use an ajax source, we
// short-circuit that and just add a new option
pages.forEach(function(page) => {
if (!$pageSelect.find("option[value='" + $.escapeSelector(page) + "']").length) {
varconst newOption = new Option(page, page, true, true);
$pageSelect.append(newOption);
}
Line 1,665 ⟶ 1,653:
 
Twinkle.block.callback.change_template = function twinkleblockcallbackChangeTemplate(e) {
varconst form = e.target.form, value = form.template.value, settings = Twinkle.block.blockPresetsInfo[value];
 
varconst blockBox = $(form).find('[name=actiontype][value=block]').is(':checked');
varconst partialBox = $(form).find('[name=actiontype][value=partial]').is(':checked');
varconst templateBox = $(form).find('[name=actiontype][value=template]').is(':checked');
 
// Block form is not present
Line 1,714 ⟶ 1,702:
 
Twinkle.block.callback.preview = function twinkleblockcallbackPreview(form) {
varconst params = {
article: form.article.value,
blank_duration: form.blank_duration ? form.blank_duration.checked : false,
Line 1,732 ⟶ 1,720:
};
 
varconst templateText = Twinkle.block.callback.getBlockNoticeWikitext(params);
 
form.previewer.beginRender(templateText, 'User_talk:' + relevantUserName); // Force wikitext/correct username
Line 1,738 ⟶ 1,726:
 
Twinkle.block.callback.evaluate = function twinkleblockCallbackEvaluate(e) {
varlet $form = $(e.target),
toBlock = $form.find('[name=actiontype][value=block]').is(':checked'),
toWarn = $form.find('[name=actiontype][value=template]').is(':checked'),
Line 1,796 ⟶ 1,784:
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(e.target);
varconst statusElement = new Morebits.status('Executing block');
blockoptions.action = 'block';
 
Line 1,831 ⟶ 1,819:
same block is still active (same status, no confirmation).
*/
varconst query = {
format: 'json',
action: 'query',
Line 1,845 ⟶ 1,833:
query.bkusers = blockoptions.user;
}
api.get(query).then(function(data) => {
varlet block = data.query.blocks[0];
// As with the initial data fetch, if an IP is blocked
// *and* rangeblocked, this would only grab whichever
Line 1,856 ⟶ 1,844:
block = data.query.blocks[1];
}
varconst logevents = data.query.logevents[0];
varconst logid = data.query.logevents.length ? logevents.logid : false;
 
if (logid !== Twinkle.block.blockLogId || !!block !== !!Twinkle.block.currentBlockInfo) {
varlet message = 'The block status of ' + blockoptions.user + ' has changed. ';
if (block) {
message += 'New status: ';
Line 1,867 ⟶ 1,855:
}
 
varlet logExpiry = '';
if (logevents.params.duration) {
if (logevents.params.duration === 'infinity') {
logExpiry = 'indefinitely';
} else {
varconst expiryDate = new Morebits.date(logevents.params.expiry);
logExpiry += (expiryDate.isBefore(new Date()) ? ', expired ' : ' until ') + expiryDate.calendar();
}
Line 1,891 ⟶ 1,879:
blockoptions.tags = Twinkle.changeTags;
blockoptions.token = mw.user.tokens.get('csrfToken');
varconst mbApi = new Morebits.wiki.api('Executing block', blockoptions, function(() => {
statusElement.info('Completed');
if (toWarn) {
Twinkle.block.callback.issue_template(templateoptions);
}
}));
mbApi.post();
});
Line 1,912 ⟶ 1,900:
// Use wgRelevantUserName to ensure the block template goes to a single IP and not to the
// "talk page" of an IP range (which does not exist)
varconst userTalkPage = 'User_talk:' + mw.config.get('wgRelevantUserName');
 
varconst params = $.extend(formData, {
messageData: Twinkle.block.blockPresetsInfo[formData.template],
reason: Twinkle.block.field_template_options.block_reason,
Line 1,925 ⟶ 1,913:
Morebits.wiki.actionCompleted.notice = 'Actions complete, loading user talk page in a few seconds';
 
varconst wikipedia_page = new Morebits.wiki.page(userTalkPage, 'User talk page modification');
wikipedia_page.setCallbackParameters(params);
wikipedia_page.load(Twinkle.block.callback.main);
Line 1,931 ⟶ 1,919:
 
Twinkle.block.callback.getBlockNoticeWikitext = function(params) {
varlet text = '{{', settings = Twinkle.block.blockPresetsInfo[params.template];
if (!settings.nonstandard) {
text += 'subst:' + params.template;
Line 1,965 ⟶ 1,953:
if (params.partial) {
if (params.pagerestrictions.length || params.namespacerestrictions.length) {
varconst makeSentence = function (array) {
if (array.length < 3) {
return array.join(' and ');
}
varconst last = array.pop();
return array.join(', ') + ', and ' + last;
 
Line 1,975 ⟶ 1,963:
text += '|area=' + (params.indefinite ? 'certain ' : 'from certain ');
if (params.pagerestrictions.length) {
text += 'pages (' + makeSentence(params.pagerestrictions.map(function(p) {=> '[[:' + p + ']]'));
return '[[:' + p + ']]';
}));
text += params.namespacerestrictions.length ? ') and certain ' : ')';
}
if (params.namespacerestrictions.length) {
// 1 => Talk, 2 => User, etc.
varconst namespaceNames = params.namespacerestrictions.map(function(id) {=> menuFormattedNamespaces[id]);
return menuFormattedNamespaces[id];
});
text += '[[Wikipedia:Namespace|namespaces]] (' + makeSentence(namespaceNames) + ')';
}
Line 2,009 ⟶ 1,993:
 
Twinkle.block.callback.main = function twinkleblockcallbackMain(pageobj) {
varlet params = pageobj.getCallbackParameters(),
date = new Morebits.date(pageobj.getLoadTime()),
messageData = params.messageData,
Line 2,022 ⟶ 2,006:
text = pageobj.getPageText();
 
varlet dateHeaderRegex = date.monthHeaderRegex(), dateHeaderRegexLast, dateHeaderRegexResult;
while ((dateHeaderRegexLast = dateHeaderRegex.exec(text)) !== null) {
dateHeaderRegexResult = dateHeaderRegexLast;
Line 2,029 ⟶ 2,013:
// \n== is not found, then the date header must be at the very start of the page. lastIndexOf
// returns -1 in this case, so lastHeaderIndex gets set to 0 as desired.
varconst lastHeaderIndex = text.lastIndexOf('\n==') + 1;
 
if (text.length > 0) {
Line 2,046 ⟶ 2,030:
 
// build the edit summary
varlet summary = messageData.summary;
if (messageData.suppressArticleInSummary !== true && params.article) {
summary += ' on [[:' + params.article + ']]';
Line 2,060 ⟶ 2,044:
 
Twinkle.addInitCallback(Twinkle.block, 'block');
})(jQuery));