User:Novem Linguae/Scripts/Links.js

From Wikipedia, the free encyclopedia
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
// <nowiki>

/*
	This script adds a left menu below the toolbox called "More tools", and includes some links:

	- userspace only
		- common.js
		- global.js
		- vector.js
		- common.css
		- central auth (good for seeing what global permissions people have)
		- rename log
		- global lock log
		- Twinkle CSD log
		- Twinkle PROD log
		- Twinkle XfD log
		- Draftify log
		- Page curation log

	- all namespaces
		- subpages

	This script also adds "Pending changes" to the left main menu.

	Skin support
		- left menu
			- vector
			- modern
			- monobook
			- timeless
		- right menu (tools menu)
			- vector-2022
		- not displayed at all
			- minerva
*/

class Links {
	constructor( mw ) {
		this.mw = mw;
	}

	async execute() {
		this.mw.util.addPortletLink( 'p-navigation', this.mw.util.getUrl( 'Special:PendingChanges' ), 'Pending changes' );
		this.pageName = this.mw.config.get( 'wgPageName' );
		this.createBottomLeftMenuContainer();
		this.username = this.getFirstMatch( this.pageName, /(?:User:|User_talk:)([^/]+).*/ );
		if ( this.username ) {
			await this.generateUserspaceLinks();
		}
		await this.generateSubpageLink();
	}

	createBottomLeftMenuContainer() {
		// Works as expected in vector, modern, monobook, timeless. Is in right menu instead of left menu in vector-2022. Doesn't show up at all in minerva.
		// Could fix in those skins by adding it before an existing nearby portlet, then using the p.parentNode.appendChild( p ); trick.
		this.mw.util.addPortlet( 'p-links', 'More tools', '#p-coll-print_export' );
	}

	async generateUserspaceLinks() {
		this.username = 'User:' + this.username;
		this.usernameURI = encodeURIComponent( this.username.replace( /_/g, ' ' ).replace( /^User:/, '' ) );

		this.mw.util.addPortletLink( 'p-links', `/wiki/${ this.username }/common.js`, 'common.js' );
		this.mw.util.addPortletLink( 'p-links', `https://meta.wikimedia.org/wiki/${ this.username }/global.js`, 'global.js' );
		this.mw.util.addPortletLink( 'p-links', `/wiki/${ this.username }/vector.js`, 'vector.js' );
		this.mw.util.addPortletLink( 'p-links', `/wiki/${ this.username }/common.css`, 'common.css' );
		this.mw.util.addPortletLink( 'p-links', `/wiki/Special:CentralAuth?target=${ this.usernameURI }`, 'Central auth' );
		this.mw.util.addPortletLink( 'p-links', `/wiki/Special:Log?type=rights&user=&page=${ this.usernameURI }`, 'User rights log' );
		this.mw.util.addPortletLink( 'p-links', `https://meta.wikimedia.org/wiki/Special:Log?type=rights&user=&page=${ this.usernameURI }@enwiki`, 'User rights log (meta)' );

		this.generateRenameLogLinks();

		this.mw.util.addPortletLink( 'p-links', `https://meta.wikimedia.org/wiki/Special:Log?page=User%3A${ this.usernameURI }%40global`, 'Global lock log' );

		await this.generateTwinkleLogLinks();

		this.mw.util.addPortletLink( 'p-links', `/wiki/Special:Log?type=pagetriage-curation&subtype=review&user=User%3A${ this.usernameURI }`, 'Page curation log' );
	}

	generateSubpageLink() {
		let parentName = this.pageName + '/';
		if ( this.pageName.includes( '/' ) ) {
			parentName = this.getFirstMatch( this.pageName, /^([^/]+\/)/ );
		}
		this.mw.util.addPortletLink( 'p-links', `/wiki/Special:PrefixIndex/${ parentName }`, 'Subpages' );
	}

	async generateTwinkleLogLinks() {
		// twinkle logs (csd, prod, xfd) and draftify log
		// check if they exist with an API query before adding links
		const logPages = await this.pagesExist( [
			`${ this.username }/CSD log`,
			`${ this.username }/PROD log`,
			`${ this.username }/XfD log`,
			`${ this.username }/Draftify log`
		] );
		for ( const title of logPages ) {
			const shortTitle = title.replace( /^.*\//, '' );
			this.mw.util.addPortletLink( 'p-links', `/wiki/${ title }`, shortTitle );
		}
	}

	generateRenameLogLinks() {
		// All modern renames seem to be put into both en:Special:Log->User rename log AND meta:Special:Log->User rename log.
		// One older rename was put only into meta:Special:Log->User rename log.
		// Another older rename was put only into en:Special:Log->User rename log, and was complicated by the fact that ~enwiki had been added to the end of it.
		// Spaces vs underscores don't seem to matter. User: or no User: doesn't seem to matter.
		this.mw.util.addPortletLink( 'p-links', `https://meta.wikimedia.org/wiki/Special:GlobalRenameProgress?username=${ this.usernameURI }`, 'Rename log 1' );
		this.mw.util.addPortletLink( 'p-links', `https://meta.wikimedia.org/wiki/Special:Log?type=renameuser&user=&page=${ this.usernameURI }`, 'Rename log 2' );
		this.mw.util.addPortletLink( 'p-links', `/wiki/Special:Log?type=renameuser&user=&page=${ this.usernameURI }%7Eenwiki`, 'Rename log 3' );
	}

	getFirstMatch( string, regex ) {
		const matches = string.match( regex );
		if ( matches && matches[ 1 ] ) {
			return matches[ 1 ];
		}
		return '';
	}

	/**
		* @param {Array} titles
	 */
	async pagesExist( titles ) {
		const api = new this.mw.Api();
		let response = await api.get( {
			action: 'query',
			format: 'json',
			prop: 'revisions',
			titles: titles.join( '|' )
		} );
		response = response.query.pages;
		const pages = [];
		for ( const key in response ) {
			// the Number class will convert any non-numbers to 0
			// the API will return -1 for non-existent pages
			// the API will return the page ID for existing pages
			if ( Number( key ) > 0 ) {
				pages.push( response[ key ].title );
			}
		}
		return pages;
	}
}

$( async function () {
	await mw.loader.using( [ 'mediawiki.api' ], async function () {
		await ( new Links( mw ) ).execute();
	} );
} );

// </nowiki>