مدیاویکی:Gadget-Twinkle-2020.js
نکته: پس از انتشار ممکن است برای دیدن تغییرات نیاز باشد که حافظهٔ نهانی مرورگر خود را پاک کنید.
- فایرفاکس / سافاری: کلید Shift را نگه دارید و روی دکمهٔ Reload کلیک کنید، یا کلیدهای Ctrl-F5 یا Ctrl-R را با هم فشار دهید (در رایانههای اپل مکینتاش کلیدهای ⌘-R)
- گوگل کروم: کلیدهای Ctrl+Shift+R را با هم فشار دهید (در رایانههای اپل مکینتاش کلیدهای ⌘-Shift-R)
- اینترنت اکسپلورر/ Edge: کلید Ctrl را نگهدارید و روی دکمهٔ Refresh کلیک کنید، یا کلیدهای Ctrl-F5 را با هم فشار دهید
- اپرا: Ctrl-F5 را بفشارید.
/**
* +-------------------------------------------------------------------------+
* | === WARNING: GLOBAL GADGET FILE === |
* | Changes to this page affect many users. |
* | Please discuss changes at [[WT:TW]] before editing. |
* +-------------------------------------------------------------------------+
*
* Imported from github [https://github.com/azatoth/twinkle].
* All changes should be made in the repository, otherwise they will be lost.
*
* ----------
*
* This is AzaToth's Twinkle, the popular script sidekick for newbies, admins, and
* every Wikipedian in between. Visit [[WP:TW]] for more information.
*/
// <nowiki>
/* global Morebits */
(function (window, document, $) { // Wrap with anonymous function
// Check if account is experienced enough to use Twinkle
if (!Morebits.userIsInGroup('autoconfirmed') && !Morebits.userIsInGroup('confirmed')) {
return;
}
var Twinkle = {};
window.Twinkle = Twinkle; // allow global access
// for use by custom modules (normally empty)
Twinkle.initCallbacks = [];
Twinkle.addInitCallback = function twinkleAddInitCallback(func) {
Twinkle.initCallbacks.push(func);
};
Twinkle.defaultConfig = {};
/**
* This holds the default set of preferences used by Twinkle.
* It is important that all new preferences added here, especially admin-only ones, are also added to
* |Twinkle.config.sections| in twinkleconfig.js, so they are configurable via the Twinkle preferences panel.
* For help on the actual preferences, see the comments in twinkleconfig.js.
*
* Formerly Twinkle.defaultConfig.twinkle and Twinkle.defaultConfig.friendly
*/
Twinkle.defaultConfig = {
// General
summaryAd: ' ([[وپ:توینکل|توینکل]])', // localized
deletionSummaryAd: ' ([[وپ:توینکل|توینکل]])', // localized
protectionSummaryAd: ' ([[وپ:توینکل|توینکل]])', // localized
userTalkPageMode: 'tab',
dialogLargeFont: false,
disabledModules: [],
disabledSysopModules: [],
// ARV
spiWatchReport: 'yes',
// Block
defaultToPartialBlocks: false,
blankTalkpageOnIndefBlock: false,
// Fluff (revert and rollback)
autoMenuAfterRollback: false,
openTalkPage: [ 'agf', 'norm', 'vand' ],
openTalkPageOnAutoRevert: false,
rollbackInPlace: false,
markRevertedPagesAsMinor: [ 'vand' ],
watchRevertedPages: [ 'agf', 'norm', 'vand', 'torev' ],
offerReasonOnNormalRevert: true,
confirmOnFluff: false,
showRollbackLinks: [ 'diff', 'others' ],
// DI (twinkleimage)
notifyUserOnDeli: true,
deliWatchPage: 'default',
deliWatchUser: 'default',
// PROD
watchProdPages: true,
markProdPagesAsPatrolled: false,
prodReasonDefault: '',
logProdPages: false,
prodLogPageName: 'نامزدهای حذف زماندار', // localized
// CSD
speedySelectionStyle: 'buttonClick',
watchSpeedyPages: [ "ع۳", "ع۵", "ع۱۰", "ع۱۱", "ع۱۲" ], // localized
markSpeedyPagesAsPatrolled: false,
// these next two should probably be identical by default
welcomeUserOnSpeedyDeletionNotification: [ "حذف سریع", "ع۱", "ع۲", "ع۳", "ع۴", "ع۶", "ع۱۰", "ع۱۱", "ع۱۲", "ع۱۳", "م۱", "م۲", "م۳", "م۵", "م۷", "م۹", "م۱۰", "م۱۱", "پ۱", "پ۲", "پ۳", "پ۷", "پ۹", "پ۱۰", "ک۳", "ک۵", "ت۲", "ت۳", "د۱", "د۲" ], // localized
notifyUserOnSpeedyDeletionNomination: [ "حذف سریع", "ع۱", "ع۲", "ع۳", "ع۴", "ع۶", "ع۱۰", "ع۱۱", "ع۱۲", "ع۱۳", "م۱", "م۲", "م۳", "م۵", "م۷", "م۹", "م۱۰", "م۱۱", "پ۱", "پ۲", "پ۳", "پ۷", "پ۹", "پ۱۰", "ک۳", "ک۵", "ت۲", "ت۳", "د۱", "د۲" ], // localized
warnUserOnSpeedyDelete: [ "حذف سریع", "ع۱", "ع۲", "ع۳", "ع۴", "ع۵", "ع۱۰", "ع۱۱", "ع۱۲", "م۱", "م۳", "م۷", "م۹", "م۱۰", "م۱۱", "پ۳", "پ۷", "پ۹", "ک۳", "ک۵", "د۱" ], // localized
promptForSpeedyDeletionSummary: [],
deleteTalkPageOnDelete: true,
deleteRedirectsOnDelete: true,
deleteSysopDefaultToDelete: false,
speedyWindowHeight: 500,
speedyWindowWidth: 800,
logSpeedyNominations: false,
speedyLogPageName: 'نامزدهای حذف سریع', // localized
noLogOnSpeedyNomination: [ 'ک۱' ], // localized
// Unlink
unlinkNamespaces: [ '0', '10', '100', '118' ],
// Warn
defaultWarningGroup: '1',
combinedSingletMenus: false,
showSharedIPNotice: true,
watchWarnings: true,
oldSelect: false,
customWarningList: [],
// XfD
logXfdNominations: false,
xfdLogPageName: 'نامزدهای حذف با نظرخواهی', // localized
noLogOnXfdNomination: [],
xfdWatchDiscussion: 'default',
xfdWatchList: 'no',
xfdWatchPage: 'default',
xfdWatchUser: 'default',
xfdWatchRelated: 'default',
markXfdPagesAsPatrolled: true,
// Hidden preferences
revertMaxRevisions: 50,
autolevelStaleDays: 3, // Huggle is 3, CBNG is 2
batchMax: 5000,
batchdeleteChunks: 50,
batchProtectChunks: 50,
batchundeleteChunks: 50,
proddeleteChunks: 50,
// Formerly defaultConfig.friendly:
// Tag
groupByDefault: true,
watchTaggedPages: true,
watchMergeDiscussions: true,
markTaggedPagesAsMinor: false,
markTaggedPagesAsPatrolled: true,
tagArticleSortOrder: 'cat',
customTagList: [],
customFileTagList: [],
customRedirectTagList: [],
// Welcome
topWelcomes: false,
watchWelcomes: true,
welcomeHeading: 'خوش آمدید', // localized
insertHeadings: true,
insertUsername: true,
insertSignature: true, // sign welcome templates, where appropriate
quickWelcomeMode: 'norm',
quickWelcomeTemplate: 'خوشامد', // localized
customWelcomeList: [],
customWelcomeSignature: true,
// Talkback
markTalkbackAsMinor: true,
insertTalkbackSignature: true, // always sign talkback templates
talkbackHeading: 'بازبحث ' + mw.config.get('wgUserName'), // localized
adminNoticeHeading: 'آگاهسازی', // localized
mailHeading: "نامه دارید!", // localized
// Shared
markSharedIPAsMinor: true
};
// now some skin dependent config.
switch (mw.config.get('skin')) {
case 'vector-2022':
case 'vector':
Twinkle.defaultConfig.portletArea = 'right-navigation';
Twinkle.defaultConfig.portletId = 'p-twinkle';
Twinkle.defaultConfig.portletName = 'توینکل'; // localized
Twinkle.defaultConfig.portletType = 'menu';
Twinkle.defaultConfig.portletNext = 'p-cactions'; // localized
break;
case 'timeless':
Twinkle.defaultConfig.portletArea = '#page-tools .sidebar-inner';
Twinkle.defaultConfig.portletId = 'p-twinkle';
Twinkle.defaultConfig.portletName = 'توینکل'; // localized
Twinkle.defaultConfig.portletType = null;
Twinkle.defaultConfig.portletNext = 'p-userpagetools';
break;
default:
Twinkle.defaultConfig.portletArea = null;
Twinkle.defaultConfig.portletId = 'p-cactions';
Twinkle.defaultConfig.portletName = null;
Twinkle.defaultConfig.portletType = null;
Twinkle.defaultConfig.portletNext = null;
}
Twinkle.getPref = function twinkleGetPref(name) {
if (typeof Twinkle.prefs === 'object' && Twinkle.prefs[name] !== undefined) {
return Twinkle.prefs[name];
}
// Old preferences format, used before twinkleoptions.js was a thing
if (typeof window.TwinkleConfig === 'object' && window.TwinkleConfig[name] !== undefined) {
return window.TwinkleConfig[name];
}
if (typeof window.FriendlyConfig === 'object' && window.FriendlyConfig[name] !== undefined) {
return window.FriendlyConfig[name];
}
return Twinkle.defaultConfig[name];
};
/**
* **************** Twinkle.addPortlet() ****************
*
* Adds a portlet menu to one of the navigation areas on the page.
* This is necessarily quite a hack since skins, navigation areas, and
* portlet menu types all work slightly different.
*
* Available navigation areas depend on the skin used.
* Vector:
* For each option, the outer div class contains "vector-menu", the inner div class is "vector-menu-content", and the ul is "vector-menu-content-list"
* "mw-panel", outer div class contains "vector-menu-portal". Existing portlets/elements: "p-logo", "p-navigation", "p-interaction", "p-tb", "p-coll-print_export"
* "left-navigation", outer div class contains "vector-menu-tabs" or "vector-menu-dropdown". Existing portlets: "p-namespaces", "p-variants" (menu)
* "right-navigation", outer div class contains "vector-menu-tabs" or "vector-menu-dropdown". Existing portlets: "p-views", "p-cactions" (menu), "p-search"
* Special layout of p-personal portlet (part of "head") through specialized styles.
* Monobook:
* "column-one", outer div class "portlet", inner div class "pBody". Existing portlets: "p-cactions", "p-personal", "p-logo", "p-navigation", "p-search", "p-interaction", "p-tb", "p-coll-print_export"
* Special layout of p-cactions and p-personal through specialized styles.
* Modern:
* "mw_contentwrapper" (top nav), outer div class "portlet", inner div class "pBody". Existing portlets or elements: "p-cactions", "mw_content"
* "mw_portlets" (sidebar), outer div class "portlet", inner div class "pBody". Existing portlets: "p-navigation", "p-search", "p-interaction", "p-tb", "p-coll-print_export"
*
* @param String navigation -- id of the target navigation area (skin dependant, on vector either of "left-navigation", "right-navigation", or "mw-panel")
* @param String id -- id of the portlet menu to create, preferably start with "p-".
* @param String text -- name of the portlet menu to create. Visibility depends on the class used.
* @param String type -- type of portlet. Currently only used for the vector non-sidebar portlets, pass "menu" to make this portlet a drop down menu.
* @param Node nextnodeid -- the id of the node before which the new item should be added, should be another item in the same list, or undefined to place it at the end.
*
* @return Node -- the DOM node of the new item (a DIV element) or null
*/
Twinkle.addPortlet = function(navigation, id, text, type, nextnodeid) {
// sanity checks, and get required DOM nodes
var root = document.getElementById(navigation) || document.querySelector(navigation);
if (!root) {
return null;
}
var item = document.getElementById(id);
if (item) {
if (item.parentNode && item.parentNode === root) {
return item;
}
return null;
}
var nextnode;
if (nextnodeid) {
nextnode = document.getElementById(nextnodeid);
}
// verify/normalize input
var skin = mw.config.get('skin');
if ( ( skin !== 'vector' && skin !== 'vector-2022' ) || (navigation !== 'left-navigation' && navigation !== 'right-navigation')) {
type = null; // menu supported only in vector's #left-navigation & #right-navigation
}
var outerDivClass, innerDivClass;
switch (skin) {
case 'vector-2022':
case 'vector':
// XXX: portal doesn't work
if (navigation !== 'portal' && navigation !== 'left-navigation' && navigation !== 'right-navigation') {
navigation = 'mw-panel';
}
outerDivClass = 'mw-portlet vector-menu vector-menu-' + (navigation === 'mw-panel' ? 'portal' : type === 'menu' ? 'dropdown vector-menu-dropdown-noicon' : 'tabs');
innerDivClass = 'vector-menu-content';
break;
case 'modern':
if (navigation !== 'mw_portlets' && navigation !== 'mw_contentwrapper') {
navigation = 'mw_portlets';
}
outerDivClass = 'portlet';
break;
case 'timeless':
outerDivClass = 'mw-portlet';
innerDivClass = 'mw-portlet-body';
break;
default:
navigation = 'column-one';
outerDivClass = 'portlet';
break;
}
// Build the DOM elements.
var outerDiv = document.createElement('nav');
outerDiv.setAttribute('aria-labelledby', id + '-label');
// Vector getting vector-menu-empty FIXME TODO
outerDiv.className = outerDivClass + ' emptyPortlet';
outerDiv.id = id;
if (nextnode && nextnode.parentNode === root) {
root.insertBefore(outerDiv, nextnode);
} else {
root.appendChild(outerDiv);
}
var h3 = document.createElement('h3');
h3.id = id + '-label';
var ul = document.createElement('ul');
if (skin === 'vector' || skin === 'vector-2022') {
// add invisible checkbox to keep menu open when clicked
// similar to the p-cactions ("More") menu
if (outerDivClass.indexOf('vector-menu-dropdown') !== -1) {
var chkbox = document.createElement('input');
chkbox.className = 'vectorMenuCheckbox vector-menu-checkbox'; // remove vectorMenuCheckbox after 1.35-wmf.37 goes live
chkbox.setAttribute('type', 'checkbox');
chkbox.setAttribute('aria-labelledby', id + '-label');
outerDiv.appendChild(chkbox);
var span = document.createElement('span');
span.appendChild(document.createTextNode(text));
h3.appendChild(span);
var a = document.createElement('a');
a.href = '#';
$(a).click(function(e) {
e.preventDefault();
});
h3.appendChild(a);
}
outerDiv.appendChild(h3);
ul.className = 'menu vector-menu-content-list'; // remove menu after 1.35-wmf.37 goes live
} else {
h3.appendChild(document.createTextNode(text));
outerDiv.appendChild(h3);
}
if (innerDivClass) {
var innerDiv = document.createElement('div');
innerDiv.className = innerDivClass;
innerDiv.appendChild(ul);
outerDiv.appendChild(innerDiv);
} else {
outerDiv.appendChild(ul);
}
return outerDiv;
};
/**
* **************** Twinkle.addPortletLink() ****************
* Builds a portlet menu if it doesn't exist yet, and add the portlet link.
* @param task: Either a URL for the portlet link or a function to execute.
*/
Twinkle.addPortletLink = function(task, text, id, tooltip) {
if (Twinkle.getPref('portletArea') !== null) {
Twinkle.addPortlet(Twinkle.getPref('portletArea'), Twinkle.getPref('portletId'), Twinkle.getPref('portletName'), Twinkle.getPref('portletType'), Twinkle.getPref('portletNext'));
}
var link = mw.util.addPortletLink(Twinkle.getPref('portletId'), typeof task === 'string' ? task : '#', text, id, tooltip);
$('.client-js .skin-vector #p-cactions').css('margin-right', 'initial');
if (typeof task === 'function') {
$(link).click(function (ev) {
task();
ev.preventDefault();
});
}
if ($.collapsibleTabs) {
$.collapsibleTabs.handleResize();
}
return link;
};
/**
* **************** General initialization code ****************
*/
var scriptpathbefore = mw.util.wikiScript('index') + '?title=',
scriptpathafter = '&action=raw&ctype=text/javascript&happy=yes';
// Retrieve the user's Twinkle preferences
$.ajax({
url: scriptpathbefore + 'User:' + encodeURIComponent(mw.config.get('wgUserName')) + '/twinkleoptions.js' + scriptpathafter,
dataType: 'text'
})
.fail(function () {
mw.notify('Could not load your Twinkle preferences', {type: 'error'});
})
.done(function (optionsText) {
// Quick pass if user has no options
if (optionsText === '') {
return;
}
// Twinkle options are basically a JSON object with some comments. Strip those:
optionsText = optionsText.replace(/(?:^(?:\/\/[^\n]*\n)*\n*|(?:\/\/[^\n]*(?:\n|$))*$)/g, '');
// First version of options had some boilerplate code to make it eval-able -- strip that too. This part may become obsolete down the line.
if (optionsText.lastIndexOf('window.Twinkle.prefs = ', 0) === 0) {
optionsText = optionsText.replace(/(?:^window.Twinkle.prefs = |;\n*$)/g, '');
}
try {
var options = JSON.parse(optionsText);
if (options) {
if (options.twinkle || options.friendly) { // Old preferences format
Twinkle.prefs = $.extend(options.twinkle, options.friendly);
} else {
Twinkle.prefs = options;
}
// v2 established after unification of Twinkle/Friendly objects
Twinkle.prefs.optionsVersion = Twinkle.prefs.optionsVersion || 1;
}
} catch (e) {
mw.notify('Could not parse your Twinkle preferences', {type: 'error'});
}
})
.always(function () {
$(Twinkle.load);
});
// Developers: you can import custom Twinkle modules here
// For example, mw.loader.load(scriptpathbefore + "User:UncleDouggie/morebits-test.js" + scriptpathafter);
Twinkle.load = function () {
// Don't activate on special pages other than those on the whitelist so that
// they load faster, especially the watchlist.
var specialPageWhitelist = [ 'Block', 'Contributions', 'Recentchanges', 'Recentchangeslinked' ]; // wgRelevantUserName defined for non-sysops on Special:Block
if (Morebits.userIsSysop) {
specialPageWhitelist = specialPageWhitelist.concat([ 'DeletedContributions', 'Prefixindex' ]);
}
if (mw.config.get('wgNamespaceNumber') === -1 &&
specialPageWhitelist.indexOf(mw.config.get('wgCanonicalSpecialPageName')) === -1) {
return;
}
// Prevent clickjacking
if (window.top !== window.self) {
return;
}
// Set custom Api-User-Agent header, for server-side logging purposes
Morebits.wiki.api.setApiUserAgent('Twinkle/2.0 (' + mw.config.get('wgDBname') + ')');
// Load all the modules in the order that the tabs should appear
var twinkleModules = [
// User/user talk-related
'arv', 'warn', 'block', 'welcome', 'shared', 'talkback',
// Deletion
'speedy', 'prod', 'xfd', 'image',
// Maintenance
'protect', 'tag',
// Misc. ones last
'diff', 'unlink', 'fluff', 'deprod', 'batchdelete', 'batchprotect', 'batchundelete'
];
// Don't load modules users have disabled
var disabledModules = Twinkle.getPref('disabledModules').concat(Twinkle.getPref('disabledSysopModules'));
twinkleModules.filter(function(mod) {
return disabledModules.indexOf(mod) === -1;
}).forEach(function(module) {
Twinkle[module]();
});
Twinkle.config.init(); // Can't turn off
// Run the initialization callbacks for any custom modules
Twinkle.initCallbacks.forEach(function (func) {
func();
});
Twinkle.addInitCallback = function (func) {
func();
};
// Increases text size in Twinkle dialogs, if so configured
if (Twinkle.getPref('dialogLargeFont')) {
mw.util.addCSS('.morebits-dialog-content, .morebits-dialog-footerlinks { font-size: 100% !important; } ' +
'.morebits-dialog input, .morebits-dialog select, .morebits-dialog-content button { font-size: inherit !important; }');
}
// Hide the lingering space if the TW menu is empty
var isVector = mw.config.get('skin') === 'vector' || mw.config.get('skin') === 'vector-2022';
if (isVector && Twinkle.getPref('portletType') === 'menu' && $('#p-twinkle').length === 0) {
$('#p-cactions').css('margin-right', 'initial');
}
};
}(window, document, jQuery)); // End wrap with anonymous function
// </nowiki>