# MediaWiki:Gadget-Twinkle-2020.js

From faraghaib
Jump to navigation Jump to search

Note: After publishing, you may have to bypass your browser's cache to see the changes.

- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press 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: 'Welcome',
    	insertHeadings: true,
    	insertUsername: true,
    	insertSignature: true,  // sign welcome templates, where appropriate
    	quickWelcomeMode: 'norm',
    	quickWelcomeTemplate: 'welcome',
    	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':
    		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' || (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':
    			// 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' : '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') {
    		// 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
    	if (mw.config.get('skin') === 'vector' && Twinkle.getPref('portletType') === 'menu' && $('#p-twinkle').length === 0) {
    		$('#p-cactions').css('margin-right', 'initial');
    	}
    };

    }(window, document, jQuery)); // End wrap with anonymous function

    // </nowiki>

Retrieved from "https://en.faraghaib.ir/w/index.php?title=MediaWiki:Gadget-Twinkle-2020.js&oldid=196"