MediaWiki:Gadget-twinkleblock.js: Difference between revisions

Content deleted Content added
Repo at 66e1c54: Add option to default to blocking the /64, off by default (#1352); remove trailing space in input field labels
deploy #2180: soft redirect to Special:Block for multi-blocked targets
 
(15 intermediate revisions by 4 users not shown)
Line 1:
// <nowiki>
 
(function() {
 
const api = new mw.Api();
(function($) {
let relevantUserName, blockedUserName, blockWindow;
 
const menuFormattedNamespaces = $.extend({}, mw.config.get('wgFormattedNamespaces'));
var api = new mw.Api(), relevantUserName, blockedUserName;
var menuFormattedNamespaces = $.extend({}, mw.config.get('wgFormattedNamespaces'));
menuFormattedNamespaces[0] = '(Article)';
 
Line 35:
Twinkle.block.field_template_options = {};
 
var WindowblockWindow = new Morebits.simpleWindowSimpleWindow(650, 530);
// need to be verbose about who we're blocking
WindowblockWindow.setTitle('Block or issue block template to ' + relevantUserName);
WindowblockWindow.setScriptName('Twinkle');
WindowblockWindow.addFooterLink('Block templates', 'Template:Uw-block/doc/Block_templates');
WindowblockWindow.addFooterLink('Block policy', 'WP:BLOCK');
WindowblockWindow.addFooterLink('Block prefs', 'WP:TW/PREF#block');
WindowblockWindow.addFooterLink('Twinkle help', 'WP:TW/DOC#block');
WindowblockWindow.addFooterLink('Give feedback', 'WT:TW');
 
// Always added, hidden later if actual user not blocked
WindowblockWindow.addFooterLink('Unblock this user', 'Special:Unblock/' + relevantUserName, true);
 
varconst form = new Morebits.quickFormQuickForm(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();
WindowblockWindow.setContent(result);
WindowblockWindow.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 155:
// Store fetched user data, only relevant if switching IPv6 to a /64
Twinkle.block.fetchedData = {};
// Processes the data from a a query response, separated from
// Twinkle.block.fetchUserInfo to allow reprocessing of already-fetched data
Twinkle.block.processUserInfo = function twinkleblockProcessUserInfo(data, fn) {
varlet blockinfo = data.query.blocks[0],;
// Soft redirect to Special:Block if the user is multi-blocked (#2178)
userinfo = data.query.users[0];
if (blockinfo && data.query.blocks.length > 1) {
// Remove submission buttons.
$(blockWindow.content).dialog('widget').find('.morebits-dialog-buttons').empty();
Morebits.Status.init(blockWindow.content.querySelector('form'));
Morebits.Status.warn(
`This target has ${data.query.blocks.length} active blocks`,
`Multiblocks is not supported by Twinkle. Use [[Special:Block/${relevantUserName}]] instead.`
);
return;
}
const userinfo = data.query.users[0];
// If an IP is blocked *and* rangeblocked, the above finds
// whichever block is more recent, not necessarily correct.
Line 171 ⟶ 182:
Twinkle.block.isRegistered = !!userinfo.userid;
if (Twinkle.block.isRegistered) {
Twinkle.block.userIsBot = !!userinfo.groupmemberships && userinfo.groupmemberships.map(function(e) {=> e.group).includes('bot');
return e.group;
}).indexOf('bot') !== -1;
} else {
Twinkle.block.userIsBot = false;
Line 188 ⟶ 197:
 
// 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
} else {
unblockLink.hidden = false, ;
unblockLink.nextSibling.hidden = false; // link+trailing bullet
}
 
Line 213 ⟶ 224:
 
Twinkle.block.fetchUserInfo = function twinkleblockFetchUserInfo(fn) {
varconst query = {
format: 'json',
action: 'query',
Line 233 ⟶ 244:
}
 
api.get(query).then(function(data) => {
Twinkle.block.processUserInfo(data, fn);
}, function(msg) => {
Morebits.statusStatus.init($('div[name="currentblock"] span').last()[0]);
Morebits.statusStatus.warn('Error fetching user info', msg);
});
};
Line 243 ⟶ 254:
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 ⟶ 262:
 
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 ⟶ 274:
// 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 ⟶ 284:
// 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 ⟶ 309:
 
Twinkle.block.callback.change_action = function twinkleblockCallbackChangeAction(e) {
varlet field_preset, field_template_options, field_block_options,;
const $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 ⟶ 328:
 
// 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 ⟶ 344:
} else {
// But first remove any prior prior
blockGroup = blockGroup.filter(function(bg) {=> bg.label !== prior.label);
return bg.label !== prior.label;
});
}
 
Line 348 ⟶ 356:
 
if (blockBox) {
field_preset = new Morebits.quickFormQuickForm.elementElement({ type: 'field', label: 'Preset', name: 'field_preset' });
field_preset.append({
type: 'select',
Line 357 ⟶ 365:
});
 
field_block_options = new Morebits.quickFormQuickForm.elementElement({ type: 'field', label: 'Block options', name: 'field_block_options' });
field_block_options.append({ type: 'div', name: 'currentblock', label: ' ' });
field_block_options.append({ type: 'div', name: 'hasblocklog', label: ' ' });
Line 403 ⟶ 411:
tooltip: '10 page max.'
});
varconst ns = field_block_options.append({
type: 'select',
multiple: true,
Line 411 ⟶ 419:
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 ⟶ 427:
}
 
varconst blockoptions = [
{
checked: Twinkle.block.field_block_options.nocreate,
Line 516 ⟶ 524:
}
}
 
// grab discretionary sanctions list from en-wiki
Twinkle.block.dsinfo = Morebits.wiki.getCachedJson('Template:Ds/topics.json');
 
Twinkle.block.dsinfo.then((dsinfo) => {
const $select = $('[name="dstopic"]');
const $options = $.map(dsinfo, (value, key) => $('<option>').val(value.code).text(key).prop('label', key));
$select.append($options);
});
 
// DS selection visible in either the template field set or preset,
// joint settings saved here
varconst dsSelectSettings = {
type: 'select',
name: 'dstopic',
Line 525 ⟶ 542:
value: '',
tooltip: 'If selected, it will inform the template and may be added to the blocking message',
event: Twinkle.block.callback.toggle_ds_reason,
list: $.map(Twinkle.block.dsinfo, function(info, label) {
return {label: label, value: info.code};
})
};
if (templateBox) {
field_template_options = new Morebits.quickFormQuickForm.elementElement({ type: 'field', label: 'Template options', name: 'field_template_options' });
field_template_options.append({
type: 'select',
Line 616 ⟶ 630:
}
 
varconst $previewlink = $('<a id="twinkleblock-preview-link">Preview</a>');
$previewlink.off('click').on('click', function() => {
Twinkle.block.callback.preview($form[0]);
});
Line 628 ⟶ 642:
}
 
varlet oldfield;
if (field_preset) {
oldfield = $form.find('fieldset[name="field_preset"]')[0];
Line 639 ⟶ 653:
oldfield.parentNode.replaceChild(field_block_options.render(), oldfield);
$form.find('fieldset[name="field_64"]').show();
 
 
$form.find('[name=pagerestrictions]').select2({
theme: 'default select2-morebits',
width: '100%',
placeholder: 'Select pages to block user from',
Line 656 ⟶ 670:
delay: 100,
data: function(params) {
varconst title = mw.Title.newFromText(params.term);
if (!title) {
return;
Line 671 ⟶ 685:
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 690 ⟶ 704:
 
$form.find('[name=namespacerestrictions]').select2({
theme: 'default select2-morebits',
width: '100%',
matcher: Morebits.select2.matchers.wordBeginning,
Line 721 ⟶ 736:
oldfield = $form.find('fieldset[name="field_template_options"]')[0];
oldfield.parentNode.replaceChild(field_template_options.render(), oldfield);
e.target.form.root.previewer = new Morebits.wiki.previewPreview($(e.target.form.root).find('#twinkleblock-previewbox').last()[0]);
} else {
$form.find('fieldset[name="field_template_options"]').hide();
Line 730 ⟶ 745:
// 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.statusStatus.init($('div[name="currentblock"] span').last()[0]);
varlet statusStr = relevantUserName + ' is ' + (Twinkle.block.currentBlockInfo.partial === '' ? 'partially blocked' : 'blocked sitewide');
 
// Range blocked
Line 742 ⟶ 757:
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 749 ⟶ 764:
if (Twinkle.block.currentBlockInfo.expiry === 'infinity') {
statusStr += ' (indefinite)';
} else if (new Morebits.dateDate(Twinkle.block.currentBlockInfo.expiry).isValid()) {
statusStr += ' (expires ' + new Morebits.dateDate(Twinkle.block.currentBlockInfo.expiry).calendar('utc') + ')';
}
 
let infoStr = 'This form will';
 
var infoStr = 'This form will';
if (sameUser) {
infoStr += ' change that block';
Line 767 ⟶ 781:
}
 
Morebits.statusStatus.warn(statusStr, infoStr);
 
// Default to the current block conditions on intial form generation
Line 777 ⟶ 791:
// 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.dateDate(lastBlockAction.timestamp).calendar('utc') + ')');
} else { // block or reblock
$blockloglink.append(' (' + lastBlockAction.params.duration + ', expired ' + new Morebits.dateDate(lastBlockAction.params.expiry).calendar('utc') + ')');
}
}
 
Morebits.statusStatus.init($('div[name="hasblocklog"] span').last()[0]);
Morebits.statusStatus.warn(Twinkle.block.currentBlockInfo ? 'Previous blocks' : 'This ' + (Morebits.ip.isRange(relevantUserName) ? 'range' : 'user') + ' has been blocked in the past', $blockloglink[0]);
}
 
Line 808 ⟶ 822:
* disabletalk: <disable user from editing their own talk page while blocked>
* expiry: <string - expiry timestamp, can include relative times like "5 months", "2 weeks" etc>
* forAnonOnlyforUnregisteredOnly: <show block option in the interface only if the relevant user is an IP>
* forRegisteredOnly: <show block option in the interface only if the relevant user is registered>
* label: <string - label for the option of the dropdown in the interface (keep brief)>
Line 814 ⟶ 828:
* pageParam: <set if the associated block template accepts a page parameter>
* prependReason: <string - prepends the value of 'reason' to the end of the existing reason, namely for when revoking talk page access>
* nocreate: <block account creation from the user's IP (for anonymousunregistered users only)>
* nonstandard: <template does not conform to stewardship of WikiProject User Warnings and may not accept standard parameters>
* reason: <string - block rationale, as would appear in the block log,
Line 830 ⟶ 844:
*/
Twinkle.block.blockPresetsInfo = {
'anonblock': {
expiry: '31 hours',
forAnonOnlyforUnregisteredOnly: true,
nocreate: true,
nonstandard: true,
Line 840 ⟶ 854:
'anonblock - school': {
expiry: '36 hours',
forAnonOnlyforUnregisteredOnly: true,
nocreate: true,
nonstandard: true,
Line 849 ⟶ 863:
'blocked proxy': {
expiry: '1 year',
forAnonOnlyforUnregisteredOnly: true,
nocreate: true,
nonstandard: true,
Line 858 ⟶ 872:
'CheckUser block': {
expiry: '1 week',
forAnonOnlyforUnregisteredOnly: true,
nocreate: true,
nonstandard: true,
Line 874 ⟶ 888:
},
'checkuserblock-wide': {
forAnonOnlyforUnregisteredOnly: true,
nocreate: true,
nonstandard: true,
Line 880 ⟶ 894:
sig: '~~~~'
},
'colocationwebhost': {
expiry: '1 year',
forAnonOnlyforUnregisteredOnly: true,
nonstandard: true,
reason: '{{colocationwebhost}}',
sig: null
},
'oversightblock': {
autoblock: true,
expiry: 'infinity',
Line 896 ⟶ 910:
},
'school block': {
forAnonOnlyforUnregisteredOnly: true,
nocreate: true,
nonstandard: true,
Line 902 ⟶ 916:
sig: '~~~~'
},
'spamblacklistblock': {
forAnonOnlyforUnregisteredOnly: true,
expiry: '1 month',
disabletalk: true,
Line 909 ⟶ 923:
reason: '{{spamblacklistblock}} <!-- editor only attempts to add blacklisted links, see [[Special:Log/spamblacklist]] -->'
},
'rangeblock': {
reason: '{{rangeblock}}',
nocreate: true,
nonstandard: true,
forAnonOnlyforUnregisteredOnly: true,
sig: '~~~~'
},
'tor': {
expiry: '1 year',
forAnonOnlyforUnregisteredOnly: true,
nonstandard: true,
reason: '{{Tor}}',
sig: null
},
'webhostblock': {
expiry: '1 year',
forAnonOnlyforUnregisteredOnly: true,
nonstandard: true,
reason: '{{webhostblock}}',
Line 942 ⟶ 956:
autoblock: true,
expiry: '31 hours',
forAnonOnlyforUnregisteredOnly: true,
nocreate: true,
pageParam: true,
Line 1,016 ⟶ 1,030:
nocreate: true,
reason: '{{uw-botuhblock}} <!-- Username implies a bot, hard block -->',
summary: 'You have been indefinitely blocked from editing because your username is a blatant violation of the [[WP:U|username policy]].'
},
'uw-causeblock': {
Line 1,075 ⟶ 1,089:
},
'uw-ipevadeblock': {
forAnonOnlyforUnregisteredOnly: true,
nocreate: true,
reason: '[[WP:Blocking policy#Evasion of blocks|Block evasion]]',
Line 1,227 ⟶ 1,241:
pageParam: true,
reason: '{{uw-vaublock}} <!-- Username violation, vandalism-only account -->',
summary: 'You have been indefinitely blocked from editing because your account is being [[WP:VOADISRUPTONLY|used only for vandalism]] and your username is a blatant violation of the [[WP:U|username policy]]'
},
'uw-vblock': {
Line 1,243 ⟶ 1,257:
nocreate: true,
pageParam: true,
reason: '[[WP:Vandalism-only accountDISRUPTONLY|Vandalism-only account]]',
summary: 'You have been indefinitely blocked from editing because your account is being [[WP:VOADISRUPTONLY|used only for vandalism]]'
},
'zombie proxy': {
expiry: '1 month',
forAnonOnlyforUnregisteredOnly: true,
nocreate: true,
nonstandard: true,
Line 1,319 ⟶ 1,333:
reasonParam: true,
summary: 'You have been indefinitely [[WP:PB|partially blocked]] from certain areas of the encyclopedia'
}
};
 
// Codes and links for Discretionary Sanctions, see [[Template:Ds/topics]]
// Used for uw-ae(p)block
Twinkle.block.dsinfo = {
'': {
code: ''
},
'Abortion': {
code: 'ab',
page: 'Wikipedia:Arbitration/Requests/Case/Abortion'
},
'American politics post-1992': {
code: 'ap',
page: 'Wikipedia:Arbitration/Requests/Case/American politics 2'
},
'Ancient Egyptian race controversy': {
code: 'aerc',
page: 'Wikipedia:Requests for arbitration/Ancient Egyptian race controversy'
},
'Arab-Israeli conflict': {
code: 'a-i',
page: 'Wikipedia:Arbitration/Index/Palestine-Israel articles'
},
'Armenia, Azerbaijan, or related conflicts': {
code: 'a-a',
page: 'Wikipedia:Requests for arbitration/Armenia-Azerbaijan 2'
},
'Biographies of Living Persons (BLPs)': {
code: 'blp',
page: 'Wikipedia:Requests for arbitration/Editing of Biographies of Living Persons'
},
'Climate change': {
code: 'cc',
page: 'Wikipedia:Arbitration/Requests/Case/Climate change'
},
'Complementary and alternative medicine': {
code: 'com',
page: 'Wikipedia:Arbitration/Requests/Case/Acupuncture'
},
'Eastern Europe or the Balkans': {
code: 'e-e',
page: 'Wikipedia:Requests for arbitration/Eastern Europe'
},
'Electronic cigarettes': {
code: 'ecig',
page: 'Wikipedia:Arbitration/Requests/Case/Editor conduct in e-cigs articles'
},
'Falun Gong': {
code: 'fg',
page: 'Wikipedia:Requests for arbitration/Falun Gong'
},
'Gender-related dispute or controversy and associated people (includes GamerGate)': {
code: 'gas',
page: 'Wikipedia:Arbitration/Requests/Case/Gender and sexuality'
},
'Genetically modified organisms (GMO)': {
code: 'gmo',
page: 'Wikipedia:Arbitration/Requests/Case/Genetically modified organisms'
},
'Gun control': {
code: 'gc',
page: 'Wikipedia:Arbitration/Requests/Case/Gun control'
},
'Horn of Africa (Ethiopia, Somalia, Eritrea, Djibouti)': {
code: 'horn',
page: 'Wikipedia:Arbitration/Requests/Case/Horn of Africa'
},
'India, Pakistan, and Afghanistan': {
code: 'ipa',
page: 'Wikipedia:Requests for arbitration/India-Pakistan'
},
'Infoboxes': {
code: 'cid',
page: 'Wikipedia:Arbitration/Requests/Case/Civility in infobox discussions'
},
'Kurds and Kurdistan': {
code: 'kurd',
page: 'Wikipedia:Arbitration/Requests/Case/Kurds and Kurdistan'
},
'Landmark Worldwide': {
code: 'lw',
page: 'Wikipedia:Arbitration/Requests/Case/Landmark Worldwide'
},
'Liancourt Rocks': {
code: 'lr',
page: 'Wikipedia:Requests for arbitration/Liancourt Rocks'
},
'Manual of Style and article titles': {
code: 'mos',
page: 'Wikipedia:Arbitration/Requests/Case/Article titles and capitalisation'
},
'Muhammad': {
code: 'muh-im',
page: 'Wikipedia:Arbitration/Requests/Case/Muhammad images'
},
'Pharmaceutical drug prices (medicine)': {
code: 'med',
page: 'Wikipedia:Arbitration/Requests/Case/Medicine'
},
'Prem Rawat': {
code: 'pr',
page: 'Wikipedia:Requests for arbitration/Prem Rawat'
},
'Pseudoscience and fringe science': {
code: 'ps',
page: 'Wikipedia:Requests for arbitration/Pseudoscience'
},
'Race/ethnicity and human abilities, behaviour, and intelligence': {
code: 'r-i',
page: 'Wikipedia:Arbitration/Requests/Case/Race and intelligence'
},
'Scientology': {
code: 'sci',
page: 'Wikipedia:Requests for arbitration/Scientology'
},
'Senkaku Islands dispute': {
code: 'sen',
page: 'Wikipedia:Arbitration/Requests/Case/Senkaku Islands'
},
'September 11 attacks': {
code: '9/11',
page: 'Wikipedia:Requests for arbitration/September 11 conspiracy theories'
},
'Shakespeare authorship question': {
code: 'saq',
page: 'Wikipedia:Arbitration/Requests/Case/Shakespeare authorship question'
},
'Transcendental Meditation movement': {
code: 'tm',
page: 'Wikipedia:Arbitration/Requests/Case/Transcendental Meditation movement'
},
'The Troubles': {
code: 'tt',
page: 'Wikipedia:Requests for arbitration/The Troubles'
},
'Waldorf education': {
code: 'we',
page: 'Wikipedia:Requests for arbitration/Waldorf education'
}
};
Line 1,464 ⟶ 1,338:
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,580 ⟶ 1,454:
}
];
 
 
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,605 ⟶ 1,478:
break;
case 'oversightblock':
if (!Morebits.userIsInGroup('oversightsuppress')) {
return;
}
Line 1,613 ⟶ 1,486:
}
 
varconst blockSettings = Twinkle.block.blockPresetsInfo[blockPreset.value];
 
var registrationRestrict = blockSettings.forRegisteredOnly ? Twinkle.block.isRegistered : blockSettings.forAnonOnly ? !Twinkle.block.isRegistered : true;
let registrationRestrict;
if (blockSettings.forRegisteredOnly) {
registrationRestrict = Twinkle.block.isRegistered;
} else if (blockSettings.forUnregisteredOnly) {
registrationRestrict = !Twinkle.block.isRegistered;
} else {
registrationRestrict = true;
}
 
if (!(blockSettings.templateName && show_template) && registrationRestrict) {
varconst templateName = blockSettings.templateName || blockPreset.value;
return {
label: (show_template ? '{{' + templateName + '}}: ' : '') + blockPreset.label,
Line 1,639 ⟶ 1,521:
 
Twinkle.block.callback.change_preset = function twinkleblockCallbackChangePreset(e) {
varconst form = e.target.form, key = form.preset.value;
if (!key) {
return;
Line 1,649 ⟶ 1,531:
Twinkle.block.callback.change_template(e);
} else {
Morebits.quickFormQuickForm.setElementVisibility(form.dstopic.parentNode, key === 'uw-aeblock' || key === 'uw-aepblock');
}
};
 
Twinkle.block.callback.change_expiry = function twinkleblockCallbackChangeExpiry(e) {
varconst expiry = e.target.form.expiry;
if (e.target.value === 'custom') {
Morebits.quickFormQuickForm.setElementVisibility(expiry.parentNode, true);
} else {
Morebits.quickFormQuickForm.setElementVisibility(expiry.parentNode, false);
expiry.value = e.target.value;
}
Line 1,665 ⟶ 1,547:
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) {
this.form.reason.value = reason;
} else if (reason.indexOfincludes('{{') !== -1) {
this.form.reason.value = reason + ' <!-- see also ' + seeAlsoMessage + ' -->';
} else {
Line 1,689 ⟶ 1,569:
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((dsinfo) => {
Twinkle.block.dsReason = Twinkle.block.dsinfo[this.options[this.selectedIndex].label].page;
const sanctionCode = this.selectedIndex;
if (!this.value) {
const sanctionName = this.options[sanctionCode].label;
this.form.reason.value = reason;
Twinkle.block.dsReason = dsinfo[sanctionName].page;
} else {
if (!this.value) {
this.form.reason.value = reason + ' ([[' + Twinkle.block.dsReason + ']])';
this.form.reason.value = reason;
}
} else {
this.form.reason.value = reason + ' ([[' + Twinkle.block.dsReason + ']])';
}
});
};
 
Twinkle.block.callback.update_form = function twinkleblockCallbackUpdateForm(e, data) {
varconst form = e.target.form,;
let expiry = data.expiry;
 
// don't override original expiry if useInitialOptions is set
Line 1,715 ⟶ 1,600:
form.expiry.value = expiry;
if (form.expiry_preset.value === 'custom') {
Morebits.quickFormQuickForm.setElementVisibility(form.expiry.parentNode, true);
} else {
Morebits.quickFormQuickForm.setElementVisibility(form.expiry.parentNode, false);
}
}
Line 1,730 ⟶ 1,615:
}
 
$(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,736 ⟶ 1,621:
}
 
varconst check = data[el.name] === '' || !!data[el.name];
$(el).prop('checked', check);
});
Line 1,748 ⟶ 1,633:
// 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,761 ⟶ 1,646:
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,782 ⟶ 1,665:
 
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,806 ⟶ 1,689:
form.expiry.value = Twinkle.block.prev_template_expiry;
}
Morebits.quickFormQuickForm.setElementVisibility(form.notalk.parentNode, !settings.nonstandard);
// Partial
Morebits.quickFormQuickForm.setElementVisibility(form.noemail_template.parentNode, partialBox);
Morebits.quickFormQuickForm.setElementVisibility(form.nocreate_template.parentNode, partialBox);
} else if (templateBox) { // Only present if block && template forms both visible
Morebits.quickFormQuickForm.setElementVisibility(
form.blank_duration.parentNode,
!settings.indefinite && !settings.nonstandard
Line 1,817 ⟶ 1,700:
}
 
Morebits.quickFormQuickForm.setElementVisibility(form.dstopic.parentNode, value === 'uw-aeblock' || value === 'uw-aepblock');
 
// Only particularly relevant if template form is present
Morebits.quickFormQuickForm.setElementVisibility(form.article.parentNode, settings && !!settings.pageParam);
Morebits.quickFormQuickForm.setElementVisibility(form.block_reason.parentNode, settings && !!settings.reasonParam);
 
// Partial block
Morebits.quickFormQuickForm.setElementVisibility(form.area.parentNode, partialBox && !blockBox);
 
form.root.previewer.closePreview();
Line 1,831 ⟶ 1,714:
 
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,849 ⟶ 1,732:
};
 
varconst templateText = Twinkle.block.callback.getBlockNoticeWikitext(params);
 
form.previewer.beginRender(templateText, 'User_talk:' + relevantUserName); // Force wikitext/correct username
Line 1,855 ⟶ 1,738:
 
Twinkle.block.callback.evaluate = function twinkleblockCallbackEvaluate(e) {
varconst $form = $(e.target),
toBlock = $form.find('[name=actiontype][value=block]').is(':checked'),
toWarn = $form.find('[name=actiontype][value=template]').is(':checked'),
toPartial = $form.find('[name=actiontype][value=partial]').is(':checked'),;
let blockoptions = {}, templateoptions = {};
 
Twinkle.block.callback.saveFieldset($form.find('[name=field_block_options]'));
Line 1,889 ⟶ 1,772:
if (toBlock) {
if (blockoptions.partial) {
if (blockoptions.disabletalk && !blockoptions.namespacerestrictions.indexOfincludes('3') === -1) {
return alert('Partial blocks cannot prevent talk page access unless also restricting them from editing User talk space!');
}
Line 1,911 ⟶ 1,794:
}
 
Morebits.simpleWindowSimpleWindow.setButtonsEnabled(false);
Morebits.statusStatus.init(e.target);
varconst statusElement = new Morebits.statusStatus('Executing block');
blockoptions.action = 'block';
 
Line 1,948 ⟶ 1,831:
same block is still active (same status, no confirmation).
*/
varconst query = {
format: 'json',
action: 'query',
Line 1,962 ⟶ 1,845:
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,973 ⟶ 1,856:
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,984 ⟶ 1,867:
}
 
varlet logExpiry = '';
if (logevents.params.duration) {
if (logevents.params.duration === 'infinity') {
logExpiry = 'indefinitely';
} else {
varconst expiryDate = new Morebits.dateDate(logevents.params.expiry);
logExpiry += (expiryDate.isBefore(new Date()) ? ', expired ' : ' until ') + expiryDate.calendar();
}
} else { // no duration, action=unblock, just show timestamp
logExpiry = ' ' + new Morebits.dateDate(logevents.timestamp).calendar();
}
message += Morebits.string.toUpperCaseFirstChar(logevents.action) + 'ed by ' + logevents.user + logExpiry +
Line 1,999 ⟶ 1,882:
 
if (!confirm(message)) {
Morebits.statusStatus.info('Executing block', 'Canceled by user');
return;
}
Line 2,008 ⟶ 1,891:
blockoptions.tags = Twinkle.changeTags;
blockoptions.token = mw.user.tokens.get('csrfToken');
varconst mbApi = new Morebits.wiki.apiApi('Executing block', blockoptions, function(() => {
statusElement.info('Completed');
if (toWarn) {
Twinkle.block.callback.issue_template(templateoptions);
}
}));
mbApi.post();
});
} else if (toWarn) {
Morebits.simpleWindowSimpleWindow.setButtonsEnabled(false);
 
Morebits.statusStatus.init(e.target);
Twinkle.block.callback.issue_template(templateoptions);
} else {
Line 2,029 ⟶ 1,912:
// 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');
 
const params = Twinkle.block.combineFormDataAndFieldTemplateOptions(
var params = $.extend(formData, {
messageData: formData,
Twinkle.block.blockPresetsInfo[formData.template],
reason: Twinkle.block.field_template_options.block_reason,
disabletalk: Twinkle.block.field_template_options.notalk,
noemail: Twinkle.block.field_template_options.noemail_template,
nocreate: Twinkle.block.field_template_options.nocreate_template
});
 
Morebits.wiki.actionCompleted.redirect = userTalkPage;
Morebits.wiki.actionCompleted.notice = 'Actions complete, loading user talk page in a few seconds';
 
varconst wikipedia_page = new Morebits.wiki.pagePage(userTalkPage, 'User talk page modification');
wikipedia_page.setCallbackParameters(params);
wikipedia_page.load(Twinkle.block.callback.main);
};
 
Twinkle.block.combineFormDataAndFieldTemplateOptions = function(formData, messageData, reason, disabletalk, noemail, nocreate) {
return $.extend(formData, {
messageData: messageData,
reason: reason,
disabletalk: disabletalk,
noemail: noemail,
nocreate: nocreate
});
};
 
Twinkle.block.callback.getBlockNoticeWikitext = function(params) {
varlet text = '{{',;
const settings = Twinkle.block.blockPresetsInfo[params.template];
if (!settings.nonstandard) {
text += 'subst:' + params.template;
Line 2,061 ⟶ 1,956:
if (params.indefinite) {
text += '|indef=yes';
} else if (!params.blank_duration && !new Morebits.dateDate(params.expiry).isValid()) {
// Block template wants a duration, not date
text += '|time=' + params.expiry;
Line 2,082 ⟶ 1,977:
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 2,092 ⟶ 1,987:
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,126 ⟶ 2,017:
 
Twinkle.block.callback.main = function twinkleblockcallbackMain(pageobj) {
varconst params = pageobj.getCallbackParameters(),
date = new Morebits.dateDate(pageobj.getLoadTime()),
messageData = params.messageData,;
let text;
 
params.indefinite = Morebits.string.isInfinity(params.expiry);
 
if (Twinkle.getPref('blankTalkpageOnIndefBlock') && params.template !== 'uw-lblock' && params.indefinite) {
Morebits.statusStatus.info('Info', 'Blanking talk page per preferences and creating a new talk page section for this month');
text = date.monthHeader() + '\n';
} else {
text = pageobj.getPageText();
 
varconst dateHeaderRegex = date.monthHeaderRegex(),;
let dateHeaderRegexLast, dateHeaderRegexResult;
while ((dateHeaderRegexLast = dateHeaderRegex.exec(text)) !== null) {
dateHeaderRegexResult = dateHeaderRegexLast;
Line 2,146 ⟶ 2,038:
// \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,153 ⟶ 2,045:
 
if (!dateHeaderRegexResult || dateHeaderRegexResult.index !== lastHeaderIndex) {
Morebits.statusStatus.info('Info', 'Will create a new talk page section for this month, as none was found');
text += date.monthHeader() + '\n';
}
Line 2,163 ⟶ 2,055:
 
// build the edit summary
varlet summary = messageData.summary;
if (messageData.suppressArticleInSummary !== true && params.article) {
summary += ' on [[:' + params.article + ']]';
Line 2,177 ⟶ 2,069:
 
Twinkle.addInitCallback(Twinkle.block, 'block');
})(jQuery));
 
 
// </nowiki>