User:Jpgordon/unblock-review.js

// ( function {    var UNBLOCK_REQ_COLOR = "rgb(235, 244, 255)";    var SIGNATURE = "" + "";    var DECLINE_REASON_HERE = "{" + "{subst:Decline reason here}}"; // broken up to prevent processing    var ADVERT = " (unblock-review)";

// Making this a function for unit test reasons. function getInitialText(wikitext, appealReason) { // https://stackoverflow.com/a/6969486/3480193 function escapeRegExp(string) { return string.replace(/[.*+?^${}|[\]\\]/g, '\\$&'); // $& means the whole matched string }

let regEx = new RegExp(escapeRegExp(appealReason), 'g'); let matches = wikitext.matchAll(regEx); matches = [...matches]; if( matches.length === 0 ) { throw new Error( "Searching for target text failed!" ); }       for ( let match of matches ) { var textIdx = match.index; var startIdx = textIdx;

// check for {{tlx|unblock. if found, this isn't what we want, skip. let startOfSplice = startIdx - 50 < 0 ? 0 : startIdx - 50; var chunkFiftyCharactersWide = wikitext.substring(startOfSplice, startIdx); if ( /\{\{\s*tlx\s*\|\s*unblock/i.test(chunkFiftyCharactersWide) ) { continue; }

let i = 0; while( wikitext[startIdx] != "{" && i < 50 ) { startIdx--; i++; }           if( i == 50 ) { continue; }

startIdx--; // templates start with two opening curly braces

var initialText = wikitext.substring( startIdx, textIdx ); return initialText; }

throw new Error( "Searching backwards failed!" ); }

if( mw.config.get( "wgNamespaceNumber" ) === 3 ) { /**        * Is there a signature (four tildes) present in the given text, * outside of a nowiki element? */       function hasSig( text ) { // no literal signature? if( text.indexOf( SIGNATURE ) < 0 ) return false;

// if there's a literal signature and no nowiki elements, // there must be a real signature if( text.indexOf( " " ) < 0 ) return true;

// Save all nowiki spans var nowikiSpanStarts = []; // list of ignored span beginnings var nowikiSpanLengths = []; // list of ignored span lengths var NOWIKI_RE = / .*?<\/nowiki>/g; var spanMatch; do { spanMatch = NOWIKI_RE.exec( text ); if( spanMatch ) { nowikiSpanStarts.push( spanMatch.index ); nowikiSpanLengths.push( spanMatch[0].length ); }           } while( spanMatch );

// So that we don't check every ignore span every time var nowikiSpanStartIdx = 0;

var SIG_RE = new RegExp( SIGNATURE, "g" ); var sigMatch;

matchLoop: do { sigMatch = SIG_RE.exec( text ); if( sigMatch ) { // Check that we're not inside a nowiki for( var nwIdx = nowikiSpanStartIdx; nwIdx <                       nowikiSpanStarts.length; nwIdx++ ) { if( sigMatch.index > nowikiSpanStarts[nwIdx] ) { if ( sigMatch.index + sigMatch[0].length <=                               nowikiSpanStarts[nwIdx] + nowikiSpanLengths[nwIdx] ) {

// Invalid sig continue matchLoop; } else {

// We'll never encounter this span again, since // headers only get later and later in the wikitext nowikiSpanStartIdx = nwIdx; }                       }                    }

// We aren't inside a nowiki return true; }           } while( sigMatch ); return false; }

/**        * Given the div of an unblock request, set up the UI and event * listeners. */       function setUpUi( unblockDiv ) { var container = document.createElement( "table" ); container.className = "unblock-review"; var hrEl = unblockDiv.querySelector( "hr" ); container.innerHTML = " " + "" + DECLINE_REASON_HERE + " " + " Accept " + " Decline  "; unblockDiv.insertBefore( container, hrEl.previousElementSibling ); var reasonArea = container.querySelector( "textarea" ); $( container ).find( "button" ).click( function {                var action = $( this ).text.toLowerCase;                var appealReason = hrEl.nextElementSibling.nextElementSibling.childNodes[0].textContent;                $.getJSON( mw.util.wikiScript( "api" ), {                       format: "json", action: "query", prop: "revisions", rvprop: "content", rvlimit: 1, titles: mw.config.get( "wgPageName" ) }               ).done( function ( data ) { // Extract wikitext from API response var pageId = Object.keys(data.query.pages)[0]; wikitext = data.query.pages[pageId].revisions[0]["*"];

var initialText = getInitialText(wikitext, appealReason);

// Build accept/decline reason var reason = reasonArea.value; if( !reason.trim ) { reason = DECLINE_REASON_HERE + " " + SIGNATURE; } else if( !hasSig( reason ) ) { reason = reason + " " + SIGNATURE; }                   wikitext = wikitext.replace( initialText + appealReason, "\n{" +                        "{unblock reviewed|" + action + "=" + reason + "|1=" + appealReason );

var summary = ( action === "accept" ? "Accepting" : "Declining" ) + " unblock request" + ADVERT;

( new mw.Api ).postWithToken( "csrf", {                       action: "edit",                        title: mw.config.get( "wgPageName" ),                        summary: summary,                        text: wikitext                    } ).done ( function ( data ) {                        if ( data && data.edit && data.edit.result && data.edit.result == "Success" ) {                            window.location.reload( true );                        } else {                            console.log( data );                        }                    } ); } );           } );        }

$.when( $.ready, mw.loader.using( [ "mediawiki.api", "mediawiki.util" ] ) ).then( function {            mw.util.addCSS( ".unblock-review td { padding: 0 }" + "td.reason-container { padding-right: 1em; width: 30em }" + ".unblock-review-reason { height: 5em }" );           importStylesheet( "User:Enterprisey/mw-ui-button.css" );            importStylesheet( "User:Enterprisey/mw-ui-input.css" );            var userBlockBoxes = document.querySelectorAll( "div.user-block" );            for( var i = 0, n = userBlockBoxes.length; i < n; i++ ) {                if( userBlockBoxes[i].style["background-color"] !== UNBLOCK_REQ_COLOR ) {                    continue;                }                // We now have a pending unblock request - add UI                setUpUi( userBlockBoxes[i] );            }        } ); } } ); //