MediaWiki:Gadget-twinkleblock.js: Difference between revisions

Content deleted Content added
Repo at 001f236: simplify IIFEs (#2074); apply autofixes 3 (#2079); apply autofixes 2 (#2069)
deploy #2180: soft redirect to Special:Block for multi-blocked targets
 
(4 intermediate revisions by one other user not shown)
Line 3:
(function() {
 
letconst api = new mw.Api(),;
let relevantUserName, blockedUserName, blockWindow;
const menuFormattedNamespaces = Object$.assignextend({}, mw.config.get('wgFormattedNamespaces'));
menuFormattedNamespaces[0] = '(Article)';
 
Line 34 ⟶ 35:
Twinkle.block.field_template_options = {};
 
const 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);
 
const form = new Morebits.quickFormQuickForm(Twinkle.block.callback.evaluate);
const actionfield = form.append({
type: 'field',
Line 125 ⟶ 126:
 
const result = form.render();
WindowblockWindow.setContent(result);
WindowblockWindow.display();
result.root = result;
 
Line 154 ⟶ 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) {
let 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 170 ⟶ 182:
Twinkle.block.isRegistered = !!userinfo.userid;
if (Twinkle.block.isRegistered) {
Twinkle.block.userIsBot = !!userinfo.groupmemberships && userinfo.groupmemberships.map((e) => e.group).indexOfincludes('bot') !== -1;
} else {
Twinkle.block.userIsBot = false;
Line 187 ⟶ 199:
const 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 233 ⟶ 247:
Twinkle.block.processUserInfo(data, fn);
}, (msg) => {
Morebits.statusStatus.init($('div[name="currentblock"] span').last()[0]);
Morebits.statusStatus.warn('Error fetching user info', msg);
});
};
Line 295 ⟶ 309:
 
Twinkle.block.callback.change_action = function twinkleblockCallbackChangeAction(e) {
let field_preset, field_template_options, field_block_options,;
const $form = $(e.target.form);
// Make ifs shorter
const blockBox = $form.find('[name=actiontype][value=block]').is(':checked');
Line 341 ⟶ 356:
 
if (blockBox) {
field_preset = new Morebits.quickFormQuickForm.elementElement({ type: 'field', label: 'Preset', name: 'field_preset' });
field_preset.append({
type: 'select',
Line 350 ⟶ 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 530 ⟶ 545:
};
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 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 732 ⟶ 747:
const sameUser = blockedUserName === relevantUserName;
 
Morebits.statusStatus.init($('div[name="currentblock"] span').last()[0]);
let statusStr = relevantUserName + ' is ' + (Twinkle.block.currentBlockInfo.partial === '' ? 'partially blocked' : 'blocked sitewide');
 
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') + ')';
}
 
Line 766 ⟶ 781:
}
 
Morebits.statusStatus.warn(statusStr, infoStr);
 
// Default to the current block conditions on intial form generation
Line 780 ⟶ 795:
const 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 1,516 ⟶ 1,531:
Twinkle.block.callback.change_template(e);
} else {
Morebits.quickFormQuickForm.setElementVisibility(form.dstopic.parentNode, key === 'uw-aeblock' || key === 'uw-aepblock');
}
};
Line 1,523 ⟶ 1,538:
const 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,533 ⟶ 1,548:
Twinkle.block.callback.toggle_see_alsos = function twinkleblockCallbackToggleSeeAlso() {
const reason = this.form.reason.value.replace(
new RegExp('( <!--|;) ' + 'see also ' + Twinkle.block.seeAlsos.join(' and ') + '( -->)?'), ''
);
 
Line 1,545 ⟶ 1,560:
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,571 ⟶ 1,586:
 
Twinkle.block.callback.update_form = function twinkleblockCallbackUpdateForm(e, data) {
letconst form = e.target.form,;
let expiry = data.expiry;
 
// don't override original expiry if useInitialOptions is set
Line 1,584 ⟶ 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,673 ⟶ 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,684 ⟶ 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,722 ⟶ 1,738:
 
Twinkle.block.callback.evaluate = function twinkleblockCallbackEvaluate(e) {
letconst $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,756 ⟶ 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,778 ⟶ 1,794:
}
 
Morebits.simpleWindowSimpleWindow.setButtonsEnabled(false);
Morebits.statusStatus.init(e.target);
const statusElement = new Morebits.statusStatus('Executing block');
blockoptions.action = 'block';
 
Line 1,856 ⟶ 1,872:
logExpiry = 'indefinitely';
} else {
const 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,866 ⟶ 1,882:
 
if (!confirm(message)) {
Morebits.statusStatus.info('Executing block', 'Canceled by user');
return;
}
Line 1,875 ⟶ 1,891:
blockoptions.tags = Twinkle.changeTags;
blockoptions.token = mw.user.tokens.get('csrfToken');
const mbApi = new Morebits.wiki.apiApi('Executing block', blockoptions, (() => {
statusElement.info('Completed');
if (toWarn) {
Line 1,884 ⟶ 1,900:
});
} else if (toWarn) {
Morebits.simpleWindowSimpleWindow.setButtonsEnabled(false);
 
Morebits.statusStatus.init(e.target);
Twinkle.block.callback.issue_template(templateoptions);
} else {
Line 1,898 ⟶ 1,914:
const userTalkPage = 'User_talk:' + mw.config.get('wgRelevantUserName');
 
const params = ObjectTwinkle.assignblock.combineFormDataAndFieldTemplateOptions(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';
 
const 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) {
let text = '{{',;
const settings = Twinkle.block.blockPresetsInfo[params.template];
if (!settings.nonstandard) {
text += 'subst:' + params.template;
Line 1,928 ⟶ 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 1,989 ⟶ 2,017:
 
Twinkle.block.callback.main = function twinkleblockcallbackMain(pageobj) {
letconst 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();
 
letconst dateHeaderRegex = date.monthHeaderRegex(),;
let dateHeaderRegexLast, dateHeaderRegexResult;
while ((dateHeaderRegexLast = dateHeaderRegex.exec(text)) !== null) {
dateHeaderRegexResult = dateHeaderRegexLast;
Line 2,016 ⟶ 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';
}