Jump to content

User:Novem Linguae/Scripts/Links.js

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Novem Linguae (talk | contribs) at 23:35, 30 April 2024 (refactor). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
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.

	| skins = minerva, modern, monobook, timeless, vector, vector-2022
*/

class Links {
	async execute() {
		this.addPendingChangesToLeftMenu();
		this.pageName = mw.config.get( 'wgPageName' );
		await this.generateLinksForUserSpace();
		await this.generateLinksForAllNameSpaces();
		this.insertLinksInLeftMenu();
	}

	async generateLinksForUserSpace() {
		this.username = this.getFirstMatch( this.pageName, /(?:User:|User_talk:)([^/]+).*/ );
		this.userLinks = '';
		if ( this.username ) {
			this.username = 'User:' + this.username;
			this.usernameURI = encodeURIComponent( this.username.replace( /_/g, ' ' ).replace( /^User:/, '' ) );

			this.generateUserSpaceJsCssLinks();
			this.userLinks += `<li><a href="/wiki/Special:CentralAuth?target=${ this.usernameURI }">Central auth</a></li>`;
			this.generateUserRightsLinks();
			this.generateRenameLogLinks();
			this.userLinks += `<li><a href="https://meta.wikimedia.org/wiki/Special:Log?page=User%3A${ this.usernameURI }%40global">Global lock log</a></li>`;
			await this.generateTwinkleLogLinks();
			this.userLinks += `<li><a href="https://en.wikipedia.org/wiki/Special:Log?type=pagetriage-curation&subtype=review&user=User%3A${ this.usernameURI }">Page curation log</a></li>`;
		}
	}

	insertLinksInLeftMenu() {
		const menuTitle = 'More tools';
		let html = '';
		const skin = mw.config.get( 'skin' );
		switch ( skin ) {
			case 'minerva':
				// TODO: insert into the "More" menu, rather than the hamburger

				html = `
					<ul id="p-npp-links">
						${ this.userLinks }
						${ this.allPageLinks }
					</ul>`;

				html = html.replace( /<li>/g, '<li class="menu__item--preferences">' );
				html = html.replace( /(<a[^>]*>)/g, '$1<span class="mw-ui-icon"></span><span>' );
				html = html.replace( /<\/a>/g, '<span></a>' );

				$( '#p-navigation' ).after( html );
				break;
			case 'monobook':
				html = `
					<div role="navigation" class="portlet mw-portlet mw-portlet-npp-links" id="p-npp-links" aria-labelledby="p-npp-links-label">
						<h3 id="p-npp-links-label">
							${ menuTitle }
						</h3>
						<div class="pBody">
							<ul>
								${ this.userLinks }
								${ this.allPageLinks }
							</ul>
						</div>
					</div>
				`;
				$( '#p-tb' ).after( html );
				break;
			case 'modern':
				html = `
					<div class="portlet mw-portlet mw-portlet-npp-links" d="p-npp-links" role="navigation">
						<h3 id="p-npp-links-label" lang="en" dir="ltr">
							${ menuTitle }
						</h3>
						<div class="mw-portlet-body">
							<ul lang="en" dir="ltr">
								${ this.userLinks }
								${ this.allPageLinks }
							</ul>
						</div>
					</div>
				`;
				$( '#p-tb' ).after( html );
				break;
			case 'timeless':
				html = `
					<div role="navigation" class="mw-portlet" id="p-npp-links" aria-labelledby="p-npp-links-label">
						<h3 id="p-npp-links-label" lang="en" dir="ltr">
							${ menuTitle }
						</h3>
						<div class="mw-portlet-body">
							<ul lang="en" dir="ltr">
								${ this.userLinks }
								${ this.allPageLinks }
							</ul>
						</div>
					</div>
				`;
				$( '#p-tb' ).after( html );
				break;
			case 'vector-2022':
				html = `
					<nav id="p-npp-links" class="vector-main-menu-group vector-menu mw-portlet mw-portlet-interaction">
						<div class="vector-menu-heading">
							<span class="vector-menu-heading-label">
								${ menuTitle }
							</span>
						</div>
						<div class="vector-menu-content">
							<ul class="vector-menu-content-list">
								${ this.userLinks }
								${ this.allPageLinks }
							</ul>
						</div>
					</nav>
				`;
				$( '#p-tb' ).after( html );
				break;
			case 'vector':
			default:
				html = `
					<nav id="p-npp-links" class="mw-portlet mw-portlet-npp-links vector-menu vector-menu-portal portal" aria-labelledby="p-npp-links-label" role="npp-links">
						<h3 id="p-npp-links-label" class="vector-menu-heading">
							${ menuTitle }
						</h3>
						<div class="vector-menu-content">
							<ul class="vector-menu-content-list">
								${ this.userLinks }
								${ this.allPageLinks }
							</ul>
						</div>
					</nav>
				`;
				$( '#p-tb' ).after( html );
				break;
		}
	}

	generateLinksForAllNameSpaces() {
		let parentName = this.pageName + '/';
		this.allPageLinks = '';
		if ( this.pageName.includes( '/' ) ) {
			parentName = this.getFirstMatch( this.pageName, /^([^/]+\/)/ );
		}
		this.allPageLinks += `<li><a href="/wiki/Special:PrefixIndex/${ parentName }">Subpages</a></li>`;
	}

	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.userLinks += `<li><a href="https://en.wikipedia.org/wiki/${ title }">${ shortTitle }</a></li>`;
		}
	}

	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.userLinks += `<li><a href="https://meta.wikimedia.org/wiki/Special:GlobalRenameProgress?username=${ this.usernameURI }">Rename log 1</a></li>`;
		this.userLinks += `<li><a href="https://meta.wikimedia.org/wiki/Special:Log?type=renameuser&user=&page=${ this.usernameURI }">Rename log 2</a></li>`;
		this.userLinks += `<li><a href="https://en.wikipedia.org/wiki/Special:Log?type=renameuser&user=&page=${ this.usernameURI }%7Eenwiki">Rename log 3</a></li>`;
	}

	generateUserRightsLinks() {
		this.userLinks += `<li><a href="/wiki/Special:Log?type=rights&user=&page=${ this.usernameURI }">User rights log</a></li>`;
		this.userLinks += `<li><a href="https://meta.wikimedia.org/wiki/Special:Log?type=rights&user=&page=${ this.usernameURI }@enwiki">User rights log (meta)</a></li>`;
	}

	/** common.js and similar */
	generateUserSpaceJsCssLinks() {
		this.userLinks += `<li><a href="/wiki/${ this.username }/common.js">common.js</a></li>`;
		this.userLinks += `<li><a href="https://meta.wikimedia.org/wiki/${ this.username }/global.js">global.js</a></li>`;
		this.userLinks += `<li><a href="/wiki/${ this.username }/vector.js">vector.js</a></li>`;
		this.userLinks += `<li><a href="/wiki/${ this.username }/common.css">common.css</a></li>`;
	}

	addPendingChangesToLeftMenu() {
		mw.util.addPortletLink(
			'p-navigation',
			mw.util.getUrl( 'Special:PendingChanges' ),
			'Pending changes' // can't put comma here, silent error
		);
	}

	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 mw.Api();
		let response = await api.get( {
			action: 'query',
			format: 'json',
			prop: 'revisions',
			titles: titles.join( '|' )
		} );

		/*
		Example format if exists:

		"66442411": {
			"pageid": 66442411,
			"ns": 2,
			"title": "User:Novem Linguae/CSD log",
			"revisions": [
				{
					"revid": 1091295780,
					"parentid": 1091255744,
					"user": "Novem Linguae",
					"timestamp": "2022-06-03T11:38:00Z",
					"comment": "Logging speedy deletion nomination of [[:Liquorose]]."
				}
			]
		},

		Example format if doesn't exist:

		"-1": {
			"ns": 2,
			"title": "User:Jmcclaskey54/CSD log",
			"missing": ""
		},
		*/
		response = response.query.pages;
		const pages = [];
		for ( const key in response ) {
			// the Number class will convert any non-numbers to zero
			if ( Number( key ) > 0 ) {
				pages.push( response[ key ].title );
			}
		}

		return pages;
	}
}

$( async function () {
	await ( new Links() ).execute();
} );

// </nowiki>