User:Fred Gandt/watchUserContribs.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.
if ( ( /^Special:Contributions/ ).test( mw.config.get( "wgPageName" ) ) ) {
	"use strict";

	let wtchlst = sessionStorage[ "fg-watchlist" ] || {};

	const STYL = document.createElement( "style" ),

		quotes2Hashes = s => s.replace( /\"/g, "#" ),

		firstInputIn = e => e.querySelector( "input" ),

		sessionStore = () => sessionStorage[ "fg-watchlist" ] = JSON.stringify( wtchlst ),

		sessionClear = () => {
			if ( confirm( "The cache will be recompiled the next time you load a user contribs page." ) ) {
				delete sessionStorage[ "fg-watchlist" ];
			}
		},

		apiQuery = ( dt, fnc ) => {
			dt.format = "json";
			$.ajax( {
				type: "POST",
				url: "/w/api.php",
				dataType: dt.format,
				data: dt,
				success: function( data ) { fnc( data ) },
				error: function( data) { console.error( data ) }
			} );
		},

		doTheDo = () => {
			$( document ).ready( () => {
				const SCTN = document.querySelector( "section.mw-pager-body" ),
					LIS = SCTN.querySelectorAll( "li[data-mw-revid]" ),

					boSelector = evt => {
						let trg = evt.target,
							chckd = trg.checked;
						LIS.forEach( li => firstInputIn( li ).checked = chckd );
						firstInputIn( trg.parentElement === TOPOPT ? BTMOPT : TOPOPT ).checked = chckd;
					},

					captainKoons = evt => {
						let wtchn = evt.target.value === "Watch",
							slctd = Array.from( LIS )
								.map( li => firstInputIn( li ) )
								.filter( npt => npt.checked && !( wtchn && npt.parentElement.classList.contains( "fg-watched" ) ) )
								.map( npt => npt.value.replace( /( talk(?=:)|Talk:)/, "" ) );
						slctd = [ ...new Set( slctd ) ];
						let ttls = slctd.splice( 0, 50 );
						while ( ttls.length ) {
							let qs = {
								action: "watch",
								titles: ttls.join( "|" ),
								token: mw.user.tokens.values.watchToken
							};
							if ( !wtchn ) {
								qs.unwatch = true;
							}
							apiQuery( qs, function( data ) {
								data.watch.map( r => r.title ).forEach( ttl => {
									if ( wtchn ) {
										wtchlst[ ttl ] = 1;
									} else {
										delete wtchlst[ ttl ];
									}
									SCTN.querySelectorAll( `input[data-title$="${quotes2Hashes( ttl )}"]` ).forEach( npt => npt.parentElement.classList.toggle( "fg-watched", wtchn ) );
								} );
								sessionStore();
							} );
							ttls = slctd.splice( 0, 50 );
						}
					},

					makeInput = ( t, v ) => {
						let npt = document.createElement( "input" );
						npt.type = t;
						npt.title = npt.value = v;
						npt.dataset.title = quotes2Hashes( v );
						npt.classList.toggle( "fg-checkbox", t === "checkbox" );
						return npt;
					},

					makeOptions = () => {
						let p = document.createElement( "p" );
							npt = makeInput( "checkbox", "(De)select all" );
						npt.addEventListener( "change", boSelector, { passive: true } );
						p.append( npt );
						npt = makeInput( "button", "Watch" );
						npt.addEventListener( "click", captainKoons, { passive: true } );
						p.append( npt );
						p.append( document.createTextNode( " or " ) );
						npt = makeInput( "button", "Unwatch" );
						npt.addEventListener( "click", captainKoons, { passive: true } );
						p.append( npt );
						p.append( document.createTextNode( " the selected pages. " ) );
						npt = makeInput( "button", "Clear watchlist cache" );
						npt.addEventListener( "click", sessionClear, { once: true, passive: true } );
						p.append( npt );
						return p;
					},

					TOPOPT = makeOptions(),
					BTMOPT = makeOptions();

				STYL.textContent = "li.fg-watched{border:0 solid #90d4e9;border-width:1px .5em}input.fg-checkbox{margin:0 1em 0 .5em;vertical-align:-10%}";
				document.querySelector( "head" ).append( STYL );

				LIS.forEach( li => {
					let ttl = li.querySelector( "a.mw-contributions-title" ).title;
					li.classList.toggle( "fg-watched", !!wtchlst[ ttl ] );
					li.prepend( makeInput( "checkbox", ttl ) );
				} );
				
				SCTN.prepend( TOPOPT );
				SCTN.append( BTMOPT );
			} );
		},

		compileWatchlist = wlr => {
			apiQuery( wlr, function( data ) {
				data.watchlistraw.forEach( ttl => wtchlst[ ttl.title ] = 1 );
				if ( data.continue ) {
					wlr.wrcontinue = data.continue.wrcontinue;
					compileWatchlist( wlr );
				} else if ( data.hasOwnProperty( "batchcomplete" ) ) {
					sessionStore();
					doTheDo();
				}
			} );
		};

	if ( $.isEmptyObject( wtchlst ) ) {
		compileWatchlist( { action: "query", list: "watchlistraw", wrlimit: 500 } );
	} else {
		wtchlst = JSON.parse( wtchlst );
		doTheDo();
	}
}