مدیاویکی:Gadget-Twinkle-2020.js - ویکیپدیا، دانشنامهٔ آزاد
نکته: برای دیدن تغییرات، ممکن است نیاز باشد که حافظهٔ نهانی مرورگر خود را پس از انتشار پاکسازی کنید. گوگل کروم، فایرفاکس، مایکروسافت اج و سافاری: کلید ⇧ Shift را نگه دارید و روی دکمهٔ Reload در نوار ابزار مرورگر کلیک کنید. برای آگاهی از جزئیات و نحوهٔ پاکسازی حافظهٔ نهانی سایر مرورگرها، صفحهٔ ویکیپدیا:میانگیر مرورگرتان را خالی کنید را ببینید.
/** * +-------------------------------------------------------------------------+ * | === 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>