مدیاویکی: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>