User:Ingenuity/combined.js

/* Evad37/OneClickArchiver.js */ /** * Derived from Technical 13's version [1] of Equazcion's OneClickArchiver [2] * [1] < https://en.wikipedia.org/wiki/User:Technical_13/Scripts/OneClickArchiver.js > * [2] < https://en.wikipedia.org/wiki/User:Equazcion/OneClickArchiver.js > */ // mw.loader.using(['mediawiki.util', 'mediawiki.api'], function { var config = mw.config.get([ 'debug', 'wgAction', 'wgArticleId', 'wgCategories', 'wgMonthNames', 'wgNamespaceNumber', 'wgPageName', 'wgRelevantUserName' ]); $( document ).ready( function { if ( ( $( '#ca-addsection' ).length > 0 || $.inArray( 'Non-talk pages that are automatically signed', config.wgCategories ) >= 0 ) && config.wgAction === 'view' && $.inArray( 'Pages that should not be manually archived', config.wgCategories ) === -1 ) { var OCAstate = mw.user.options.get( 'userjs-OCA-enabled', 'true' ); var pageid = config.wgArticleId; var errorLog = { errorCount: 0 }; new mw.Api.get( { action: 'query', prop: [ 'revisions', 'info' ], rvsection: 0, rvprop: 'content', pageids: pageid, indexpageids: 1, rawcontinue: '' } ).done( function ( response0 ) { var thisMonthNum, thisMonthFullName, monthNamesShort, thisMonthShortName, thisYear, archiveNum; var content0 = response0.query.pages[ pageid ].revisions[ 0 ][ '*' ]; /* archiveme */// Find out if there is already an request, and if it is between 1-2 months old if ( config.wgNamespaceNumber === 3 ) { thisMonthNum = new Date.getMonth; thisMonthFullName = config.wgMonthNames[ thisMonthNum + 1 ]; monthNamesShort = [ "", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ]; thisMonthShortName = monthNamesShort[ thisMonthNum + 1 ]; thisYear = new Date.getFullYear; var nowOcto = parseInt( ( ( thisYear * 12 ) + thisMonthNum + 1 ), 10 ); var archiveme = content0.match( /\{\{Archive ?me(\| *date *= *(January|February|March|April|May|June|July|August|September|October|November|December) ([\d]{4}))?\}\}/i ); if ( archiveme === null || archiveme === undefined ) { errorLog.errorCount++; errorLog.archiveme = ' not found.'; } else { /* Archive me found - how old is it? */ var archivemeMonth = archiveme[ 2 ]; var archivemeMonthNum = 0; if ( typeof archivemeMonth === 'number' ) { archivemeMonthNum = parseInt( archivemeMonth, 10 ); } else { for ( var i in config.wgMonthNames ) { if ( archivemeMonth === config.wgMonthNames[ i ] ) { archivemeMonthNum = parseInt( i, 10 ); } else if ( archivemeMonth === monthNamesShort[ i ] ) { archivemeMonthNum = parseInt( i, 10 ); } } } var archivemeYear = parseInt( archiveme[ 3 ], 10 ); var archivemeOcto = parseInt( ( ( archivemeYear * 12 ) + archivemeMonthNum ), 10 ); var archivemeSafe = parseInt( ( nowOcto - 2 ), 10 ); archiveme = archiveme[ 0 ]; } } /* counter */// Get the counter value var counterRegEx = new RegExp( '\\| *counter *= *(\\d+)' ); var counter = counterRegEx.exec( content0 ); if ( counter === null || counter === undefined ) { counter = 1; errorLog.errorCount++; errorLog.counter = counter; } else { counter = counter[ 1 ]; archiveNum = counter; } /* archiveName */// Get the archiveName value var archiveNameRegEx = /\| *archive *= *(.*\%\(counter\)d.*) *(-->)?/; var archiveName = archiveNameRegEx.exec( content0 ); var rootBase = config.wgPageName .replace( /\/.*/g, '' )// Chop off the subpages .replace( /_/g, ' ' );// Replace underscores with spaces if ( archiveName === null || archiveName === undefined ) { archiveName = rootBase + '/Archive ' + counter; errorLog.errorCount++; errorLog.archiveName = archiveName; } else { archiveName = archiveName[ 1 ] .replace( /\| *archive *= */, '' ) .replace( /\%\(year\)d/g, thisYear ) .replace( /\%\(month\)d/g, thisMonthNum ) .replace( /\%\(monthname\)s/g, thisMonthFullName ) .replace( /\%\(monthnameshort\)s/g, thisMonthShortName ) .replace( /\%\(counter\)d/g, archiveNum ); var archiveBase = archiveName .replace( /\/.*/, '' )// Chop off the subpages .replace( /_/g, ' ' );// Replace underscores with spaces var archiveSub = archiveName .replace( /_/g, ' ' )// Replace underscores with spaces .replace( archiveBase, '' );// Chop off the base pagename if ( archiveBase != rootBase ) { errorLog.errorCount++; errorLog.archiveName = 'Archive name mismatch:

Found: ' + archiveName; errorLog.archiveName += ' Expected: ' + rootBase.replace( '_', ' ' ) + archiveSub + '

'; } } /* archivepagesize */// Get the size of the destination archive from the API new mw.Api.get( { action: 'query', prop: 'revisions',rvlimit: 1, rvprop: [ 'size', 'content' ], titles: archiveName, list: 'usercontribs', uclimit: 1, ucprop: 'timestamp', ucuser: ( ( config.wgRelevantUserName ) ? config.wgRelevantUserName : 'Example' ), rawcontinue: '', } ).done( function ( archivePageData ) { var archivePageSize = 0; if ( archivePageData.query.pages[ -1 ] === undefined ) { for ( var a in archivePageData.query.pages ) { archivePageSize = parseInt( archivePageData.query.pages[ a ].revisions[ 0 ].size, 10 ); archiveName = archivePageData.query.pages[ a ].title; } } else { archivePageSize = -1; archiveName = archivePageData.query.pages[ archivePageSize ].title; errorLog.errorCount++; errorLog.archivePageSize = -1; errorLog.archiveName = '' + archiveName + ''; } /* maxarchivesize */// Get the defined max archive size from template var maxArchiveSizeRegEx = new RegExp( '\\| *maxarchivesize *= *(\\d+K?)' ); var maxArchiveSize = maxArchiveSizeRegEx.exec( content0 ); if ( maxArchiveSize === null || maxArchiveSize[ 1 ] === undefined ) { maxArchiveSize = parseInt( 153600, 10 ); errorLog.errorCount++; errorLog.maxArchiveSize = maxArchiveSize; } else if ( maxArchiveSize[ 1 ].slice( -1 ) === "K" && $.isNumeric( maxArchiveSize[ 1 ].slice( 0, maxArchiveSize[ 1 ].length-1 ) ) ) { maxArchiveSize = parseInt( maxArchiveSize[ 1 ].slice( 0, maxArchiveSize[ 1 ].length - 1 ), 10 ) * 1024; } else if ( $.isNumeric( maxArchiveSize[ 1 ].slice ) ) { maxArchiveSize = parseInt( maxArchiveSize[ 1 ].slice, 10 ); } /* pslimit */// If maxArchiveSize is defined, and archivePageSize >= maxArchiveSize increment counter and redfine page name. if ( !errorLog.maxArchiveSize && archivePageSize >= maxArchiveSize ) { counter++; archiveName = archiveNameRegEx.exec( content0 ); archiveName = archiveName[ 1 ] .replace( /\| *archive *= */, '' ) .replace( /\%\(year\)d/g, thisYear ) .replace( /\%\(month\)d/g, thisMonthNum ) .replace( /\%\(monthname\)s/g, thisMonthFullName ) .replace( /\%\(monthnameshort\)s/g, thisMonthShortName ) .replace( /\%\(counter\)d/g, counter ); var oldCounter = counterRegEx.exec( content0 ); var newCounter = '|counter=1'; if ( oldCounter !== null && oldCounter !== undefined ) { newCounter = oldCounter[ 0 ].replace( oldCounter[ 1 ], counter ); oldCounter = oldCounter[ 0 ]; } else { errorLog.errorCount++; errorLog.newCounter = newCounter; } } /* archiveheader */// Get the defined archive header to place on archive page if it doesn't exist var archiveHeaderRegEx = new RegExp( '\\| *archiveheader *= *(\{\{[^\r\n]*\}\})' ); var archiveHeader = archiveHeaderRegEx.exec( content0 ); if ( archiveHeader === null || archiveHeader === undefined ) { archiveHeader = ""; errorLog.errorCount++; errorLog.archiveHeader = archiveHeader; } else { archiveHeader = archiveHeader[ 1 ]; } /* headerlevel */// Get the headerlevel value or default to '2' var headerLevelRegEx = new RegExp( '\\| *headerlevel *= *(\\d+)' ); var headerLevel = headerLevelRegEx.exec( content0 ); if ( headerLevel === null || headerLevel === undefined ) { headerLevel = 2; errorLog.errorCount++; errorLog.headerLevel = headerLevel; } else { headerLevel = parseInt( headerLevel[ 1 ] ); } /* debug */// Table to report the values found. if ( config.debug === true ) { var OCAreport = ' '; mw.notify( $( OCAreport ), { title: 'OneClickArchiver report!', tag: 'OCA', autoHide: false } ); } var OCAerror = ' The following errors detected: '; if ( errorLog.counter ) { OCAerror += '&bull; Unable to find |counter= Default value: 1 '; } if ( errorLog.archiveName && errorLog.archiveName.search( 'defaulted to' ) !== -1 ) { OCAerror += '&bull; Unable to find |archive= Default value: ' + archiveName + ' '; } if ( errorLog.archiveName && errorLog.archiveName.search( 'mismatch' ) !== -1 ) { OCAerror += '&bull; Archive name mismatch detected. '; } if ( errorLog.headerLevel ) { OCAerror += ' Unable to find |headerlevel= Default value: 2 '; } if ( errorLog.archiveHeader ) { OCAerror += ' Unable to find |archiveheader= Default value: "" '; } if ( errorLog.maxArchiveSize ) { OCAerror += ' Unable to find |maxarchivesize= Default value: 153600 '; } if ( errorLog.counter || errorLog.archiveName ) { OCAerror += ' &bull; Causing the script to abort. '; } OCAerror += ' Please, see the documentation for details. '; var archiverReport = mw.util.addPortletLink( 'p-cactions', '#archiverNoLink', '|Archive', 'pt-OCA-report', 'Report for why there are no |Archive links on this page', null, null ); $( archiverReport ).click( function ( e ) { e.preventDefault; mw.notify( $( OCAerror ), { title: 'OneClickArchiver errors!', tag: 'OCAerr', autoHide: false } ); } ); if ( config.wgNamespaceNumber === 3 && ( errorLog.counter || errorLog.archiveName ) && config.debug === true && errorLog.archiveme ) { if ( confirm( 'Click [OK] to post to the top of the page and abort or\n\t[Cancel] to attempt running with default values.' ) === true ) { new mw.Api.postWithToken( 'edit', { action: 'edit', section: 0, pageid: pageid, text: '\n' + content0, summary: ' posted with OneClickArchiver.' } ).done( function { alert( 'Request for user to set up archiving posted.' ); location.reload; } ); } } else if ( config.wgNamespaceNumber === 3 && archivemeOcto >= archivemeSafe ) { /* Archive me request was made, give the user a chance to comply */ } else if ( config.wgNamespaceNumber === 3 && ( errorLog.counter || errorLog.archiveName ) && config.debug === true && confirm( ' found on the top of the page:\n\n\t Click [OK] abort or\n\t[Cancel] to attempt running with default values.' ) === true ) { /* User aborted script */ } else { $( 'h' + headerLevel + ' span.mw-headline' ).each( function { var sectionName = $( this ).text; var editSectionUrl = $( this ).parent.find('.mw-editsection a').not('.mw-editsection-visualeditor').first.attr( 'href' ); var sectionReg = /&section=(.*)/; var sectionRaw = sectionReg.exec( editSectionUrl ); if ( sectionRaw != null && sectionRaw[ 1 ].indexOf( 'T' ) < 0 ) { var sectionNumber = parseInt( sectionRaw[ 1 ] ); if ( $( this ).parent.prop( 'tagName' ) === 'H' + headerLevel ) { $( this ).parent( 'h' + headerLevel ).append( ' | ' + 'Archive' + ' ' ); $(this).parent('h' + headerLevel).find('a.archiverLink').attr('title', 'Archive to: "'+archiveName+'"'); $( this ).parent( 'h' + headerLevel ).find( 'a.archiverLink' ).click( function { var mHeaders = ' Retrieving headers... '; var mSection = 'retrieving section content...'; var mPosting = ' Content retrieved, performing edits...'; var mPosted = ' Archive appended... '; var mCleared = ' Section cleared... '; var mReloading = ' All done! Reloading...'; $( 'body' ).append( '<div class="overlay" style="background-color: #000000; opacity: 0.4; position: fixed; top: 0px; left: 0px; width: 100%; height: 100%; z-index: 500;"> ' ); $( 'body' ).prepend( '<div class="arcProg" style="font-weight: bold; box-shadow: 7px 7px 5px #000000; font-size: 0.9em; line-height: 1.5em; z-index: 501; opacity: 1; position: fixed; width: 50%; left: 25%; top: 30%; background: #F7F7F7; border: #222222 ridge 1px; padding: 20px;"> ' ); $( '.arcProg' ).append( ' ' + mHeaders + ' ' ); $( '.arcProg' ).append( ' ' + 'Archive name ' + archiveName + ' found, ' + mSection + ' (' + archivePageSize + 'b) ' ); new mw.Api.get( { action: 'query', pageids: pageid, rvsection: sectionNumber, prop: [ 'revisions', 'info' ], rvprop: 'content', indexpageids: 1, rawcontinue: '' } ).done( function ( responseSection ) { var sectionContent = responseSection.query.pages[ pageid ].revisions[ 0 ][ '*' ]; $( '.arcProg' ).append( ' ' + mPosting + ' ' ); var dnau = sectionContent.match( // ); var dnauDate; if ( dnau === null || dnau === undefined ) { dnauDate = Date.now; dnau = null; } else { dnau = dnau[ 1 ] + ':' + dnau[ 2 ] + ' ' + dnau[ 3 ] + ' ' + dnau[ 4 ] + ' ' + dnau[ 5 ]; dnauDate = new Date( dnau ); dnauDate = dnauDate.valueOf; } if ( dnauDate > Date.now ) { $( '.arcProg' ).remove; $( '.overlay' ).remove; var dnauAbortMsg = ' This section has been marked \"Do Not Archive Until\" ' + dnau + ', so archiving was aborted.

Please, see <a href="/wiki/User:Technical_13/Scripts/OneClickArchiver" title="User:Technical 13/Scripts/OneClickArchiver">the documentation</a> for details. '; mw.notify( $( dnauAbortMsg ), { title: 'OneClickArchiver aborted!', tag: 'OCAdnau', autoHide: false } ); } else { var archiveAction = 'adding section'; if ( archivePageSize <= 0 || ( archivePageSize >= maxArchiveSize && !errorLog.maxArchiveSize ) ) { sectionContent = archiveHeader + '\n\n' + sectionContent; archiveAction = 'creating'; mPosted = ' Archive created... '; } else { sectionContent = '\n\n

\n' + sectionContent; } if ( dnau != null ) { sectionContent = sectionContent.replace( //g, '' ); } new mw.Api.postWithToken( 'edit', { action: 'edit', title: archiveName, appendtext: sectionContent, summary: '/* '+sectionName+' */ archived using OneClickArchiver)' } ).done( function { $( '.arcProg' ).append( ' ' + mPosted + ' ' ); new mw.Api.postWithToken( 'edit', { action: 'edit', section: sectionNumber, pageid: pageid, text: , summary: 'OneClickArchived "' + sectionName + '" to ' + archiveName +  } ).done( function { $( '.arcProg' ).append( ' ' + mCleared + ' ' ); if ( archivePageSize >= maxArchiveSize && !errorLog.maxArchiveSize ) { var mUpdated = ' Counter updated... '; new mw.Api.postWithToken( 'edit', { action: 'edit', section: 0, pageid: pageid, text: content0.replace( oldCounter, newCounter ), summary: 'OneClickArchiver updating counter.' } ).done( function { $( '.arcProg' ).append( ' ' + mUpdated + ' ' ); $( '.arcProg' ).append( ' ' + mReloading + ' ' ); location.reload; } ); } else { $( '.arcProg' ).append( ' ' + mReloading + ' ' ); location.reload; } } ); } ); } } ); } ); } } } ); } } ); } ); var linkTextD = '1CA is on', linkDescD = 'Disable OneClickArchiver'; var linkTextE = '1CA is off', linkDescE = 'Enable OneClickArchiver'; var linkText = linkTextD, linkDesc = linkDescD; if ( OCAstate === 'false' ) { linkText = linkTextE; linkDesc = linkDescE; $( 'div.archiverDiv, li#pt-OCA-report' ).css( 'display', 'none' ); } var archiverToggle = mw.util.addPortletLink( 'p-cactions', '#archiverLink', linkText, 'pt-OCA', linkDesc, 'o', null ); $( archiverToggle ).click( function ( e ) { e.preventDefault; /* Toggle the archiveLinks */ $( 'div.archiverDiv' ).css( 'display', function ( _i, val ) { return val === 'none' ? '' : 'none'; }); /* Toggle the toggle link */ $( 'li#pt-OCA a' ).html( function ( _i, val ) { return val === linkTextD ? linkTextE : linkTextD; }); /* Toggle the toggle description */ $( 'li#pt-OCA a' ).attr( 'title', function ( _i, val ) { return val === linkDescD ? linkDescE : linkDescD; }); /* Toggle the error report link */ if ( ( errorLog.counter || errorLog.archiveName ) ) { $( 'li#pt-OCA-report' ).css( 'display', function ( _i, val ) { return val === 'none' ? '' : 'none'; }); } /* Toggle default state */ new mw.Api.postWithToken( 'options', { action: 'options', optionname: 'userjs-OCA-enabled', optionvalue: OCAstate === 'true' ? 'false' : 'true' } ).done( function { var resultMsg = 'OneClickArchiver is now ' + ( OCAstate === 'true' ? 'disabled' : 'enabled' ) + ' by default.'; mw.notify(resultMsg); OCAstate = OCAstate === 'true' ? 'false' : 'true'; } ); } ); } } ); }); //

/* Evad37/MoveToDraft.js */ /*************************************************************************************************** MoveToDraft - Version 2.5.0 - A script to move unsourced articles to draft space, including cleanup and author notification. - Moves page to draftspace - Checks if any files used are non-free - Checks if any redirects pointed to the page - Comments out non-free files, turn categories into links, add afc draft template, add redirects - Adds notification message on author talk page - Updates talk page banners - Logs draftification in user subpage /* jshint laxbreak: true, undef: true, maxerr:999 */ /* globals console, window, document, $, mw, OO, extraJs */ // $.when( // Resource loader modules mw.loader.using(['mediawiki.util', 'mediawiki.api', 'mediawiki.Title', 'ext.gadget.libExtraUtil']), // Page ready $.ready ).then(function { /* ========== Config ============================================================================ */ var config = { // Script info script: { advert: ' (via script)', // For edit summaries version: '2.5.1' }, // MediaWiki configuration values mw: mw.config.get( [ 'wgArticleId', 'wgCurRevisionId', 'wgPageName', 'wgUserGroups', 'wgUserName', 'wgMonthNames', 'wgNamespaceNumber', 'wgTitle' ] ) }; /* ========== API =============================================================================== */ var API = new mw.Api( { ajax: { headers: { 'Api-User-Agent': 'MoveToDraft/' + config.script.version + ' ( https://en.wikipedia.org/wiki/User:Evad37/MoveToDraft )' } } } ); var moveToDraft = function moveToDraft { /* ========== Additional config ================================================================= */ // Wikitext strings config.wikitext = { 'rationale':window.m2d_rationale || 'Not ready for mainspace, incubate in draftspace', 'editsummary':window.m2d_editsummary || window.m2d_rationale || 'AFC draft', 'notification_heading': '$1 moved to draftspace', 'notification':window.m2d_notification || "An article you recently created, $1, is not suitable as written to remain published. It needs more citations from reliable, independent sources. (?) Information that can't be referenced should be removed (verifiability is of central importance on Wikipedia). I've moved your draft to draftspace (with a prefix of \" \" before the article title) where you can incubate the article with minimal disruption. When you feel the article meets Wikipedia's general notability guideline and thus is ready for mainspace, please click on the \"Submit your draft for review!\" button at the top of the page. ~", 'logMsg':'#$1 moved to $2 at ' }; config.doNotLog = window.m2d_doNotLog ? true : false; // Page data -- to be retreived later from api config.pagedata = {}; // Helper functions // - prettify an encoded page title (or at least replace underscores with spaces) var getPageText = function(p) { var t = mw.Title.newFromText( decodeURIComponent(p) ); if (t) { return t.getPrefixedText; } else { return p.replace(/_/g, " "); } };

/* ========== Tasks ============================================================================= */ // Grab page data - initial author, current wikitext, any redirects, if Draft: page already exists var grabPageData = function { var patt_isRedirect = /^\s*#redirect/i; var checkedPageTriageStatus = false; // Function to check if all done var checkPageData = function { if ( config.pagedata.author != null && config.pagedata.oldwikitext != null && config.pagedata.redirects != null && checkedPageTriageStatus ) { //all done - go to next screen screen1; } }; /* -- Initial author */ /* Try making an api call for just the first revision - but if that is a redirect, then get 'max' number of revisions, and look for first non-redirect revision - use this as the initial author, not the creator of the redirect. var processMaxRvAuthorQuery = function (result) { var revisions = result.query.pages[config.mw.wgArticleId].revisions; for ( var i=1; i<revisions.length; i++ ) { if ( !patt_isRedirect.test(revisions[i]['*']) ) { config.pagedata.author = revisions[i].user; break; } } //Check that we actually found an author (i.e. not all revisions were redirects if ( config.pagedata.author == null ) { API.abort; var retry = confirm("Could not retrieve page author:\n"+extraJs.makeErrorMsg(c, r)+"\n\nTry again?"); if ( retry ) { screen0; } else { $("#M2D-modal").remove; } } checkPageData; }; var processAuthorQuery = function (result) { // Check if page is currently a redirect if ( result.query.pages[config.mw.wgArticleId].redirect ) { API.abort; alert("Error: " + config.mw.wgPageName + " is a redirect"); return; } // Check if first revision is a redirect rvwikitext = result.query.pages[config.mw.wgArticleId].revisions[0]['*']; if ( patt_isRedirect.test(rvwikitext) ) { // query to look for first non-redirect revision API.get( { action: 'query', pageids: config.mw.wgArticleId, prop: 'revisions', rvprop: ['user', 'content'], rvlimit: 'max', rvdir: 'newer' } ) .done( processMaxRvAuthorQuery ) .fail( function(c,r) { if ( r.textStatus === 'abort' ) { return; } API.abort; var retry = confirm("Could not retrieve page author:\n"+extraJs.makeErrorMsg(c, r)+"\n\nTry again?"); if ( retry ) { screen0; } else { $("#M2D-modal").remove; } } ); return; } config.pagedata.author = result.query.pages[config.mw.wgArticleId].revisions[0].user; checkPageData; }; //Get author API.get( { action: 'query', pageids: config.mw.wgArticleId, prop: ['revisions', 'info'], rvprop: ['user', 'content'], rvlimit: 1, rvdir: 'newer' } ) .done( processAuthorQuery ) .fail( function(c,r) { if ( r.textStatus === 'abort' ) { return; } API.abort; var retry = confirm("Could not retrieve page author:\n"+extraJs.makeErrorMsg(c, r)+"\n\nTry again?"); if ( retry ) { screen0; } else { $("#M2D-modal").remove; } } ); /* -- Current wikitext -- */ API.get( { action: 'query', pageids: config.mw.wgArticleId, prop: 'revisions', rvprop: 'content' } ) .done( function(result) { config.pagedata.oldwikitext = result.query.pages[config.mw.wgArticleId].revisions[0]['*']; checkPageData; } ) .fail( function(c,r) { if ( r.textStatus === 'abort' ) { return; } API.abort; var retry = confirm("Could not retrieve page wikitext:\n"+ extraJs.makeErrorMsg(c, r)+"\n\nTry again?"); if ( retry ) { screen0; } else { $("#M2D-modal").remove; } } ); //TODO(?): also get proposed Draft: page (to check if it is empty or not) /* -- Redirects - */ var redirectTitles = []; var processRedirectsQuery = function(result) { if ( !result.query || !result.query.pages ) { // No results config.pagedata.redirects = false; checkPageData; return; } // Gather redirect titles into array $.each(result.query.pages, function(_id, info) { redirectTitles.push(info.title); }); // Continue query if needed if ( result.continue ) { doRedirectsQuery($.extend(redirectsQuery, result.continue)); return; } // Check if redirects were found if ( redirectTitles.length === 0 ) { config.pagedata.redirects = false; checkPageData; return; } // Set redirects config.pagedata.redirects = ( redirectTitles.length === 0 ) ? false : redirectTitles; checkPageData; }; var redirectsQuery = { action: 'query', pageids: config.mw.wgArticleId, generator: 'redirects', grdlimit: 500 }; var doRedirectsQuery = function(q) { API.get( q ) .done( processRedirectsQuery ) .fail( function(c,r) { if ( r.textStatus === 'abort' ) { return; } API.abort; var retry = confirm("Could not retrieve redirects:\n" + extraJs.makeErrorMsg(c, r) + "\n\nTry again? (or Cancel to skip)"); if ( retry ) { screen0; } else { config.pagedata.redirects = false; checkPageData; } } ); }; doRedirectsQuery(redirectsQuery); /* -- Review (Page Triage) status - */ API.get( { action: 'pagetriagelist', page_id: config.mw.wgArticleId } ) .done( function(result) { if ( !result.pagetriagelist.pages.length ) { var keepGoing = confirm('WARNING: Page has already been reviewed by a New Page Patroller. Are you sure you want to draftify this page?'); if ( !keepGoing ) { API.abort; $("#M2D-modal").remove; return; } } checkedPageTriageStatus = true; checkPageData; } ) .fail( function(c,r) { if ( r.textStatus === 'abort' ) { return; } API.abort; var retry = confirm("Could not retrieve page triage status:\n"+ extraJs.makeErrorMsg(c, r)+"\n\nTry again?"); if ( retry ) { screen0; } else { $("#M2D-modal").remove; } } ); }; //Move page var movePage = function { $("#M2D-task0").css({"color":"#00F", "font-weight":"bold"}); $("#M2D-status0").html("..."); // First check the page hasn't been draftified in the meantime API.get({ action: "query", pageids: config.mw.wgArticleId, format: "json", formatversion: "2" }).then(function(response) { var page = response && response.query && response.query.pages && response.query.pages[0]; if (!page) { return $.Deferred.reject; } else if (page.missing) { return $.Deferred.reject("moveToDraft-pagemissing"); } else if (page.ns === 118 /* Draft NS */) { return $.Deferred.reject("moveToDraft-alreadydraft"); } else if (page.ns !== config.mw.wgNamespaceNumber) { return $.Deferred.reject("moveToDraft-movednamespace"); } return API.postWithToken( 'csrf', { action: 'move', fromid: config.mw.wgArticleId, to: config.inputdata.newTitle, movetalk: 1, noredirect: 1, reason: config.inputdata.rationale + config.script.advert } ); }) .done( function { if ( -1 === $.inArray('sysop', config.mw.wgUserGroups) && -1 === $.inArray('extendedmover', config.mw.wgUserGroups) ) { // Newly created redirect to be tagged for speedy deletion tagRedrect; return; } $("#M2D-task0").css({"color":"#000", "font-weight":""}); $("#M2D-status0").html("Done!"); getImageInfo; } ) .fail( function(c,r) { if ( r && r.textStatus === 'abort' ) { return; } else if (c === "moveToDraft-pagemissing") { alert("The page no longer appears to exists. It may have been deleted."); $("#M2D-modal").remove; window.location.reload; return; } else if (c === "moveToDraft-alreadydraft") { alert("Aborted: The page has already been moved to draftspace."); $("#M2D-modal").remove; window.location.reload; return; } else if (c === "moveToDraft-alreadydraft") { alert("Aborted: The page has already been moved out of mainspace."); $("#M2D-modal").remove; window.location.reload; return; } var retry = confirm("Could not move page:\n"+ extraJs.makeErrorMsg(c, r)+"\n\nTry again?"); if ( retry ) { movePage; } else { screen1(true); } } ); }; var tagRedrect = function { $("#M2D-status0").html("Done, Tagging redirect for speedy deletion..."); API.postWithToken( 'csrf', { action: 'edit', title: config.mw.wgPageName, prependtext: '\n', summary: 'R2 speedy deletion request (article moved to draftspace)' + config.script.advert } ) .done( function { $("#M2D-task0").css({"color":"#000", "font-weight":""}); $("#M2D-status0").append(" Done!"); getImageInfo; } ) .fail( function(c,r) { if ( r.textStatus === 'abort' ) { return; } var retry = confirm("Could not tag redirect for speedy deletion:\n"+ extraJs.makeErrorMsg(c, r) + "\n\nTry again?"); if ( retry ) { tagRedrect; } else { $("#M2D-task0").css({"color":"#F00", "font-weight":""}); $("#M2D-status0").append(" Skipped"); getImageInfo; } } ); }; //Find which images are non-free var getImageInfo = function { $("#M2D-task1").css({"color":"#00F", "font-weight":"bold"}); $("#M2D-status1").html("..."); processImageInfo = function(result) { var nonfreefiles = []; if ( result && result.query ) { $.each(result.query.pages, function(id, page) { if ( id > 0 && page.categories ) { nonfreefiles.push(page.title); } }); } editWikitext(nonfreefiles); }; API.get( { action: 'query', pageids: config.mw.wgArticleId, generator: 'images', gimlimit: 'max', prop: 'categories', cllimit: 'max', clcategories: 'Category:All non-free media', } ) .done( function(result){ $("#M2D-task1").css({"color":"#000", "font-weight":""}); $("#M2D-status1").html("Done!"); processImageInfo(result); } ) .fail( function(c,r) { if ( r.textStatus === 'abort' ) { return; } var retry = confirm("Could not find if there are non-free files:\n"+ extraJs.makeErrorMsg(c, r)+"\n\n[Okay] to try again, or [Cancel] to skip"); if ( retry ) { getImageInfo; } else { $("#M2D-task1").css({"color":"#F00", "font-weight":""}); $("#M2D-status1").html("Skipped"); editWikitext([]); } } ); };

//Comment out non-free files, turn categories into links, add afc draft template, list any redirects var editWikitext = function(nonfreefiles) { $("#M2D-task2").css({"color":"#00F", "font-weight":"bold"}); $("#M2D-status2").html("..."); var redirectsList = ( !config.pagedata.redirects ) ? '' : '\n'+ '\n'; var wikitext = "" + config.inputdata.authorName + "\n" + redirectsList + config.pagedata.oldwikitext.replace(/\[\[\s*[Cc]ategory\s*:/g, "Category:") + "\n"; // non-free files // (derived from [[WP:XFDC - https://en.wikipedia.org/wiki/User:Evad37/XFDcloser.js ) if ( nonfreefiles.length > 0 ) { // Start building regex strings normal_regex_str = "("; gallery_regex_str = "("; free_regex_str = "("; for ( var i=0; i<nonfreefiles.length; i++ ) { // Take off namespace prefix filename = nonfreefiles[i].replace(/^.*?:/, ""); // For regex matching: first character can be either upper or lower case, special // characters need to be escaped, spaces can be either spaces or underscores filename_regex_str = "[" + mw.util.escapeRegExp(filename.slice(0, 1).toUpperCase) + mw.util.escapeRegExp(filename.slice(0, 1).toLowerCase) + "]" + mw.util.escapeRegExp(filename.slice(1)).replace(/ /g, "[ _]"); // Add to regex strings normal_regex_str += "\\[\\[\\s*(?:[Ii]mage|[Ff]ile)\\s*:\\s*" + filename_regex_str + "\\s*\\|?.*?(?:(?:\\[\\[.*?\\]\\]).*?)*\\]\\]"; gallery_regex_str += "^\\s*(?:[Ii]mage|[Ff]ile):\\s*" + filename_regex_str + ".*?$"; free_regex_str += "\\|\\s*(?:[\\w\\s]+\\=)?\\s*(?:(?:[Ii]mage|[Ff]ile):\\s*)?" + filename_regex_str; if ( i+1 === nonfreefiles.length ) { normal_regex_str += ")(?![^<]*?-->)"; gallery_regex_str += ")(?![^<]*?-->)"; free_regex_str += ")(?![^<]*?-->)"; } else { normal_regex_str += "|"; gallery_regex_str += "|"; free_regex_str += "|"; } } // Check for normal file usage, i.e. Foobar.png var normal_regex = new RegExp( normal_regex_str, "g"); wikitext = wikitext.replace(normal_regex, ""); // Check for gallery usage, i.e. instances that must start on a new line, eventually // preceded with some space, and must include File: or Image: prefix var gallery_regex = new RegExp( gallery_regex_str, "mg" ); wikitext = wikitext.replace(gallery_regex, ""); // Check for free usages, for example as template argument, might have the File: or Image: // prefix excluded, but must be preceeded by an | var free_regex = new RegExp( free_regex_str, "mg" ); wikitext = wikitext.replace(free_regex, ""); } API.postWithToken( 'csrf', { action: 'edit', pageid: config.mw.wgArticleId, text: wikitext, summary: config.wikitext.editsummary + config.script.advert } ) .done( function{ $("#M2D-task2").css({"color":"#000", "font-weight":""}); $("#M2D-status2").html("Done!"); notifyAuthor; } ) .fail( function(c,r) { if ( r.textStatus === 'abort' ) { return; } var retry = confirm("Could not edit draft artice:\n"+ extraJs.makeErrorMsg(c, r)+"\n\n[Okay] to try again, or [Cancel] to skip"); if ( retry ) { editWikitext(nonfreefiles); } else { $("#M2D-task2").css({"color":"#F00", "font-weight":""}); $("#M2D-status2").html("Skipped"); notifyAuthor; } } ); }; var notifyAuthor = function { if ( !config.inputdata.notifyEnable ) { updateTalk; return; } $("#M2D-task3").css({"color":"#00F", "font-weight":"bold"}); $("#M2D-status3").html("..."); API.postWithToken( 'csrf', { action: 'edit', title: 'User talk:' + config.inputdata.authorName, section: 'new', sectiontitle: config.inputdata.notifyMsgHead, text: config.inputdata.notifyMsg, } ) .done( function{ $("#M2D-task3").css({"color":"#000", "font-weight":""}); $("#M2D-status3").html("Done!"); updateTalk; } ) .fail( function(c,r) { if ( r.textStatus === 'abort' ) { return; } var retry = confirm("Could not edit author talk page:\n"+ extraJs.makeErrorMsg(c, r)+"\n\n[Okay] to try again, or [Cancel] to skip"); if ( retry ) { notifyAuthor; } else { $("#M2D-task3").css({"color":"#F00", "font-weight":""}); $("#M2D-status3").html("Skipped"); updateTalk; } } ); }; var updateTalk = function { $("#M2D-task4").css({"color":"#00F", "font-weight":"bold"}); $("#M2D-status4").html("..."); //if page exists, do a regex search/repace for class/importances parameters var processTalkWikitext = function(result) { var talk_id = result.query.pageids[0]; if ( talk_id < 0 ) { $("#M2D-task4").css({"color":"#000", "font-weight":""}); $("#M2D-status4").html("Done (talk page does not exist)"); draftifyLog; return; } var old_talk_wikitext = result.query.pages[talk_id].revisions[0]['*']; var new_talk_wikitext = old_talk_wikitext.replace(/(\|\s*(?:class|importance)\s*=\s*)[^\|}]*(?=[^}]*}})/g, "$1"); if ( new_talk_wikitext === old_talk_wikitext ) { $("#M2D-task4").css({"color":"#000", "font-weight":""}); $("#M2D-status4").html("Done (no changes needed)"); draftifyLog; return; } API.postWithToken( 'csrf', { action: 'edit', pageid: talk_id, section: '0', text: new_talk_wikitext, summary: 'Remove class/importance from project banners' + config.script.advert } ) .done( function{ $("#M2D-task4").css({"color":"#000", "font-weight":""}); $("#M2D-status4").html("Done!"); draftifyLog; } ) .fail( function(c,r) { if ( r.textStatus === 'abort' ) { return; } var retry = confirm("Could not edit draft's talk page:\n"+ extraJs.makeErrorMsg(c, r)+"\n\n[Okay] to try again, or [Cancel] to skip"); if ( retry ) { updateTalk; } else { $("#M2D-task4").css({"color":"#F00", "font-weight":""}); $("#M2D-status4").html("Skipped"); draftifyLog; } } ); }; //get talk page wikitext (section 0) API.get( { action: 'query', titles: config.inputdata.newTitle.replace("Draft:", "Draft talk:"), prop: 'revisions', rvprop: 'content', rvsection: '0', indexpageids: 1 } ) .done( processTalkWikitext ) .fail( function(c,r) { if ( r.textStatus === 'abort' ) { return; } var retry = confirm("Could not find draft's talk page:\n"+ extraJs.makeErrorMsg(c, r)+"\n\n[Okay] to try again, or [Cancel] to skip"); if ( retry ) { updateTalk; } else { $("#M2D-task4").css({"color":"#F00", "font-weight":""}); $("#M2D-status4").html("Skipped"); draftifyLog; } } ); }; var draftifyLog = function { if (config.doNotLog) { $("#M2D-finished, #M2D-abort").toggle; return; } $("#M2D-task5").css({"color":"#00F", "font-weight":"bold"}); $("#M2D-status5").html("..."); var logpage = 'User:' + config.mw.wgUserName + '/Draftify_log'; var monthNames = config.mw.wgMonthNames.slice(1); var now = new Date; var heading = '== ' + monthNames[now.getUTCMonth] + ' ' + now.getUTCFullYear + ' =='; var headingPatt = RegExp(heading); var processLogWikitext = function(result) { var logpage_wikitext = ; var id = result.query.pageids[0]; if ( id < 0 ) { var createlog = confirm('Log draftification (at ' +  logpage + ') ?'); if ( !createlog ) { $("#M2D-task5").css({"color":"#F00", "font-weight":""}); $("#M2D-status5").empty.append("Skipped"); $("#M2D-finished, #M2D-abort").toggle; return; } logpage_wikitext = 'This is a log of pages moved to draftspace using the MoveToDraft script.'; } else { logpage_wikitext = result.query.pages[id].revisions[0]['*'].trim; } if ( !headingPatt.test(logpage_wikitext) ) { logpage_wikitext += '\n\n' + heading; } logpage_wikitext += '\n' + config.inputdata.logMsg; API.postWithToken( 'csrf', { action: 'edit', title: logpage, text: logpage_wikitext, summary: 'Logging '+config.inputdata.newTitle+ + config.script.advert } ) .done( function{ $("#M2D-task5").css({"color":"#000", "font-weight":""}); $("#M2D-status5").html("Done!"); $("#M2D-finished, #M2D-abort").toggle; } ) .fail( function(c,r) { if ( r.textStatus === 'abort' ) { return; } var retry = confirm("Could not edit log page:\n"+ extraJs.makeErrorMsg(c, r)+"\n\n[Okay] to try again, or [Cancel] to skip"); if ( retry ) { draftifyLog; } else { $("#M2D-task5").css({"color":"#F00", "font-weight":""}); $("#M2D-status5").html("Skipped"); $("#M2D-finished, #M2D-abort").toggle; } } ); }; //get log page wikitext API.get( { action: 'query', titles: logpage, prop: 'revisions', rvprop: 'content', indexpageids: 1 } ) .done( processLogWikitext ) .fail( function(c,r) { if ( r.textStatus === 'abort' ) { return; } var retry = confirm("Could not find log page:\n"+ extraJs.makeErrorMsg(c, r)+"\n\n[Okay] to try again, or [Cancel] to skip"); if ( retry ) { draftifyLog; } else { $("#M2D-task5").css({"color":"#F00", "font-weight":""}); $("#M2D-status5").html("Skipped"); $("#M2D-finished, #M2D-abort").toggle; } } ); }; // --- Interface screens --- //0) Initial screen var screen0 = function { $("#M2D-interface-header, #M2D-interface-content, #M2D-interface-footer").empty; $("#M2D-interface-header").text("Move To Draft..."); $("#M2D-interface-content").text("Loading..."); grabPageData; }; //1) User inputs /** *  * @param {boolean} restoreValues Restore previously set values */ var screen1 = function(restoreValues) { $("#M2D-interface-header, #M2D-interface-content, #M2D-interface-footer").empty; $("#M2D-interface-header").text("Move To Draft: options"); $("#M2D-interface-content").append( $(' ').css('margin-bottom','0.5em').append( $(' ') .css({ display: 'block', color: 'darkred' }).append( 'Please ensure draftifying is appropriate per ', extraJs.makeLink("WP:DRAFTIFY") ), $(' ').attr('for','M2D-option-newtitle').append( 'Move to ', $('<b>').text('Draft:') ), $(' ').attr({'type':'text', 'name':'M2D-option-newtitle', 'id':'M2D-option-newtitle'}) ), $(' ').css('margin-bottom','0.5em').append( $(' ').attr({'for':'M2D-option-movelog', 'id':'M2D-option-movelog-label'}) .css('display','block').text('Reason for move (log summary):'), $(' ').attr({'rows':'1', 'name':'M2D-option-movelog', 'id':'M2D-option-movelog'}) .css('width','99%') ), $(' ').css('margin-bottom','0.5em').append( $(' ').attr({'for':'M2D-option-author', 'id':'M2D-option-author-label'}).text('Author:'), $(' ').attr({'type':'text', 'name':'M2D-option-author', 'id':'M2D-option-author'}) ), $(' ').attr({'for':'M2D-option-message-enable'}).append( $(' ').attr({'type':'checkbox', 'id':'M2D-option-message-enable'}) .prop('checked', true), 'Notify author' ), $(' ').attr({'for':'M2D-option-message-head', 'id':'M2D-option-message-head-label'}) .css({'display':'block', 'margin-top':'0.5em'}).text('Notification heading'), $(' ').attr({'id':'M2D-option-message-head', 'rows':'1'}) .css({'width':'99%', 'margin-bottom':'0.5em'}), $(' ').attr({'for':'M2D-option-message', 'id':'M2D-option-message-label'}) .css('display','block').text('Notification message:'), $(' ').attr({'id':'M2D-option-message', 'rows':'6'}) .css('width','99%'), $(' ').attr({'display': 'block'}).css({'font-style': 'italic', 'color':'darkred'}) .text("Please add to or replace the default notification text so the author understands your specific reasons for draftification!") ); $('#M2D-option-movelog').val(config.wikitext.rationale); $('#M2D-option-newtitle').val(getPageText(config.mw.wgPageName)).change(function { $('#M2D-option-message-head').val( $('#M2D-option-message-head').val.trim .replace(/\[\[Draft\:.*?\|/, "[[Draft:" + $('#M2D-option-newtitle').val.trim + "|") ); $('#M2D-option-message').val( $('#M2D-option-message').val.trim .replace(/\[\[Draft\:.*?\|/, "[[Draft:" + $('#M2D-option-newtitle').val.trim + "|") ); }); $('#M2D-option-author').val(config.pagedata.author); $('#M2D-option-message-enable').change(function { $('#M2D-option-message-head').prop('disabled', !this.checked); $('#M2D-option-message').prop('disabled', !this.checked); }); $('#M2D-option-message-head').val(config.wikitext.notification_heading.replace(/\$1/g, getPageText(config.mw.wgPageName))); $('#M2D-option-message').val(config.wikitext.notification.replace(/\$1/g, getPageText(config.mw.wgPageName))); $("#M2D-interface-footer").append( $(' ').attr('id', 'M2D-next').text('Continue'), $(' ').attr('id', 'M2D-cancel').css('margin-left','3em').text('Cancel') ); $("#M2D-cancel").click(function{ $("#M2D-modal").remove; }); if (restoreValues) { $('#M2D-option-movelog').val(config.inputdata.rationale); $('#M2D-option-newtitle').val(config.inputdata.newTitle); $('#M2D-option-author').val(config.inputdata.authorName); $('#M2D-option-message-enable').prop('checked', config.inputdata.notifyEnable); $('#M2D-option-message-head').val(config.inputdata.notifyMsgHead); $('#M2D-option-message').val(config.inputdata.notifyMsg); }

$("#M2D-next").click(function{ //Gather inputs config.inputdata = { rationale:$('#M2D-option-movelog').val.trim, newTitle: "Draft:" + $('#M2D-option-newtitle').val.trim, authorName: $('#M2D-option-author').val.trim, notifyEnable:$('#M2D-option-message-enable').prop('checked'), notifyMsgHead:$('#M2D-option-message-head').val.trim, notifyMsg:$('#M2D-option-message').val.trim }; config.inputdata.logMsg = config.wikitext.logMsg .replace(/\$1/g, getPageText(config.mw.wgPageName)) .replace(/\$2/g, config.inputdata.newTitle); //Verify inputs var errors=[]; if ( config.inputdata.newTitle.length === 0 ) { errors.push("Invalid draft title"); } if ( config.inputdata.authorName.length === 0 ) { errors.push("Invalid user name"); } if ( config.inputdata.rationale.length === 0 ) { errors.push("Move log reason is empty"); } if ( config.inputdata.notifyEnable ) { if ( config.inputdata.notifyMsgHead.length === 0 ) { errors.push("Notification heading is empty"); } if ( config.inputdata.notifyMsg.length === 0 ) { errors.push("Notification message is empty"); } } if ( errors.length >= 1 ) { alert("Error:\n\n" + errors.join(";\n")); return; } //start process off screen2; }); }; //2) Progress indicators var screen2 = function { $("#M2D-interface-header, #M2D-interface-content, #M2D-interface-footer").empty; $("#M2D-interface-header").text("Move To Draft: In progress..."); $("#M2D-interface-content").append( $('<ul>').attr('id', 'M2D-tasks').css("color", "#888").append( $('<li>').attr('id', 'M2D-task0').append( 'Moving page... ', $(' ').attr('id','M2D-status0').text('waiting') ), $('<li>').attr('id', 'M2D-task1').append( 'Checking images... ', $(' ').attr('id','M2D-status1').text('waiting') ), $('<li>').attr('id', 'M2D-task2').append( 'Editing page wikitext... ', $(' ').attr('id','M2D-status2').text('waiting') ), config.inputdata.notifyEnable ? $('<li>').attr('id', 'M2D-task3').append( 'Notifying author... ', $(' ').attr('id','M2D-status3').text('waiting') )

$('<li>').attr('id', 'M2D-task4').append( 'Updating talk page banners... ', $(' ').attr('id','M2D-status4').text('waiting') ), $('<li>').attr('id', 'M2D-task5').append( 'Logging... ', config.doNotLog ? $(' ').attr('font-size', '90%' ).text('disabled')
 * $(' ').attr('id','M2D-status5').text('waiting')

) ) ); $("#M2D-interface-footer").append( $(' ').attr('id', 'M2D-abort').text('Abort uncompleted tasks'), $(' ').attr('id', 'M2D-finished').hide.append( 'Finished!', $(' ').attr('id', 'M2D-close').text('Close') ) ); $("#M2D-close").click( function{ $("#M2D-modal").remove; window.location.reload; } ); $("M2D-abort").click( function{ API.abort; $("#M2D-modal").remove; window.location.reload; } ); //Start task 0. The rest are done sequentially as each task is completed (or skipped). movePage; }; // --- Add link to 'More' menu (or user-specified portlet) which starts everything --- mw.util.addPortletLink( (window.m2d_portlet||'p-cactions'), '#', 'Move to draft', 'ca-m2d', null, null, "#ca-move"); $('#ca-m2d').on('click', function(e) { e.preventDefault; // Add interface shell $('body').prepend(' '+ ' '+ '<h4 id="M2D-interface-header"> '+ ' '+ ' '+ ' '+ ' '+ ' '+ ' '); // Interface styling $("#M2D-modal").css({ "position": "fixed", "z-index": "1", "left": "0", "top": "0", "width": "100%", "height": "100%", "overflow": "auto", "background-color": "rgba(0,0,0,0.4)" }); $("#M2D-interface").css({ "background-color": "#f0f0f0", "margin": "15% auto", "padding": "2px 20px", "border": "1px solid #888", "width": "80%", "max-width": "60em", "font-size": "90%" }); $("#M2D-interface-content").css("min-height", "7em"); $("#M2D-interface-footor").css("min-height", "3em"); // Initial interface content screen0; });

// End of function moveToDraft }; /* ========== Log draftifications for a user ==================================================== */ function logDraftifications(username, fromDate) { var targetUser = username; if (!targetUser && targetUser!=="") { var pageNameParts = config.mw.wgPageName.split('/'); targetUser = (pageNameParts.length > 1) ? pageNameParts[1] : ''; } $('#mw-content-text').empty; // TODO: Form for setting user var today = new Date.toISOString.slice(0,10); var MoveToDraftEpoch = "2017-05-29"; $('#mw-content-text').append( $(`<form id='draftifyLogForm' style='border: 1px solid #ccc; margin: 1em 0; padding: 0 0.5em;'> <label for="draftifyUsername">User: <input type="text" name="username" id="draftifyUsername" /> <label for="draftifyFromDate">From date (and earlier) <input type="date" id="draftifyFromDate" name="fromDate" value="${fromDate || today}" /> <input type="submit" value="Show" /> `) ); $('#draftifyUsername').val(targetUser); $('#draftifyLogForm').on('submit', function(e) { e.preventDefault; $('#draftifyLog, #draftifyLogWikitext').show; logDraftifications($('#draftifyUsername').val, $('#draftifyFromDate').val); }); $('#mw-content-text').append( $(` <textarea id="draftifyLogWikitext" disabled="disabled" rows="10"> `) ); $('#draftifyLogWikitext').val(`{|class="wikitable" !scope='col'|From !scope='col'|To !scope='col'|Time !scope='col'|User !scope='col'|Reason var query = { action: "query", format: "json", list: "logevents", leprop: "title|timestamp|comment|details|user", letype: "move", lenamespace: "0", lelimit: "500", lestart: (fromDate || today) + "T23:59:59Z" }; if (targetUser) { query.leuser = targetUser; } var continueInfo = {}; function onLoadMoreClick(e) { e.preventDefault; $('#draftifyStatus').empty.text("Loading..."); searchAndShowResults; } function parseLogTable(wikitext) { API.post({ "action": "parse", "format": "json", "text": wikitext, "prop": "text", "contentmodel": "wikitext" }).then(function(response) { $parsedLogTable = $(response.parse.text['*']); $('#draftifyLog tbody').empty.append( $parsedLogTable.find('tr').slice(1) ); }); } function searchAndShowResults { API.get( $.extend({}, query, continueInfo) ) .then(function(response) { // Store continuing info, if any continueInfo = response.continue || {}; // Reset status, add a "Load more" if there are more results $('#draftifyStatus').empty.append( response.continue ? $('<a>').css("cursor", "pointer").text('Load more').click(onLoadMoreClick)
 * }`);
 * null

); // Filter to only MoveToDraft script moves var draftifyEvents = response.query && response.query.logevents && response.query.logevents.filter(function(logevent) { return logevent.params.target_ns === 118; // Moved to Draft namespace }); var noDraftifyEvents = !draftifyEvents || !draftifyEvents.length; switch(true) { case noDraftifyEvents && !response.continue: $('#draftifyStatus').empty.text( $('#draftifyLog tbody tr').length == 0 ? "No results" : "No further results" ); break; case noDraftifyEvents: // Continue with next batch of results, otherwise table will initially have no results but a load more link, // or clicking "Load more" will appear to show "Loading..." but not actually add any results searchAndShowResults; break; case !response.continue: $('#draftifyStatus').empty.text("No further results"); /* falls through */ default: draftifyEvents.forEach(function(logevent) { var fromTitle = logevent.title; var toTitle = logevent.params.target_title; var timeOfMove = new Date(logevent.timestamp).toUTCString.replace("GMT", "(UTC)"); var user = logevent.user; var comment = logevent.comment; var wikitext = $('#draftifyLogWikitext').val.replace("|}", `|- $('#draftifyLogWikitext').val(wikitext); parseLogTable(wikitext); }); } }); } // Run by default, unless page loaded without a /username suffix if (username || username==="") { searchAndShowResults; } else { $('#draftifyLog, #draftifyLogWikitext').hide; } // End of function logDraftifications } /* ========== Setup ============================================================================= */ // Access draftifications using Special:Draftify_log/USER_NAME var isDraftifyLogPage = config.mw.wgPageName.indexOf("Special:Draftify_log") === 0; var isUserPage = config.mw.wgNamespaceNumber === 2 || config.mw.wgNamespaceNumber === 3; if (isDraftifyLogPage) { document.title = "Draftify log - Wikipedia"; $('h1').text("Draftify log"); $('#mw-content-text').empty .text("Loading...") .before( $(' ').append( 'Note: This page only works with the ', $('<a>').attr('href','/wiki/User:Evad37/MoveToDraft').text('MoveToDraft'), ' userscript installed.' ), $(' ') ); logDraftifications; } else if (isUserPage) { var user = config.mw.wgTitle.split('/')[0]; var url = mw.util.getUrl("Special:Draftify_log/" + user); mw.util.addPortletLink( (window.m2d_portlet||'p-cactions'), url, 'Draftify log', 'ca-m2dlog', null, null, "#ca-move"); } // Only operate in article namespace if( config.mw.wgNamespaceNumber !== 0 ) { return; } // Only operate for existing pages if ( config.mw.wgCurRevisionId === 0 ) { return; } moveToDraft;
 * ${fromTitle}
 * ${toTitle}
 * ${timeOfMove}
 * ${user}
 * ${comment}
 * }`);

}); //

/* Lourdes/PageCuration.js */ /* This script adds a "Page Curation" link to the top toolbar that points to Special:NewPagesFeed. It is primarily designed to assist new page reviewers. Page Curation is a feature-rich purpose-built system to review new pages. Be sure to have read and fully understood the instructions at New Pages Patrol. To use the script, add the following line to Special:MyPage/common.js: importScript('User:Lourdes/PageCuration.js'); // Linkback: User:Lourdes/PageCuration.js $.when( mw.loader.using( ['mediawiki.util'] ), $.ready ).done( function { mw.util.addPortletLink( 'p-personal', mw.util.getUrl('Special:NewPagesFeed'), 'Page Curation', 'pt-pagecuration', 'View the new pages feed', null, '#pt-preferences' ); });

/* Novem Linguae/Scripts/CiteHighlighter.js */ // class CiteHighlighter { /** CAREFUL. This is case sensitive. */ deleteAll(haystack, ...strings) { for ( let string of strings ) { for ( let key in haystack ) { haystack[key] = this.deleteFromArray(haystack[key], string); } } } deleteFromArray(haystack, needle) { const index = haystack.indexOf(needle); if (index > -1) { haystack.splice(index, 1); } return haystack; } async getWikicode(title) { if ( ! mw.config.get('wgCurRevisionId') ) return ''; // if page is deleted, return blank var wikicode = ''; title = encodeURIComponent(title); await $.ajax({ url: '/w/api.php?action=parse&page='+title+'&prop=wikitext&formatversion=2&format=json', success: function (result) { wikicode = result['parse']['wikitext']; }, dataType: "json", async: false }); return wikicode; } async getWikitextFromCache(title) { var api = new mw.ForeignApi('https://en.wikipedia.org/w/api.php'); var wikitext = ''; await api.get( { action: 'query', prop: 'revisions', titles: title, rvslots: '*', rvprop: 'content', formatversion: '2', uselang: 'content', // needed for caching smaxage: '86400', // cache for 1 day maxage: '86400' // cache for 1 day } ).done( function ( data ) { wikitext = data.query.pages[0].revisions[0].slots.main.content; } ); return wikitext; } getUnreliableWords { // /(blog|blogspot|caard|\/comment|fandom|forum|preprint|railfan|thread|weebly|wix|wordpress|blockchain|crypto|innovative|podcast|about|newswire|release|announce|acquire)/gm return [ '/comment', 'about-me', 'about-us', '/about/', 'acquire', 'announce', //'blockchain', 'blog', // by far the most common hit 'blogspot', 'businesswire', 'caard', 'contact-us', 'contactus', //'crypto', 'fandom', '/forum/', 'google.com/search', 'innovative', 'newswire', 'podcast', '/post/', 'preprint', 'press-release', 'pressrelease', 'prnews', 'railfan', 'sponsored', 'thread', 'weebly', 'wix', 'wordpress', ]; } async getListOfSourcesAndRatings { let sources = await this.getWikitextFromCache('User:Novem Linguae/Scripts/CiteHighlighter/SourcesJSON.js'); sources = JSON.parse(sources); return sources; } setConfigVariableDefaultsIfNeeded { if ( window.citeHighlighterHighlightEverything === undefined ) { window.citeHighlighterHighlightEverything = false; } if ( window.citeHighlighterLighterColors === undefined ) { window.citeHighlighterLighterColors = false; } if ( window.citeHighlighterAlwaysHighlightSourceLists === undefined ) { window.citeHighlighterAlwaysHighlightSourceLists = false; } } /** * Don't highlight certain pages, for speed and visual appearance reasons. * * On pages with a lot of links (watchlist, WP:FA), highlighting EVERYTHING will double the * load time. e.g. watchlist 5 seconds -> 10 seconds. */ isSlowPage { if ( mw.config.get('wgAction') == 'history' || this.articleTitle == 'Main_Page' || this.articleTitle == 'Wikipedia:Featured_articles' || this.articleTitle == 'Special:Watchlist' ) { return true; } return false; } /** * If page is a source quality list, highlight everything, even if highlightEverything = false; * Goal: easily see if the script is highlighting anything wrong. */ highlightSourceListsMoreAggressively { let highlightEverythingList = [ 'Wikipedia:Reliable_sources/Perennial_sources', 'Wikipedia:New_page_patrol_source_guide', 'Wikipedia:WikiProject_Albums/Sources', 'Wikipedia:WikiProject_Video_games/Sources#Reliable_sources', 'Wikipedia:WikiProject_Anime_and_manga/Online_reliable_sources', 'Wikipedia:WikiProject_Africa/Africa_Sources_List', 'Wikipedia:WikiProject_Dungeons_%26_Dragons/References', ]; if ( window.citeHighlighterAlwaysHighlightSourceLists == true) { if ( highlightEverythingList.includes(this.articleTitle) ) { window.citeHighlighterHighlightEverything = true; } } } /** * If page is a draft, highlight everything, as the # of links is small, and oftentimes * inline citations are malformed */ highlightDraftsMoreAggressively { if ( mw.config.get('wgNamespaceNumber') == 118 ) { window.citeHighlighterHighlightEverything = true; } } /** * If highlightEverything = true, delete wikipedia.org and wiktionary. Too many false positives. */ preventWikipediaFalsePositives { if ( window.citeHighlighterHighlightEverything ) { this.deleteAll(this.sources, 'en.wikipedia.org', 'wikipedia.org', 'wiktionary.org'); this.deleteFromArray(this.unreliableWordsForOrangeHighlighting, 'wiki'); } } getColors { if ( window.citeHighlighterLighterColors ) { return { 'unreliableWord':'#ffb347', 'preprint':'#ffcfd5', 'doi':'transparent', 'medrs': '#63ff70', 'green': '#a6ffb9', 'yellow': '#ffffcc', 'red': '#ffcfd5', }; } else { return { // order of these first 3 fixes an issue where published academic papers were being colored preprint red // lowest priority 'unreliableWord':'#ffb347', // orange for now, for easier testing. later will be red. 'preprint':'lightcoral', 'doi':'transparent', 'medrs': 'limegreen', 'green': 'lightgreen', 'yellow': 'khaki', 'red': 'lightcoral', //'aggregator':'plum',// turning off aggregator for now, red/yellow/green is nice and simple, purple makes the color scheme more complicated // highest priority }; } } writeCSSForEachColor { for ( let key in this.colors ) { mw.util.addCSS('.cite-highlighter-' + key + ' {background-color: ' + this.colors[key] + ';}'); mw.util.addCSS('.rt-tooltipTail.cite-highlighter-' + key + '::after {background: ' + this.colors[key] + ';}'); } } addHTMLClassesToRefs { for ( let color in this.colors ) { if ( typeof this.sources[color] === 'undefined' ) continue; for ( let source of this.sources[color] ) { // This code makes the algorithm more efficient, by not adding CSS for sources that aren't found in the wikicode. // I programmed some exceptions to fix bugs. For example: // - with a pubmed ID generates nih.gov without putting it in the wikicode // - generates twitter.com without putting it in the wikicode if ( this.wikicode.includes(source) || source === 'nih.gov' || source === 'twitter.com' ) { // highlight external links, if it contains a period and no space (i.e. a domain name) if ( source.includes('.') && ! source.includes(' ') ) { // highlight whole cite // [title="source" i]... the "i" part is not working in :has for some reason // use .toLowerCase for now // using .addClass instead of .css or .attr('style') because I'm having issues getting medrs to override arXiv/Wikidata/other red sources $('li[id^="cite_note-"]').has('a[href*="/'+source.toLowerCase+'"]').addClass('cite-highlighter-' + color); $('li[id^="cite_note-"]').has('a[href*=".'+source.toLowerCase+'"]').addClass('cite-highlighter-' + color); // Also support any template in a list. For example, a works cited section supporting a references section consisting of "Smith 1986, pp. 573-574" type citations. Example: https://en.wikipedia.org/wiki/C._J._Cregg#Articles_and_tweets $('li').has('.citation a[href*="/'+source.toLowerCase+'"]').addClass('cite-highlighter-' + color); $('li').has('.citation a[href*=".'+source.toLowerCase+'"]').addClass('cite-highlighter-' + color); if ( window.citeHighlighterHighlightEverything ) { // highlight external link only // !important; needed for highlighting PDF external links. otherwise the HTML that generates the PDF icon has higher specificity, and makes it transparent // [title="source" i]... the "i" means case insensitive. Default is case sensitive. mw.util.addCSS('#bodyContent a[href*="/'+source+'" i] {background-color: '+this.colors[color]+' !important;}'); mw.util.addCSS('#bodyContent a[href*=".'+source+'" i] {background-color: '+this.colors[color]+' !important;}'); } } } } } } /** Observe and highlight popups created by the gadget Reference Tooltips. */ observeAndAddClassesToTooltips { new MutationObserver(function (mutations) { var el = document.getElementsByClassName('rt-tooltip')[0]; if (el) { for (let color in this.colors) { if (typeof sources[color] === 'undefined') continue; for (let source of sources[color]) { if (wikicode.includes(source) || source === 'nih.gov' || source === 'twitter.com') { if (source.includes('.') && !source.includes(' ')) { $(el).has(`a[href*="${source.toLowerCase}"]`).addClass('cite-highlighter-' + color); $(el).has(`a[href*="${source.toLowerCase}"]`).children.first.addClass('cite-highlighter-' + color); } } } } } }).observe(document.body, { subtree: false, childList: true, }); } /** * Be more aggressive with this list of words. Doesn't have to be the domain name. Can be * anywhere in the URL. Example unreliableWord: blog. */ addHTMLClassesForUnreliableWords { for ( let word of this.unreliableWordsForOrangeHighlighting ) { let color = 'unreliableWord'; if ( this.wikicode.includes(word) ) { $('li[id^="cite_note-"]').has('a[href*="'+word.toLowerCase+'"]').addClass('cite-highlighter-' + color); if ( window.citeHighlighterHighlightEverything ) { mw.util.addCSS('#bodyContent a[href*="'+word+'" i] {background-color: '+this.colors[color]+' !important;}'); } } } } async execute { this.sources = await this.getListOfSourcesAndRatings; this.unreliableWordsForOrangeHighlighting = this.getUnreliableWords; this.setConfigVariableDefaultsIfNeeded; this.articleTitle = mw.config.get('wgPageName'); if ( this.isSlowPage ) { return; } this.highlightSourceListsMoreAggressively; this.highlightDraftsMoreAggressively; this.preventWikipediaFalsePositives; this.colors = this.getColors; this.writeCSSForEachColor; this.wikicode = await this.getWikicode(this.articleTitle); this.addHTMLClassesToRefs; this.addHTMLClassesForUnreliableWords; this.observeAndAddClassesToTooltips; } } // TODO: Idea from chlod: use mw.hook("wikipage.content").add( => { rehiglight; } ); instead. will listen for VE finishes saving or the page gets reloaded in any way. Gets called multiple times by accident sometimes though, so need to be careful not to apply duplicate classes to HTML elements. $(async function { // TODO: I don't think I use mediawiki.Title. Remove that, and replace with mediawiki.Api? await mw.loader.using(['mediawiki.util','mediawiki.Uri', 'mediawiki.Title'], async => { let ch = new CiteHighlighter; await ch.execute; }); }); //

/* Novem Linguae/Scripts/VoteCounter.js */ // /* - Gives an approximate count of keeps, deletes, supports, opposes, etc. in deletion discussions and RFCs. - For AFD, MFD, and GAR, displays them at top of page. - For everything else, displays them by the section heading. - Counts are approximate. If people do weird things like Delete/Merge, it will be counted twice. - Adds an extra delete vote to AFDs and MFDs, as it's assumed the nominator is voting delete. - If you run across terms that aren't counted but should be, leave a message on the talk page. Let's add as many relevant terms as we can :) $(async function { async function getWikicode(title) { if ( ! mw.config.get('wgCurRevisionId') ) return ''; // if page is deleted, return blank let api = new mw.Api; let response = await api.get( { "action": "parse", "page": title, "prop": "wikitext", "formatversion": "2", "format": "json" } ); return response['parse']['wikitext']; } /** returns the pagename, including the namespace name, but with spaces replaced by underscores */ function getArticleName { return mw.config.get('wgPageName'); } class VoteCounter { _countRegExMatches(matches) { return (matches || []).length; } _capitalizeFirstLetter(str) { return str.charAt(0).toUpperCase + str.slice(1); } _countVotes { // delete all strikethroughs this.modifiedWikicode = this.modifiedWikicode.replace(/ [^<]*<\/strike>/gmi, ''); this.modifiedWikicode = this.modifiedWikicode.replace(/ [^<]*<\/s>/gmi, ''); this.modifiedWikicode = this.modifiedWikicode.replace(/{{S\|[^}]*}}/gmi, ''); this.modifiedWikicode = this.modifiedWikicode.replace(/{{Strike\|[^}]*}}/gmi, ''); this.modifiedWikicode = this.modifiedWikicode.replace(/{{Strikeout\|[^}]*}}/gmi, ''); this.modifiedWikicode = this.modifiedWikicode.replace(/{{Strikethrough\|[^}]*}}/gmi, ''); this.votes = {}; for ( let voteToCount of this.votesToCount ) { let regex = new RegExp("[^']{0,30}"+voteToCount+"(?!ing comment)[^']{0,30}", "gmi"); // limit to 30 chars to reduce false positives. sometimes you can have bold bunchOfRandomTextIncludingKeep bold, and the in between gets detected as a keep vote let matches = this.modifiedWikicode.match(regex); let count = this._countRegExMatches(matches); if ( ! count ) continue; // only log it if there's votes for it this.votes[voteToCount] = count; } } constructor(wikicode, votesToCount) { this.originalWikicode = wikicode; this.modifiedWikicode = wikicode; this.votesToCount = votesToCount; this.voteSum = 0; this._countVotes; if ( ! this.votes ) return; // if yes or no votes are not present in wikitext, but are present in the votes array, they are likely false positives, delete them from the votes array let yesNoVotesForSurePresent = this.modifiedWikicode.match(/(yes|no)/gi); if ( ! yesNoVotesForSurePresent ) { delete this.votes.yes; delete this.votes.no; } for ( let count of Object.entries(this.votes) ) { this.voteSum += count[1]; } this.voteString = ''; for ( let key in this.votes ) { let humanReadable = key; humanReadable = humanReadable.replace(/\(\?<!.+\)/, ''); // remove regex lookbehind humanReadable = this._capitalizeFirstLetter(humanReadable); this.voteString += this.votes[key] + ' ' + humanReadable + ', '; } this.voteString = this.voteString.slice(0, -2); // trim extra comma at end this.voteString = this._htmlEscape(this.voteString); } _convertWikicodeHeadingToHTMLSectionID(wikicode) { // remove == == from headings wikicode = wikicode.replace(/^=+\s*/, ''); wikicode = wikicode.replace(/\s*=+$/, ''); // remove wikilinks wikicode = wikicode.replace(/\[\[:?/g, ''); wikicode = wikicode.replace(/\]\]/g, ''); // remove bold and italic wikicode = wikicode.replace(/'{2,5}/g, ''); // convert spaces to _ wikicode = wikicode.replace(/ /g, '_'); return wikicode; } _jQueryEscape(str) { return str.replace(/(:|\.|\[|\]|,|=|@)/g, "\\$1"); } _doubleQuoteEscape(str) { return str.replace(/"/g, '\\"'); } getHeadingForJQuery { let firstLine = this.originalWikicode.split('\n')[0]; let htmlHeadingID = this._convertWikicodeHeadingToHTMLSectionID(firstLine); let jQuerySearchString = '[id="' + this._doubleQuoteEscape(htmlHeadingID) + '"]'; return jQuerySearchString; } getVotes { return this.votes; } getVoteSum { return this.voteSum; } /* HTML escaped */ getVoteString { return this.voteString; } _htmlEscape(unsafe){ return unsafe .replace(/&/g, "&amp;") .replace(/</g, "&lt;") .replace(/>/g, "&gt;") .replace(/"/g, "&quot;") .replace(/'/g, "&#039;"); } } votesToCount = [ // AFD 'keep', 'delete', 'merge', 'draftify', 'userfy', 'redirect', 'stubify', 'stubbify', 'TNT', // RFC 'support', 'oppose', 'neutral', // move review 'endorse', 'overturn', 'relist', 'procedural close', // GAR 'delist', // RFC 'option 1', 'option 2', 'option 3', 'option 4', 'option 5', 'option 6', 'option 7', 'option 8', 'option A', 'option B', 'option C', 'option D', 'option E', 'option F', 'option G', 'option H', 'yes', 'no', 'bad rfc', 'remove', 'include', 'exclude', // RSN 'agree', 'disagree', 'status quo', '(?<!un)reliable', 'unreliable', // RFD '(?<!re)move', 'retarget', 'disambiguate', 'withdraw', 'setindex', // MFD 'historical', // mark historical // TFD 'rename', ]; // don't run when not viewing articles let action = mw.config.get('wgAction'); if ( action != 'view' ) return; let title = getArticleName; // only run in talk namespaces (all of them) or Wikipedia namespace let isEnglishWikipedia = mw.config.get('wgDBname') === 'enwiki'; if ( isEnglishWikipedia ) { let namespace = mw.config.get('wgNamespaceNumber'); if ( ! mw.Title.isTalkNamespace(namespace) && namespace !== 4 && title != 'User:Novem_Linguae/sandbox' ) { return; } } // get wikitext let wikicode = await getWikicode(title); if ( ! wikicode ) return; let isAFD = title.match(/^Wikipedia:Articles_for_deletion\//i); let isMFD = title.match(/^Wikipedia:Miscellany_for_deletion\//i); let isGAR = title.match(/^Wikipedia:Good_article_reassessment\//i); if ( isAFD || isMFD || isGAR ) { // delete everything above the first heading, to prevent the closer's vote from being counted wikicode = wikicode.replace(/^.*?(===.*)$/s, '$1'); // add a delete vote. the nominator is assumed to be voting delete if ( isAFD || isMFD ) { wikicode += "delete"; } let vc = new VoteCounter(wikicode, votesToCount); let votes = vc.getVotes; let voteString = vc.getVoteString; if ( ! voteString ) return; let percentsHTML = ''; if ( isAFD || isMFD ) { let counts = {}; for ( let key of votesToCount ) { let value = votes[key]; if ( typeof value === 'undefined' ) { value = 0; } counts[key] = value; } let keep = counts['keep'] + counts['stubify'] + counts['stubbify'] + counts['TNT']; let _delete = counts['delete'] + counts['redirect'] + counts['merge'] + counts['draftify'] + counts['userfy']; let total = keep + _delete; let keepPercent = keep / total; let deletePercent = _delete / total; keepPercent = Math.round(keepPercent * 100); deletePercent = Math.round(deletePercent * 100); percentsHTML = ` ${keepPercent}% <abbr title="Keep, Stubify, TNT">Keep-ish, ${deletePercent}% <abbr title="Delete, Redirect, Merge, Draftify, Userfy">Delete-ish `; } allHTML = ` ${voteString} (approximately) ${percentsHTML} `; $('#contentSub').before(allHTML); } else { // make a list of the strpos of each heading let matches = wikicode.matchAll(/(?<=\n)(?===)/g); let sections = [0]; for ( let match of matches ) { sections.push(match.index); } let isXFD = title.match(/_for_(?:deletion|discussion)\//i); // now that we know where the fenceposts are, calculate everything else, then inject the appropriate HTML let sectionsLength = sections.length; for ( let i = 0; i < sectionsLength ; i++ ) { let startPosition = sections[i]; let lastSection = i === sectionsLength - 1; let endPosition; if ( lastSection ) { endPosition = wikicode.length; } else { endPosition = sections[i + 1]; // Don't subtract 1. That will delete a character. } let sectionWikicode = wikicode.slice(startPosition, endPosition); // slice and substring (which both use (startPos, endPos)) are the same. substr(startPos, length) is deprecated. if ( isXFD ) { let proposeMerging = sectionWikicode.match(/Propose merging/i); // add a vote for the nominator if ( proposeMerging ) { sectionWikicode += "merge"; } else { sectionWikicode += "delete"; } // delete "result of the discussion was X", to prevent it from being counted sectionWikicode = sectionWikicode.replace(/The result of the discussion was(?::)? [^']+/ig, ); } let vc = new VoteCounter(sectionWikicode, votesToCount); let voteSum = vc.getVoteSum; if ( voteSum < 3 ) continue; let voteString = vc.getVoteString; let allHTML = ` ${voteString} (approximately) `; let isLead = startPosition === 0; if ( isLead ) { $('#contentSub').before(allHTML); } else { let headingForJQuery = vc.getHeadingForJQuery(startPosition); if ( ! $(headingForJQuery).length ) { console.warn('User:Novem Linguae/Scripts/VoteCounter.js: ERROR: Heading ID not found. This indicates a bug in _convertWikicodeHeadingToHTMLSectionID that Novem Linguae needs to fix. Please report this on his talk page along with the page name and heading ID. The heading ID is: ' + headingForJQuery) } $(headingForJQuery).parent.first.after(allHTML); // prepend is interior, before is exterior } } } // TODO: write a parser that keeps track of pairs of , to fix issue with vote text vote''' sometimes counting the text between them // TODO: handle CFD big merge lists, e.g. https://en.wikipedia.org/wiki/Wikipedia:Categories_for_discussion/Log/2021_December_10#Category:Cornish_emigrans_and_related_subcats }); //

/* Novem Linguae/Scripts/NPPLinks.js */ // /* var request = require('request').defaults({jar: true}), url = "https://en.wikipedia.org/w/api.php"; function purge(title) { var params = { action: "purge", titles: title, format: "json" }; request.post({ url: url, form: params }, function (error, res, body) { if (error) { return; } console.log(body); }); } function escapeDoubleQuotes(input) { return input.replace(/"/g, '&quot;'); } /** pageName has namespace, undescores, no quotes, parentheses */ function buildURIComponent(wgPageName, wgNamespaceNumber, namespace, underscores, quotes, parentheses) { let output = wgPageName; // The order of all of these is important, because of RegEx patterns. if ( ! namespace && wgNamespaceNumber != 0 ) output = output.replace(/^.+?:/, ''); if ( ! parentheses ) { let matches = output.match(/^(.*)_\((.+?)\)$/); if ( typeof matches !== 'undefined' && matches && matches[1] ) { output = matches[1]; } } if ( quotes ) { // If there's parentheses on the right, put the parentheses on the outside of the quotes, and remove the characters, but not their inner text let matches = output.match(/^(.*)_\((.+?)\)$/); // if parentheses on the right if ( typeof matches !== 'undefined' && matches && matches[2] ) { output = '"' + matches[1] + '"_' + matches[2]; } else { output = '"' + output + '"'; } } if ( ! underscores ) output = output.replace(/_/g, ' '); output = encodeURIComponent(output); return output; } let sameTab = window.NPPLinksSameTab ? '' : 'target="_blank"'; // add NPP, Earwig, WP:BEFORE, CSE, Wikipedia duplicate page links to left menu $(function { // only include most links for action = view and namespace = main, draft let action = mw.config.get('wgAction'); let namespace = mw.config.get('wgNamespaceNumber'); let desiredNamespace = [0, 118].includes(namespace); let lessLinks = false; if ( action != 'view' || ! desiredNamespace ) { lessLinks = true; } let pageName = mw.config.get('wgPageName'); // has underscores instead of spaces. has namespace prefix let isAFD = pageName.startsWith('Wikipedia:Articles_for_deletion/'); if ( isAFD ) { lessLinks = false; pageName = pageName.replace('Wikipedia:Articles_for_deletion/', ''); } // Draft:Andrew_Hill_(pharmacologist) let underscores = buildURIComponent(pageName, namespace, true, true, false, true); // Andrew_Hill_(pharmacologist) let pageNameNoNamespace = buildURIComponent(pageName, namespace, false, true, false, true); // "Andrew_Hill"_pharmacologist let quotedName = buildURIComponent(pageName, namespace, false, true, true, true); // "Andrew_Hill" let quotedNoParentheses = buildURIComponent(pageName, namespace, false, true, true, false); // "Andrew Hill" pharmacologist let quotedNoUnderscores = buildURIComponent(pageName, namespace, false, false, true, true); // Andrew Hill (pharmacologist) let noUnderscores = buildURIComponent(pageName, namespace, false, false, false, true); // "Andrew Hill" let quotedNoUnderscoresNoParentheses = buildURIComponent(pageName, namespace, false, false, true, false); // Andrew Hill let noUnderscoresNoParentheses = buildURIComponent(pageName, namespace, false, false, false, false); /* console.log(underscores); console.log(pageNameNoNamespace); console.log(quotedName); console.log(quotedNoParentheses); console.log(quotedNoUnderscores); console.log(noUnderscores); console.log(quotedNoUnderscoresNoParentheses); console.log(noUnderscoresNoParentheses); let copyvioURL = 'https://tools.wmflabs.org/copyvios/?lang=en&project=wikipedia&title='+underscores; let webSearchURL = 'https://www.google.com/search?q='+quotedNoUnderscores+'+-wikipedia.org'; let bookSearchURL = 'https://www.google.com/search?q='+quotedNoUnderscores+'&tbm=bks'; let newsSearchURL = 'https://www.google.com/search?q='+quotedNoUnderscores+'&tbm=nws'; let newsInTitleSearchURL = 'https://www.google.com/search?q=intitle:'+quotedNoUnderscores+'&tbm=nws'; let oldNewsSearchURL = 'https://www.google.com/search?q='+quotedNoUnderscores+'%20site:news.google.com/newspapers'; let journalSearchURL = 'https://scholar.google.com/scholar?q='+quotedNoUnderscores+''; // let profileSearchURL = 'https://scholar.google.com/citations?hl=en&view_op=search_authors&mauthors='+quotedNoUnderscoresNoParentheses+''; let profileSearchURL = 'https://www.google.com/search?q='+noUnderscoresNoParentheses+'%20%22h-index%22'; let cseSearchURL = 'https://cse.google.com/cse?cx=007734830908295939403:galkqgoksq0&q='+quotedNoUnderscores+''; let wikipediaDuplicateCheckURL = 'https://en.wikipedia.org/w/index.php?search='+noUnderscores+'&title=Special:Search&profile=advanced&fulltext=1&advancedSearch-current=%7B%7D&ns0=1'; let wikidataSearchURL = `https://www.wikidata.org/w/index.php?search=${quotedNoParentheses}&title=Special%3ASearch&go=Go&ns0=1&ns120=1` let messages = ''; // WP:BEFORE wikis in other languages if ( $('#p-lang li').length ) { messages += "<li>WP:BEFORE check foreign wikis</li>\n"; } // WP:BEFORE foreign script search let articleBody = $('#mw-content-text').html; articleBody = articleBody.replace(/(<([^>]+)>)/gi, ''); // remove HTML tags articleBody = articleBody.trim.split('Contents')[0]; // lead only. trim everything after the word "Contents" (table of contents) let ansiOnly = /^[\u0000-\u036f\ua792\u200b\u2009\u2061\u200e–—−▶◀•←†↓√≠≈→⋯’\u0020-\u002F\u0030-\u0039\u003A-\u0040\u0041-\u005A\u005B-\u0060\u0061-\u007A\u007B-\u007E\u00C0-\u00C3\u00C8-\u00CA\u00CC-\u00CD\u00D0\u00D2-\u00D5\u00D9-\u00DA\u00DD\u00E0-\u00E3\u00E8-\u00EA\u00EC-\u00ED\u00F2-\u00F5\u00F9-\u00FA\u00FD\u0102-\u0103\u0110-\u0111\u0128-\u0129\u0168-\u0169\u01A0-\u01B0\u1EA0-\u1EF9\u02C6-\u0323]*$/.test(articleBody); // the ones at the end are vietnamese if ( ! ansiOnly ) { messages += "<li>WP:BEFORE search for foreign name</li>\n"; } /* // debugging/logging code console.log(articleBody); matches = articleBody.match(/[^\u0000-\u036f\ua792\u200b\u2009\u2061\u200e–—−▶◀•←†↓√≠≈→⋯’\u0020-\u002F\u0030-\u0039\u003A-\u0040\u0041-\u005A\u005B-\u0060\u0061-\u007A\u007B-\u007E\u00C0-\u00C3\u00C8-\u00CA\u00CC-\u00CD\u00D0\u00D2-\u00D5\u00D9-\u00DA\u00DD\u00E0-\u00E3\u00E8-\u00EA\u00EC-\u00ED\u00F2-\u00F5\u00F9-\u00FA\u00FD\u0102-\u0103\u0110-\u0111\u0128-\u0129\u0168-\u0169\u01A0-\u01B0\u1EA0-\u1EF9\u02C6-\u0323]/g); if ( matches ) { for ( match of matches ) { console.log(match); console.log(match.charCodeAt(0)); } } let links = ''; if ( ! lessLinks ) { links += ` <li><a href="`+copyvioURL+`" `+sameTab+`>Copyvio check</a></li> <li><a href="`+wikipediaDuplicateCheckURL+`" `+sameTab+`>Duplicate article check</a></li> <li><a href="`+webSearchURL+`" `+sameTab+`>WP:BEFORE web</a></li> <li><a href="`+newsSearchURL+`" `+sameTab+`>WP:BEFORE news</a></li> <li><a href="`+oldNewsSearchURL+`" `+sameTab+`>WP:BEFORE news archive</a></li> <li><a href="`+bookSearchURL+`" `+sameTab+`>WP:BEFORE books</a></li> <li><a href="`+journalSearchURL+`" `+sameTab+`>WP:BEFORE scholar</a></li> `+messages+` <li><a href="`+profileSearchURL+`" `+sameTab+`>h-index</a></li> <li><a href="`+cseSearchURL+`" `+sameTab+`>Google CSE</a></li> <li><a href="`+newsInTitleSearchURL+`" `+sameTab+`>News (name in title)</a></li> <li><a href="`+wikidataSearchURL+`" `+sameTab+`>Wikidata</a></li> `; /* TODO: https://en.wikipedia.org/wiki/MediaWiki:Gadget-purgetab.js <li><a onclick="purge(&quot;`+escapeDoubleQuotes(pageNameNoNamespace)+`&quot;);">Purge (for orphan check)</a></li> <li> Orphan check</a></li> // TODO: display message if orphan // TODO: display message if no categories } $('#p-navigation').after(` <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"> New page patrol <ul class="vector-menu-content-list"> <li><a href="/wiki/Special:NewPagesFeed">New pages feed</a></li> `+links+` </ul> `); }); //

/* Ingenuity/quickNavigate.js */ let lastPressed = -1; window.addEventListener("keydown", event => { if (event.key === "\\") { if (new Date.getTime - lastPressed < 1000) { let input = document.createElement("input", { id: "searchInput" }); document.body.appendChild(input); input.style.border = "none"; input.style.outline = "none"; input.style.background = "none"; input.style.position = "fixed"; input.style.top = "calc(100% - 100px)"; input.style.left = "0px"; input.style.width = "100%"; input.style.textAlign = "center"; input.style.zIndex = "5"; input.style.fontSize = "3em"; input.focus; input.onblur = function { input.remove; } input.onkeydown = e => { if (e.key.toLowerCase === "enter") { location.href = "https://en.wikipedia.org/wiki/" + input.value; } } input.oninput = e => { if (e.data === "\\") { input.value = ""; } } } else { lastPressed = new Date.getTime; } } });

/* Ingenuity/cleaner.js */ const regexes = { findReflist: /( " + "<input type='checkbox' id='cv-rd-cclean' />" + "<label for='cv-rd-cclean'>Add to " +  ( ( mw.config.get( "wgNamespaceNumber" ) % 2 ) ? "this" : "talk" ) +  " page "+ " <button id='cv-rd-submit' class='mw-ui-button"+  " mw-ui-progressive'>Submit " +  "<button id='cv-rd-close' class='mw-ui-button mw-ui-quiet'>Close "+  "  "; document.getElementById( "bodyContent" ).insertBefore( panel, document.getElementById( "mw-content-text" ) ); // Add range-add buttons before each of the buttons // that say "Compare selected revisions" var cmpSelRevsBtns = document.getElementsByClassName( "historysubmit" ); for( var i = 0, n = cmpSelRevsBtns.length; i < n; i++ ) { var rangeBtn = document.createElement( "button" ); rangeBtn.textContent = "Add range to revdel template"; rangeBtn.className = "mw-ui-button + mw-ui-progressive"; rangeBtn.id = "cv-rd-add-range" cmpSelRevsBtns[i].parentNode.insertBefore( rangeBtn, cmpSelRevsBtns[i] ); rangeBtn.addEventListener( "click", function ( evt ) { evt.preventDefault; var oldidStart = document.querySelector( "li.selected.after" ).dataset.mwRevid; var oldidEnd = document.querySelector( "li.selected.before" ).dataset.mwRevid; // Add new row to ranges table var rangesTable = document.getElementById( "cv-rd-ranges" ).getElementsByTagName( "tbody" )[0]; var newRow = rangesTable.insertRow( rangesTable.rows.length ); newRow.insertCell( 0 ).appendChild( makeOldidLink( oldidStart ) ); newRow.insertCell( 1 ).appendChild( makeOldidLink( oldidEnd ) ); newRow.insertCell( 2 ).innerHTML = "<input type='checkbox' />"; newRow.cells[2].childNodes[0].checked = true; newRow.cells[2].childNodes[0].addEventListener( "click", function { this.parentNode.previousElementSibling.childNodes[0].className = this.checked ? "" : "disabled"; } ); var deleteBtn = document.createElement( "button" ); deleteBtn.textContent = "Delete"; deleteBtn.className = "delete"; deleteBtn.addEventListener( "click", function { this.parentNode.parentNode.parentNode.removeChild( this.parentNode.parentNode ); } ); newRow.insertCell( 3 ).appendChild( deleteBtn ); } ); } // Panel submission handler document.getElementById( "cv-rd-submit" ).addEventListener( "click", function { $( this ).prop( "disabled", true ); $( "#cv-rd-status" ).empty; var urls = Array.prototype.map.call( document.getElementById( "cv-rd-urls" ).children, function ( e ) { return e.children[1].value; } ); var deferreds = [ addCvRevdel( urls ) ]; if( document.getElementById( "cv-rd-cclean" ).checked ) { deferreds.push( addCclean( urls ) ); } $.when.apply( $, deferreds ).then( function { // Return to content page document.querySelector( "#ca-view a" ).click; }, function { $( this ).prop( "disabled", false ); }.bind( this ) ); } ); // "Add URL" handler document.getElementById( "cv-rd-url-add" ).addEventListener( "click", function { var numUrls = document.querySelectorAll( "#cv-rd-urls div" ).length; if( numUrls < 3 ) { var newDiv = document.createElement( "div" ); newDiv.innerHTML = "<label for='cv-rd-url" + urlCounter + "'>URL: "+ "<input type='text' id='cv-rd-url" + urlCounter + "' class='mw-ui-input mw-ui-input-inline'/>"+ "<button class='mw-ui-button mw-ui-quiet'>Remove "; document.getElementById( "cv-rd-urls" ).appendChild( newDiv ); urlCounter++; this.disabled = numUrls >= 2; } } ); // Remove URL handler document.getElementById( "cv-rd-urls" ).addEventListener( "click", function ( e ) { var numUrls = document.querySelectorAll( "#cv-rd-urls div" ).length; if( e.target && e.target.nodeType === 1 && numUrls > 1 && e.target.tagName.toLowerCase === "button" ) { this.removeChild( document.getElementById( e.target.previousElementSibling.getAttribute( "id" ) ).parentNode ); document.getElementById( "cv-rd-url-add" ).disabled = false; } } ); // Close handler document.getElementById( "cv-rd-close" ).addEventListener( "click", function { $( "#cv-revdel" ).remove; $( ".cv-rd-add-range" ).remove; } ); document.querySelector( "#cv-rd-urls input" ).focus; } mw.loader.using( [ "mediawiki.api", "mediawiki.util" ], function { importStylesheet( "User:Enterprisey/mw-ui-button.css" ); importStylesheet( "User:Enterprisey/mw-ui-input.css" ); pageName = mw.config.get( "wgPageName" ); if( mw.config.get( "wgAction" ) == "history" ) { var link = mw.util.addPortletLink( "p-cactions", "#", "Request CV revdel", "pt-cv-revdel" ); link.addEventListener( "click", load ); if( mw.util.getParamValue( "open_cv_revdel" ) === "true" ) { load; } } else if( mw.config.get( "wgNamespaceNumber" ) >= 0 ) { var historyPage = mw.util.getUrl( pageName, { "action": "history", "open_cv_revdel": "true" } ); var link = mw.util.addPortletLink( "p-cactions", historyPage, "Request CV revdel", "pt-cv-revdel" ); } } ); } ); //

/* Guywan/Scripts/ConfirmLogout.js */ // // $( => { var logout = $("#pt-logout a")[0]; if(!logout) return; // Create new logout link. var new_logout = document.createElement("a"); new_logout.innerText = logout.innerText; // Insert new logout link and remove old. logout.insertAdjacentElement("afterend", new_logout); logout.remove; // Get user-defined styling. var fw = window.us_conlog_font_weight ? window.us_conlog_font_weight : "bold"; var fs = window.us_conlog_font_size ? window.us_conlog_font_size : "86%"; var bg = window.us_conlog_background ? window.us_conlog_background : "#FFDBDB"; var fg = window.us_conlog_color ? window.us_conlog_color : "#000"; var bd = window.us_conlog_border ? window.us_conlog_border : "2px solid #BB7070"; // Add styling to the page. document.getElementsByTagName("body")[0].insertAdjacentHTML("afterbegin", " #use-conlog button { margin: 0 0.2em; border-radius: 9em; padding: 0 0.7em; box-sizing: border-box;" + `font-weight: ${fw}; font-size: ${fs}; background: ${bg}; color: ${fg}; border: ${bd}}` + "#use-conlog button:active { background:rgba(255,255,255,0.6)} "); // Main grouping div for conlog elements. document.getElementsByTagName("body")[0].insertAdjacentHTML("afterbegin", " "); new_logout.addEventListener("click", event => { // Create the logout confirmation prompt. var conlog = document.getElementById("use-conlog"); conlog.insertAdjacentHTML("beforeend", "<div onclick='this.style.transform = \"translateY(-130%)\";setTimeout(function{this.remove}.bind(this), 500);' style='" + "position:fixed; top:0; left:0; right:0; margin: 0 0 auto 0; height: auto; line-height: 1.4em; " + "padding: 0.6em 2em; opacity: 1; text-align: center; z-index: 9999; box-shadow: 0 2px 5px rgba(0,0,0,0.2); " + "transform: translateY(-130%); transition: all 0.2s;" + `font-weight: ${fw}; font-size: ${fs}; background: ${bg}; color: ${fg}; border: ${bd}'>` + " You clicked on a log-out link. Do you want to continue? " + " No <button id='conlog-logout'>Log out " ); // Logout handler. document.getElementById("conlog-logout").addEventListener("click", event => { new mw.Api.post( { "action": "logout", "token": mw.user.tokens.get("csrfToken") }) .fail(result => { mw.notify("Failed to log out: " + result); }) .done( => { window.location.href = mw.util.getUrl(mw.config.get("wgPageName")); }); }); // The logout confirmation prompt pops down from the top. setTimeout( => { conlog.lastChild.style.transform = "translateY(0%)"; }, 10); event.preventDefault; event.stopPropagation; }); }); //

/* PleaseStand/userinfo.js */ // based on http://en.wikipedia.org/wiki/User:Fran Rogers/dimorphism.js // and on http://en.wikipedia.org/wiki/User:Splarka/sysopdectector.js function UserinfoJsFormatQty(qty, singular, plural) { return String(qty).replace(/\d{1,3}(?=(\d{3})+(?!\d))/g, "$&,") + "\u00a0" + (qty == 1 ? singular : plural); } function UserinfoJsFormatDateRel(old) { // The code below requires the computer's clock to be set correctly. var age = new Date.getTime - old.getTime; var ageNumber, ageRemainder, ageWords; if(age < 60000) { // less than one minute old ageNumber = Math.floor(age / 1000); ageWords = UserinfoJsFormatQty(ageNumber, "second", "seconds"); } else if(age < 3600000) { // less than one hour old ageNumber = Math.floor(age / 60000); ageWords = UserinfoJsFormatQty(ageNumber, "minute", "minutes"); } else if(age < 86400000) { // less than one day old ageNumber = Math.floor(age / 3600000); ageWords = UserinfoJsFormatQty(ageNumber, "hour", "hours"); ageRemainder = Math.floor((age - ageNumber * 3600000) / 60000); } else if(age < 604800000) { // less than one week old ageNumber = Math.floor(age / 86400000); ageWords = UserinfoJsFormatQty(ageNumber, "day", "days"); } else if(age < 2592000000) { // less than one month old ageNumber = Math.floor(age / 604800000); ageWords = UserinfoJsFormatQty(ageNumber, "week", "weeks"); } else if(age < 31536000000) { // less than one year old ageNumber = Math.floor(age / 2592000000); ageWords = UserinfoJsFormatQty(ageNumber, "month", "months"); } else { // one year or older ageNumber = Math.floor(age / 31536000000); ageWords = UserinfoJsFormatQty(ageNumber, "year", "years"); ageRemainder = Math.floor((age - ageNumber * 31536000000) / 2592000000); if(ageRemainder) { ageWords += " " + UserinfoJsFormatQty(ageRemainder, "month", "months"); }  }   return ageWords; } // If on a user or user talk page, and not a subpage... if((mw.config.get("wgNamespaceNumber") == 2 || mw.config.get("wgNamespaceNumber") == 3) && !(/\//.test(mw.config.get("wgTitle")))) { // add a hook to... mw.loader.using( ['mediawiki.util'], function { $(function{ // Request the user's information from the API. // Note that this is allowed to be up to 5 minutes old. var et = encodeURIComponent(mw.config.get("wgTitle")); $.getJSON(mw.config.get("wgScriptPath") + "/api.php?format=json&action=query&list=users|usercontribs&usprop=blockinfo|editcount|gender|registration|groups&uclimit=1&ucprop=timestamp&ususers=" + et + "&ucuser=" + et + "&meta=allmessages&amprefix=grouppage-&amincludelocal=1") .done(function(query) { // When response arrives extract the information we need. if(!query.query) { return; } // Suggested by Gary King to avoid JS errors --PS 2010-08-25 query = query.query; var user, invalid, missing, groups, groupPages={}, editcount, registration, blocked, gender, lastEdited; try { user = query.users[0]; invalid = typeof user.invalid != "undefined"; missing = typeof user.missing != "undefined"; groups = (typeof user.groups == "object") ? user.groups : []; editcount = (typeof user.editcount == "number") ? user.editcount : null; registration = (typeof user.registration == "string") ? new Date(user.registration) : null; blocked = typeof user.blockedby != "undefined"; gender = (typeof user.gender == "string") ? user.gender : null; lastEdited = (typeof query.usercontribs[0] == "object") && (typeof query.usercontribs[0].timestamp == "string") ? new Date(query.usercontribs[0].timestamp) : null; for (var am=0; am<query.allmessages.length; am++) { groupPages[query.allmessages[am]["name"].replace("grouppage-","")] = query.allmessages[am]["*"].replace("   :","Project:"); } } catch(e) { return; // Not much to do if the server is returning an error (e.g. if the username is malformed). } // Format the information for on-screen display var statusText = ""; var ipUser = false; var ipv4User = false; var ipv6User = false; // User status if(blocked) { statusText += "<a href=\"" + mw.config.get("wgScriptPath") + "/index.php?title=Special:Log&amp;page=" + encodeURIComponent(mw.config.get("wgFormattedNamespaces")[2] + ":" + user.name) + "&amp;type=block\">blocked</a> "; } if (missing) { statusText += "username not registered"; } else if (invalid) { ipv4User = mw.util.isIPv4Address(user.name); ipv6User = mw.util.isIPv6Address(user.name); ipUser = ipv4User || ipv6User; if (ipv4User) { statusText += "anonymous IPv4 user"; } else if (ipv6User) { statusText += "anonymous IPv6 user"; } else { statusText += "invalid username"; } } else { // User is registered and may be in a privileged group. Below we have a list of user groups. // Only need the ones different from the software's name (or ones to exclude), though. var friendlyGroupNames = { // Exclude implicit user group information provided by MW 1.17 --PS 2010-02-17 '*': false, 'user': false, 'autoconfirmed': false, 'named': false, sysop: "administrator", accountcreator: "account creator", 'import': "importer", transwiki: "transwiki importer", 'ipblock-exempt': "IP block exemption", confirmed: "confirmed user", abusefilter: "edit filter manager", 'abusefilter-helper': "edit filter helper", autoreviewer: "autopatrolled user", epcoordinator: "Education Program course coordinator", epcampus: "Education Program campus volunteer", epinstructor: "Education Program instructor", eponline: "Education Program online volunteer", filemover: "file mover", 'massmessage-sender': "mass message sender", templateeditor: "template editor", extendedconfirmed: "extended confirmed user", extendedmover: "page mover", 'flow-bot': "Flow bot", reviewer: "pending changes reviewer", suppress: "oversighter", patroller: "new page reviewer" }; var friendlyGroups = []; for(var i = 0; i < groups.length; ++i) { var s = groups[i]; var t = friendlyGroupNames.hasOwnProperty(s) ? friendlyGroupNames[s] : s; if (t) { if (groupPages.hasOwnProperty(s)) { friendlyGroups.push("<a href=\"/wiki/" + encodeURIComponent( groupPages[s] ) + "\">" + t + "</a>"); } else { friendlyGroups.push(t); } } } switch(friendlyGroups.length) { case 0: // User not in a privileged group // Changed to "registered user" by request of User:Svanslyck // --PS 2010-05-16 // statusText += "user"; if(blocked) { statusText += "user"; } else { statusText += "registered user"; } break; case 1: statusText += friendlyGroups[0]; break; case 2: statusText += friendlyGroups[0] + " and " + friendlyGroups[1]; break; default: statusText += friendlyGroups.slice(0, -1).join(", ") + ", and " + friendlyGroups[friendlyGroups.length - 1]; break; } } // Registration date if(registration) { var firstLoggedUser = new Date("22:16, 7 September 2005"); // When the Special:Log/newusers was first activated if(registration >= firstLoggedUser) { statusText += ", <a href='" + mw.config.get("wgScriptPath") + "/index.php?title=Special:Log&amp;type=newusers&amp;dir=prev&amp;limit=1&amp;user=" + et + "'>" + UserinfoJsFormatDateRel(registration) + "</a> old"; } else { statusText += ", <a href='" + mw.config.get("wgScriptPath") + "/index.php?title=Special:ListUsers&amp;limit=1&amp;username=" + et + "'>" + UserinfoJsFormatDateRel(registration) + "</a> old"; } } // Edit count if(editcount !== null) { statusText += ", with " + "<a href=\"//tools.wmflabs.org/xtools-ec/?user=" + encodeURIComponent(user.name) + "&amp;project=en.wikipedia.org&amp;uselang=en\">" + UserinfoJsFormatQty(editcount, "edit", "edits") + "</a>"; } // Prefix status text with correct article if("AEIOaeio".indexOf(statusText.charAt(statusText.indexOf('>')+1)) >= 0) { statusText = "An " + statusText; } else { statusText = "A " + statusText; } // Add full stop to status text statusText += "."; // Last edited --PS 2010-06-27 // Added link to contributions page --PS 2010-07-03 if(lastEdited) { statusText += " Last edited <a href=\"" + mw.config.get("wgArticlePath").replace("$1", "Special:Contributions/" + encodeURIComponent(user.name)) + "\">" + UserinfoJsFormatDateRel(lastEdited) + " ago</a>."; } // Show the correct gender symbol var fh = document.getElementById("firstHeading") || document.getElementById("section-0"); if(!fh) return; // e.g. Minerva user talk pages // Add classes for blocked, registered, and anonymous users var newClasses = []; if(blocked) { newClasses.push("ps-blocked"); } if(ipUser) { newClasses.push("ps-anonymous"); } else if(invalid) { newClasses.push("ps-invalid"); } else { newClasses.push("ps-registered"); } fh.className += (fh.className.length ? " " : "") + groups.map(function(s) { return "ps-group-" + s; }).concat(newClasses).join(" "); var genderSpan = document.createElement("span"); genderSpan.id = "ps-gender-" + (gender || "unknown"); genderSpan.style.paddingLeft = "0.25em"; genderSpan.style.fontFamily = '"Lucida Grande", "Lucida Sans Unicode", "sans-serif"'; genderSpan.style.fontSize = "75%"; var genderSymbol; switch(gender) { case "male": genderSymbol = "\u2642"; break; case "female": genderSymbol = "\u2640"; break; default: genderSymbol = ""; break; } genderSpan.appendChild(document.createTextNode(genderSymbol)); fh.appendChild(genderSpan); // Now show the other information. Non-standard? Yes, but it gets the job done. // Add a period after the tagline when doing so. --PS 2010-07-03 var ss = document.getElementById("siteSub"); if(!ss) { ss = document.createElement("div"); ss.id = "siteSub"; ss.innerHTML = "From Wikipedia, the free encyclopedia"; var bc = document.getElementById("bodyContent"); bc.insertBefore(ss, bc.firstChild); } ss.innerHTML = ' ' + statusText + ' ' + ss.innerHTML + '.'; ss.style.display = "block"; }); }); }); }

/* Splarka/oldafd.js */ /* AFD age detector, version [0.0.2a] Originally from: http://en.wikipedia.org/wiki/User:Splarka/oldafd.js Notes: if(mw.config.get('wgCurRevisionId') != 0 && mw.config.get('wgNamespaceNumber') == 4 && (mw.config.get('wgPageName').indexOf('_for_deletion/') != -1 || mw.config.get('wgPageName').indexOf('_for_discussion/') != -1)) addOnloadHook(function { var url = mw.config.get('wgScriptPath') + '/api.php?maxage=3600&smaxage=3600&action=query&indexpageids&prop=revisions&rvdir=newer&rvlimit=1&rvprop=timestamp|comment|user&format=json&callback=ageCheckAFDCB&pageids=' + mw.config.get('wgArticleId');  mw.loader.load(url); }); function ageCheckAFDCB(obj) { var sub = document.getElementById('contentSub') || document.getElementById('topbar'); var div = document.createElement('div'); sub.appendChild(div); if(!obj['query'] || !obj['query']['pages'] || obj['query']['pages'].length == 0 || !obj['query']['pageids'] || obj['query']['pageids'].length == 0 || obj['error']) { div.appendChild(document.createTextNode('Api error in AFD Age Detector.')); return; } var id = obj['query']['pageids'][0]; var page = obj['query']['pages'][id]; var rev = page['revisions'][0]; if(!rev || !rev['timestamp'] || !rev['user']) return var now = new Date; var tsd = new Date; tsd.setISO8601(rev['timestamp']); var timesince = Math.floor((now - tsd)/1000); if(timesince == '') timesince = -1 var revinfo = 'Page created: ' + duration(timesince) + ' ago by ' + rev['user']; if(rev['comment']) div.setAttribute('title',rev['comment']) if(!rev['comment'] || rev['comment'].indexOf('Created') == -1) div.style.color = '#ff0000' div.appendChild(document.createTextNode(revinfo + '.')); if(timesince > 604800) appendCSS('body {background:#ffaaff !important;}') } function duration(input,depth) { var num = input; var out = ''; var s = num % 60; num = Math.floor(num / 60); var m = num % 60; num = Math.floor(num / 60); var h = num % 24; num = Math.floor(num / 24); var d = num % 7; num = Math.floor(num / 7); var w = num % 52; num = Math.floor(num / 52); var y = num if(y > 0) out += y + 'yrs ' if(y + w > 0) out += w + 'wks ' if(y + w + d > 0) out += d + 'days ' if(y + w + d + h > 0) out += h + 'hrs ' if(y + w + d + h + m > 0) out += m + 'mins ' out += s + 'secs'; if(depth && depth < out.split(' ').length) { out = out.split(' ').slice(0,depth).join(' '); } return out; } //ISO 8601 date module by Paul Sowden, licensed under AFL. Date.prototype.setISO8601 = function(string) { if(!string) return var regexp = '([0-9]{4})(-([0-9]{2})(-([0-9]{2})(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?'; var d = string.match(new RegExp(regexp)); if(d.length < 1) return var offset = 0; var date = new Date(d[1], 0, 1); if(d[3]) date.setMonth(d[3] - 1) if(d[5]) date.setDate(d[5]) if(d[7]) date.setHours(d[7]) if(d[8]) date.setMinutes(d[8]) if(d[10]) date.setSeconds(d[10]) if(d[12]) date.setMilliseconds(Number('0.' + d[12]) * 1000) if(d[14]) { offset = (Number(d[16]) * 60) + Number(d[17]); offset *= ((d[15] == '-') ? 1 : -1); } offset -= date.getTimezoneOffset; time = (Number(date) + (offset * 60 * 1000)); this.setTime(Number(time)); }
 * Stuff

/* Novem Linguae/Scripts/NotSoFast.js */ // /* In Special:NewPagesFeed, this script highlights articles newer than 15 minutes RED, and articles newer than 1 hour YELLOW. This is to remind you not to patrol those articles yet. WP:NPP says that articles should not be patrolled until at least 15 minutes have elapsed. This is to give the writer time to work on the article without getting CSD tagged, maintenance tagged, edit conflicted, etc. $(function { function checkAndHighlight(obj) { let currentTimestamp = Math.floor(Date.now / 1000); let fifteenMinutesAgo = currentTimestamp - 60*15; let oneHourAgo = currentTimestamp - 60*60; $(obj).find('.mwe-pt-creation-date').each(function(i) { let dateTimeString = $(this).html.trim; // example: 00:34, 5 March 2022 dateTimeString = convertNPPDateTimeToJSDateTime(dateTimeString); // example: 5 March 2002 00:34 let milliseconds = Date.parse(dateTimeString); oldTimeStamp = millisecondsToSeconds(milliseconds); let browserTimeZoneOffset = new Date.getTimezoneOffset; let mediaWikiTimeZone = mw.user.options.get('timecorrection'); newTimeStamp = convertTimeZoneFromMediaWikiToBrowser( oldTimeStamp, mediaWikiTimeZone, browserTimeZoneOffset ); if ( newTimeStamp > fifteenMinutesAgo ) { $(this).css("background-color", "#F79C8F"); } else if ( newTimeStamp > oneHourAgo ) { $(this).css("background-color", "#F5F591"); } }); } function convertTimeZoneFromMediaWikiToBrowser(timestamp, medaWikiTimeZoneString, browserTimeZoneOffset) { let mediaWikiTimeZoneOffsetInMinutes = getMediaWikiTimeZoneOffset(medaWikiTimeZoneString); let mediaWikiTimeZoneOffsetInSeconds = mediaWikiTimeZoneOffsetInMinutes * 60; let browserTimeZoneOffsetInSeconds = browserTimeZoneOffset * 60; let conversion = parseInt(mediaWikiTimeZoneOffsetInSeconds) - parseInt(browserTimeZoneOffsetInSeconds); return parseInt(timestamp) + conversion; } /** * Converts a MediaWiki mw.user.options.get('timecorrection') from something like 'ZoneInfo|-420|America/Los_Angeles' or 'System|0' to -420 or 0.  */ function getMediaWikiTimeZoneOffset(string) { return parseInt(string.match(/\d+/)[0]); } /**  * Returns the pagename, including the namespace name, but with spaces replaced by underscores  */ function getArticleName { return mw.config.get('wgPageName'); } /** * Flips date and time, so that Date.parse recognizes it. Example: 00:34, 5 March 2022 becomes 5 March 2002 00:34 */ function convertNPPDateTimeToJSDateTime(nppDateTime) { return nppDateTime.replace(/(^.*), (.*)$/, '$2 $1'); } function millisecondsToSeconds(milliseconds) { return milliseconds / 1000; } function inAFCMode { return $('#mwe-pt-radio-afc').is(':checked'); } let title = getArticleName; if ( title != 'Special:NewPagesFeed' ) return; // then run it again whenever a DOM node is inserted (the list refreshes as you scroll down, so this can be anytime you scroll down). could also be because this script loads BEFORE the the NPP applet (race condition) new MutationObserver( => { if ( ! inAFCMode ) { checkAndHighlight(this); } }).observe($('#mwe-pt-list-view')[0], {childList: true}); // run it once in case the NPP applet loaded BEFORE this script (race condition) $('.mwe-pt-list-item').each(function { if ( ! inAFCMode ) { checkAndHighlight(this); } }); }); /* Nardog suggestions: 1) switch to MutationObserver, 2) handle time zones, 3) use console.log to check for code execution instead of breakpoints, 4) "I see no reason to look for .mwe-pt-list-item btw. I would just query .mwe-pt-creation-date directly and give them a class once they're processed, and exclude them in the query (e.g. $('.mwe-pt-creation-date:not(.notsofast-processed)').each(function{)" //

/* Novem Linguae/Scripts/DetectG4G5.js */ // /* - Displays an alert if an article may be a CSD G4 (previous AFD) or CSD G5 (created by a sockpuppet) - Useful for new page patrolling - Only runs on pages that have not been marked as reviewed // TODO: Code review. Is there a way to reduce the # of API queries? $(async function { async function getWikicode(title) { if ( ! mw.config.get('wgCurRevisionId') ) return ; // if page is deleted, return blank var wikicode = ; title = encodeURIComponent(title); await $.ajax({ url: 'https://en.wikipedia.org/w/api.php?action=parse&page='+title+'&prop=wikitext&formatversion=2&format=json', success: function (result) { wikicode = result['parse']['wikitext']; }, dataType: "json", async: false }); return wikicode; } async function hasAFDTemplate(title) { let wikicode = await getWikicode(title); return wikicode.indexOf('{{Article for deletion') !== -1; } function displayWarning(html) { $('#contentSub').before(` ${html} `); } async function isReviewed(pageID) { let api = new mw.Api; let response = await api.get( { action: 'pagetriagelist', format: 'json', page_id: pageID, } ); // no result if ( response.pagetriagelist.result !== 'success' || response.pagetriagelist.pages.length === 0 ) { return true; // 1, 2, or 3 } else if ( parseInt(response.pagetriagelist.pages[0].patrol_status) > 0 ) { return true; // 0 } else { return false; } } async function afdExists(title) { title = 'Wikipedia:Articles_for_deletion/' + title; return await pageExists(title); } async function pageExists(title) { let api = new mw.Api; let response = await api.get( { action: 'query', format: 'json', prop: 'revisions', titles: title, } ); let exists = typeof response.query.pages['-1'] === 'undefined'; return exists; } async function isBlocked(username) { let api = new mw.Api; let response = await api.get( { action: "query", format: "json", list: "users", usprop: "blockinfo", ususers: username, } ); let isBlocked = typeof response.query.users[0].blockid !== 'undefined'; return isBlocked; } async function isGloballyLocked(username) { let api = new mw.Api; let response = await api.get( { action: 'query', list: 'globalallusers', agulimit: '1', agufrom: username, aguto: username, aguprop: 'lockinfo', } ); let isLocked = response.query.globalallusers.length !== 0 && response.query.globalallusers[0].locked === ''; return isLocked; } function getFirstValueInObject(obj) { return obj[Object.keys(obj)[0]]; } async function getPageCreator(title) { let api = new mw.Api; let response = await api.get( { "action": "query", "format": "json", "prop": "revisions", "titles": title, "rvlimit": "1", "rvdir": "newer" } ); let page = getFirstValueInObject(response.query.pages); let pageCreator = page.revisions[0].user; return pageCreator; } function shouldRunOnThisPage { // don't run when not viewing articles let action = mw.config.get('wgAction'); if ( action != 'view' ) return false; // don't run when viewing diffs let isDiff = mw.config.get('wgDiffNewId'); if ( isDiff ) return false; let isDeletedPage = ( ! mw.config.get('wgCurRevisionId') ); if ( isDeletedPage ) return false; // Only run in mainspace let namespace = mw.config.get('wgNamespaceNumber'); let title = mw.config.get('wgPageName'); // includes namespace, underscores instead of spaces if ( namespace !== 0 && title != 'User:Novem_Linguae/sandbox' ) return false; return true; } if ( ! shouldRunOnThisPage ) return; let title = mw.config.get('wgPageName'); // includes namespace, underscores instead of spaces let pageID = mw.config.get('wgArticleId'); if ( await isReviewed(pageID) ) { return; } if ( await afdExists(title) && ! await hasAFDTemplate(title) ) { let href = mw.config.get('wgArticlePath').replace('$1', 'Wikipedia:Articles_for_deletion/' + title); displayWarning(` CSD G4: There is an <a href="${href}">AFD page</a> for this article. It may qualify for CSD G4.`); } let pageCreator = await getPageCreator(title); if ( await isBlocked(pageCreator) ) { displayWarning(' CSD G5: The creator of this page is blocked. This article may qualify for CSD G5.'); } if ( await isGloballyLocked(pageCreator) ) { displayWarning(' CSD G5: The creator of this page is globally locked. This article may qualify for CSD G5.'); } }); //

/* Writ_Keeper/Scripts/deletionFinder.js */ $(document).ready(deletionChecker); encodedTitle = encodeURIComponent(mw.config.get("wgPageName")); function deletionChecker { var encodedTitle = encodeURIComponent(mw.config.get("wgPageName")); var delRequest = $.get("/w/api.php?action=query&list=logevents&format=json&leprop=ids&letype=delete&letitle=" + encodedTitle + "&lelimit=1", null, delCallback, "json"); if(mw.config.get("wgCanonicalNamespace") == "") { var afdRequest = $.get("/w/api.php?action=query&list=allpages&format=json&apfrom=Articles%20for%20deletion%2F" + encodedTitle + "&apto=Articles%20for%20deletion%2F" + encodedTitle + "&apnamespace=4&aplimit=1", null, afdCallback, "json"); } } function delCallback(delResults) { if(delResults.query.logevents[0] != null) { /* var searchNode = document.createElement("a"); searchNode.href = mw.config.get("wgServer") + "/w/index.php?title=Special%3ALog&type=delete&page=" + encodedTitle; searchNode.target = "_blank"; searchNode.innerHTML = " prev dels "; document.getElementById("firstHeading").appendChild(searchNode); var searchNode = " <a id='prevDelsLink' target='_blank'> prev dels </a>" $("#firstHeading").append(searchNode); $("#prevDelsLink").attr("href", mw.config.get("wgServer") + "/w/index.php?title=Special%3ALog&type=delete&page=" + encodedTitle); } } function afdCallback(afdResults) { if(afdResults.query.allpages[0] != null) { /* var searchNode = document.createElement("a"); searchNode.href = mw.config.get("wgServer") + "/w/index.php?title=Special%3AAllPages&from=Articles+for+deletion%2F" + encodedTitle + "&to=Articles+for+deletion%2F" + encodedTitle + "+%289z%29&namespace=4"; searchNode.target = "_blank"; searchNode.innerHTML = " prev AfDs "; document.getElementById("firstHeading").appendChild(searchNode); var searchNode = " <a id='prevAFDsLink' target='_blank'> prev AfDs </a>" $("#firstHeading").append(searchNode); $("#prevAFDsLink").attr("href", mw.config.get("wgServer") + "/w/index.php?title=Special%3AAllPages&from=Articles+for+deletion%2F" + encodedTitle + "&to=Articles+for+deletion%2F" + encodedTitle + "+%289z%29&namespace=4"); } }

/* DannyS712/SATG.js */ //Written by Abelmoschus Esculentus //Date: 12 January 2019 //Copied from [[User:Abelmoschus Esculentus/SATG.js] // function screen2(number) { var source = ["placeholder"], ind = ["placeholder"], ind_just = ["placeholder"], rel = ["placeholder"], rel_just = ["placeholder"], sig = ["placeholder"], sig_just = ["placeholder"]; $('.SATG-source').each(function  { source.push(this.value);  });  $('.SATG-selecti').each(function  { ind.push(this.value);  });  $('.SATG-justi').each(function  { ind_just.push(this.value);  });  $('.SATG-selectr').each(function  { rel.push(this.value);  });  $('.SATG-justr').each(function  { rel_just.push(this.value);  });  $('.SATG-selects').each(function  { sig.push(this.value);  });  $('.SATG-justs').each(function  { sig_just.push(this.value);  });  var output = ""; $("#SATG-interface-content").css({  "min-height": "7em",  "width" : "875px",  "height" : "400px",  "overflow-y": "hidden" }); $("#SATG-interface-content").empty; $("#SATG-interface-content").text('Generating...'); $('.SATG-tip').remove; $("#SATG-interface-content").empty; $("#SATG-interface-content").append( $(' ').attr('id','SATG-copy').text('Copy'), $(' ').attr('id','SATG-copied').text(), $(' ').css({'resize':'none'}).attr({'id':'SATG-output','readonly':'true','rows':'20','cols':'35'}).text(output) ); $('#SATG-copy').click(function { var copy = document.getElementById("SATG-output"); copy.select; document.execCommand("copy"); $('#SATG-copied').text(' Copied to your clipboard!'); }); } function screen1(number) { if ($("#SATG-interface-content").text == "Loading form...") { $("#SATG-interface-content").empty; } $("#SATG-interface-footer").append( $(' ').attr('id', 'SATG-back').css('margin-left','1em').text('Back') ); $("#SATG-interface-footer").prepend( $(' ').attr('class','SATG-tip').text('**Only available for options "Yes", "No", "Partially" and "Unknown"'), $(' ').attr('class','SATG-tip') ); $('#SATG-back').click(function  { $('#SATG-back').remove; $("#SATG-interface-content").css({ "min-height": "7em", "width" : "875px", "height" : "400px", "overflow-y": "scroll" }); screen0; }); /*var arr = [ {val : 1, text: 'Yes'}, {val : 2, text: 'No'}, {val : 3, text: 'Partially'}, {val : 4, text: 'Unknown'}, {val : 5, text: 'None'} ];*/ for (var i = 1; i <= number; i++) { if (i != 1) { $("#SATG-interface-content").append(' '); } $("#SATG-interface-content").append( $(' ').css('margin-bottom','0.5em').append( $(' ').text('Source '+i+': '), $(' ').attr({'type':'text','class':'SATG-source'}) ),  $(' ').css('margin-bottom','0.5em').append( $(' ').text('Independent? '), $(' ').attr('class','SATG-selecti') .append($(" ").attr('value','y').text('Yes')) .append($(" ").attr('value','n').text('No')) .append($(" ").attr('value','-').text('Partially')) .append($(" ").attr('value','?').text('Unknown')) .append($(" ").attr({'value':,'selected':'true'}).text('None')), $(' ').text(' **Justification: '), $(' ').attr({'type':'text','class':'SATG-justi'})  ),  $(' ').css('margin-bottom','0.5em').append( $(' ').text('Reliable? '), $(' ').attr('class','SATG-selectr') .append($(" ").attr('value','y').text('Yes')) .append($(" ").attr('value','n').text('No')) .append($(" ").attr('value','-').text('Partially')) .append($(" ").attr('value','?').text('Unknown')) .append($(" ").attr({'value':,'selected':'true'}).text('None')), $(' ').text(' **Justification: '), $(' ').attr({'type':'text','class':'SATG-justr'}) ), $(' ').css('margin-bottom','0.5em').append(  $(' ').attr('id','SATG-labels-'+i).text('Significant coverage? '), $(' ').attr('class','SATG-selects') .append($(" ").attr('value','y').text('Yes')) .append($(" ").attr('value','n').text('No')) .append($(" ").attr('value','-').text('Partially')) .append($(" ").attr('value','?').text('Unknown')) .append($(" ").attr({'value':,'selected':'true'}).text('None')), $(' ').text(' **Justification: '), $(' ').attr({'type':'text','class':'SATG-justs'})  ) ); } $("#SATG-interface-content").append( $(' ').attr('id','SATG-generate').text('Generate!') ); $('#SATG-generate').click(function { screen2(number); }); } function satg_init {  mw.util.addPortletLink('p-tb', 'javascript:void(0)', 'SA Table Generator', 'aca-satg', null, null);  $('#aca-satg').on('click', function { $('body').prepend(' '+  ' '+ '<h4 id="SATG-interface-header"> '+ ' '+ ' '+ ' '+ ' '+  ' '+ ' '); $("#SATG-modal").css({  "position": "fixed",  "z-index": "1",  "left": "0",  "top": "0",  "width": "100%",  "height": "100%",  "overflow": "hidden",  "background-color": "rgba(0,0,0,0.4)" }); $("#SATG-interface").css({  "background-color": "#e8f0ff",  "margin": "15% auto",  "padding": "2px 20px",  "border": "1px solid #888",  "width": "80%",  "max-width": "60em",  "font-size": "90%" }); $("#SATG-interface-content").css({  "min-height": "7em",  "width" : "875px",  "height" : "400px",  "overflow-y": "scroll" }); $("#SATG-interface-footor").css("min-height", "3em"); screen0;  }); } var screen0 = function {  $("#SATG-interface-header, #SATG-interface-content, #SATG-interface-footer").empty;  $("#SATG-interface-header").text("Source Assess Table Generator");  $("#SATG-interface-content").append( $(' ').css('margin-bottom','0.5em').append(  $(' ').attr({'for':'SATG-userinput-label', 'id':'SATG-userinput-label'}).text('How many sources do you want to assess? '),  $(' ').attr({'type':'number', 'name':'SATG-userinput','id':'SATG-userinput','min':'1','max':'100','value':'1'}) //set limits ), $(' ').css('margin-bottom','0.5em').append(  $(' ').attr({'name':'SATG-userinput-button', 'id':'SATG-userinput-button'}).text('Load') )  );  $("#SATG-interface-footer").append( $(' ').attr('id', 'SATG-cancel').text('Close')  );  $('#SATG-cancel').click(function { $('#SATG-modal').remove;  });  $('#SATG-userinput-button').click(function {  var temp = $('#SATG-userinput').val;  if (temp > 100 || temp < 1) {  alert('Invalid value');  }  else {  $('#SATG-interface-content').empty;  $('#SATG-interface-content').text('Loading form...');  screen1(temp);  }  }); }; mw.loader.using(['mediawiki.util'], function {  satg_init; }); //

/* Jackmcbarn/editProtectedHelper.js */ // var browserHasDataCorruptionBug = $(' ')[0].outerHTML != ' '; if(browserHasDataCorruptionBug && ('undefined' === typeof ephAllowDataCorruptionBug || !ephAllowDataCorruptionBug)) { $(document).ready(function { $('.editrequest .mbox-text').append(' The editProtectedHelper script is currently disabled because your browser has a bug which will cause corruption of pages that it edits with Parsoid. See <a href="https://www.mediawiki.org/wiki/User:Catrope/IE_bugs#style_attributes_are_aggressively_normalized.2C_causing_data_loss">here</a> for details. To override this, add "ephAllowDataCorruptionBug = true;" immediately above the line where you load this script. You are responsible for any damage to pages that this causes. '); }); // only enable this on the latest revision of the page } else if(mw.config.get('wgRevisionId') == mw.config.get('wgCurRevisionId')) { $(document).ready(function { mw.loader.using( ['mediawiki.api'], function { 'use strict'; var templateResponses = [ [ '', '(No template response)' ], [ 'd', 'Done' ], [ 'pd', 'Partly done:' ], [ 'nd', 'Not done:' ], [ 'nfn', 'Not done for now:' ], [ 'c', 'Not done: please establish a consensus for this alteration before using the template.'], // TODO make dynamic [ 'rs', 'Not done: please provide reliable sources that support the change you want to be made.' ], [ 'xy', 'Not done: it\'s not clear what changes you want made. Please mention the specific changes in a "change X to Y" format.' ], [ 'mis', 'Not done: this is the talk page for discussing improvements to the template. Please make your request at the talk page for the article concerned.'], // TODO make dynamic [ 'sb', 'Not done: please make your requested changes to the template\'s sandbox first; see WP:TESTCASES.' ], [ 'tp', 'Not done: this is the talk page for discussing improvements to the template. If possible, please make your request at the talk page for the article concerned. If you cannot edit the article\'s talk page, you can instead make your request at Wikipedia:Requests for page protection#Current requests for edits to a protected page.' ], [ 'a', 'Already done' ], [ 'hr', 'Not done: According to the page\'s protection level and your user rights, you should be able to edit the page yourself. If you seem to be unable to, please reopen the request with further details.'], [ 'nlp', 'Not done: The page\'s protection level has changed since this request was placed. You should now be able to edit the page yourself. If you still seem to be unable to, please reopen the request with further details.'], [ 'doc', 'Not done: is usually not required for edits to the documentation, categories, or interlanguage links of templates using a documentation subpage. Use the \'edit\' link at the top of the green "Template documentation" box to edit the documentation subpage.' ], [ 'drv', 'Not done: requests for recreating deleted pages protected against creation should be made at Wikipedia:Deletion review.' ], [ 'r', 'Not done: requests for increases to the page protection level should be made at Wikipedia:Requests for page protection.' ], [ 'ru', 'Not done: requests for decreases to the page protection level should be directed to the protecting admin or to Wikipedia:Requests for page protection if the protecting admin is not active or has declined the request.' ], [ 'p', 'Not done: this is not the right page to request additional user rights. You may reopen this request with the specific changes to be made and someone will add them for you.'], // TODO make dynamic [ 'm', 'Not done: page move requests should be made at Wikipedia:Requested moves.' ], [ 'q', 'Question:' ], [ 'note', 'Note:' ], [ 'udp', 'Undone: This request (or the completed portion of it) has been undone.' ], [ 'ud', 'Undone: This request has been undone.' ] ]; var selector = $('.editrequest .mbox-text'); // Global variable. In onParsoidDomReceived, this is set // to the ETag header so that we can pass it back later // in convertModifiedDom. var gEtag; // Global variable. In onParsoidDomReceived, this is // set to the string we get back from the Parsoid API. // Used in convertModifiedDom. var parsoidDom; var selectedLevel = { semi: [' selected="selected"', , , , ], extended: [, ' selected="selected"', , , ], template: [, , ' selected="selected"', , ], full: [, , , ' selected="selected"', ], interface: [, , , , ' selected="selected"'] }; var templateLevel = { semi: 'semi-', extended: 'extended-', template: 'template-', full: 'fully-', interface: 'interface-' }; var responseLevel = { semi: '{' + '{subst:ESp|', extended: '{' + '{subst:EEp|', template: '{' + '{subst:ETp|', full: '{' + '{subst:EP|', interface: '{' + '{subst:EIp|' }; var warnOnRespond = false, warnOnQuickRespond = true, warnOnRemove = true, autoFixLevel = true; var quickResponses = [ [ 'd', '', 'Done'], [ 'rs', '', 'Needs reliable sources'], [ 'xy', '', 'Unclear/X to Y'], [ 'hr', '', 'Could always edit'], [ 'nlp', '', 'Can edit now'], [ 'mis', '', 'Misplaced'] ]; if('undefined' !== typeof ephWarnOnRespond) { warnOnRespond = ephWarnOnRespond; } if('undefined' !== typeof ephWarnOnQuickRespond) { warnOnQuickRespond = ephWarnOnQuickRespond; } if('undefined' !== typeof ephWarnOnRemove) { warnOnRemove = ephWarnOnRemove; } if('undefined' !== typeof ephAutoFixLevel) { autoFixLevel = ephAutoFixLevel; } if('undefined' !== typeof ephQuickResponses) { quickResponses = ephQuickResponses; } function yesno(val, def) { if(typeof val === 'string') { val = val.toLowerCase; } if(typeof val === 'undefined' || val === '') { return undefined; } else if(val === 'yes' || val === 'y' || val === 'true' || val === 1) { return true; } else if(val === 'no' || val === 'n' || val === 'false' || val === 0) { return false; } return def; } function getBanner(level, pagename, answered, force, demo) { return  : '}}'); } function getResponse(level, template, free) { return ':' + (template ===  ?  : responseLevel[level] + template + '}} ') + (free ===  ?  : free + ' ') +  + "\n"; } function makeUniqueString(index) { // this looks like a strip marker on purpose return "\x7fUNIQ" + Math.random.toString(16).substr(2) + '-editProtectedHelper-' + index + "-QINU\x7f"; } function saveWikitextFromParsoid(parsoidObj, data) { var newWikitext, tmp, removerequest = false; if(parsoidObj.removerequest) { tmp = data.split(parsoidObj.templateMarker); removerequest = true; newWikitext = tmp[0]; if(parsoidObj.respondedInPage) { tmp = tmp[1].split(parsoidObj.responseMarker); newWikitext = newWikitext.replace(/\n+$/, ) + "\n\n" + tmp[1].replace(/^\n+/, ); } } else { var response = getResponse(parsoidObj.form.level.value, parsoidObj.form.responsetemplate.value, parsoidObj.form.responsefree.value); var banner = getBanner(parsoidObj.form.level.value, parsoidObj.form.pagetoedit.value, parsoidObj.form.answered.checked, parsoidObj.form.force.checked, parsoidObj.demo); // prevent empty response if(response == ':' + "\n") { response = ''; } tmp = data.split(parsoidObj.templateMarker); newWikitext = tmp[0].replace(/\n*$/, tmp[0].trim.length ? "\n\n" : '') + banner + tmp[1].replace(/^\n*/, "\n"); if(parsoidObj.respondedInPage) { tmp = newWikitext.split(parsoidObj.responseMarker); newWikitext = tmp[0].replace(/\n*$/, "\n") + response + tmp[1].replace(/^\n*/, "\n"); } else { newWikitext = newWikitext.replace(/\n*$/, "\n") + response; } } var resultObj = parsoidObj.resultObj, sectionName = parsoidObj.section && parsoidObj.section.text.trim; if(sectionName && (sectionName.indexOf(parsoidObj.templateMarker) !== -1 || sectionName.indexOf(parsoidObj.responseMarker) !== -1)) { // someone put an edit request template inside the section header, or something like that. // don't even try to include a working section link in that case sectionName = null; } //console.log(newWikitext); resultObj.text('Saving...'); debugger; // triggers only if debugger is active new mw.Api.get( { action: 'query', prop: 'revisions', rvprop: 'timestamp', revids: mw.config.get('wgRevisionId') }).done(function(data) { new mw.Api.postWithEditToken( { action: 'edit', pageid: mw.config.get('wgArticleId'), text: newWikitext, summary: (sectionName ? '/' + '* ' + sectionName + ' *' + '/ ' : '') + (removerequest ? 'Removed' : 'Responded to') + ' edit request', tags: 'editProtectedHelper', notminor: true, nocreate: true, basetimestamp: data.query.pages[mw.config.get('wgArticleId')].revisions[0].timestamp } ).done(function(result) { if(typeof(result.edit.newrevid) === 'undefined' || typeof(result.edit.oldrevid) === 'undefined') { resultObj.css('color', 'red').text("Error: The API response omitted required information. Please check the page's history manually to see whether your edit was saved properly. The console may contain details."); } else { resultObj.css('color', 'green').text('Success: Loading diff...'); if( window.editProtectedHelperReloadAfter ) { window.location.reload( /* forcedReload */ true ); } else { location.href = mw.config.get('wgScript') + '?diff=' + result.edit.newrevid + '&oldid=' + result.edit.oldrevid; } } }).fail(function(err) { resultObj.css('color', 'red').text('Error: ' + err + '. The console may contain details.'); }).always(console.log); }); } function convertModifiedDom(e) { var parsoidObj, nextRequestBeforeHeader = false; if(warnOnRespond && !confirm('Are you sure you want to respond to this edit request?')) { return false; } $('.editrequest button').prop('disabled', true); parsoidObj = e.data; parsoidObj.resultObj.text('Preparing new wikitext...'); var editReqTpl = $(parsoidObj); // Don't break about continuity - get to the first // element of the tranclusion block while (editReqTpl && editReqTpl.attr('data-mw') === undefined) { editReqTpl = editReqTpl.prev; } if (!editReqTpl) { console.log("Error 3: Unexpected error traversing DOM. Cannot save!"); return; } var error = false; editReqTpl.before(parsoidObj.templateMarker); $('h1,h2,h3,h4,h5,h6,.editrequest', parsoidDom).each(function { var obj = $(this); if(!obj.hasClass('editrequest')) { // obj is a heading if(obj.closest('[typeof="mw:Transclusion"]').length) { // it's from a template transclusion. ignore return true; } if(obj.add(parsoidObj)[0] === this) { // (section) heading shows up before the edit request banner. // Set as our section header (tentatively) and otherwise ignore. parsoidObj.section = obj; return true; } } else { // obj is a (potentially different) edit request. if(obj.add(parsoidObj)[0] === this) { // not after our edit request banner. ignore return true; } else { nextRequestBeforeHeader = true; } } if (obj.attr('about')) { // Don't break about continuity - get to the first // element of the tranclusion block while (obj && obj.attr('data-mw') === undefined) { obj = obj.prev; } if (!obj) { error = true; // Don't know what happened here! return false; } } obj.before(parsoidObj.responseMarker); parsoidObj.respondedInPage = true; return false; }); if (error) { console.log("Error 4: Unexpected error traversing DOM. Cannot save!"); return; } // If the section header is immediately before a request being removed, remove it too // Do this before removing the edit req (template) if(parsoidObj.removerequest && !nextRequestBeforeHeader && editReqTpl.prev.is(parsoidObj.section)) { parsoidObj.section.remove; } // Remove the edit req (template) $('[about="' + $(parsoidObj).attr('about').replace('\\', '\\\\').replace('"', '\\"') + '"]', parsoidDom).remove; $.ajax({ type: 'POST', url: '/api/rest_v1/transform/html/to/wikitext/' + encodeURIComponent(mw.config.get('wgPageName')) + '/' + mw.config.get('wgRevisionId'), headers: { Accept: 'text/plain; charset=utf-8; profile="mediawiki.org/specs/wikitext/1.0.0"', 'Api-User-Agent': 'editProtectedHelper (https://en.wikipedia.org/wiki/User:Jackmcbarn/editProtectedHelper)', 'If-Match': gEtag }, data: { html: parsoidDom.documentElement.outerHTML }, success: function(data) { return saveWikitextFromParsoid(parsoidObj, data); } }); } function appendForm(obj, level, pagename, answered, force, parsoidObj) { if(browserHasDataCorruptionBug) { $(obj).append(' WARNING: Your browser has a bug which will cause corruption of pages that it edits with Parsoid. See <a href="https://www.mediawiki.org/wiki/User:Catrope/IE_bugs#style_attributes_are_aggressively_normalized.2C_causing_data_loss">here</a> for details. You are responsible for any damage to pages that this causes. '); } var form = $('<form class="editProtectedHelper" style="display: none" />'); form.append(' .mw-ui-input { background-color: white; } '); if(selectedLevel[level]) { form.append(' Level: <select name="level" class="mw-ui-input mw-ui-input-inline"><option value="semi"' + selectedLevel[level][0] + '>Semi-protected <option value="extended"' + selectedLevel[level][1] + '>Extended-confirmed-protected <option value="template"' + selectedLevel[level][2] + '>Template-protected <option value="full"' + selectedLevel[level][3] + '>Fully protected <option value="interface"' + selectedLevel[level][4] + '>Interface-protected  '); } if(force) { form.append(' Disable protection level autodetection (use only if necessary): <input type="checkbox" name="force" checked="checked" /> ' ); } else { form.append('<input type="checkbox" name="force" style="display: none" />' ); // if this is off and you want to turn it on, do it with firebug or something. otherwise people will use this when they shouldn't } var label = $(' Page to be edited: '); label.append($('<input type="text" name="pagetoedit" class="mw-ui-input mw-ui-input-inline" />').attr('value', pagename !== '' ? pagename : '')); form.append(label); form.append(' Answered: <input type="checkbox" name="answered"' + (answered ? ' checked="checked"' : '') + ' /> Response: '); var select = $('<select name="responsetemplate" class="mw-ui-input" />'); templateResponses.forEach(function(r) { select.append($(' ').attr('value', r[0]).text(r[1])); }); form.append(select); form.append('<textarea name="responsefree" class="mw-ui-input" placeholder="Enter any custom response text here. A signature will automatically be appended."> ' + ' '); var resultObj = $(' '); var button = $('<button type="button" class="mw-ui-button mw-ui-constructive">Submit ').click(parsoidObj, convertModifiedDom); form.append(button); form.append(' '); $(obj).append(form); var buttons = $(' '); buttons.append($('<button type="button" class="mw-ui-button mw-ui-progressive">Respond ').click(function{ buttons.hide; $(obj).closest('.editrequest').removeClass('mbox-small'); form.show; })) .append(' ') .append($('<button type="button" class="mw-ui-button mw-ui-destructive">Remove request ').click(function{ if(!warnOnRemove || confirm('Are you sure you want to completely remove this edit request from the page? In general, this should not be done to good-faith requests unless they are blank or duplicates of other requests by the same user, etc.')) { warnOnRespond = false; parsoidObj.removerequest = true; button.click; } })); if(!answered) { quickResponses.forEach(function(r){ buttons.append(' ').append($('<button type="button" class="mw-ui-button mw-ui-constructive" />').text(r[2]).click(function{ if(!warnOnQuickRespond || confirm('Are you sure you want to respond to this edit request?')) { warnOnRespond = false; parsoidObj.form.answered.checked = true; parsoidObj.form.responsetemplate.value = r[0]; parsoidObj.form.responsefree.value = r[1]; button.click; } })); }); } $(obj).append(buttons); $(obj).append(resultObj); parsoidObj.form = form[0]; parsoidObj.resultObj = resultObj; } function parsoidSetupFieldsForTemplate(index) { var mboxObj = $(this); var obj = mboxObj; // Get to the first element of the tranclusion block // to correctly recover 'data-mw'. var aboutId = obj.attr('about'); if (aboutId === undefined) { console.log("Error 1: No data-mw attribute was found on edit request banner " + index + ". This could be because some template above it on the page opened an HTML tag but didn't close it."); return; } var data_mw = obj.attr('data-mw'); var done = data_mw !== undefined; while (!done) { obj = obj.prev; if (!obj || obj.attr('about') !== aboutId) { console.log("Error 2: The HTML seems broken. Either the script is broken and needs an update or this is a Parsoid bug."); return; } data_mw = obj.attr('data-mw'); done = data_mw !== undefined; } data_mw = JSON.parse(data_mw); var level = mboxObj.attr('data-origlevel'); if (autoFixLevel) { switch(this.id) { case 'editsemiprotected': level = 'semi'; break; case 'editextendedprotected': level = 'extended'; break; case 'edittemplateprotected': level = 'template'; break; case 'editprotected': level = 'full'; break; case 'editinterfaceprotected': level = 'interface'; } } var params = []; for(var key in data_mw.parts[0].template.params) { params[key] = data_mw.parts[0].template.params[key].wt; } // this only runs on numerical parameters params.forEach(function(value, key) { if(/=/.test(value)) { params[key] = key + '=' + value; } }); var pagename = params.join('|').replace(/^\|+|\|+$/g, '').replace(/\|+/g, '|'); var answered = yesno(params.ans || params.answered, true); this.demo = yesno(params.demo); var force = yesno(params.force); this.params = params; this.templateMarker = makeUniqueString(2 * index); this.responseMarker = makeUniqueString(2 * index + 1); appendForm(selector[index], level, pagename, answered, force, this); } function onParsoidDomReceived(data, textStatus, jqXHR) { // Grab the ETag header and store it in the global // gEtag for later use var headers = jqXHR.getAllResponseHeaders.split( "\r\n" ); for( var i = 0, n = headers.length; i < n; i++ ) { if( headers[i].substring( 0, headers[i].indexOf( ":" ) ) === "etag" ) { gEtag = headers[i].substring( headers[i].indexOf( ":" ) + 2 ); break; } } parsoidDom = new DOMParser.parseFromString(data, 'text/html'); if(!parsoidDom) { // blech. Safari. parsoidDom = document.implementation.createHTMLDocument(''); parsoidDom.documentElement.innerHTML = data; } var parsoidSelector = $('.editrequest', parsoidDom); if(selector.length != parsoidSelector.length) { console.log('Sanity check failed: ' + selector.length + ' edit requests exist in the page but only ' + parsoidSelector.length + ' were found in Parsoid\'s output.'); return; } parsoidSelector.each(parsoidSetupFieldsForTemplate); } if(selector.length) { mw.loader.load('mediawiki.api'); mw.loader.load('mediawiki.ui.button'); mw.loader.load('mediawiki.ui.input'); $.ajax({ url: '/api/rest_v1/page/html/' + encodeURIComponent(mw.config.get('wgPageName')) + '/' + mw.config.get('wgRevisionId'), headers: { Accept: 'text/html; charset=utf-8; profile="https://www.mediawiki.org/wiki/Specs/HTML/2.5.0"', 'Api-User-Agent': 'editProtectedHelper (https://en.wikipedia.org/wiki/User:Jackmcbarn/editProtectedHelper)' }, success: onParsoidDomReceived }); } }); }); } //

/* TheTVExpert/submitRMTR.js */ //submitRMTR // $(function { function submitRMTR { var oldTitle = $('input[name=wpOldTitle]').val; var newNamespace = mw.config.get('wgFormattedNamespaces')[$('select[name=wpNewTitleNs]').val]; var newTitle = $('input[name=wpNewTitleMain]').val; var newTitleFull = (newNamespace === '' ? newTitle : newNamespace + ':' + newTitle); var reason = $('input[name=wpReason]').val; var rmtrText =  + oldTitle + ; var textToFind = / and enter on a new line.* -->/; var result; var api = new mw.Api; var params = { action: 'query', prop: 'revisions', rvprop: 'content', rvlimit: 1, titles: 'Wikipedia:Requested moves/Technical requests' }; api.get(params).done(function(data) { var page; for (page in data.query.pages){ result = data.query.pages[page].revisions[0]['*']; var newResult = result.replace(textToFind, '$&\n' + rmtrText); var params2 = { action: 'edit', title: 'Wikipedia:Requested moves/Technical requests', text: newResult, summary: "Add request using submitRMTR" }, api2 = new mw.Api; api2.postWithToken('csrf',params2).done(function(data){ console.log(data); alert("Success."); window.location = mw.util.getUrl('Wikipedia:Requested moves/Technical requests'); }); } }); } if (window.location.href.match('Special:MovePage')) { var $rmtrButton = new OO.ui.ButtonWidget({ label:'Submit Technical Request', flags: ['primary','progressive'] }).$element .on('click',submitRMTR) .appendTo($('button[name=wpMove]').parent.parent) } }); //

/* Andy_M._Wang/closeRM.js */ //

/* Suffusion_of_Yellow/effp-helper.js */ /* * WP:EF/FP helper * * Copies text from Special:AbuseLog pages, and merges with later revisions if possible. * */ // if (mw.config.get('wgAbuseFilterVariables')) { if (window.effpUseDev === true) mw.loader.load("https://en.wikipedia.org/w/index.php?title=User:Suffusion_of_Yellow/effp-helper-dev.js&action=raw&ctype=text/javascript"); else mw.loader.load("https://en.wikipedia.org/w/index.php?title=User:Suffusion_of_Yellow/effp-helper-core.js&action=raw&ctype=text/javascript"); } //

/* DannyS712/EFFPRH.js */ // //Largely based on User:Enterprisey/AFCFFU.js //Copied from User:Abelmoschus Esculentus/EFFPRH.js //Includes contributions from User:EpicPupper var EFFPRH_config = { name: 'EFFPRH', version: "1.3.1", debug: false }; var effp_ending = ' (' + EFFPRH_config.name + ' v.' + EFFPRH_config.version + ')'; var effp_effpPageName = mw.config.get('wgPageName').replace(/_/g, ' '); var effp_effpSubmissions = new Array; var effp_effpSections = new Array; var effp_numTotal = 0; var effp_AJAXnumber = 0; var effp_Submissions = new Array; var wgArticlePath = mw.config.get( 'wgArticlePath' ); function effp_editPage(title, newtext, summary, createonly, nopatrol) { var edittoken = mw.user.tokens.get('csrfToken'); summary += effp_ending; $("#effp_finished_wrapper").html('<span id="effp_AJAX_finished_' + effp_AJAXnumber + '" style="display:none">' + $("#effp_finished_wrapper").html + ' '); var func_id = effp_AJAXnumber; effp_AJAXnumber++; $('#effp_status').html($('#effp_status').html + '<li id="effp_edit' + jqEsc(title) + '">Editing <a href="' + wgArticlePath.replace("$1", encodeURI(title)) + '" title="' + title + '">' + title + '</a></li>'); var request = { 'action': 'edit', 'title': title, 'text': newtext, 'summary': summary, 'token': edittoken }; if (createonly) request.createonly = true; var api = new mw.Api; api.post(request) .done(function ( data ) { if ( data && data.edit && data.edit.result && data.edit.result == 'Success' ) { $('#effp_edit' + jqEsc(title)).html('Saved <a href="' + wgArticlePath.replace("$1", encodeURI(title)) + '" title="' + title + '">' + title + '</a>'); } else { $('#effp_edit' + jqEsc(title)).html('<span class="effp_notice">Edit failed on <a href="' + wgArticlePath.replace("$1", encodeURI(title)) + '" title="' + title + '">' + title + '</a>. Error info: ' + JSON.stringify(data)); window.console && console.error('Edit failed on %s (%s). Error info: %s', wgArticlePath.replace("$1", encodeURI(title)), title, JSON.stringify(data)); } } ) .fail( function ( error ) { if (createonly && error == "articleexists") $('#effp_edit' + jqEsc(title)).html('<span class="effp_notice">Edit failed on <a href="' + wgArticlePath.replace("$1", encodeURI(title)) + '" title="' + title + '">' + title + '</a>. Error info: The article already exists!'); else $('#effp_edit' + jqEsc(title)).html('<span class="effp_notice">Edit failed on <a href="' + wgArticlePath.replace("$1", encodeURI(title)) + '" title="' + title + '">' + title + '</a>. Error info: ' + error); }) .always( function { $("#effp_AJAX_finished_" + func_id).css("display", ''); }); if (!nopatrol) { if ($('.patrollink').length) { var patrolhref = $('.patrollink a').attr('href'); var rcid = mw.util.getParamValue('rcid', patrolhref); if (rcid) { $('#effp_status').html($('#effp_status').html + '<li id="effp_patrol' + jqEsc(title) + '">Marking <a href="' + wgArticlePath.replace("$1", encodeURI(title)) + '" title="' + title + '">' + title + ' as patrolled</a></li>'); var patrolrequest = { 'action': 'patrol', 'format': 'json', 'token': mw.user.tokens.get('patrolToken'), 'rcid': rcid }; api.post(patrolrequest) .done(function ( data ) { if ( data ) { $('#effp_patrol' + jqEsc(title)).html('Marked <a href="' + wgArticlePath.replace("$1", encodeURI(title)) + '" title="' + title + '">' + title + '</a> as patrolled'); } else { $('#effp_patrol' + jqEsc(title)).html('<span class="effp_notice">Patrolling failed on <a href="' + wgArticlePath.replace("$1", encodeURI(title)) + '" title="' + title + '">' + title + '</a> with an unknown error'); window.console && console.error('Patrolling failed on %s (%s) with an unknown error.', wgArticlePath.replace("$1", encodeURI(title)), title); } } ) .fail( function ( error ) { $('#effp_patrol' + jqEsc(title)).html('<span class="effp_notice">Patrolling failed on <a href="' + wgArticlePath.replace("$1", encodeURI(title)) + '" title="' + title + '">' + title + '</a>. Error info: ' + error); }); } } } } function effp_escapeHtmlChars(original) { return original.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;"); } function jqEsc(expression) { return expression.replace(/[!"#$%&'*+,.\/:;<=>?@\[\\\]^`{|}~ ]/g, ''); } function effp_generateSelect(title, options, onchange) { var text = '<select name="' + title + '" id="' + title + '" '; if (onchange !== null) text += 'onchange = "' + onchange + '" '; text += '>'; for (var i = 0; i < options.length; i++) { var o = options[i]; text += '<option value="' + effp_escapeHtmlChars(o.value) + '" '; if (o.selected) text += 'selected="selected" '; if (o.disabled) text += 'disabled '; text += '>' + o.label + ' '; } text += " "; return text; } function effp_getPageText(title, show, redirectcheck, timestamp) { if (show) $('#effp_status').html($('#effp_status').html + '<li id="effp_get' + jqEsc(title) + '">Getting <a href="' + wgArticlePath.replace("$1", encodeURI(title)) + '" title="' + title + '">' + title + '</a></li>'); var request = { 'action': 'query', 'prop': 'revisions', 'rvprop': 'content', 'format': 'json', 'indexpageids': true, 'titles' : title }; if (redirectcheck) request.redirects = true; if (timestamp) request.rvprop = 'content|timestamp'; var response = JSON.parse( $.ajax({ url: mw.util.wikiScript('api'), data: request, async: false }) .responseText ); pageid = response['query']['pageids'][0]; if (pageid === "-1") { if (show) $('#effp_get' +jqEsc(title)).html('The page <a class="new" href="' + wgArticlePath.replace("$1", encodeURI(title)) + '" title="' + title + '">' + title + '</a> does not exist'); return ''; } var newtext = response['query']['pages'][pageid]['revisions'][0]['*']; if (redirectcheck && response['query']['redirects'] /* If &redirects if specified but there is no redirect, this stops us from getting an error */){ var oldusername = response['query']['redirects'][0]['from']; var newusername = response['query']['redirects'][0]['to']; if ((typeof(oldusername) !== 'undefined') && (typeof(newusername) !== 'undefined') && (oldusername != newusername)){ usertalkpage = newusername; if (show) { $('#effp_status').html($('#effp_status').html + '<li id="effp_get' + jqEsc(title) + '">Got <a href="' + wgArticlePath.replace("$1", encodeURI(title)) + '" title="' + newusername + '">' + newusername + '</a> (page was renamed from ' + oldusername + ')</li>'); } } else { redirectcheck = false; } } else { redirectcheck = false; } if (show && !redirectcheck)$('#effp_status').html($('#effp_status').html + '<li id="effp_get' + jqEsc(title) + '">Got <a href="' + wgArticlePath.replace("$1", encodeURI(title)) + '" title="' + title + '">' + title + '</a></li>'); if (!timestamp) return newtext; else return {'pagetext':newtext,'timestamp':response['query']['pages'][pageid]['revisions'][0]['timestamp']}; } function effp_init { var pagetext = effp_getPageText(effp_effpPageName, false); var section_re = /==[^=]*==/; pagetext = pagetext.substring(pagetext.search(section_re)); section_re = /==[^=]*==/g; var section_headers = pagetext.match(section_re); for (var i = 0; i < section_headers.length; i++) { var section_start = pagetext.indexOf(section_headers[i]); var section_text = pagetext.substring(section_start); if (i < section_headers.length - 1) { var section_end = section_text.substring(section_headers[i].length).indexOf(section_headers[i + 1]) + section_headers[i].length; section_text = section_text.substring(0, section_end); } effp_effpSections.push(section_text); } for (var i = 0; i < effp_effpSections.length; i++) { var header = effp_effpSections[i].match(section_re)[0]; header = header.slice(2, (header.length - 2)); var submission = { type: 'effp', from: new Array, section: i, blockeduser: '', admin: '', cmt: '', filteruser: '', user: '', title: header, action: 'none', blockuser : false, expiry : '', blockreason: '', blockacc: false, blockemail: false, blocktalk: false, blockauto: false, anononly: false, watch: false, comment: '' }; effp_effpSubmissions.push(submission); effp_numTotal++; } for (var k = 0; k < effp_effpSubmissions.length; k++) { var text = '<ul>'; text += '<li>Response: '; selectoptions = [ { label: 'None', selected: true, value: 'none' }, { label: 'Done (no change to filter)', value: 'done' }, { label: 'Done (may need a change to filter)', value: 'defm' }, { label: 'Not Done (filter working properly)', value: 'notdone' }, { label: 'Not Done (may need a change to filter)', value: 'ndefm' }, { label: 'Not Done (notable people)', value: 'r' }, { label: 'Already Done', value: 'alreadydone' }, { label: 'Decline (edits are vandalism)', value: 'denied' }, { label: 'Checking', value: 'checking' }, { label: 'User blocked', value: 'blocked' }, { label: 'Request on article talk page', value: 'talk' }, { label: 'Fixed filter', value: 'fixed' }, { label: 'Question', value: 'question' }, { label: 'Note', value: 'note' }, { label: 'Private filter', value: 'private' } ]; text += ' <label for="effp_effp_action' + '">Action: ' + effp_generateSelect('effp_effp_action_'+k, selectoptions, 'effp_effp_onActionChange(' + k + ')') + '<div id="effp_effp_extra_' + k + '"> </li>'; text += '</ul></li>'; text += '</ul>'; text += '<input type="button" id="effp_effp_done_button" name="effp_effp_done_button" value="Done" onclick="effp_effp_performActions(' + k + ')" />'; displayMessage_inline(text, 'effp-review-' + effp_effpSubmissions[k].section); } } function effp_effp_performActions(sectionNumber) { for (var i = 0; i < effp_effpSubmissions.length; i++) { var action = $("#effp_effp_action_" + i).val; effp_effpSubmissions[i].action = action; if (action == 'none') continue; if (action == 'blocked') { effp_effpSubmissions[i].blockeduser = $.trim($("#effp_effp_blockeduser_" + i).val); effp_effpSubmissions[i].admin = $.trim($("#effp_effp_admin_" + i).val); effp_effpSubmissions[i].cmt = $.trim($("#effp_effp_cmt_" + i).val); if (document.getElementById('effp_effp_blockuser_'+i).checked) { effp_effpSubmissions[i].blockuser = true; if ($('#effp_effp_blockreason_'+i).val == 'custom') { effp_effpSubmissions[i].blockreason = $.trim($('#effp_effp_customblockreason_'+i).val); } else effp_effpSubmissions[i].blockreason = $.trim($('#effp_effp_blockreason_'+i).val); effp_effpSubmissions[i].expiry = $.trim($('#effp_effp_blockexpiry_'+i).val); effp_effpSubmissions[i].blockacc = document.getElementById('effp_effp_blockacc_'+i).checked; effp_effpSubmissions[i].blockemail = document.getElementById('effp_effp_blockemail_'+i).checked; effp_effpSubmissions[i].blocktalk = document.getElementById('effp_effp_blocktalk_'+i).checked; effp_effpSubmissions[i].anononly = document.getElementById('effp_effp_anononly_'+i).checked; effp_effpSubmissions[i].watch = document.getElementById('effp_effp_watch_'+i).checked; } } if (action == 'fixed') { effp_effpSubmissions[i].filteruser = $.trim($("#effp_effp_filteruser_" + i).val); effp_effpSubmissions[i].cmt = $.trim($("#effp_effp_cmt_" + i).val); } if (action == 'alreadydone') { effp_effpSubmissions[i].user = $.trim($("#effp_effp_user_" + i).val); effp_effpSubmissions[i].cmt = $.trim($("#effp_effp_cmt_" + i).val); } if (action == 'talk') { effp_effpSubmissions[i].talk = $.trim($("#effp_effp_talk_" + i).val); effp_effpSubmissions[i].cmt = $.trim($("#effp_effp_cmt_" + i).val); } if (action != 'none' && action != 'blocked' && action != 'fixed' && action != 'alreadydone') { effp_effpSubmissions[i].cmt = $.trim($("#effp_effp_cmt_" + i).val); } } displayMessage_inline('<ul><li><b>Now processing...</li></ul><ul id="effp_status"></ul><ul id="effp_finish"></ul>', 'effp-review-' + sectionNumber); $('#effp_finish').html('<span id="effp_finished_main" style="display:none"><li id="effp_done">Done (<a href="' + wgArticlePath.replace("$1", encodeURI(effp_effpPageName)) + '?action=purge" title="' + effp_effpPageName + '">Reload page</a>)</li> '); pagetext = effp_getPageText(effp_effpPageName, true); var effp_total = 0; for (var i = 0; i < effp_effpSubmissions.length; i++) { var sub = effp_effpSubmissions[i]; if (pagetext.indexOf(effp_effpSections[sub.section]) == -1) { var $error = $('<li>').text( 'Skipping ' + sub.title + ': Cannot find section. Perhaps it was modified in the mean time?'); $('#effp_status').append( $error ); continue; } var origtext = effp_effpSections[sub.section]; var text = effp_effpSections[sub.section]; var startindex = pagetext.indexOf(effp_effpSections[sub.section]); var endindex = startindex + text.length; if (text === origtext) { var sub_m = effp_effpSubmissions[i]; if (sub_m.blockuser == true) { var match = /\[\[(?:User[_ ]talk:|User:|Special:Contributions\/)([^\||\]\]]*)([^\]]*?)\]\]/i.exec(text); if (match) { var vandal = match[1]; var API = new mw.Api; API.postWithToken("block", { 'action': 'block', 'expiry': sub_m.expiry, 'allowusertalk': !sub_m.blocktalk, 'noemail': sub_m.blockemail, 'anononly': sub_m.anononly, 'autoblock': sub_m.blockauto, 'nocreate': sub_m.blockacc, 'reason': sub_m.blockreason, 'watchuser': sub_m.watch, 'user': vandal }).done(function(blockData) { $('#effp_status').html($('#effp_status').html+'<li>Blocked '+vandal+' succesfully</li>'); effp_effp_templateUser(sub_m.expiry, vandal); }).fail(function(error) { $("#effp_status").html( $('#effp_status').html+"<li>Error blocking <a href='"+mw.util.getUrl('User:'+vandal)+"'>"+vandal+"</a>: "+error+"</li>" ); effp_effp_templateUser(sub_m.expiry, vandal); }); } } if (sub_m.action == 'blocked') { text += '\n*\{\{effp|blocked|'+sub_m.blockeduser+'|'+sub_m.admin+'\}\} '+sub_m.cmt+' \~\~\~\~\n'; effp_total++; } if (sub_m.action == 'fixed') { text += '\n*\{\{effp|fixed|'+sub_m.filteruser+'\}\} '+sub_m.cmt+' \~\~\~\~\n'; effp_total++; } if (sub_m.action == 'alreadydone') { text += '\n*\{\{effp|alreadydone|'+sub_m.user+'\}\} '+sub_m.cmt+' \~\~\~\~\n'; effp_total++; } if (sub_m.action == 'talk') { text += '\n*\{\{effp|talk|'+sub_m.talk+'\}\} '+sub_m.cmt+' \~\~\~\~\n'; effp_total++; } if (sub_m.action == undefined) { // Something went wrong... continue; } if (sub_m.action != 'none' && sub_m.action != 'blocked' && sub_m.action != 'talk' && sub_m.action != 'fixed' && sub_m.action != 'alreadydone') { text += '\n*\{\{effp|'+sub_m.action+'\}\} '+sub_m.cmt+' \~\~\~\~\n'; effp_total++; } pagetext = pagetext.substring(0, startindex) + text + pagetext.substring(endindex); } } var summary; if (effp_total==1) summary = "Responding to "+effp_total+" report"; else summary = "Responding to "+effp_total+" reports"; pagetext = pagetext.replace(/[\n\r]{3,}/g,"\n\n"); pagetext = pagetext.replace(/[\n\r]+==/g,"\n\n=="); effp_editPage(effp_effpPageName, pagetext, summary, false); $(document).ajaxStop(function { $("#effp_finished_main").css("display", ""); }); } function effp_effp_templateUser(duration, vandal) { var monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], d = new Date; var ApI = new mw.Api; ApI.postWithToken( "edit", { action: "edit", section: 'new', watchlist: "nochange", sectiontitle: monthNames[d.getMonth] + ' ' + d.getFullYear, summary: "You have been blocked from editing for abuse of editing privileges."+effp_ending, text: "\n", title: "User talk:"+vandal }).done(function(editData) { $('#effp_status').html($('#effp_status').html+'<li>Successfully posted block notice on <a href="' + wgArticlePath.replace("$1", encodeURI('User talk:'+vandal)) + '">User talk:'+vandal+'</a></li>'); }).fail(function(error) { $('#effp_status').html($('#effp_status').html+'<li>Error posting block notice on <a href="' + wgArticlePath.replace("$1", encodeURI('User talk:'+vandal)) + '">User talk:'+vandal+'</a></li>'); }); } function effp_effp_onActionChange(id) { var extra = $("#effp_effp_extra_" + id); var selectValue = $("#effp_effp_action_" + id).val; if (selectValue == 'none') extra.html(''); if (selectValue == 'blocked' && !Morebits.userIsInGroup('sysop')) { extra.html('<label for="effp_effp_blockeduser_'+id+'">Which user is blocked? <input type="text" '+'name="effp_effp_blockeduser_'+id+'" id="effp_effp_blockeduser_'+id+'"> <label for="effp_effp_admin_'+id+'">Who blocked the user? <input type="text" '+'name="effp_effp_admin_'+id+'" id="effp_effp_admin_'+id+'"> <label for="effp_effp_cmt_'+id+'">Comment: <input type="text" '+'name="effp_effp_cmt_'+id+'" id="effp_effp_cmt_'+id+'">'); } if (selectValue == 'fixed') { extra.html('<label for="effp_effp_filteruser_'+id+'">Who fixed the filter? <input type="text" '+'name="effp_effp_filteruser_'+id+'" id="effp_effp_filteruser_'+id+'"> <label for="effp_effp_cmt_'+id+'">Comment: <input type="text" '+'name="effp_effp_cmt_'+id+'" id="effp_effp_cmt_'+id+'">'); } if (selectValue == 'alreadydone') { extra.html('<label for="effp_effp_user_'+id+'">Who made the edit? <input type="text" '+'name="effp_effp_user_'+id+'" id="effp_effp_user_'+id+'"> <label for="effp_effp_cmt_'+id+'">Comment: <input type="text" '+'name="effp_effp_cmt_'+id+'" id="effp_effp_cmt_'+id+'">'); } if (selectValue == 'talk') { extra.html('<label for="effp_effp_talk_'+id+'">Please enter the article name: <input type="text" '+'name="effp_effp_talk_'+id+'" id="effp_effp_talk_'+id+'"> <label for="effp_effp_cmt_'+id+'">Comment: <input type="text" '+'name="effp_effp_cmt_'+id+'" id="effp_effp_cmt_'+id+'">'); } if (selectValue != 'none' && selectValue != 'blocked' && selectValue != 'fixed' && selectValue != 'talk' && selectValue != 'alreadydone') { extra.html('<label for="effp_effp_cmt_'+id+'">Comment: <input type="text" '+'name="effp_effp_cmt_'+id+'" id="effp_effp_cmt_'+id+'">'); } if (Morebits.userIsInGroup('sysop')) { if (selectValue == 'blocked') { extra.html('<label for="effp_effp_blockeduser_'+id+'">Which user is blocked? <input type="text" '+'name="effp_effp_blockeduser_'+id+'" id="effp_effp_blockeduser_'+id+'"> <label for="effp_effp_admin_'+id+'">Who blocked the user? <input type="text" '+'name="effp_effp_admin_'+id+'" id="effp_effp_admin_'+id+'"> <label for="effp_effp_cmt_'+id+'">Comment: <input type="text" '+'name="effp_effp_cmt_'+id+'" id="effp_effp_cmt_'+id+'"> <input type="checkbox" '+'name="effp_effp_blockuser_'+id+'" id="effp_effp_blockuser_'+id+'" onclick="effp_effp_onActionChange2('+id+');"/><label for="effp_effp_blockuser_'+id+'">Block user <div id="effp_effp_blockuserdiv_'+id+'"> '); } } } function effp_effp_onActionChange2(id) { var blockuserdiv = $("#effp_effp_blockuserdiv_"+id); if (!document.getElementById('effp_effp_blockuser_'+id).checked) { blockuserdiv.html(''); } else { var blockreasons = [ { label: 'None', selected: true, value: '' }, { label: 'Vandalism', value: 'Vandalism' }, { label: 'Deliberately triggering the edit filter', value: 'Deliberately triggering the edit filter' }, { label: 'Disruptive editing', value: 'Disruptive editing' }, { label: 'Clearly not here to contribute to the encyclopedia', value: 'Clearly not here to contribute to the encyclopedia' }, { label: 'Custom', value: 'custom' } ]; var blockexpiration = [ { label: '31 hours', selected: true, value: '31 hours' }, { label: 'indefinite', value: 'indefinite' }, { label: '24 hours', value: '24 hours' }, { label: '48 hours', value: '48 hours' }, { label: '72 hours', value: '72 hours' }, { label: '1 week', value: '1 week' }, { label: '2 weeks', value: '2 weeks' }, { label: '3 weeks', value: '3 weeks' }, { label: '1 month', value: '1 month' }, { label: '2 months', value: '2 months' }, { label: '3 months', value: '3 months' }, { label: '6 months', value: '6 months' }, { label: '1 year', value: '1 year' }, { label: '2 years', value: '2 years' } ]; blockuserdiv.html('<label for="effp_effp_blockreason_'+id+'">Block reason: '+effp_generateSelect('effp_effp_blockreason_'+id, blockreasons)+'<label for="effp_effp_customblockreason_'+id+'"> Custom block reason (if "custom" is selected): <input type="text" id="effp_effp_customblockreason_'+id+'" name="effp_effp_customblockreason_'+id+'"> <label for="effp_effp_customblockreason_'+id+'">Expiration: '+effp_generateSelect('effp_effp_blockexpiry_'+id, blockexpiration)+' <input type="checkbox" '+'name="effp_effp_blockacc_'+id+'" id="effp_effp_blockacc_'+id+'"><label for="effp_effp_blockacc_'+id+'">Prevent account creation <input type="checkbox" '+'name="effp_effp_blockemail_'+id+'" id="effp_effp_blockemail_'+id+'"><label for="effp_effp_blockemail_'+id+'">Prevent user from sending Email <input type="checkbox" '+'name="effp_effp_blocktalk_'+id+'" id="effp_effp_blocktalk_'+id+'"><label for="effp_effp_blocktalk_'+id+'">Block user from editing their talk page <input type="checkbox" '+'name="effp_effp_blockauto_'+id+'" id="effp_effp_blockauto_'+id+'"><label for="effp_effp_blockauto_'+id+'">Automatically block the last IP address used by this user, and any subsequent IP addresses they try to edit from <input type="checkbox" '+'name="effp_effp_anononly_'+id+'" id="effp_effp_anononly_'+id+'"><label for="effp_effp_anononly_'+id+'">Prevent logged-in users from editing from this IP address <input type="checkbox" '+'name="effp_effp_watch_'+id+'" id="effp_effp_watch_'+id+'"><label for="effp_effp_watch_'+id+'">Watch this user'+"'"+'s user and talk pages '); } } function displayMessage_inline(message, div, className) { var divtitle = '#' + div; if (message === '' || message === null) { $(divtitle).empty.hide; return true; } else { var $messageDiv = $(divtitle); $messageDiv.attr('style', "margin:1em;padding:0.5em 2.5%;border:solid 1px #ddd;background-color:#fcfcfc"); if (!$messageDiv.length) { if (mw.util.$content.length) { mw.util.$content.prepend($messageDiv); } else { return false; } } if (typeof message === 'object') { $messageDiv.empty; $messageDiv.append(message); } else { $messageDiv.html(message); } $messageDiv.slideDown; return true; } } function effp_links { var sectionHeaders = $("#mw-content-text h2"); var offset = 1; sectionHeaders.each(function(index, element) { var not_archived = !$(element).next.length || $(element).next.html.indexOf('This is an archived discussion.') == -1; if (index > 0) var idtitle = "effp-review-" + (index - 1); $('<div id="' + idtitle + '" style="display:none;"> ').insertAfter(element); var editSectionLink = $(element).children(".mw-editsection"); if ((editSectionLink.length > 0) && (not_archived)) { editSectionLink = editSectionLink[0]; var reviewlink = document.createElement("a"); reviewlink.href = "#" + idtitle; $(reviewlink).attr("sectionIndex", index + offset); reviewlink.innerHTML = "Review report"; var editSectionContents = $(editSectionLink).html; editSectionLink.innerHTML = "["; editSectionLink.appendChild(reviewlink); editSectionLink.innerHTML = editSectionLink.innerHTML + "] " + editSectionContents; reviewlink.onclick = (function { $(reviewlink).remove; effp_init; }); } else { offset = offset - 1; } }); $('body [sectionIndex]').click((function { $('body [sectionIndex]').each(function(i) { $(this).html("Reviewing reports...").contents.unwrap; }); effp_init; })); } mw.loader.using(['ext.gadget.Twinkle', 'mediawiki.util', 'mediawiki.api', 'mediawiki.Title'], function { $(document).ready( function { setTimeout(function { if ( mw.config.get( "wgPageName" ) === "Wikipedia:Edit_filter/False_positives/Reports" || mw.config.get( "wgPageName" ) === "User:DannyS712/EFFPRH/sandbox" ) { effp_links; } }, 1000); } ); }); //

/* Wugapodes/Capricorn.js */ // // This is a modified version of User:Sam Sailor/Scripts/Sagittarius+.js (Special:PermaLink/899463476) // Docs: User:Wugapodes/Capricorn /*jshint undef:true, latedef:true, shadow:true, loopfunc:true, scripturl:true, undef:true */ /*globals jQuery, mw, importStylesheet */ //////////////////////////////////////////////////////////////////////////////// // Helper function definitions function encodeCodePoint(c) { if (c === 0x20) return '_'; if (c < 0x80) { return '.' + c.toString(16).toUpperCase; } else if (c < 0x800) { return '.' + (0xc0 | (c >>>  6)).toString(16).toUpperCase + '.' + (0x80 | ( c & 0x3f)).toString(16).toUpperCase; } else if (c < 0x10000) { return '.' + (0xe0 | (c >>> 12)).toString(16).toUpperCase + '.' + (0x80 | ((c >>> 6) & 0x3f)).toString(16).toUpperCase + '.' + (0x80 | ( c & 0x3f)).toString(16).toUpperCase; } else if (c < 0x200000) { return '.' + (0xf0 | (c >>> 18)).toString(16).toUpperCase + '.' + (0x80 | ((c >>> 12) & 0x3f)).toString(16).toUpperCase + '.' + (0x80 | ((c >>> 6) & 0x3f)).toString(16).toUpperCase + '.' + (0x80 | ( c & 0x3f)).toString(16).toUpperCase; } else if (c < 0x4000000) { return '.' + (0xf8 | (c >>> 24)).toString(16).toUpperCase + '.' + (0x80 | ((c >>> 18) & 0x3f)).toString(16).toUpperCase + '.' + (0x80 | ((c >>> 12) & 0x3f)).toString(16).toUpperCase + '.' + (0x80 | ((c >>> 6) & 0x3f)).toString(16).toUpperCase + '.' + (0x80 | ( c & 0x3f)).toString(16).toUpperCase; } else if (c < 0x80000000) { return '.' + (0xfc | (c >>> 30)).toString(16).toUpperCase + '.' + (0x80 | ((c >>> 24) & 0x3f)).toString(16).toUpperCase + '.' + (0x80 | ((c >>> 18) & 0x3f)).toString(16).toUpperCase + '.' + (0x80 | ((c >>> 12) & 0x3f)).toString(16).toUpperCase + '.' + (0x80 | ((c >>> 6) & 0x3f)).toString(16).toUpperCase + '.' + (0x80 | ( c & 0x3f)).toString(16).toUpperCase; } } function normaliseAnchor(anchor) { // "." is not escaped! return anchor.replace(/[^0-9A-Za-z_:\.]/g, function (m) { /* [\ud800-\udbff][\udc00-\dfff]| */ if (m.length === 2) { // surrogate pair return encodeCodePoint((m.charCodeAt(0) & 0x3ff) << 10 | m.charCodeAt(1) & 0x3ff); } else { return encodeCodePoint(m.charCodeAt(0)); } }); } function normaliseTitle(title) { try { var t = new mw.Title(title); return t.getPrefixedText; } catch (e) { return null; } } function el(tag, child, attr, events) { var node = document.createElement(tag); if (child) { if ((typeof child === 'string') || (typeof child.length !== 'number')) child = [child]; for (var i = 0; i < child.length; ++i) { var ch = child[i]; if ((ch === void(null)) || (ch === null)) continue; else if (typeof ch !== 'object') ch = document.createTextNode(String(ch)); node.appendChild(ch); } } if (attr) for (var key in attr) { if ((attr[key] === void(0)) || (attr[key] === null)) continue; node.setAttribute(key, String(attr[key])); } if (events) for (var key in events) { var handler = events[key]; if ((key === 'input') && (window.oninput === void(0))) { key = 'change'; } node.addEventListener(key, handler, false); } return node; } function link(child, href, attr, ev) { attr = attr || {}; ev = ev || {}; if (typeof attr === 'string') { attr = { "title": attr }; } if (typeof href === 'string') attr.href = href; else { attr.href = 'javascript:void(null);'; ev.click = href; } return el('a', child, attr, ev); } var templateGroups = { "fromRelatedInfo": "Related information, From", "toRelatedInfo": "Related information, To", "fromPartOfSpeech": "Parts of speech, From", "fromEngVar": "English variant spelling, From", "fromOrthographicModification": "Orthographic difference, From", "toOrthographicModification": "Orthographic difference, To", "fromAlt": "Alternative names, From", "fromDisambiguation": "Ambiguity, From", //"toDisambiguation": "Ambiguity, To", //"fromSpecificity": "Specificity, From", "fromAnthroponym": "Anthroponym, From", "fromFiction": "Fiction, From", "fromWork": "Works of art and works generally, From", "toWork": "Works of art and works generally, To", "fromLocationOrInfrastructure": "Geographic location or infrastructure, From", "fromFormerName": "Former names, From", "toFormerName": "Former names, To", "fromSystematicName": "Systematic name, From", "toSystematicName": "Systematic name, To", "fromPostal": "From postal information", "fromOrganization": "From organization", "fromMath": "From mathematical topic", "fromComic": "Comics, From", "toComic": "Comics, To", "fromMiddleEarth": "Middle-earth topic, From", "toMiddleEarth": "Middle-earth topic, To", "fromMisc": "From miscellaneous information", "fromMeta": "Meta information, From", "toMeta": "Meta information, To", "fromProtected": "Protection level, From", "toNameSpace": "Namespaces, To", "fromPrintworthiness":"Printworthiness" }; //////////////////////////////////////////////////////////////////////////////// // Callback functions function mainCallback(aliasJSON, templateJSON) { var templateAliases = aliasJSON; var redirectTemplates = templateJSON; //console.log(templateAliases); //console.log(redirectTemplates); // 'use strict'; importStylesheet('User:Wugapodes/Capricorn.css');

var wgNamespaceIds = mw.config.get('wgNamespaceIds'); var contentText = document.getElementById('mw-content-text'); var firstHeading = document.getElementById('firstHeading'); var redirMsg = contentText.getElementsByClassName('redirectMsg')[0]; var uiWrapper = el('div'); var edittoken = null;

function MarkupBlob(markup) { if (!markup) { this.target = ''; this.rcatt = {}; this.tail = ''; } else this.parse(markup); } MarkupBlob.prototype.parse = function (markup) { var rdrx = /^#REDIRECT:?\s*\[\[\s*([^\|{}[\]]+?)\s*]]\s*/i; var tprx = /^\s*{{([A-Za-z ]+)((?:\|(?:[^|{}]*|{{[^|}]*}})+)*)}}\s*/i; var m; m = rdrx.exec(markup.trim) markup = markup.substr(m[0].length); this.target = m[1]; this.rcatt = {}; out: while ((m = tprx.exec(markup))) { var alias = normaliseTitle(m[1]); while (templateAliases[alias]) alias = templateAliases[alias]; // hopefully there are no loops. if (alias === "This is a redirect") { var params = m[2].split('|'); for (var j = 0; j < params.length; ++j) { if (!params[j]) continue; if (params[j].indexOf('=') !== -1) break out; alias = normaliseTitle("R " + params[j]); while (templateAliases[alias]) alias = templateAliases[alias]; // hopefully there are still no loops. if (alias in redirectTemplates) this.rcatt[alias] = true; else break out; } } else if (alias === "Redirect category shell") { var mm, rr = //g; while (mm = rr.exec(m[2])) { alias = normaliseTitle(mm[1]); while (templateAliases[alias]) alias = templateAliases[alias]; if (alias in redirectTemplates) this.rcatt[alias] = true; } } else if (alias in redirectTemplates) { if (m[2]) // TODO break; this.rcatt[alias] = true; } else { break; } markup = markup.substr(m[0].length); } this.tail = markup; }; MarkupBlob.prototype.toString = function { var markup = '#REDIRECT ' + this.target + '\n'; var tail = ''; var wrapped = []; for (var key in this.rcatt) { if (this.rcatt[key]) if ((wrapped.length < 6) && /^R\s+/.test(key)) wrapped.push('\n'); else tail += '\n'; } if (wrapped.length) markup += "\n\n"; markup += tail + '\n'; markup += this.tail; return markup; }; function buildTagList(rcatt) { function makeCheckBox(key) { return el('label', [ el('input', null, { type: "checkbox", checked: (key in rcatt) ? "checked" : null, }, { change: function (ev) { rcatt[key] = this.checked; } }), ' ', redirectTemplates[key].label ], { "title": redirectTemplates[key].tooltip }); } var list = el('dl', null, { "class": "tag-list" }); var group = {}; for (var key in templateGroups) { list.appendChild(el('dt', templateGroups[key])); list.appendChild(el('dd', group[key] = el('ul'))); } for (var key in redirectTemplates) { var label = makeCheckBox(key); group[redirectTemplates[key].group].appendChild(el('li', label)); } var collapsibleContent = el('div', list, { "class": "mw-collapsible-content", "id": "capricorn-toggle-content" }) return collapsibleContent; } // // Interface creation // function buildEditingUI(mblob, saveCallback) { var statusbar; var needsCheck = true; var doSave; var uiLink, uiTarget; mblob = mblob || new MarkupBlob; // Change text of status bar function setStatus(status) { while (statusbar.firstChild) // Remove previous statuses statusbar.removeChild(statusbar.firstChild); if (status) { // If status is a string, add it if (typeof status === 'string') statusbar.appendChild(document.createTextNode(status)); else { // Otherwise, loop through list and add statuses for (var j = 0; j < status.length; ++j) { if (typeof status[j] === 'string') statusbar.appendChild(document.createTextNode(status[j])); else statusbar.appendChild(status[j]); } } } } // Check if the target has changed?? // Not actually sure what this does yet 21 Oct 2019 function inputChanged(ev) { /*jshint validthis:true */ try { mblob.target = this.value; var t = new mw.Title(this.value); var frag = t.getFragment ? '#' + normaliseAnchor(t.getFragment) : ''; if (uiLink) uiLink.href = mw.util.getUrl(t.getPrefixedDb, { redirect: "no" }) + frag; setStatus; } catch (e) { setStatus('Invalid title.'); if (uiLink) uiLink.href = 'javascript:void(0);'; } needsCheck = true; } var uiStatusLine; var patrolLine; var origTarget = mblob.target var ui = el('form', [ el('div', [ el('ul', [ el('li', [ uiTarget = el('input', null, { 'type': 'text', 'class': 'redirectText', 'value': mblob.target }, { 'input': inputChanged, 'change': inputChanged, 'blur': function (ev) { // i would not have to write this, if it were not for jQuery. seriously. if (mblob.target === this.value) return; inputChanged.call(this, ev); } }) ]) ], { 'class': 'redirectText' }), el('input', null, { "type": "button", "class":"capricorn-toggle", "id": "capricorn-toggle-button", "value": "Hide rcat list" }, { 'click': function { $( "#capricorn-toggle-content" ).toggle; var buttonText = $("#capricorn-toggle-button")[0].value; if (buttonText === "Hide rcat list") { $("#capricorn-toggle-button")[0].value = "Show rcat list" } else { $("#capricorn-toggle-button")[0].value = "Hide rcat list" }; } }) ], { 'class': 'redirectMsg' }), buildTagList(mblob.rcatt), uiStatusLine = el('p', [ patrolLine = el('span', [], {}), statusbar = el('span', [], { 'class': 'status-line' }), el('span', [ link(["Statistics for this page"], 'https://tools.wmflabs.org/pageviews?project=en.wikipedia.org&pages=' + encodeURIComponent(mw.config.get('wgPageName'))), ' • ', link(["WP:TMR"], mw.util.getUrl("Wikipedia:Template messages/Redirect pages")), ' • ', link(["About Capricorn"], mw.util.getUrl("User:Wugapodes/Capricorn")) ], { 'style': 'float: right;' }) ]) ], { 'action': 'javascript:void(0)', 'class': 'kephir-sagittarius-editor' }, { 'submit': function (ev) { ev.preventDefault; if (uiStatusLine.childNodes[1].childNodes[0]) { var patrolVal = uiStatusLine.childNodes[1].childNodes[0].childNodes[0].checked; if (patrolVal) { api.get({ "action": "query", "format": "json", "prop": "revisions", "meta": "tokens", "titles": mw.config.get('wgPageName'), "rvprop": "ids", "rvslots": "", "rvlimit": "1", "rvdir": "newer", "type": "patrol" }, { success: function (result) { //console.log(mw.config.get('wgPageName')) var patrolToken = result["query"]["tokens"]["patroltoken"]; var revIDpart = result["query"]["pages"]; var revID = null; for (var page in revIDpart) { revID = revIDpart[page]["revisions"][0]["revid"]; } //console.log(revID) api.post({ "action": 'patrol', "revid": revID, "token": patrolToken }, { success: function (result) { if (result.error) { console.log(result.error); setStatus([ 'API error: "', result.error.info, '" [code: ', el('code', [result.error.code]), ']' ]); console.log(result.error) return; } } }); } }); } } ui.doCheck(saveCallback); } }); ui.statusLine = uiStatusLine; ui.patrolLine = patrolLine; ui.origTarget = origTarget var sectCache = {}; var $uiTarget = jQuery(uiTarget); $uiTarget.suggestions({ submitOnClick: false, delay: 500, fetch: function (query) { $uiTarget.suggestions('suggestions', []); if (query.indexOf('#') !== -1) { var title = query.substr(0, query.indexOf('#')); var sect = query.substr(query.indexOf('#') + 1); if (sectCache[title]) { var normSect = normaliseAnchor(sect); $uiTarget.suggestions('suggestions', sectCache[title].filter(function (item) { var norm = normaliseAnchor(item.anchor); return norm.substr(0, normSect.length) === normSect; }) ); return; } api.get({ action: 'parse', page: title, prop: 'sections|properties', redirects: '1' }).then(function (result) { if (result.parse.redirects && result.parse.redirects.length) { // XXX return; } var disambig = false; // XXX var normSect = normaliseAnchor(sect); sectCache[title] = result.parse.sections.map(function (item) { return { anchor: item.anchor, title: title + '#' + decodeURIComponent(item.anchor.replace(/_/g, ' ').replace(/\.([0-9A-Fa-f][0-9A-Fa-f])/g, '%')), // XXX: hack disambig: disambig, toString: function { return this.title; } }; }); $uiTarget.suggestions('suggestions', sectCache[title].filter(function (item) { var norm = normaliseAnchor(item.anchor); return norm.substr(0, normSect.length) === normSect; }) ); }); return; } api.get({ action: 'query', generator: 'allpages', gapprefix: query, gaplimit: 16, prop: 'info|pageprops', }).then(function (result) { var pglist = []; for (var pgid in result.query.pages) { var page = result.query.pages[pgid]; pglist.push({ title: page.title, pageid: page.pageid, disambig: page.pageprops && ('disambiguation' in page.pageprops), redirect: 'redirect' in page, toString: function { return this.title; } }); } $uiTarget.suggestions('suggestions', pglist); }); }, result: { render: function (item, content) { var elm = this[0]; elm.appendChild(el('span', [item.title], { style: item.redirect ? 'font-style: italic' : '' })); if (item.disambig) elm.appendChild(el('small', [' (disambiguation page)'])); if (item.redirect) elm.appendChild(el('small', [' (redirect)'])); }, select: function ($textbox) { var item = this.data('text'); var textbox = $textbox[0]; textbox.value = item.title; if (item.redirect) { api.get({ action: 'query', pageids: item.pageid, redirects: '1' }).then(function (result) { var redir = result.query.redirects.pop; textbox.value = redir.to + (redir.tofragment ? '#' + redir.tofragment : ''); }); } return true; } } }); ui.doCheck = function (callback) { var that = this; if (!/^\s*[^\|{}[\]]+\s*$/.test(mblob.target)) { setStatus(['Error: the target page name is invalid.']); return; } if (needsCheck) { var oldTarget = mblob.target; var normTarget; try { normTarget = new mw.Title(oldTarget); } catch (e) { setStatus(['"', oldTarget, '" is not a valid page name. Try again to proceed anyway.']); return; } setStatus(['Checking target validity...']); needsCheck = false; api.get({ action: 'parse', page: oldTarget = mblob.target, prop: 'sections', redirects: '1' }, { success: function (result) { var m; if (result.error) { if (result.error.code === 'missingtitle') { setStatus([ 'Error: The target page "', link([normTarget.getPrefixedText], mw.util.getUrl(normTarget.getPrefixedText, { "class": "new" })), '" does not exist. Try again to proceed anyway.' ]); } else { setStatus([ 'API error: "', result.error.info, '" [code: ', el('code', [result.error.code]), ']' ]); } return; } if (result.parse.redirects && result.parse.redirects[0]) { var newTarget = result.parse.redirects[0].to + (result.parse.redirects[0].tofragment ? "#" + result.parse.redirects[0].tofragment : ""); setStatus([ 'Error: The target page "', link([normTarget.getPrefixedText], mw.util.getUrl(normTarget.getPrefixedText, { redirect: "no" })), '" is already a redirect to "', link([newTarget], mw.util.getUrl(newTarget, { redirect: "no" })), '". Try again to proceed anyway, or ', link(['retarget this redirect to point there directly'], function { uiTarget.value = mblob.target = newTarget + ((!result.parse.redirects[0].tofragment && normTarget.fragment) ? '#' + normTarget.fragment : ''); needsCheck = true; }), '.' ]); return; } if (normTarget.fragment) { // we have a section link var sect = normaliseAnchor(normTarget.fragment); var isValidSect = false; var sectlist = result.parse.sections; for (var j = 0; j < sectlist.length; ++j) { if (sectlist[j].anchor === sect) isValidSect = true; } if (!isValidSect) { setStatus([ 'Error: The target page "', link([normTarget.getPrefixedText], mw.util.getUrl(normTarget.getPrefixedText, { redirect: "no" })), '" does not have a a section called "', normTarget.fragment, '". Try again to proceed anyway.' ]); return; } } callback(setStatus); } }); return; } callback(setStatus); }; return ui; } function setSummary(current,orig) { var summary; if (orig === current) { summary= "Modifying redirect categories using Capricorn ♑"; } else { summary = 'Redirecting to ' + current + ' (♑)' } return summary } if ((mw.config.get('wgAction') === 'view') && (mw.config.get('wgArticleId') === 0)) { // nonexistent page. uiWrapper.appendChild(el('div', [ link(['Create a redirect'], function { while (uiWrapper.hasChildNodes) uiWrapper.removeChild(uiWrapper.firstChild); var mblob = new MarkupBlob; var ui = buildEditingUI(mblob, function (setStatus) { setStatus(['Saving...']); var summary = setSummary(mblob.target,ui.origTarget) api.post({ action: 'edit', title: mw.config.get('wgPageName'), createonly: 1, summary: summary, text: mblob.toString, token: mw.user.tokens.get('csrfToken') }, { success: function (result) { if (result.error) { setStatus([ 'API error: "', result.error.info, '" [code: ', el('code', [result.error.code]), ']' ]); return; } setStatus(['Saved. Reloading page...']); if (/redirect=no/.test(location.href)) // XXX location.reload; else location.search = location.search ? location.search + '&redirect=no' : '?redirect=no'; } }); }); ui.statusLine.insertBefore(el('input', null, { type: 'submit', value: 'Save' }), ui.statusLine.firstChild); uiWrapper.appendChild(ui); }), ' from this page with Capricorn' ], { "class": "kephir-sagittarius-invite" })); contentText.parentNode.insertBefore(uiWrapper, contentText); } else if ((mw.config.get('wgAction') === 'view') && mw.config.get('wgIsRedirect') && redirMsg) { // start editor immediately uiWrapper.appendChild(el('div', ['Loading page source…'], { "class": "kephir-sagittarius-loading" })); contentText.insertBefore(uiWrapper, contentText.firstChild); api.get({ action: 'query', prop: 'revisions', rvprop: 'timestamp|content', pageids: mw.config.get('wgArticleId'), rvstartid: mw.config.get('wgRevisionId'), rvlimit: 1, rvdir: 'older' }, { success: function (result) { if (result.error) { uiWrapper.appendChild(el('div', [ 'API error: "', result.error.info, '" [code: ', el('code', [result.error.code]), ']. Reload to try again.' ], { "class": "kephir-sagittarius-error" })); return; } while (uiWrapper.hasChildNodes) uiWrapper.removeChild(uiWrapper.firstChild); var page = result.query.pages[mw.config.get('wgArticleId')]; var mblob; var token = mw.user.tokens.get('csrfToken') try { mblob = new MarkupBlob(page.revisions[0]['*']); } catch(e) { uiWrapper.appendChild(el('div', ['Error: unable to parse page. Edit the source manually.'], { "class": "kephir-sagittarius-error" })); return; } redirMsg.parentNode.removeChild(redirMsg); var ui = buildEditingUI(mblob, function (setStatus) { setStatus(['Saving...']); var summary = setSummary(mblob.target,ui.origTarget) api.post({ action: 'edit', title: mw.config.get('wgPageName'), basetimestamp: page.revisions[0].timestamp, summary: summary, text: mblob.toString, token: mw.user.tokens.get('csrfToken') }, { success: function (result) { if (result.error) { setStatus([ 'API error: "', result.error.info, '" [code: ', el('code', [result.error.code]), ']' ]); return; } setStatus(['Saved. Reloading page...']); if (/redirect=no/.test(location.href)) // XXX location.reload; else location.search = location.search ? location.search + '&redirect=no' : '?redirect=no'; } }); }); var userName = mw.user.getName; api.get({ "action": "query", "format": "json", "list": "users", "usprop": "groups", "ususers": userName }, { success: function(result) { var groups = result["query"]["users"][0]["groups"] if (groups.includes("patroller")) { ui.patrolLine.insertBefore(el('label', [ el('input', [], { 'class': 'checkbox', 'type': 'checkbox', 'id': 'patrol', 'value': 'patrol' }), 'Mark as patrolled?']),null); } } }); ui.statusLine.insertBefore(el('input', null, { type: 'submit', value: 'Save' }), ui.statusLine.firstChild); uiWrapper.appendChild(ui); } }); } else if ((mw.config.get('wgPageContentModel') === 'wikitext') && ((mw.config.get('wgAction') === 'edit') || (mw.config.get('wgAction') === 'submit'))) { if (mw.util.getParamValue('section')) return; var editform = document.getElementById('editform'); if (!editform || !editform.wpTextbox1 || editform.wpTextbox1.readOnly) return; var uiPivot = document.getElementsByClassName('wikiEditor-ui')[0]; var ui, mblob; firstHeading.appendChild(document.createTextNode(' ')); firstHeading.appendChild(link(['♑'], function { if (ui && ui.parentNode) ui.parentNode.removeChild(ui); try { mblob = new MarkupBlob(editform.wpTextbox1.value); } catch (e) { alert("Error: unable to parse page. This page is probably not a redirect."); return; } currentTarget = mblob.target ui = buildEditingUI(mblob, function { editform.wpSummary.value = 'Redirecting to ' + mblob.target + ' (♑)'; editform.wpTextbox1.value = mblob.toString; mblob = null; ui.style.display = 'none'; uiPivot.style.display = ''; }); ui.style.display = 'none'; ui.statusLine.insertBefore(el('input', null, { type: "button", value: "Cancel", }, { click: function { mblob = null; ui.style.display = 'none'; uiPivot.style.display = ''; } }), ui.statusLine.firstChild); ui.statusLine.insertBefore(el('input', null, { type: "submit", value: "Check" }), ui.statusLine.firstChild); uiPivot.parentNode.insertBefore(ui, uiPivot); uiPivot.style.display = 'none'; ui.style.display = ''; }, { "class": "kephir-sagittarius-editlink", "title": "Edit this redirect with Capricorn" })); var submitButton; var inputs = editform.getElementsByTagName('input'); for (var i = 0; i < inputs.length; ++i) { inputs[i].addEventListener('click', function (ev) { submitButton = this; }, false); } editform.addEventListener('submit', function (ev) { if (submitButton !== editform.wpSave) return; if (mblob) { ev.preventDefault; ev.stopImmediatePropagation; ui.doCheck(function (setStatus) { setStatus(['Proceeding with saving...']); var summary = setSummary(currentTarget,ui.origTarget) editform.wpTextbox1.value = mblob.toString; editform.wpSummary.value = summary; mblob = null; editform.submit; }); } }, false); } if (!window.kephirSagittariusFollowCategoryRedirects) if ((mw.config.get('wgAction') === 'view') && (mw.config.get('wgNamespaceNumber') === wgNamespaceIds.category)) { var pagesList = document.getElementById('mw-pages').getElementsByClassName('mw-redirect'); for (var i = 0; i < pagesList.length; ++i) { pagesList[i].href += '?redirect=no'; } } } function abortConditions { if (window.location.href.includes('&diff=')) { throw 'Capricorn does not run when viewing page diffs. Please revert before editing redirect.'; } if (mw.config.get('wgNamespaceNumber') < 0) { throw 'Page is in a virtual namespace. Capricorn aborts.'; } } function Capricorn { $.getJSON("https://en.wikipedia.org/w/index.php?title=User:Wugapodes/Capricorn/RedirectAliases.json&action=raw&ctype=application/json", function(aliasJSON) { $.getJSON("https://en.wikipedia.org/w/index.php?title=User:Wugapodes/Capricorn/RedirectTemplates.json&action=raw&ctype=application/json",function(templateJSON) { mw.loader.using(['jquery.suggestions', 'mediawiki.api', 'mediawiki.Title', 'mediawiki.action.view.redirectPage'], function { mainCallback(aliasJSON,templateJSON); }); }); }); } var api = new mw.Api; api.get({ action: "query", format: "json", prop: "info", formatversion: 2, titles: mw.config.get('wgPageName') }, { success: function (result) { try { abortConditions; } catch(abortMessage) { console.info(abortMessage) return; } if (result.query.pages[0].redirect || (window.location.href.includes('&redlink=1'))) { Capricorn; } else { console.debug('Page is not a redirect.') return; } } }); /*

//

/* Novem Linguae/Scripts/GANReviewTool.js */ // // === Compiled with Novem Linguae's publish.php script ====================== $(async function { // === GANReviewTool.js ====================================================== //

$(async function { let ganController = new GANReviewController; await ganController.execute($, mw, location, new GANReviewWikicodeGenerator, new GANReviewHTMLGenerator); let garController = new GARCloserController; await garController.execute($, mw, location, new GARCloserWikicodeGenerator, new GARCloserHTMLGenerator); // TODO: create an environment class such as Environment, State, Output, or GANReviewIO. It can wrap all the IO junk such as $, mw, etc. Should make it easier to mock and unit test. // TODO: extract utility functions such as escapeRegEx into a GARReviewToolUtil class // TODO: create Page object? could store title, wikicode, etc. }); // // === modules/GANReviewController.js ======================================================

class GANReviewController { /** * @param {function} $ jQuery * @param {Object} mw mediawiki, https://doc.wikimedia.org/mediawiki-core/master/js/#!/api/mw * @param {Location} location https://developer.mozilla.org/en-US/docs/Web/API/Window/location * @param {GANReviewWikicodeGenerator} wg * @param {GANReviewHTMLGenerator} hg */ async execute($, mw, location, wg, hg) { if ( arguments.length !== 5 ) throw new Error('Incorrect # of arguments'); this.$ = $; this.mw = mw; this.location = location; this.wg = wg; this.hg = hg; this.ganReviewPageTitle = this.mw.config.get('wgPageName'); // includes namespace, underscores instead of spaces this.ganReviewPageTitle = this.ganReviewPageTitle.replace(/_/g, ' '); // underscores to spaces. prevents some bugs later if ( ! this.shouldRunOnThisPageQuickChecks(this.ganReviewPageTitle) ) return; if ( ! await this.shouldRunOnThisPageSlowChecks ) return; this.displayForm; await this.warnUserIfNotReviewCreator; this.handleUserChangingFormType; this.$(`#GANReviewTool-Submit`).on('click', async => { await this.clickSubmit; }); } /** * @private */ async clickSubmit { if ( arguments.length !== 0 ) throw new Error('Incorrect # of arguments'); this.readFormAndSetVariables; let hasFormValidationErrors = this.validateForm; if ( hasFormValidationErrors ) { return; } this.$(`#GANReviewTool-Form`).hide; this.$(`#GANReviewTool-ProcessingMessage`).show; this.editSummarySuffix = ' (GANReviewTool)'; this.reviewTitle = this.ganReviewPageTitle; this.error = false; try { if ( this.passOrFail === 'pass' ) { await this.doPass; } else if ( this.passOrFail === 'fail' ) { await this.doFail; } } catch(err) { this.pushStatus(' An error occurred :( '); this.error = err; } await this.writeToLog; if ( ! this.error ) { this.pushStatus('Script complete. Refreshing page.'); // TODO: 1 second delay? location.reload; } } /** * @return {boolean} hasFormValidationErrors * @private */ validateForm { this.$(`.GANReviewTool-ValidationError`).hide; let hasFormValidationErrors = false; // if pass, a WP:GA subpage heading must be selected if ( this.passOrFail === 'pass' && ! this.detailedTopic ) { this.$(`#GANReviewTool-NoTopicMessage`).show; hasFormValidationErrors = true; } // "Wikicode to display" text box must not contain a pipe. Prevents this kind of thing from being written to the WP:GA subpages: HMS Resistance (1801)|HMS Resistance (1801) if ( this.$(`[name="GANReviewTool-DisplayWikicode"]`).val.includes('|') ) { this.$(`#GANReviewTool-NoPipesMessage`).show; hasFormValidationErrors = true; } return hasFormValidationErrors; } /** * @private */ async doPass { if ( arguments.length !== 0 ) throw new Error('Incorrect # of arguments'); this.editSummary = `promote ${this.gaTitle} to good article` + this.editSummarySuffix; this.gaSubpageShortTitle = this.$(`[name="GANReviewTool-Topic"]`).val; if ( this.needsATOP ) { await this.processPassForGANPage; } await this.processPassForTalkPage; await this.processPassForGASubPage; } /** * @private */ async doFail { if ( arguments.length !== 0 ) throw new Error('Incorrect # of arguments'); this.editSummary = `close ${this.gaTitle} good article nomination as unsuccessful` + this.editSummarySuffix; if ( this.needsATOP ) { await this.processFailForGANPage; } await this.processFailForTalkPage; } /** * @private */ async processFailForGANPage { if ( arguments.length !== 0 ) throw new Error('Incorrect # of arguments'); this.pushStatus('Placing and  on GA review page.'); let reviewWikicode = await this.getWikicode(this.ganReviewPageTitle); // get this wikicode again, in case it changed between page load and "submit" button click reviewWikicode = this.wg.getFailWikicodeForGANPage(reviewWikicode); this.reviewRevisionID = await this.makeEdit(this.reviewTitle, this.editSummary, reviewWikicode); } /** * @private */ async processFailForTalkPage { if ( arguments.length !== 0 ) throw new Error('Incorrect # of arguments'); this.pushStatus('Deleting from article talk page.'); this.pushStatus('Adding or  to article talk page.'); let talkWikicode = await this.getWikicode(this.gaTalkTitle); // get this wikicode again, in case it changed between page load and "submit" button click talkWikicode = this.wg.getFailWikicodeForTalkPage(talkWikicode, this.reviewTitle); this.talkRevisionID = await this.makeEdit(this.gaTalkTitle, this.editSummary, talkWikicode); } /** * @private */ async processPassForTalkPage { if ( arguments.length !== 0 ) throw new Error('Incorrect # of arguments'); this.pushStatus('Deleting from article talk page.'); this.pushStatus('Adding or  to article talk page.'); this.pushStatus('Changing WikiProject template class parameters to GA on article talk page.'); let talkWikicode = await this.getWikicode(this.gaTalkTitle); // get this wikicode again, in case it changed between page load and "submit" button click talkWikicode = this.wg.getPassWikicodeForTalkPage(talkWikicode, this.reviewTitle, this.gaSubpageShortTitle); this.talkRevisionID = await this.makeEdit(this.gaTalkTitle, this.editSummary, talkWikicode); } /** * @private */ async processPassForGASubPage { if ( arguments.length !== 0 ) throw new Error('Incorrect # of arguments'); this.pushStatus('Adding to appropriate subpage of WP:GA'); let gaSubpageLongTitle = `Wikipedia:Good articles/` + this.gaSubpageShortTitle; let gaDisplayTitle = this.$(`[name="GANReviewTool-DisplayWikicode"]`).val; let gaSubpageWikicode = await this.getWikicode(gaSubpageLongTitle); gaSubpageWikicode = this.wg.getPassWikicodeForGAListPage(this.detailedTopic, gaSubpageWikicode, this.gaTitle, gaDisplayTitle); this.gaRevisionID = await this.makeEdit(gaSubpageLongTitle, this.editSummary, gaSubpageWikicode); } /** * @private */ async processPassForGANPage { if ( arguments.length !== 0 ) throw new Error('Incorrect # of arguments'); this.pushStatus('Placing and  on GA review page.'); let reviewWikicode = await this.getWikicode(this.ganReviewPageTitle); // get this wikicode again, in case it changed between page load and "submit" button click reviewWikicode = this.wg.getPassWikicodeForGANPage(reviewWikicode); this.reviewRevisionID = await this.makeEdit(this.reviewTitle, this.editSummary, reviewWikicode); } /** * @private */ readFormAndSetVariables { if ( arguments.length !== 0 ) throw new Error('Incorrect # of arguments'); this.passOrFail = this.$(`[name="GANReviewTool-PassOrFail"]:checked`).val; this.needsATOP = this.$(`[name="GANReviewTool-ATOPYesNo"]`).is(":checked"); this.detailedTopic = document.querySelector(`[name="GANReviewTool-Topic"]`); // TODO: change this to jquery, so less dependencies, more unit testable this.detailedTopic = this.detailedTopic.options[this.detailedTopic.selectedIndex]; this.detailedTopic = this.detailedTopic.text; } /** * Show or hide different parts of the form depending on whether the user clicks pass or fail. * @private */ handleUserChangingFormType { if ( arguments.length !== 0 ) throw new Error('Incorrect # of arguments'); this.$(`[name="GANReviewTool-PassOrFail"]`).on('change', => { if ( this.$(`[name="GANReviewTool-PassOrFail"]:checked`).val === 'pass' ) { this.$(`#GANReviewTool-PassDiv`).show; } else { this.$(`#GANReviewTool-PassDiv`).hide; this.$(`#GANReviewTool-NoTopicMessage`).hide; } }); } /** * Show a warning if viewer is not the creator of the GAN Review page. This is to help prevent accidentally closing the wrong GAN Review. * @private */ async warnUserIfNotReviewCreator { if ( arguments.length !== 0 ) throw new Error('Incorrect # of arguments'); let pageCreator = await this.getPageCreator(this.ganReviewPageTitle); if ( pageCreator !== this.mw.config.get('wgUserName') ) { this.$('.GANReviewTool-NotCreatorNotice').show; } else { this.$('#GANReviewTool-MainForm').show; } this.$('#GANReviewTool-ReviewAnywayLink').on('click', => { this.$('.GANReviewTool-NotCreatorNotice').hide; this.$('#GANReviewTool-MainForm').show; }); } /** * @private */ displayForm { if ( arguments.length !== 0 ) throw new Error('Incorrect # of arguments');

this.$('#mw-content-text').prepend(this.hg.getHTML(this.gaTitle)); } /** * @private */ async shouldRunOnThisPageSlowChecks { if ( arguments.length !== 0 ) throw new Error('Incorrect # of arguments'); // only run if this review hasn't already been closed. check for let reviewWikicode = await this.getWikicode(this.ganReviewPageTitle); if ( reviewWikicode.match(/\{\{atop/i) ) { return false; } // only run if talk page has this.gaTitle = this.getGATitle(this.ganReviewPageTitle); this.gaTalkTitle = this.getGATalkTitle(this.gaTitle); let talkWikicode = await this.getWikicode(this.gaTalkTitle); if ( this.ganReviewPageTitle !== 'User:Novem_Linguae/sandbox' && ! talkWikicode.match(/\{\{GA nominee/i) ) { return false; } return true; } /** * @private */ async writeToLog { if ( arguments.length !== 0 ) throw new Error('Incorrect # of arguments'); // always log no matter what. hopefully log some errors so I can fix them this.pushStatus('Adding to log'); let username = this.mw.config.get('wgUserName'); let textToAppend = this.wg.getLogMessageToAppend(username, this.passOrFail, this.reviewTitle, this.reviewRevisionID, this.talkRevisionID, this.gaRevisionID, this.error); await this.appendToPage('User:Novem Linguae/Scripts/GANReviewTool/GANReviewLog', this.editSummary, textToAppend); } /** * @private */ async getWikicode(title) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); let api = new this.mw.Api; let params = { "action": "parse", "page": title, "prop": "wikitext", "format": "json", }; let result = await api.post(params); if ( result['error'] ) return ''; let wikicode = result['parse']['wikitext']['*']; return wikicode; } /** * @private */ async makeEdit(title, editSummary, wikicode) { if ( arguments.length !== 3 ) throw new Error('Incorrect # of arguments'); let api = new this.mw.Api; let params = { "action": "edit", "format": "json", "title": title, "text": wikicode, "summary": editSummary, }; let result = await api.postWithToken('csrf', params); let revisionID = result['edit']['newrevid']; return revisionID; } /** * @private */ async getPageCreator(title) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); let api = new this.mw.Api; let params = { action: 'query', prop: 'revisions', titles: title, rvlimit: 1, rvprop: 'user', rvdir: 'newer', format: 'json', }; let result = await api.post(params); let wikicode = result['query']['pages'] let key = Object.keys(wikicode)[0]; wikicode = wikicode[key]['revisions'][0]['user']; return wikicode; } /** * Lets you append without getting the Wikicode first. Saves an API query. * @private */ async appendToPage(title, editSummary, wikicodeToAppend) { if ( arguments.length !== 3 ) throw new Error('Incorrect # of arguments'); let api = new this.mw.Api; let params = { "action": "edit", "format": "json", "title": title, "appendtext": wikicodeToAppend, "summary": editSummary, }; let result = await api.postWithToken('csrf', params); let revisionID = result['edit']['newrevid']; return revisionID; } /** * @private */ pushStatus(statusToAdd) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); this.$(`#GANReviewTool-ProcessingMessage > p`).append(' ' + statusToAdd); } /** * @private */ shouldRunOnThisPageQuickChecks(title) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); // don't run when not viewing articles let action = this.mw.config.get('wgAction'); if ( action != 'view' ) return false; // don't run when viewing diffs let isDiff = this.mw.config.get('wgDiffNewId'); if ( isDiff ) return false; let isDeletedPage = ( ! this.mw.config.get('wgCurRevisionId') ); if ( isDeletedPage ) return false; // always run in Novem's sandbox if ( title === 'User:Novem_Linguae/sandbox' ) return true; // only run in talk namespace let namespace = this.mw.config.get('wgNamespaceNumber'); let isTalkNamespace = ( namespace === 1 ); if ( ! isTalkNamespace ) return false; // only run on pages that end in /GA## if ( ! this.isGASubPage(title) ) return false; return true; } /** * @private */ isGASubPage(title) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); return Boolean(title.match(/\/GA\d{1,2}$/)); } /** * @private */ getGATitle(title) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); title = title.replace('Talk:', ''); title = title.replace(/_/g, ' '); title = title.match(/^[^\/]*/)[0]; return title; } /** * @private */ getGATalkTitle(gaTitle) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); if ( gaTitle.includes(':') ) { return gaTitle.replace(/^([^:]*)(:.*)$/gm, '$1 talk$2'); } else { return 'Talk:' + gaTitle; } } } // === modules/GANReviewHTMLGenerator.js ====================================================== class GANReviewHTMLGenerator { getHTML(gaTitle) { return ` border: 1px solid black; padding: 1em; margin-bottom: 1em; } margin-top: 0; } text-decoration: underline; } /* font-family: monospace; */ } width: 50em; } margin-top: 1.5em; margin-bottom: 1.5em; line-height: 1.5em; } font-weight: bold; color: green; } display: none; } .GANReviewTool-ValidationError { display: none; color: red; font-weight: bold; } .GANReviewTool-NotCreatorNotice { display: none; color: red; font-weight: bold; } .GANReviewTool-ErrorNotice { color: red; font-weight: bold; } display: none; } GAN Review Tool <p class="GANReviewTool-NotCreatorNotice"> You are not the creator of this GAN page. <a id="GANReviewTool-ReviewAnywayLink">Click here</a> to review anyway. Pass or fail?
 * 1) GANReviewTool {
 * 1) GANReviewTool h2 {
 * 1) GANReviewTool strong {
 * 1) GANReviewTool code {
 * 1) GANReviewTool input[type="text"] {
 * 1) GANReviewTool p {
 * 1) GANReviewTool option:disabled {
 * 1) GANReviewTool-ProcessingMessage {
 * 1) GANReviewTool-MainForm {

<input type="radio" name="GANReviewTool-PassOrFail" value="pass" checked /> Pass <input type="radio" name="GANReviewTool-PassOrFail" value="fail" /> Fail <input type="checkbox" name="GANReviewTool-ATOPYesNo" value="1" checked /> Apply template to GA review

Topic, subtopic, and sub-subtopic:

<select name="GANReviewTool-Topic"> <option value="Agriculture, food and drink" disabled>==Agriculture, food, and drink== <option value="Agriculture, food and drink" disabled>===Agriculture, food, and drink=== <option value="Agriculture, food and drink">=====Agriculture and farming===== <option value="Agriculture, food and drink">=====Horticulture and forestry===== <option value="Agriculture, food and drink">=====Food and drink establishments===== <option value="Agriculture, food and drink">=====Cuisines===== <option value="Agriculture, food and drink">=====Food===== <option value="Agriculture, food and drink">=====Drink===== <option value="Agriculture, food and drink">=====Food and drink companies===== <option value="Agriculture, food and drink">=====Food and drink people===== <option value="Agriculture, food and drink">=====Cookery books===== <option value="Agriculture, food and drink">=====Miscellaneous===== <option value="Art and architecture" disabled>==Art and architecture== <option value="Art and architecture" disabled>===Art=== <option value="Art and architecture">=====Art===== <option value="Art and architecture">=====Artists and art organizations===== <option value="Art and architecture" disabled>===Architecture=== <option value="Art and architecture">=====Architecture===== <option value="Art and architecture">=====Architecture – Bridges and tunnels===== <option value="Art and architecture">=====Architecture – Buildings===== <option value="Art and architecture">=====Architecture – Hotels and inns===== <option value="Art and architecture">=====Architecture – Houses and residences===== <option value="Art and architecture">=====Architecture – Lighthouses===== <option value="Art and architecture">=====Architecture – Memorials and monuments===== <option value="Art and architecture">=====Architecture – Museums and galleries===== <option value="Art and architecture">=====Architecture – Religious===== <option value="Art and architecture">=====Architects===== <option value="Engineering and technology" disabled>==Engineering and technology== <option value="Engineering and technology" disabled>===Computing and engineering=== <option value="Engineering and technology">=====Computer-related organizations and people===== <option value="Engineering and technology">=====Cryptography===== <option value="Engineering and technology">=====Engineers and inventors===== <option value="Engineering and technology">=====Engineering technology===== <option value="Engineering and technology">=====Engineering failures and disasters===== <option value="Engineering and technology">=====Hardware, standards and protocols===== <option value="Engineering and technology">=====Power and water infrastructure===== <option value="Engineering and technology">=====Programming===== <option value="Engineering and technology">=====Software===== <option value="Engineering and technology">=====Websites and the Internet===== <option value="Engineering and technology" disabled>===Transport=== <option value="Engineering and technology">=====Air transport===== <option value="Engineering and technology">=====Maritime transport===== <option value="Engineering and technology">=====Rail transport===== <option value="Engineering and technology">=====Rail bridges, tunnels, and stations===== <option value="Engineering and technology">=====Trains and locomotives===== <option value="Engineering and technology">=====Road infrastructure: Canada===== <option value="Engineering and technology">=====Road infrastructure: United States===== <option value="Engineering and technology">=====Road infrastructure: Northeastern United States===== <option value="Engineering and technology">=====Road infrastructure: Southern United States===== <option value="Engineering and technology">=====Road infrastructure: Midwestern United States===== <option value="Engineering and technology">=====Road infrastructure: Western United States===== <option value="Engineering and technology">=====Road infrastructure: Other===== <option value="Engineering and technology">=====Road transportation: Buses, vans, and paratransit===== <option value="Engineering and technology">=====Road transportation: Cars and motorcycles===== <option value="Engineering and technology">=====Road transportation: Other===== <option value="Engineering and technology">=====Transport by region===== <option value="Geography and places" disabled>==Geography and places== <option value="Geography and places" disabled>===Geography=== <option value="Geography and places">=====Bodies of water and water formations===== <option value="Geography and places">=====Geographers and explorers===== <option value="Geography and places">=====General and human geography===== <option value="Geography and places">=====Islands===== <option value="Geography and places">=====Landforms===== <option value="Geography and places">=====National and state parks, nature reserves, conservation areas, and countryside routes===== <option value="Geography and places">=====Urban and historical sites===== <option value="Geography and places">=====Geography miscellanea===== <option value="Geography and places" disabled>===Places=== <option value="Geography and places">=====Countries===== <option value="Geography and places">=====Africa===== <option value="Geography and places">=====Antarctica===== <option value="Geography and places">=====Asia===== <option value="Geography and places">=====Australia and the Pacific===== <option value="Geography and places">=====Europe===== <option value="Geography and places">=====Middle East===== <option value="Geography and places">=====North America===== =====South America===== <option value="History" disabled>==History== <option value="History" disabled>===World history=== <option value="History">=====Archaeology and archaeologists===== <option value="History">=====Historians, chroniclers and history books===== <option value="History">=====Historical figures: heads of state and heads of government===== <option value="History">=====Historical figures: politicians===== <option value="History">=====Historical figures: other===== <option value="History">=====African history===== <option value="History">=====North American history===== <option value="History">=====South American history===== <option value="History">=====Asian history===== <option value="History">=====Australian and Oceania history===== <option value="History">=====European history===== <option value="History">=====Middle Eastern history===== <option value="History">=====Global history===== <option value="History" disabled>===Royalty, nobility, and heraldry=== <option value="History">=====Flags and heraldry===== <option value="History">=====Monarchs===== <option value="History">=====Royalty and nobility===== <option value="Language and literature" disabled>==Language and literature== <option value="Language and literature" disabled>===Language and literature=== <option value="Language and literature">=====Alphabets and transliteration===== <option value="Language and literature">=====Ancient texts===== <option value="Language and literature">=====Biographies, autobiographies, essays, diaries, and travelogues===== <option value="Language and literature">=====Characters and fictional items===== <option value="Language and literature">=====Children's books, fairy tales, and nursery rhymes===== <option value="Language and literature">=====Comics===== <option value="Language and literature">=====Genres and literary theory===== <option value="Language and literature">=====Languages===== <option value="Language and literature">=====Linguists and philologists===== <option value="Language and literature">=====Non-fiction===== <option value="Language and literature">=====Novels===== <option value="Language and literature">=====Plays===== <option value="Language and literature">=====Poetry===== <option value="Language and literature">=====Short fiction and anthologies===== <option value="Language and literature">=====Words and linguistics===== <option value="Language and literature">=====Writers, publishers, and critics===== <option value="Mathematics" disabled>==Mathematics== <option value="Mathematics" disabled>===Mathematics and mathematicians=== <option value="Mathematics">=====Mathematical concepts and topics===== <option value="Mathematics">=====Mathematical texts and artifacts===== <option value="Mathematics">=====Mathematicians===== <option value="Media and drama" disabled>==Media and drama== <option value="Media and drama" disabled>===Film=== <option value="Media and drama">=====Cinema===== <option value="Media and drama">=====Film franchises, overview articles and production articles===== <option value="Media and drama">=====Pre-1910s films===== <option value="Media and drama">=====1910s films===== <option value="Media and drama">=====1920s films===== <option value="Media and drama">=====1930s films===== <option value="Media and drama">=====1940s films===== <option value="Media and drama">=====1950s films===== <option value="Media and drama">=====1960s films===== <option value="Media and drama">=====1970s films===== <option value="Media and drama">=====1980s films===== <option value="Media and drama">=====1990s films===== <option value="Media and drama">=====2000 to 2004 films===== <option value="Media and drama">=====2005 to 2009 films===== <option value="Media and drama">=====2010 to 2014 films===== <option value="Media and drama">=====2015 to 2019 films===== <option value="Media and drama">=====2020 to 2024 films===== <option value="Media and drama" disabled>===Television=== <option value="Media and drama">=====Television networks and overview articles===== <option value="Media and drama">====Television series==== <option value="Media and drama">=====30 Rock===== <option value="Media and drama">=====Ackley Bridge===== <option value="Media and drama">=====Adventure Time===== <option value="Media and drama">=====American Dad!===== <option value="Media and drama">=====American Horror Story===== <option value="Media and drama">=====Archer===== <option value="Media and drama">=====Arrowverse===== <option value="Media and drama">=====Avatarverse===== <option value="Media and drama">=====Awake===== <option value="Media and drama">=====Battlestar Galactica===== <option value="Media and drama">=====Better Call Saul===== <option value="Media and drama">=====The Big Bang Theory===== <option value="Media and drama">=====Black Mirror===== <option value="Media and drama">=====Body of Proof===== <option value="Media and drama">=====BoJack Horseman===== <option value="Media and drama">=====Breaking Bad===== <option value="Media and drama">=====Buffy the Vampire Slayer===== <option value="Media and drama">=====Casualty===== <option value="Media and drama">=====Cheers===== <option value="Media and drama">=====Chuck===== <option value="Media and drama">=====Cold Feet===== <option value="Media and drama">=====Community===== <option value="Media and drama">=====Coronation Street===== <option value="Media and drama">=====Daredevil===== <option value="Media and drama">=====Desperate Housewives===== <option value="Media and drama">=====Dexter===== <option value="Media and drama">=====Doctor Who series===== <option value="Media and drama">=====Doctor Who episodes===== <option value="Media and drama">=====Doctors===== <option value="Media and drama">=====EastEnders===== <option value="Media and drama">=====Ed, Edd n Eddy===== <option value="Media and drama">=====Emmerdale===== <option value="Media and drama">=====Family Guy===== <option value="Media and drama">=====Friends===== <option value="Media and drama">=====Fringe series===== <option value="Media and drama">=====Fringe episodes===== <option value="Media and drama">=====Futurama===== <option value="Media and drama">=====Game of Thrones===== <option value="Media and drama">=====Glee series===== <option value="Media and drama">=====Glee episodes===== <option value="Media and drama">=====The Good Place===== <option value="Media and drama">=====Gossip Girl===== <option value="Media and drama">=====Grey's Anatomy series===== <option value="Media and drama">=====Grey's Anatomy episodes===== <option value="Media and drama">=====Hawaii Five-0 (2010 TV series)===== <option value="Media and drama">=====The Hills===== <option value="Media and drama">=====Home and Away===== <option value="Media and drama">=====Holby City===== <option value="Media and drama">=====Hollyoaks===== <option value="Media and drama">=====Homicide: Life on the Street===== <option value="Media and drama">=====House===== <option value="Media and drama">=====House of Cards===== <option value="Media and drama">=====The House of Flowers===== <option value="Media and drama">=====Inside No. 9===== <option value="Media and drama">=====Last Week Tonight with John Oliver===== <option value="Media and drama">=====Law & Order: Special Victims Unit===== <option value="Media and drama">=====Lost series===== <option value="Media and drama">=====Lost episodes===== <option value="Media and drama">=====Mad Men===== <option value="Media and drama">=====Magnum P.I.===== <option value="Media and drama">=====Marvel Cinematic Universe===== <option value="Media and drama">=====Millennium===== <option value="Media and drama">=====Modern Family===== <option value="Media and drama">=====Monk===== <option value="Media and drama">=====My Little Pony: Friendship Is Magic===== <option value="Media and drama">=====Neighbours===== <option value="Media and drama">=====Neon Genesis Evangelion===== <option value="Media and drama">=====The Office series===== <option value="Media and drama">=====The Office episodes===== <option value="Media and drama">=====Once Upon a Time===== <option value="Media and drama">=====Parks and Recreation===== <option value="Media and drama">=====Phineas and Ferb===== <option value="Media and drama">=====Psych===== <option value="Media and drama">=====Rugrats===== <option value="Media and drama">=====Sanctuary===== <option value="Media and drama">=====Seinfeld===== <option value="Media and drama">=====Sesame Street series and co-productions===== <option value="Media and drama">=====The Simpsons series===== <option value="Media and drama">=====The Simpsons episodes===== <option value="Media and drama">=====Skins===== <option value="Media and drama">=====Smallville===== <option value="Media and drama">=====South Park series===== <option value="Media and drama">=====South Park episodes===== <option value="Media and drama">=====The Spectacular Spider-Man===== <option value="Media and drama">=====SpongeBob SquarePants===== <option value="Media and drama">=====Spooks===== <option value="Media and drama">=====Stargate===== <option value="Media and drama">=====Star Trek series===== <option value="Media and drama">=====Star Trek series episodes===== <option value="Media and drama">=====Supernatural===== <option value="Media and drama">=====Thunderbirds===== <option value="Media and drama">=====Torchwood===== <option value="Media and drama">=====Twin Peaks===== <option value="Media and drama">=====Ugly Americans===== <option value="Media and drama">=====Veronica Mars===== <option value="Media and drama">=====The Walking Dead===== <option value="Media and drama">=====WandaVision===== <option value="Media and drama">=====The West Wing===== <option value="Media and drama">=====White Collar===== <option value="Media and drama">=====Will & Grace===== <option value="Media and drama">=====The X-Files series===== <option value="Media and drama">=====The X-Files episodes===== <option value="Media and drama">=====Other television series, 1950s debuts===== <option value="Media and drama">=====Other television series, 1960s debuts===== <option value="Media and drama">=====Other television series, 1970s debuts===== <option value="Media and drama">=====Other television series, 1980s debuts===== <option value="Media and drama">=====Other television series, 1990s debuts===== <option value="Media and drama">=====Other television series, 2000s debuts===== <option value="Media and drama">=====Other television series, 2010s debuts===== <option value="Media and drama">=====Other television series, 2020s debuts===== <option value="Media and drama">=====Other television seasons and related articles===== <option value="Media and drama">=====Other episodes and specials===== <option value="Media and drama" disabled>===Media and drama=== <option value="Media and drama">=====Actors, directors, models, performers, and celebrities===== <option value="Media and drama">=====Animation===== <option value="Media and drama">=====Fictional characters and technologies===== <option value="Media and drama">=====Radio===== <option value="Media and drama">=====Theatre, musical theatre, dance, and opera===== <option value="Music" disabled>==Music== <option value="Music" disabled>===Albums=== <option value="Music">=====1950 to 1969 albums===== <option value="Music">=====1970 to 1979 albums===== <option value="Music">=====1980 to 1989 albums===== <option value="Music">=====1990 to 1994 albums===== <option value="Music">=====1995 to 1999 albums===== <option value="Music">=====2000 to 2004 albums===== <option value="Music">=====2005 to 2009 albums===== <option value="Music">=====2010 to 2014 albums===== <option value="Music">=====2015 to 2019 albums===== <option value="Music">=====2020 to 2024 albums===== <option value="Music">=====Soundtracks===== <option value="Music">=====Video albums===== <option value="Music" disabled>===Classical compositions=== <option value="Music">=====Classical compositions===== <option value="Music" disabled>===Songs=== <option value="Music">=====Pre-1900 songs===== <option value="Music">=====1900 to 1959 songs===== <option value="Music">=====1960 to 1969 songs===== <option value="Music">=====1970 to 1979 songs===== <option value="Music">=====1980 to 1989 songs===== <option value="Music">=====1990 to 1999 songs===== <option value="Music">=====2000 to 2004 songs===== <option value="Music">=====2005 to 2006 songs===== <option value="Music">=====2007 to 2008 songs===== <option value="Music">=====2009 songs===== <option value="Music">=====2010 songs===== <option value="Music">=====2011 songs===== <option value="Music">=====2012 songs===== <option value="Music">=====2013 songs===== <option value="Music">=====2014 songs===== <option value="Music">=====2015 songs===== <option value="Music">=====2016 songs===== <option value="Music">=====2017 songs===== <option value="Music">=====2018 songs===== <option value="Music">=====2019 songs===== <option value="Music">=====2020 songs===== <option value="Music">=====2021 songs===== <option value="Music">=====2022 songs===== <option value="Music" disabled>===Other music articles=== <option value="Music">=====Music awards===== <option value="Music">=====Music by nation, people, region, or country===== <option value="Music">=====Music genres, music styles, music eras===== <option value="Music">=====Musical theory, musical instruments, and music techniques===== <option value="Music">=====Music businesses and events===== <option value="Music">=====Performers, groups, composers, and other music-related people===== <option value="Natural sciences" disabled>==Natural sciences== <option value="Natural sciences" disabled>===Biology and medicine=== <option value="Natural sciences">====Biology==== <option value="Natural sciences">=====Anatomy===== <option value="Natural sciences">=====Biologists===== <option value="Natural sciences">=====Biology books===== <option value="Natural sciences">=====Ecology===== <option value="Natural sciences">=====Evolution and reproduction===== <option value="Natural sciences">=====Molecular and cellular biology===== <option value="Natural sciences">=====Miscellaneous biology===== <option value="Natural sciences">====Medicine==== <option value="Natural sciences">=====Medicine books===== <option value="Natural sciences">=====Diseases and medical conditions===== <option value="Natural sciences">=====History of medicine===== <option value="Natural sciences">=====Medical people and institutions===== <option value="Natural sciences">=====Medical procedures===== <option value="Natural sciences">====Pharmacology==== <option value="Natural sciences">=====Vaccines===== <option value="Natural sciences">=====Drug classes and individual drugs===== <option value="Natural sciences">=====Pharmacology miscellanea===== <option value="Natural sciences">====Viruses==== <option value="Natural sciences">====Organisms==== <option value="Natural sciences">=====Bacterial species===== <option value="Natural sciences">=====Protists===== <option value="Natural sciences">=====Fungi===== <option value="Natural sciences">=====Plants===== <option value="Natural sciences">=====Animals===== <option value="Natural sciences">======Mammals and other synapsids====== <option value="Natural sciences">======Birds====== <option value="Natural sciences">======Non-avian dinosaurs====== <option value="Natural sciences">======Reptiles and amphibians====== <option value="Natural sciences">======Fish====== <option value="Natural sciences">======Arthropods====== <option value="Natural sciences">======Other invertebrates====== <option value="Natural sciences">======Animal domestic breeds, types, and individuals====== <option value="Natural sciences" disabled>===Chemistry and materials science=== <option value="Natural sciences">=====Areas of chemistry theory===== <option value="Natural sciences">=====Chemistry books===== <option value="Natural sciences">=====Types of chemical analyses===== <option value="Natural sciences">=====Types of chemical transformations===== <option value="Natural sciences">=====Named reactions===== <option value="Natural sciences">=====Classes of chemical compounds and materials===== <option value="Natural sciences">=====Chemical compounds and materials===== <option value="Natural sciences">=====Periodic table groups and periods===== <option value="Natural sciences">=====Elements===== <option value="Natural sciences">=====Chemistry and materials science organizations===== <option value="Natural sciences">=====Chemists and materials scientists===== <option value="Natural sciences">=====Chemistry miscellanea===== <option value="Natural sciences">=====Materials science miscellanea===== <option value="Natural sciences" disabled>===Earth science=== <option value="Natural sciences">====Geology==== <option value="Natural sciences">=====Geology and geophysics===== <option value="Natural sciences">=====Geologists, geophysicists and mineralogists===== <option value="Natural sciences">=====Mineralogy===== <option value="Natural sciences">=====Earthquakes and similar natural disasters===== <option value="Natural sciences">====Meteorology==== <option value="Natural sciences">=====Climate===== <option value="Natural sciences">=====Climate change===== <option value="Natural sciences">=====Meteorological observatories===== <option value="Natural sciences">=====Storm sciences, tropical cyclone seasons, and storm effects===== <option value="Natural sciences">=====Tropical cyclones: Atlantic===== <option value="Natural sciences">=====Tropical cyclones: Eastern Pacific===== <option value="Natural sciences">=====Tropical cyclones: Northwestern Pacific===== <option value="Natural sciences">=====Tropical cyclones: Southern Pacific and the Indian Ocean===== <option value="Natural sciences">=====Weather===== <option value="Natural sciences">=====Severe weather and winter storms===== <option value="Natural sciences" disabled>===Physics and astronomy=== <option value="Natural sciences">====Physics==== <option value="Natural sciences">=====Physics===== <option value="Natural sciences">=====Physicists===== <option value="Natural sciences">====Astronomy==== <option value="Natural sciences">=====Space travelers===== <option value="Natural sciences">=====Astronomy and astrophysics===== <option value="Natural sciences">=====Astronomers and astrophysicists===== <option value="Natural sciences">=====Solar system===== <option value="Natural sciences">=====Constellations and asterisms===== <option value="Natural sciences">=====Stars, galaxies and extrasolar objects===== <option value="Natural sciences">=====Rocketry and spaceflight===== <option value="Natural sciences">=====Astronomy miscellanea===== <option value="Philosophy and religion" disabled>==Philosophy and religion== <option value="Philosophy and religion" disabled>===Philosophy=== <option value="Philosophy and religion">=====Divinities and protohistoric figures===== <option value="Philosophy and religion">=====Myths, mythology, and miracles===== <option value="Philosophy and religion">=====Philosophies and philosophical movements===== <option value="Philosophy and religion">=====Philosophical doctrines, teachings, texts, events, and symbols===== <option value="Philosophy and religion">=====Philosophers===== <option value="Philosophy and religion" disabled>===Religion=== <option value="Philosophy and religion">=====Religions and religious movements===== <option value="Philosophy and religion">=====Religious congregations, denominations, and organizations===== <option value="Philosophy and religion">=====Religious doctrines, teachings, texts, events, and symbols===== <option value="Philosophy and religion">=====Religious figures===== <option value="Social sciences and society" disabled>==Social sciences and society== <option value="Social sciences and society" disabled>===Culture, sociology, and psychology=== <option value="Social sciences and society">=====Culture and cultural studies===== <option value="Social sciences and society">=====Cultural symbols===== <option value="Social sciences and society">=====Internet culture===== <option value="Social sciences and society">=====Cultural organizations and events===== <option value="Social sciences and society">=====Ethnic groups===== <option value="Social sciences and society">=====Psychology and psychologists===== <option value="Social sciences and society">=====Anthropology, anthropologists, sociology and sociologists===== <option value="Social sciences and society">=====Globalization===== <option value="Social sciences and society" disabled>===Education=== <option value="Social sciences and society">=====Educational institutions===== <option value="Social sciences and society">=====Educators===== <option value="Social sciences and society">=====Education miscellanea===== <option value="Social sciences and society" disabled>===Economics and business=== <option value="Social sciences and society">=====Advertising and marketing===== <option value="Social sciences and society">=====Businesspeople===== <option value="Social sciences and society">=====Businesses and organizations===== <option value="Social sciences and society">=====Economics===== <option value="Social sciences and society">=====Numismatics and currencies===== <option value="Social sciences and society" disabled>===Law=== <option value="Social sciences and society">=====Case law and litigation===== <option value="Social sciences and society">=====Constitutional law===== <option value="Social sciences and society">=====Criminal justice, law enforcement, and ethics===== <option value="Social sciences and society">=====Criminals, crimes, allegations, and victims===== <option value="Social sciences and society">=====Domestic law===== <option value="Social sciences and society">=====International laws and treaties===== <option value="Social sciences and society">=====Lawyers, judges and legal academics===== <option value="Social sciences and society">=====Legal institutions, publications, and buildings===== <option value="Social sciences and society">=====Legislation and statutory law===== <option value="Social sciences and society">=====Law miscellanea===== <option value="Social sciences and society" disabled>===Magazines and print journalism=== <option value="Social sciences and society">=====Journalism and newspapers===== <option value="Social sciences and society">=====Magazines and journals===== <option value="Social sciences and society" disabled>===Politics and government=== <option value="Social sciences and society">=====Heads of state and heads of government===== <option value="Social sciences and society">=====Spouses of heads of state and heads of government===== <option value="Social sciences and society">=====Intelligence and espionage===== <option value="Social sciences and society">=====International organizations===== <option value="Social sciences and society">=====National non-governmental organizations===== <option value="Social sciences and society">=====Political and governmental institutions===== <option value="Social sciences and society">=====Political districts, direction and governance===== <option value="Social sciences and society">=====Political events and elections===== <option value="Social sciences and society">=====Political figures===== <option value="Social sciences and society">=====Political issues, theory and analysis===== <option value="Social sciences and society">=====Political parties and movements===== <option value="Sports and recreation" disabled>==Sports and recreation== <option value="Sports and recreation" disabled>===Football=== <option value="Sports and recreation">=====American football teams, events, seasons, concepts===== <option value="Sports and recreation">=====American football people===== <option value="Sports and recreation">=====Association football teams, events, and concepts===== <option value="Sports and recreation">=====Association football people===== <option value="Sports and recreation">=====Australian rules and Gaelic football===== <option value="Sports and recreation">=====Canadian football===== <option value="Sports and recreation">=====Rugby and rugby league football===== <option value="Sports and recreation" disabled>===Baseball=== <option value="Sports and recreation">=====Baseball teams, venues, events, and concepts===== <option value="Sports and recreation">=====Baseball people===== <option value="Sports and recreation" disabled>===Basketball=== <option value="Sports and recreation">=====Basketball teams, venues and events===== <option value="Sports and recreation">=====Basketball people===== <option value="Sports and recreation" disabled>===Cricket=== <option value="Sports and recreation">=====Cricket teams, venues and events===== <option value="Sports and recreation">=====Cricket people===== <option value="Sports and recreation" disabled>===Hockey=== <option value="Sports and recreation">=====Field hockey===== <option value="Sports and recreation">=====Ice hockey teams, venues and events===== <option value="Sports and recreation">=====Ice hockey people===== <option value="Sports and recreation" disabled>===Motorsport=== <option value="Sports and recreation">=====Races and seasons===== <option value="Sports and recreation">=====Racers, racecars, and tracks===== ===Pro wrestling=== <option value="Sports and recreation">=====Professional wrestling events===== <option value="Sports and recreation">=====Professional wrestling groups===== <option value="Sports and recreation">=====Professional wrestling people===== <option value="Sports and recreation">=====Professional wrestling championships===== <option value="Sports and recreation">=====Professional wrestling (other)===== <option value="Sports and recreation" disabled>===Recreation=== <option value="Sports and recreation">=====Board, card, and role-playing games===== <option value="Sports and recreation">=====Chess===== <option value="Sports and recreation">=====Climbing===== <option value="Sports and recreation">=====Diving===== <option value="Sports and recreation">=====Poker===== <option value="Sports and recreation">=====Toys===== <option value="Sports and recreation">=====Stadiums, public parks, and amusements===== <option value="Sports and recreation">=====Yoga===== <option value="Sports and recreation">=====Zoos and public aquariums===== <option value="Sports and recreation" disabled>===Multi-sport event=== <option value="Sports and recreation">=====Olympics===== <option value="Sports and recreation">=====Summer Olympics===== <option value="Sports and recreation">=====Winter Olympics===== <option value="Sports and recreation">=====Paralympics===== <option value="Sports and recreation">=====Other multi-sport events===== <option value="Sports and recreation" disabled>===Other sports=== <option value="Sports and recreation">=====Archery===== <option value="Sports and recreation">=====Badminton===== <option value="Sports and recreation">=====Cue sports===== <option value="Sports and recreation">=====Curling===== <option value="Sports and recreation">=====Cycling===== <option value="Sports and recreation">=====Darts===== <option value="Sports and recreation">=====Equestrianism/Horse racing===== <option value="Sports and recreation">=====Fencing===== <option value="Sports and recreation">=====Goalball===== <option value="Sports and recreation">=====Golf===== <option value="Sports and recreation">=====Gymnastics===== <option value="Sports and recreation">=====Handball===== <option value="Sports and recreation">=====Lacrosse===== <option value="Sports and recreation">=====Mixed martial arts, martial arts, and boxing===== <option value="Sports and recreation">=====Netball===== <option value="Sports and recreation">=====Rowing===== <option value="Sports and recreation">=====Running, track and field===== <option value="Sports and recreation">=====Shooting===== <option value="Sports and recreation">=====Skating===== <option value="Sports and recreation">=====Skiing===== <option value="Sports and recreation">=====Snowboarding===== <option value="Sports and recreation">=====Softball===== <option value="Sports and recreation">=====Squash===== <option value="Sports and recreation">=====Swimming and water sports===== <option value="Sports and recreation">=====Table tennis===== <option value="Sports and recreation">=====Tennis===== <option value="Sports and recreation">=====Volleyball===== <option value="Sports and recreation">=====Sports mascots and supporters===== <option value="Sports and recreation">=====Multiple sports===== <option value="Sports and recreation">=====Sports miscellanea===== <option value="Video games" disabled>==Video games== <option value="Video games" disabled>===Video games=== <option value="Video games">=====Early video games===== <option value="Video games">=====1970s video games===== <option value="Video games">=====1980–84 video games===== <option value="Video games">=====1985–89 video games===== <option value="Video games">=====1990–94 video games===== <option value="Video games">=====1995–99 video games===== <option value="Video games">=====2000–04 video games===== <option value="Video games">=====2005–09 video games===== <option value="Video games">=====2010–14 video games===== <option value="Video games">=====2015–19 video games===== <option value="Video games">=====2020–24 video games===== <option value="Video games">=====Cancelled video games===== <option value="Video games">=====Video game series===== <option value="Video games">=====Video game characters===== <option value="Video games">=====Video game genres===== <option value="Video games">=====Video game systems and services===== <option value="Video games">=====Video game history and development===== <option value="Video games">=====Video game industry and developers===== <option value="Video games">=====Video game terms and game elements===== <option value="Video games">=====Video game miscellanea===== <option value="Warfare" disabled>==Warfare== <option value="Warfare" disabled>===Armies and military units=== <option value="Warfare">====Air force==== <option value="Warfare">====Army==== <option value="Warfare">=====Australian army===== <option value="Warfare">=====United States and Confederate armies===== <option value="Warfare">====Navy==== <option value="Warfare">====Other==== <option value="Warfare" disabled>===Battles, exercises, and conflicts=== <option value="Warfare">====Ancient and classical history (before 500)==== <option value="Warfare">====Middle Ages (500–1499)==== <option value="Warfare">====Early modern period (1500–1799)==== <option value="Warfare">====American Revolutionary War (1775–1783)==== <option value="Warfare">====French Revolutionary and Napoleonic Wars (1792–1815)==== <option value="Warfare">====Long nineteenth century (1800–1914)==== <option value="Warfare">====World War I and interwar (1914–1939)==== <option value="Warfare">====World War II (1939–1945)==== <option value="Warfare">====Post-World War II (1945–present)==== <option value="Warfare">====Massacres, war crimes, and legal issues of warfare==== <option value="Warfare" disabled>===Military aircraft=== <option value="Warfare">====Aircraft technology and doctrine==== <option value="Warfare">====Military aircraft==== <option value="Warfare" disabled>===Military decorations and memorials=== <option value="Warfare">====Awards and decorations==== <option value="Warfare">====Military museums and memorials==== <option value="Warfare" disabled>===Military people=== <option value="Warfare">====Military people (A–C)==== <option value="Warfare">====Military people (D–F)==== <option value="Warfare">====Military people (G–K)==== <option value="Warfare">====Military people (L–M)==== <option value="Warfare">====Military people (N–R)==== <option value="Warfare">====Military people (S–Z)==== <option value="Warfare">====Warfare and race==== <option value="Warfare" disabled>===Military ranks and positions=== <option value="Warfare">====Military ranks and positions==== <option value="Warfare" disabled>===Warships and naval units=== <option value="Warfare">====Ship types==== <option value="Warfare">====Naval technology==== <option value="Warfare">====Warships==== <option value="Warfare">=====Warships of Argentina===== <option value="Warfare">=====Warships of Australia===== <option value="Warfare">=====Warships of Austria-Hungary===== <option value="Warfare">=====Warships of Belgium===== <option value="Warfare">=====Warships of Brazil===== <option value="Warfare">=====Warships of Canada===== <option value="Warfare">=====Warships of Chile===== <option value="Warfare">=====Warships of China===== <option value="Warfare">=====Warships of the Confederate States of America===== <option value="Warfare">=====Warships of Croatia===== <option value="Warfare">=====Warships of Denmark===== <option value="Warfare">=====Warships of France===== <option value="Warfare">=====Warships of Germany===== <option value="Warfare">=====Warships of Greece===== <option value="Warfare">=====Warships of Iceland===== <option value="Warfare">=====Warships of India===== <option value="Warfare">=====Warships of Indonesia===== <option value="Warfare">=====Warships of Italy===== <option value="Warfare">=====Warships of Japan===== <option value="Warfare">=====Warships of Norway===== <option value="Warfare">=====Warships of Peru===== <option value="Warfare">=====Warships of Portugal===== <option value="Warfare">=====Warships of Romania===== <option value="Warfare">=====Warships of Russia and the Soviet Union===== <option value="Warfare">=====Warships of South Africa===== <option value="Warfare">=====Warships of Spain===== <option value="Warfare">=====Warships of Sweden===== <option value="Warfare">=====Warships of Turkey and the Ottoman Empire===== <option value="Warfare">=====Warships of the United Kingdom===== <option value="Warfare">=====Warships of the United States===== <option value="Warfare">=====Warships of Yugoslavia===== <option value="Warfare" disabled>===Weapons, equipment, and buildings=== <option value="Warfare">====Weapons, military equipment and programs==== <option value="Warfare">====Military uniforms and clothing==== <option value="Warfare">====Fortifications and military installations==== <option value="Warfare">====Castles==== Wikicode to display when adding this to the list of good articles at <a href="/wiki/Wikipedia:Good_articles">WP:GA</a>

People should be in format:

Albums, television shows, <a href="/wiki/Genus">genus</a>, <a href="/wiki/Binomial_nomenclature">species</a> should be italicized:

Television episodes should be surrounded by double quotes:

Parentheses at the end should not be formatted:

Artwork, poetry, etc. may also require special formatting

More info at <a href="/wiki/Wikipedia:Manual_of_Style/Titles_of_works and <a href="/wiki/Wikipedia:Manual_of_Style/Titles_of_works

<input type="text" name="GANReviewTool-DisplayWikicode" value="${this.escapeHtml(gaTitle)}" />

<button id="GANReviewTool-Submit">Submit You must select a topic from the combo box above. "Wikicode to display" should not contain a pipe "|" Processing... `; } /** * CC BY-SA 4.0, bjornd, https://stackoverflow.com/a/6234804/3480193 * @private */ escapeHtml(unsafe) { return unsafe .replace(/&/g, "&amp;") .replace(/</g, "&lt;") .replace(/>/g, "&gt;") .replace(/"/g, "&quot;") .replace(/'/g, "&#039;"); } } // === modules/GANReviewWikicodeGenerator.js ====================================================== class GANReviewWikicodeGenerator { getPassWikicodeForGANPage(reviewWikicode) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); return this.placeATOP(reviewWikicode, 'Passed. ~', 'green') } getPassWikicodeForTalkPage(talkWikicode, reviewTitle, topic) { if ( arguments.length !== 3 ) throw new Error('Incorrect # of arguments'); // Deleting from article talk page. let gaPageNumber = this.getTemplateParameter(talkWikicode, 'GA nominee', 'page'); talkWikicode = this.deleteGANomineeTemplate(talkWikicode); // Adding  or  to article talk page. // TODO: get top revision ID of main article, pass it into below functions, have it add the revision ID let boolHasArticleHistoryTemplate = this.hasArticleHistoryTemplate(talkWikicode); if ( boolHasArticleHistoryTemplate ) { talkWikicode = this.updateArticleHistory(talkWikicode, topic, reviewTitle, 'listed'); } else { talkWikicode = this.addGATemplate(talkWikicode, topic, gaPageNumber); } // Changing WikiProject template class parameters to GA on article talk page. talkWikicode = this.changeWikiProjectArticleClassToGA(talkWikicode); return talkWikicode; } getPassWikicodeForGAListPage(gaSubpageHeading, gaSubpageWikicode, gaTitle, gaDisplayTitle) { if ( arguments.length !== 4 ) throw new Error('Incorrect # of arguments'); // find heading let headingStartPosition = this.getGASubpageHeadingPosition(gaSubpageHeading, gaSubpageWikicode); // now move down a bit, to the first line with an item. skip, ', gaSubpageWikicode, headingStartPosition); gaDisplayTitle = gaDisplayTitle.trim; let wikicodeToInsert = this.getWikicodeToInsert(gaTitle, gaDisplayTitle); let insertPosition; let startOfLine = headingStartPosition; while ( startOfLine < headingEndPosition ) { let endOfLine = this.findFirstStringAfterPosition('\n', gaSubpageWikicode, startOfLine); let line = gaSubpageWikicode.slice(startOfLine, endOfLine); let lineWithSomeFormattingRemoved = this.removeFormattingThatInterferesWithSort(line); let displayTitleWithSomeFormattingRemoved = this.removeFormattingThatInterferesWithSort(gaDisplayTitle); if ( ! this.aSortsLowerThanB(lineWithSomeFormattingRemoved, displayTitleWithSomeFormattingRemoved) ) { insertPosition = startOfLine; break; } startOfLine = endOfLine + 1; } if ( ! insertPosition ) { insertPosition = headingEndPosition; } return this.insertStringIntoStringAtPosition(gaSubpageWikicode, wikicodeToInsert, insertPosition); } getFailWikicodeForGANPage(reviewWikicode) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); return this.placeATOP(reviewWikicode, 'Unsuccessful. ~', 'red'); } getFailWikicodeForTalkPage(talkWikicode, reviewTitle) { if ( arguments.length !== 2 ) throw new Error('Incorrect # of arguments'); // Deleting from article talk page. let topic = this.getTopicFromGANomineeTemplate(talkWikicode); let gaPageNumber = this.getTemplateParameter(talkWikicode, 'GA nominee', 'page'); talkWikicode = this.deleteGANomineeTemplate(talkWikicode); // Adding  or  to article talk page. // TODO: get top revision ID of main article, pass it into below functions, have it add the revision ID let boolHasArticleHistoryTemplate = this.hasArticleHistoryTemplate(talkWikicode); if ( boolHasArticleHistoryTemplate ) { talkWikicode = this.updateArticleHistory(talkWikicode, topic, reviewTitle, 'failed'); } else { talkWikicode = this.addFailedGATemplate(talkWikicode, topic, gaPageNumber); } return talkWikicode; } getLogMessageToAppend(username, passOrFail, reviewTitle, reviewRevisionID, talkRevisionID, gaRevisionID, error) { if ( arguments.length !== 7 ) throw new Error('Incorrect # of arguments'); let textToAppend = `\n* `; if ( error ) { textToAppend += ` ERROR: ${error}. ` } textToAppend += `${username} ${passOrFail}ed ${reviewTitle} at. `; if ( reviewRevisionID ) { textToAppend += `[Atop]`; } if ( talkRevisionID ) { textToAppend += `[Talk]`; } if ( gaRevisionID ) { textToAppend += `[List]`; } return textToAppend; } /** * @private */ getWikicodeToInsert(gaTitle, gaDisplayTitle) { if ( arguments.length !== 2 ) throw new Error('Incorrect # of arguments'); if ( gaDisplayTitle === gaTitle ) { // use a non-piped wikilink, when possible return `${gaTitle}\n`; } else if ( gaDisplayTitle === `${gaTitle}` ) { // put italics on the outside, when possible return `${gaTitle}\n`; } else if ( gaDisplayTitle === `"${gaTitle}"` ) { // put double quotes on the outside, when possible return `"${gaTitle}"\n`; } else { return `${gaDisplayTitle}\n`; } } /** * @private */ placeATOP(wikicode, result, color) { if ( arguments.length !== 3 ) throw new Error('Incorrect # of arguments'); let colorCode = ''; switch ( color ) { case 'green': colorCode = 'g'; break; case 'red': colorCode = 'r'; break; } // place top piece after first H2, if it exists let prependText = ``; let hasH2 = wikicode.match(/^==[^=]+==$/m); if ( hasH2 ) { wikicode = wikicode.replace(/^(.*?==[^=]+==\n)(.*)$/s, '$1' + prependText + '\n$2'); } else { wikicode = prependText + "\n" + wikicode; } // place bottom piece at end let appendText = ``; wikicode = wikicode.trim; wikicode += `\n${appendText}\n`; return wikicode; } /** * @private */ getTopicFromGANomineeTemplate(talkWikicode) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); let topic = this.getTemplateParameter(talkWikicode, 'GA nominee', 'topic'); if ( ! topic ) { topic = this.getTemplateParameter(talkWikicode, 'GA nominee', 'subtopic'); } return topic; } /** * @private */ getTemplateParameter(wikicode, templateName, parameterName) { if ( arguments.length !== 3 ) throw new Error('Incorrect # of arguments'); templateName = this.regExEscape(templateName); parameterName = this.regExEscape(parameterName); let regex = new RegExp(`\\{\\{${templateName}[^\\}]+\\|${parameterName}\\s*=\\s*([^\\}\\|]+)\\s*[^\\}]*\\}\\}`, 'i'); let parameterValue = wikicode.match(regex) if ( Array.isArray(parameterValue) && parameterValue[1] !== undefined ) { return parameterValue[1].trim; } else { return null; } } /** * CC BY-SA 4.0, coolaj86, https://stackoverflow.com/a/6969486/3480193 * @private */ regExEscape(string) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); return string.replace(/[.*+?^${}|[\]\\]/g, '\\$&'); // $& means the whole matched string } /** * @private */ deleteGANomineeTemplate(talkWikicode) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); return talkWikicode.replace(/\{\{GA nominee[^\}]+\}\}\n?/i, ''); } /** * @private */ addGATemplate(talkWikicode, topic, gaPageNumber) { if ( arguments.length !== 3 ) throw new Error('Incorrect # of arguments'); let codeToAdd = `\n`; return this.addTemplateInCorrectMOSTalkOrderPosition(talkWikicode, codeToAdd); } /** * @private */ addFailedGATemplate(talkWikicode, topic, gaPageNumber) { if ( arguments.length !== 3 ) throw new Error('Incorrect # of arguments'); let codeToAdd = `\n`; return this.addTemplateInCorrectMOSTalkOrderPosition(talkWikicode, codeToAdd); } /** * @private */ addTemplateInCorrectMOSTalkOrderPosition(talkWikicode, codeToAdd) { if ( arguments.length !== 2 ) throw new Error('Incorrect # of arguments'); let templateName = this.getFirstTemplateNameFromWikicode(codeToAdd); let templatesThatGoBefore; switch ( templateName ) { case 'FailedGA': case 'GA': templatesThatGoBefore = ['GA nominee', 'Featured article candidates', 'Peer review', 'Skip to talk', 'Talk header', 'Talkheader', 'Talk page header', 'Vital article', 'Ds/talk notice', 'Gs/talk notice', 'BLP others', 'Calm', 'Censor', 'Controversial', 'Not a forum', 'FAQ', 'Round in circles', 'American English', 'British English']; // MOS:TALKORDER break; default: throw new Error('addTemplateInCorrectMOSTalkOrderPosition: Supplied template is not in dictionary. Unsure where to place it.'); } return this.addWikicodeAfterTemplates(talkWikicode, templatesThatGoBefore, codeToAdd); } /** * @private */ getFirstTemplateNameFromWikicode(wikicode) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); let match = wikicode.match(/(?<=\{\{)[^\|\}]+/) if ( ! match ) { throw new Error('getFirstTemplateNameFromWikicode: No template found in Wikicode.'); } return match[0]; } /** * Search algorithm looks for \n after the searched templates. If not present, it will not match. * @param {string[]} templates * @private */ addWikicodeAfterTemplates(wikicode, templates, codeToAdd) { if ( arguments.length !== 3 ) throw new Error('Incorrect # of arguments'); /* Started to write a lexer that would solve the edge case of putting the template too low when the MOS:TALKORDER is incorrect. It's a lot of work though. Pausing for now. // Note: the MOS:TALKORDER $templates variable is fed to us as a parameter let whitespace = ["\t", "\n", " "]; let lastTemplateNameBuffer = ''; let currentTemplateNameBuffer = ''; let templateNestingCount = 0; for ( i = 0; i < wikicode.length; i++ ) { let toCheck = wikicode.slice(i); if ( toCheck.startsWith('') ) { templateNestingCount--; } // TODO: need to build the templateNameBuffer. need to look for termination characters | or } let insertPosition = 0; for ( let template of templates ) { // TODO: handle nested templates let regex = new RegExp(`{{${this.regExEscape(template)}[^\\}]*}}\\n`, 'ig'); let endOfTemplatePosition = this.getEndOfStringPositionOfLastMatch(wikicode, regex); if ( endOfTemplatePosition > insertPosition ) { insertPosition = endOfTemplatePosition; } } return this.insertStringIntoStringAtPosition(wikicode, codeToAdd, insertPosition); } /** * @param {RegExp} regex /g flag must be set * @returns {number} endOfStringPosition Returns zero if not found * @private */ getEndOfStringPositionOfLastMatch(haystack, regex) { if ( arguments.length !== 2 ) throw new Error('Incorrect # of arguments'); let matches = [...haystack.matchAll(regex)]; let hasMatches = matches.length; if ( hasMatches ) { let lastMatch = matches[matches.length - 1]; let lastMatchStartPosition = lastMatch['index']; let lastMatchStringLength = lastMatch[0].length; let lastMatchEndPosition = lastMatchStartPosition + lastMatchStringLength; return lastMatchEndPosition; } return 0; } /** * @private */ changeWikiProjectArticleClassToGA(talkWikicode) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); // replace existing |class= talkWikicode = talkWikicode.replace(/(\|\s*class\s*=\s*)(a|b|c|start|stub|list|fa|fl)?(?=[\}\s\|])/gi, '$1GA'); // add |class= to templates containing no parameters talkWikicode = talkWikicode.replace(/(\{\{WikiProject [^\|\}]+)(\}\})/gi, `$1|class=GA$2`); return talkWikicode; } /** * Determine next |action= number in template. This is so we can insert an action. * @private */ determineNextActionNumber(talkWikicode) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); let i = 1; while ( true ) { let regex = new RegExp(`\\|\\s*action${i}\\s*=`, 'i'); let hasAction = talkWikicode.match(regex); if ( ! hasAction ) { return i; } i++; } } /** * @private */ updateArticleHistory(talkWikicode, topic, nominationPageTitle, listedOrFailed) { if ( arguments.length !== 4 ) throw new Error('Incorrect # of arguments'); let nextActionNumber = this.determineNextActionNumber(talkWikicode); if ( listedOrFailed !== 'listed' && listedOrFailed !== 'failed' ) { throw new Error('InvalidArgumentException'); } // always write our own topic. especially importnat for passing, because we want to use what the reviewer picked in the combo box, not what was already in the template. talkWikicode = this.firstTemplateDeleteParameter(talkWikicode, 'Article ?history', 'topic'); let topicString = `\n|topic = ${topic}`; // https://en.wikipedia.org/wiki/Template:Article_history#How_to_use_in_practice let existingStatus = this.firstTemplateGetParameterValue(talkWikicode, 'Artricle history', 'currentstatus') talkWikicode = this.firstTemplateDeleteParameter(talkWikicode, 'Article ?history', 'currentstatus'); let currentStatusString = this.getArticleHistoryNewStatus(existingStatus, listedOrFailed); let addToArticleHistory = `|action${nextActionNumber} = GAN addToArticleHistory += currentStatusString + topicString; talkWikicode = this.firstTemplateInsertCode(talkWikicode, 'Article ?history', addToArticleHistory); return talkWikicode; } /** * @private */ getArticleHistoryNewStatus(existingStatus, listedOrFailed) { if ( arguments.length !== 2 ) throw new Error('Incorrect # of arguments'); if ( listedOrFailed === 'listed' ) { switch ( existingStatus ) { case 'FFA': return '\n|currentstatus = FFA/GA'; case 'FFAC': return '\n|currentstatus = FFAC/GA'; default: return '\n|currentstatus = GA'; } } else { switch ( existingStatus ) { case 'FFA': return '\n|currentstatus = FFA'; case 'FFAC': return '\n|currentstatus = FFAC'; case 'DGA': return '\n|currentstatus = DGA'; default: return '\n|currentstatus = FGAN'; } } } /** * @private */ firstTemplateInsertCode(wikicode, templateNameRegExNoDelimiters, codeToInsert) { if ( arguments.length !== 3 ) throw new Error('Incorrect # of arguments'); // TODO: handle nested templates let regex = new RegExp(`(\\{\\{${templateNameRegExNoDelimiters}[^\\}]*)(\\}\\})`, 'i'); return wikicode.replace(regex, `$1\n${codeToInsert}\n$2`); } /** * @private */ firstTemplateGetParameterValue(wikicode, template, parameter) { if ( arguments.length !== 3 ) throw new Error('Incorrect # of arguments'); // TODO: rewrite to be more robust. currently using a simple algorithm that is prone to failure // new algorithm: // find start of template. use regex /i (ignore case) // iterate using loops until end of template found // handle // handle triple {{{ // handle nested let regex = new RegExp(`\\|\\s*${parameter}\\s*=\\s*([^\\n\\|\\}]*)\\s*`, ''); let result = wikicode.match(regex); if ( wikicode.match(regex) === null ) return null; return result[1]; } /** * @param {RegExp} regex * @private */ preg_position(regex, haystack) { if ( arguments.length !== 2 ) throw new Error('Incorrect # of arguments'); let matches = [...haystack.matchAll(regex)]; let hasMatches = matches.length; if ( hasMatches ) { return matches[0]['index']; } return false; } /** * @private */ findEndOfTemplate(wikicode, templateStartPosition) { // TODO: handle triple braces, handle tags let nesting = 0; let templateEndPosition = -1; for ( let i = templateStartPosition + 1 /* +1 to skip the first {{, will throw off our nesting count */; i < wikicode.length; i++ ) { let nextTwoChars = wikicode.slice(i, i + 2); if ( nextTwoChars === '{{' ) { nesting++; continue; } else if ( nextTwoChars === '}}' ) { if ( nesting > 0 ) { nesting--; continue; } else { templateEndPosition = i + 2; break; } } } return templateEndPosition; } /** * @private */ firstTemplateDeleteParameter(wikicode, templateRegEx, parameter) { if ( arguments.length !== 3 ) throw new Error('Incorrect # of arguments'); // templateStartPosition let regex = new RegExp('\{\{' + templateRegEx, 'gi'); let templateStartPosition = this.preg_position(regex, wikicode); // templateEndPosition let templateEndPosition = this.findEndOfTemplate(wikicode, templateStartPosition); // slice let firstPiece = wikicode.slice(0, templateStartPosition); let secondPiece = wikicode.slice(templateStartPosition, templateEndPosition); let thirdPiece = wikicode.slice(templateEndPosition); // replace only inside the slice let regex2 = new RegExp(`\\|\\s*${parameter}\\s*=\\s*([^\\n\\|\\}]*)\\s*`, ''); secondPiece = secondPiece.replace(regex2, ''); // glue back together wikicode = firstPiece + secondPiece + thirdPiece; return wikicode; } /** * @private */ removeFormattingThatInterferesWithSort(str) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); return str.replace(/^[^\[]*\[\[(?:[^\|]+\|)?/, ) // delete anything in front of, [[, and anything inside the left half of a piped wikilink .replace(/\]\][^\*$/, ) // delete ]], and anything after ]] .replace(/"/g, '') // delete " .replace(//g, ) // delete '' but not ' .replace(/^A /gi, '') // delete indefinite article "a" .replace(/^An /gi, '') // delete indefinite article "an" .replace(/^The /gi, '') // delete definite article "the" } /** * @private */ aSortsLowerThanB(a, b) { if ( arguments.length !== 2 ) throw new Error('Incorrect # of arguments'); // JavaScript appears to use an ASCII sort. See https://en.wikipedia.org/wiki/ASCII#Printable_characters // make sure "A" and "a" sort the same. prevents a bug a = a.toLowerCase; b = b.toLowerCase; a = this.removeDiacritics(a); b = this.removeDiacritics(b); let arr1 = [a, b]; let arr2 = [a, b]; // Sort numerically, not lexographically. // Fixes a bug where the sort is 79, 8, 80 instead of 8, 79, 80 // Jon Wyatt, CC BY-SA 4.0, https://stackoverflow.com/a/44197285/3480193 let sortNumerically = (a, b) => a.localeCompare(b, 'en', { numeric: true }); return JSON.stringify(arr1.sort(sortNumerically)) === JSON.stringify(arr2); } /** * Jeroen Ooms, CC BY-SA 3.0, https://stackoverflow.com/a/18123985/3480193 * @private */ removeDiacritics(str) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); var defaultDiacriticsRemovalMap = [ {'base':'A', 'letters':/[\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u00C4\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F]/g}, {'base':'AA','letters':/[\uA732]/g}, {'base':'AE','letters':/[\u00C6\u01FC\u01E2]/g}, {'base':'AO','letters':/[\uA734]/g}, {'base':'AU','letters':/[\uA736]/g}, {'base':'AV','letters':/[\uA738\uA73A]/g}, {'base':'AY','letters':/[\uA73C]/g}, {'base':'B', 'letters':/[\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181]/g}, {'base':'C', 'letters':/[\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E]/g}, {'base':'D', 'letters':/[\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779]/g}, {'base':'DZ','letters':/[\u01F1\u01C4]/g}, {'base':'Dz','letters':/[\u01F2\u01C5]/g}, {'base':'E', 'letters':/[\u0045\u24BA\uFF25\u00C8\u00C9\u00CA\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u0112\u1E14\u1E16\u0114\u0116\u00CB\u1EBA\u011A\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u0118\u1E18\u1E1A\u0190\u018E]/g}, {'base':'F', 'letters':/[\u0046\u24BB\uFF26\u1E1E\u0191\uA77B]/g}, {'base':'G', 'letters':/[\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E]/g}, {'base':'H', 'letters':/[\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D]/g}, {'base':'I', 'letters':/[\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197]/g}, {'base':'J', 'letters':/[\u004A\u24BF\uFF2A\u0134\u0248]/g}, {'base':'K', 'letters':/[\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2]/g}, {'base':'L', 'letters':/[\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780]/g}, {'base':'LJ','letters':/[\u01C7]/g}, {'base':'Lj','letters':/[\u01C8]/g}, {'base':'M', 'letters':/[\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C]/g}, {'base':'N', 'letters':/[\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4]/g}, {'base':'NJ','letters':/[\u01CA]/g}, {'base':'Nj','letters':/[\u01CB]/g}, {'base':'O', 'letters':/[\u004F\u24C4\uFF2F\u00D2\u00D3\u00D4\u1ED2\u1ED0\u1ED6\u1ED4\u00D5\u1E4C\u022C\u1E4E\u014C\u1E50\u1E52\u014E\u022E\u0230\u00D6\u022A\u1ECE\u0150\u01D1\u020C\u020E\u01A0\u1EDC\u1EDA\u1EE0\u1EDE\u1EE2\u1ECC\u1ED8\u01EA\u01EC\u00D8\u01FE\u0186\u019F\uA74A\uA74C]/g}, {'base':'OI','letters':/[\u01A2]/g}, {'base':'OO','letters':/[\uA74E]/g}, {'base':'OU','letters':/[\u0222]/g}, {'base':'P', 'letters':/[\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754]/g}, {'base':'Q', 'letters':/[\u0051\u24C6\uFF31\uA756\uA758\u024A]/g}, {'base':'R', 'letters':/[\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782]/g}, {'base':'S', 'letters':/[\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784]/g}, {'base':'T', 'letters':/[\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786]/g}, {'base':'TZ','letters':/[\uA728]/g}, {'base':'U', 'letters':/[\u0055\u24CA\uFF35\u00D9\u00DA\u00DB\u0168\u1E78\u016A\u1E7A\u016C\u00DC\u01DB\u01D7\u01D5\u01D9\u1EE6\u016E\u0170\u01D3\u0214\u0216\u01AF\u1EEA\u1EE8\u1EEE\u1EEC\u1EF0\u1EE4\u1E72\u0172\u1E76\u1E74\u0244]/g}, {'base':'V', 'letters':/[\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245]/g}, {'base':'VY','letters':/[\uA760]/g}, {'base':'W', 'letters':/[\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72]/g}, {'base':'X', 'letters':/[\u0058\u24CD\uFF38\u1E8A\u1E8C]/g}, {'base':'Y', 'letters':/[\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE]/g}, {'base':'Z', 'letters':/[\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762]/g}, {'base':'a', 'letters':/[\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u00E4\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250]/g}, {'base':'aa','letters':/[\uA733]/g}, {'base':'ae','letters':/[\u00E6\u01FD\u01E3]/g}, {'base':'ao','letters':/[\uA735]/g}, {'base':'au','letters':/[\uA737]/g}, {'base':'av','letters':/[\uA739\uA73B]/g}, {'base':'ay','letters':/[\uA73D]/g}, {'base':'b', 'letters':/[\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253]/g}, {'base':'c', 'letters':/[\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184]/g}, {'base':'d', 'letters':/[\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A]/g}, {'base':'dz','letters':/[\u01F3\u01C6]/g}, {'base':'e', 'letters':/[\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD]/g}, {'base':'f', 'letters':/[\u0066\u24D5\uFF46\u1E1F\u0192\uA77C]/g}, {'base':'g', 'letters':/[\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F]/g}, {'base':'h', 'letters':/[\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265]/g}, {'base':'hv','letters':/[\u0195]/g}, {'base':'i', 'letters':/[\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131]/g}, {'base':'j', 'letters':/[\u006A\u24D9\uFF4A\u0135\u01F0\u0249]/g}, {'base':'k', 'letters':/[\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3]/g}, {'base':'l', 'letters':/[\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747]/g}, {'base':'lj','letters':/[\u01C9]/g}, {'base':'m', 'letters':/[\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F]/g}, {'base':'n', 'letters':/[\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5]/g}, {'base':'nj','letters':/[\u01CC]/g}, {'base':'o', 'letters':/[\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u00F6\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275]/g}, {'base':'oi','letters':/[\u01A3]/g}, {'base':'ou','letters':/[\u0223]/g}, {'base':'oo','letters':/[\uA74F]/g}, {'base':'p','letters':/[\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755]/g}, {'base':'q','letters':/[\u0071\u24E0\uFF51\u024B\uA757\uA759]/g}, {'base':'r','letters':/[\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783]/g}, {'base':'s','letters':/[\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B]/g}, {'base':'t','letters':/[\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787]/g}, {'base':'tz','letters':/[\uA729]/g}, {'base':'u','letters':/[\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u00FC\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289]/g}, {'base':'v','letters':/[\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C]/g}, {'base':'vy','letters':/[\uA761]/g}, {'base':'w','letters':/[\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73]/g}, {'base':'x','letters':/[\u0078\u24E7\uFF58\u1E8B\u1E8D]/g}, {'base':'y','letters':/[\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF]/g}, {'base':'z','letters':/[\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763]/g} ]; for(var i=0; i<defaultDiacriticsRemovalMap.length; i++) { str = str.replace(defaultDiacriticsRemovalMap[i].letters, defaultDiacriticsRemovalMap[i].base); } return str; } /** * @private */ getGASubpageHeadingPosition(shortenedVersionInComboBox, wikicode) { if ( arguments.length !== 2 ) throw new Error('Incorrect # of arguments'); // chop off equals at beginning and end of line. we want to isolate a smaller piece to use as our needle. let needle = /^={2,5}\s*(.*?)\s*={2,5}$/gm.exec(shortenedVersionInComboBox)[1]; // keep the === === equals signs surrounding the heading the same. to prevent matching the wrong heading when there are multiple headings with the same needle, e.g. ===Art=== and =====Art===== let equalsSignsOnOneSide = /^(={2,5})/gm.exec(shortenedVersionInComboBox)[1]; // then insert this needle into a wide regex that includes equals, optional spaces next to the equals, and optional let regex = new RegExp(`^${equalsSignsOnOneSide}\\s*(?:\\\\]\\s*)?${this.regExEscape(needle)}\\s*${equalsSignsOnOneSide}$`, 'gm'); let result = regex.exec(wikicode); // if needle not found in haystack, return -1 if ( ! Array.isArray(result) ) return -1; // else return location of first match return result.index; } /** * @private */ findFirstStringAfterPosition(needle, haystack, position) { if ( arguments.length !== 3 ) throw new Error('Incorrect # of arguments'); let len = haystack.length; for ( let i = position; i < len; i++ ) { let buffer = haystack.slice(i, len); if ( buffer.startsWith(needle) ) { return i; } } return -1; } /** * CC BY-SA 4.0, jAndy, https://stackoverflow.com/a/4364902/3480193 * @private */ insertStringIntoStringAtPosition(bigString, insertString, position) { if ( arguments.length !== 3 ) throw new Error('Incorrect # of arguments'); return [ bigString.slice(0, position), insertString, bigString.slice(position) ].join(''); } /** * @private */ hasArticleHistoryTemplate(wikicode) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); return Boolean(wikicode.match(/\{\{Article ?history/i)); } } // === modules/GARCloserController.js ======================================================
 * action${nextActionNumber}date =
 * action${nextActionNumber}link = ${nominationPageTitle}
 * action${nextActionNumber}result = ${listedOrFailed}`;

class GARCloserController { /** * @param {function} $ jQuery * @param {Object} mw mediawiki, https://doc.wikimedia.org/mediawiki-core/master/js/#!/api/mw * @param {Location} location https://developer.mozilla.org/en-US/docs/Web/API/Window/location * @param {GARCloserWikicodeGenerator} wg * @param {GARCloserHTMLGenerator} hg */ async execute($, mw, location, wg, hg) { if ( arguments.length !== 5 ) throw new Error('Incorrect # of arguments'); this.$ = $; this.mw = mw; this.location = location; this.wg = wg; this.hg = hg; this.scriptLogTitle = `User:Novem Linguae/Scripts/GANReviewTool/GARLog`; this.editSummarySuffix = ' (GANReviewTool)'; this.garPageTitle = this.mw.config.get('wgPageName'); // includes namespace, underscores instead of spaces this.garPageTitle = this.garPageTitle.replace(/_/g, ' '); // underscores to spaces. prevents some bugs later if ( ! this.shouldRunOnThisPageQuickChecks ) { return; } this.parentArticle = await this.confirmGARAndGetArticleName; if ( ! this.parentArticle ) { return; } this.talkPageTitle = `Talk:${this.parentArticle}`; let hasGARLinkTemplate = await this.hasGARLinkTemplate(this.talkPageTitle); let hasATOP = await this.hasATOP(this.garPageTitle); if ( ! hasGARLinkTemplate || hasATOP ) { return; } // place HTML on page this.$('#mw-content-text').prepend(hg.getHTML) this.$(`#GARCloser-Keep`).on('click', async => { await this.clickKeep; }); this.$(`#GARCloser-Delist`).on('click', async => { await this.clickDelist; }); } /** * @private */ async clickKeep { if ( arguments.length !== 0 ) throw new Error('Incorrect # of arguments'); // TODO: ~ ? Ask Femke. May need to check if user already did it. Would do for both keep and delist. try { this.editSummary = `close GAR ${this.garPageTitle} as keep` + this.editSummarySuffix; this.deactivateBothButtons; await this.processKeepForGARPage; await this.processKeepForTalkPage; if ( this.isCommunityAssessment ) { await this.makeCommunityAssessmentLogEntry; } } catch(err) { this.error = err; console.error(err); } await this.makeScriptLogEntry('keep'); if ( ! this.error ) { this.pushStatus(`Done! Reloading...`); location.reload; } else { this.pushStatus(` An error occurred :( `); } } /** * @private */ async clickDelist { if ( arguments.length !== 0 ) throw new Error('Incorrect # of arguments'); try { this.editSummary = `close GAR ${this.garPageTitle} as delist` + this.editSummarySuffix; this.deactivateBothButtons; await this.processDelistForGARPage; await this.processDelistForTalkPage; await this.processDelistForArticle; await this.processDelistForGAList; if ( this.isCommunityAssessment ) { await this.makeCommunityAssessmentLogEntry; } } catch(err) { this.error = err; console.error(err); } await this.makeScriptLogEntry('delist'); if ( ! this.error ) { this.pushStatus(`Done! Reloading...`); location.reload; } else { this.pushStatus(` An error occurred :( `); } } /** * @private */ async hasGARLinkTemplate(title) { let wikicode = await this.getWikicode(title); return Boolean(wikicode.match(/\{\{GAR\/link/i)); } /** * @private */ async hasATOP(title) { let wikicode = await this.getWikicode(title); return Boolean(wikicode.match(/\{\{Atop/i)); // TODO: don't match a small ATOP, must be ATOP of entire talk page } /** * @private */ deactivateBothButtons { if ( arguments.length !== 0 ) throw new Error('Incorrect # of arguments'); this.$(`#GARCloser-Keep`).prop('disabled', true); this.$(`#GARCloser-Delist`).prop('disabled', true); } /** * @private */ async processKeepForGARPage { if ( arguments.length !== 0 ) throw new Error('Incorrect # of arguments'); this.pushStatus(`Place on GAR page. Replace  if present.`); let wikicode = await this.getWikicode(this.garPageTitle); let message = this.$(`#GARCloser-Message`).val; wikicode = this.wg.processKeepForGARPage(wikicode, message, this.isCommunityAssessment); this.garPageRevisionID = await this.makeEdit(this.garPageTitle, this.editSummary, wikicode); if ( this.garPageRevisionID === undefined ) { throw new Error('Generated wikicode and page wikicode were identical, resulting in a null edit.'); } } /** * @private */ async processDelistForGARPage { if ( arguments.length !== 0 ) throw new Error('Incorrect # of arguments'); this.pushStatus(`Place on GAR page`); let wikicode = await this.getWikicode(this.garPageTitle); let message = this.$(`#GARCloser-Message`).val; wikicode = this.wg.processDelistForGARPage(wikicode, message, this.isCommunityAssessment); this.garPageRevisionID = await this.makeEdit(this.garPageTitle, this.editSummary, wikicode); if ( this.garPageRevisionID === undefined ) { throw new Error('Generated wikicode and page wikicode were identical, resulting in a null edit.'); } } /** * @private */ async processKeepForTalkPage { if ( arguments.length !== 0 ) throw new Error('Incorrect # of arguments'); this.pushStatus(`Remove from talk page, and update `); let wikicode = await this.getWikicode(this.talkPageTitle); wikicode = this.wg.processKeepForTalkPage(wikicode, this.garPageTitle, this.talkPageTitle); this.talkRevisionID = await this.makeEdit(this.talkPageTitle, this.editSummary, wikicode); if ( this.talkRevisionID === undefined ) { throw new Error('Generated wikicode and page wikicode were identical, resulting in a null edit.'); } } /** * @private */ isCommunityAssessment { if ( arguments.length !== 0 ) throw new Error('Incorrect # of arguments'); if ( this.garPageTitle.startsWith('Wikipedia:Good article reassessment/') ) { return true; } return false; } /** * @private */ async makeCommunityAssessmentLogEntry { if ( arguments.length !== 0 ) throw new Error('Incorrect # of arguments'); this.pushStatus(`Add entry to community assessment log`); let archiveTitle = await this.getHighestNumberedPage("Wikipedia:Good article reassessment/Archive "); // TODO: handle no existing log pages at all let wikicode = await this.getWikicode(archiveTitle); let garTemplateCount = this.countGARTemplates(wikicode); let maximumNumberOfHeadingsAllowedInArchive = 82; let newArchive = false; if ( garTemplateCount >= maximumNumberOfHeadingsAllowedInArchive ) { archiveTitle = this.incrementArchiveTitle(archiveTitle); newArchive = true; wikicode = ``; this.incrementGARArchiveTemplate(archiveTitle); } let newWikicode = this.wg.makeCommunityAssessmentLogEntry(this.garPageTitle, wikicode, newArchive, archiveTitle); this.garLogRevisionID = await this.makeEdit(archiveTitle, this.editSummary, newWikicode) if ( this.garLogRevisionID === undefined ) { throw new Error('Generated wikicode and page wikicode were identical, resulting in a null edit.'); } } /** * @private */ async incrementGARArchiveTemplate(archiveTitle) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); this.pushStatus(`Update count at Template:GARarchive`); let wikicode = await this.getWikicode('Template:GARarchive'); let newTemplateWikicode = this.wg.setGARArchiveTemplate(archiveTitle, wikicode); this.garArchiveTemplateRevisionID = await this.makeEdit('Template:GARarchive', this.editSummary, newTemplateWikicode); if ( this.garArchiveTemplateRevisionID === undefined ) { throw new Error('Generated wikicode and page wikicode were identical, resulting in a null edit.'); } } /** * Takes a Wikipedia page name with a number on the end, and returns that page name with the number on the end incremented by one. Example: "Wikipedia:Good article reassessment/Archive 67" -> "Wikipedia:Good article reassessment/Archive 68" * @private */ incrementArchiveTitle(title) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); let number = title.match(/\d{1,}$/); number++; let titleWithNoNumber = title.replace(/\d{1,}$/, ''); return titleWithNoNumber + number.toString; } /** * Counts number of times "{{Wikipedia:Good article reassessment/" occurs in wikicode. * @private */ countGARTemplates(wikicode) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); return this.countOccurrencesInString(/\{\{Wikipedia:Good article reassessment\//g, wikicode); } /** * CC BY-SA 4.0, Lorenz Lo Sauer, https://stackoverflow.com/a/10671743/3480193 * @param {RegExp} needleRegEx Make sure to set the /g parameter. * @private */ countOccurrencesInString(needleRegEx, haystack) { if ( arguments.length !== 2 ) throw new Error('Incorrect # of arguments'); return (haystack.match(needleRegEx)||[]).length; } /** * @param {'keep'|'delist'} keepOrDelist * @private */ async makeScriptLogEntry(keepOrDelist) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); this.pushStatus(`Add entry to GARCloser debug log`); let username = this.mw.config.get('wgUserName'); let wikicode = this.wg.makeScriptLogEntryToAppend( username, keepOrDelist, this.garPageTitle, this.garPageRevisionID, this.talkRevisionID, this.articleRevisionID, this.gaListRevisionID, this.garLogRevisionID, this.garArchiveTemplateRevisionID, this.error ); await this.appendToPage(this.scriptLogTitle, this.editSummary, wikicode); } /** * @private */ async processDelistForTalkPage { if ( arguments.length !== 0 ) throw new Error('Incorrect # of arguments'); this.pushStatus(`Remove from talk page, update, remove |class=GA`); let wikicode = await this.getWikicode(this.talkPageTitle); this.gaListTitle = this.getGAListTitleFromTalkPageWikicode(wikicode); wikicode = this.wg.processDelistForTalkPage(wikicode, this.garPageTitle, this.talkPageTitle); this.talkRevisionID = await this.makeEdit(this.talkPageTitle, this.editSummary, wikicode); if ( this.talkRevisionID === undefined ) { throw new Error('Generated wikicode and page wikicode were identical, resulting in a null edit.'); } } /** * @private */ getGAListTitleFromTalkPageWikicode(wikicode) { let dictionary = { 'agriculture': 'Wikipedia:Good articles/Agriculture, food and drink', 'agriculture, food, and drink': 'Wikipedia:Good articles/Agriculture, food and drink', 'cuisine': 'Wikipedia:Good articles/Agriculture, food and drink', 'cuisines': 'Wikipedia:Good articles/Agriculture, food and drink', 'cultivation': 'Wikipedia:Good articles/Agriculture, food and drink', 'drink': 'Wikipedia:Good articles/Agriculture, food and drink', 'farming and cultivation': 'Wikipedia:Good articles/Agriculture, food and drink', 'farming': 'Wikipedia:Good articles/Agriculture, food and drink', 'food and drink': 'Wikipedia:Good articles/Agriculture, food and drink', 'food': 'Wikipedia:Good articles/Agriculture, food and drink', 'art': 'Wikipedia:Good articles/Art and architecture', 'architecture': 'Wikipedia:Good articles/Art and architecture', 'art and architecture': 'Wikipedia:Good articles/Art and architecture', 'engtech': 'Wikipedia:Good articles/Engineering and technology', 'applied sciences and technology': 'Wikipedia:Good articles/Engineering and technology', 'applied sciences': 'Wikipedia:Good articles/Engineering and technology', 'computers': 'Wikipedia:Good articles/Engineering and technology', 'computing and engineering': 'Wikipedia:Good articles/Engineering and technology', 'computing': 'Wikipedia:Good articles/Engineering and technology', 'eng': 'Wikipedia:Good articles/Engineering and technology', 'engineering': 'Wikipedia:Good articles/Engineering and technology', 'technology': 'Wikipedia:Good articles/Engineering and technology', 'transport': 'Wikipedia:Good articles/Engineering and technology', 'geography': 'Wikipedia:Good articles/Geography and places', 'geography and places': 'Wikipedia:Good articles/Geography and places', 'places': 'Wikipedia:Good articles/Geography and places', 'history': 'Wikipedia:Good articles/History', 'archaeology': 'Wikipedia:Good articles/History', 'heraldry': 'Wikipedia:Good articles/History', 'nobility': 'Wikipedia:Good articles/History', 'royalty': 'Wikipedia:Good articles/History', 'royalty, nobility and heraldry': 'Wikipedia:Good articles/History', 'world history': 'Wikipedia:Good articles/History', 'langlit': 'Wikipedia:Good articles/Language and literature', 'language and literature': 'Wikipedia:Good articles/Language and literature', 'languages and linguistics': 'Wikipedia:Good articles/Language and literature', 'languages and literature': 'Wikipedia:Good articles/Language and literature', 'languages': 'Wikipedia:Good articles/Language and literature', 'linguistics': 'Wikipedia:Good articles/Language and literature', 'lit': 'Wikipedia:Good articles/Language and literature', 'literature': 'Wikipedia:Good articles/Language and literature', 'math': 'Wikipedia:Good articles/Mathematics', 'mathematics and mathematicians': 'Wikipedia:Good articles/Mathematics', 'mathematics': 'Wikipedia:Good articles/Mathematics', 'maths': 'Wikipedia:Good articles/Mathematics', 'drama': 'Wikipedia:Good articles/Media and drama', 'ballet': 'Wikipedia:Good articles/Media and drama', 'dance': 'Wikipedia:Good articles/Media and drama', 'film': 'Wikipedia:Good articles/Media and drama', 'films': 'Wikipedia:Good articles/Media and drama', 'media and drama': 'Wikipedia:Good articles/Media and drama', 'media': 'Wikipedia:Good articles/Media and drama', 'opera': 'Wikipedia:Good articles/Media and drama', 'television': 'Wikipedia:Good articles/Media and drama', 'theater': 'Wikipedia:Good articles/Media and drama', 'theatre': 'Wikipedia:Good articles/Media and drama', 'theatre, film and drama': 'Wikipedia:Good articles/Media and drama', 'music': 'Wikipedia:Good articles/Music', 'albums': 'Wikipedia:Good articles/Music', 'classical compositions': 'Wikipedia:Good articles/Music', 'other music articles': 'Wikipedia:Good articles/Music', 'songs': 'Wikipedia:Good articles/Music', 'natsci': 'Wikipedia:Good articles/Natural sciences', 'astronomy': 'Wikipedia:Good articles/Natural sciences', 'astrophysics': 'Wikipedia:Good articles/Natural sciences', 'atmospheric science': 'Wikipedia:Good articles/Natural sciences', 'biology and medicine': 'Wikipedia:Good articles/Natural sciences', 'biology': 'Wikipedia:Good articles/Natural sciences', 'chemistry and materials science': 'Wikipedia:Good articles/Natural sciences', 'chemistry': 'Wikipedia:Good articles/Natural sciences', 'cosmology': 'Wikipedia:Good articles/Natural sciences', 'earth science': 'Wikipedia:Good articles/Natural sciences', 'earth sciences': 'Wikipedia:Good articles/Natural sciences', 'geology': 'Wikipedia:Good articles/Natural sciences', 'geophysics': 'Wikipedia:Good articles/Natural sciences', 'medicine': 'Wikipedia:Good articles/Natural sciences', 'meteorology and atmospheric sciences': 'Wikipedia:Good articles/Natural sciences', 'meteorology': 'Wikipedia:Good articles/Natural sciences', 'mineralogy': 'Wikipedia:Good articles/Natural sciences', 'natural science': 'Wikipedia:Good articles/Natural sciences', 'natural sciences': 'Wikipedia:Good articles/Natural sciences', 'physics and astronomy': 'Wikipedia:Good articles/Natural sciences', 'physics': 'Wikipedia:Good articles/Natural sciences', 'philrelig': 'Wikipedia:Good articles/Philosophy and religion', 'mysticism': 'Wikipedia:Good articles/Philosophy and religion', 'myth': 'Wikipedia:Good articles/Philosophy and religion', 'mythology': 'Wikipedia:Good articles/Philosophy and religion', 'phil': 'Wikipedia:Good articles/Philosophy and religion', 'philosophy and religion': 'Wikipedia:Good articles/Philosophy and religion', 'philosophy': 'Wikipedia:Good articles/Philosophy and religion', 'relig': 'Wikipedia:Good articles/Philosophy and religion', 'religion': 'Wikipedia:Good articles/Philosophy and religion', 'religion, mysticism and mythology': 'Wikipedia:Good articles/Philosophy and religion', 'socsci': 'Wikipedia:Good articles/Social sciences and society', 'business and economics': 'Wikipedia:Good articles/Social sciences and society', 'business': 'Wikipedia:Good articles/Social sciences and society', 'culture and society': 'Wikipedia:Good articles/Social sciences and society', 'culture': 'Wikipedia:Good articles/Social sciences and society', 'culture, society and psychology': 'Wikipedia:Good articles/Social sciences and society', 'economics and business': 'Wikipedia:Good articles/Social sciences and society', 'economics': 'Wikipedia:Good articles/Social sciences and society', 'education': 'Wikipedia:Good articles/Social sciences and society', 'gov': 'Wikipedia:Good articles/Social sciences and society', 'government': 'Wikipedia:Good articles/Social sciences and society', 'journalism and media': 'Wikipedia:Good articles/Social sciences and society', 'journalism': 'Wikipedia:Good articles/Social sciences and society', 'law': 'Wikipedia:Good articles/Social sciences and society', 'magazines and print journalism': 'Wikipedia:Good articles/Social sciences and society', 'media and journalism': 'Wikipedia:Good articles/Social sciences and society', 'politics and government': 'Wikipedia:Good articles/Social sciences and society', 'politics': 'Wikipedia:Good articles/Social sciences and society', 'psychology': 'Wikipedia:Good articles/Social sciences and society', 'social science': 'Wikipedia:Good articles/Social sciences and society', 'social sciences and society': 'Wikipedia:Good articles/Social sciences and society', 'social sciences': 'Wikipedia:Good articles/Social sciences and society', 'society': 'Wikipedia:Good articles/Social sciences and society', 'sports': 'Wikipedia:Good articles/Sports and recreation', 'everyday life': 'Wikipedia:Good articles/Sports and recreation', 'everydaylife': 'Wikipedia:Good articles/Sports and recreation', 'games': 'Wikipedia:Good articles/Sports and recreation', 'recreation': 'Wikipedia:Good articles/Sports and recreation', 'sport and recreation': 'Wikipedia:Good articles/Sports and recreation', 'sport': 'Wikipedia:Good articles/Sports and recreation', 'sports and recreation': 'Wikipedia:Good articles/Sports and recreation', 'video games': 'Wikipedia:Good articles/Video games', 'video and computer games': 'Wikipedia:Good articles/Video games', 'war': 'Wikipedia:Good articles/Warfare', 'aircraft': 'Wikipedia:Good articles/Warfare', 'battles and exercises': 'Wikipedia:Good articles/Warfare', 'battles': 'Wikipedia:Good articles/Warfare', 'decorations and memorials': 'Wikipedia:Good articles/Warfare', 'military': 'Wikipedia:Good articles/Warfare', 'military people': 'Wikipedia:Good articles/Warfare', 'units': 'Wikipedia:Good articles/Warfare', 'war and military': 'Wikipedia:Good articles/Warfare', 'warfare': 'Wikipedia:Good articles/Warfare', 'warships': 'Wikipedia:Good articles/Warfare', 'weapons and buildings': 'Wikipedia:Good articles/Warfare', 'weapons': 'Wikipedia:Good articles/Warfare', } let topic = wikicode.match(/\|\s*(?:sub)?topic\s*=\s*([^\|\}\n]+)/i)[1]; topic = topic.toLowerCase; return dictionary[topic]; } /** * @private */ async processDelistForArticle { if ( arguments.length !== 0 ) throw new Error('Incorrect # of arguments'); this.pushStatus(`Remove {{Good article}} from article`); let wikicode = await this.getWikicode(this.parentArticle); wikicode = this.wg.processDelistForArticle(wikicode); this.articleRevisionID = await this.makeEdit(this.parentArticle, this.editSummary, wikicode); if ( this.articleRevisionID === undefined ) { throw new Error('Generated wikicode and page wikicode were identical, resulting in a null edit.'); } } /** * @private */ async processDelistForGAList { if ( arguments.length !== 0 ) throw new Error('Incorrect # of arguments'); this.pushStatus(`Remove article from list of good articles`); let wikicode = await this.getWikicode(this.gaListTitle); wikicode = this.wg.processDelistForGAList(wikicode, this.parentArticle); this.gaListRevisionID = await this.makeEdit(this.gaListTitle, this.editSummary, wikicode); if ( this.gaListRevisionID === undefined ) { throw new Error('Generated wikicode and page wikicode were identical, resulting in a null edit.'); } } /** * This also checks if GARCloser should run at all. A falsey result means that the supplied title is not a GAR page. * @private */ async confirmGARAndGetArticleName { if ( arguments.length !== 0 ) throw new Error('Incorrect # of arguments'); let parentArticle = ``; // CASE 1: INDIVIDUAL ================================== let namespace = this.mw.config.get('wgNamespaceNumber'); let isTalkNamespace = ( namespace === 1 ); let isGASubPage = this.isGASubPage(this.garPageTitle); let couldBeIndividualReassessment = isTalkNamespace && isGASubPage; if ( couldBeIndividualReassessment ) { parentArticle = this.getIndividualReassessmentParentArticle(this.garPageTitle); let parentArticleWikicode = await this.getWikicode(`Talk:${parentArticle}`); if ( parentArticleWikicode.match(/\{\{GAR\/link/i) ) { return parentArticle; } } // CASE 2: COMMUNITY =================================== let couldBeCommunityReassessment = this.garPageTitle.startsWith('Wikipedia:Good article reassessment/'); if ( couldBeCommunityReassessment ) { parentArticle = this.getCommunityReassessmentParentArticle(this.garPageTitle); let parentArticleWikicode = await this.getWikicode(`Talk:${parentArticle}`); if ( parentArticleWikicode.match(/\{\{GAR\/link/i) ) { return parentArticle; } } } /** * @private */ getIndividualReassessmentParentArticle(title) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); return title.match(/Talk:(.*)\/GA/)[1]; } /** * @private */ getCommunityReassessmentParentArticle(title) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); return title.match(/Wikipedia:Good article reassessment\/(.*)\/\d/)[1]; } /** * @private */ async getWikicode(title) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); let api = new this.mw.Api; let params = { "action": "parse", "page": title, "prop": "wikitext", "format": "json", }; let result = await api.post(params); if ( result['error'] ) return ''; let wikicode = result['parse']['wikitext']['*']; return wikicode; } /** * @private */ async makeEdit(title, editSummary, wikicode) { if ( arguments.length !== 3 ) throw new Error('Incorrect # of arguments'); let api = new this.mw.Api; let params = { "action": "edit", "format": "json", "title": title, "text": wikicode, "summary": editSummary, }; let result = await api.postWithToken('csrf', params); let revisionID = result['edit']['newrevid']; return revisionID; } /** * Lets you append without getting the Wikicode first. Saves an API query. * @private */ async appendToPage(title, editSummary, wikicodeToAppend) { if ( arguments.length !== 3 ) throw new Error('Incorrect # of arguments'); let api = new this.mw.Api; let params = { "action": "edit", "format": "json", "title": title, "appendtext": wikicodeToAppend, "summary": editSummary, }; let result = await api.postWithToken('csrf', params); let revisionID = result['edit']['newrevid']; return revisionID; } /** * Example: To get the latest archive of "Wikipedia:Good article reassessment/Archive ", use getHighestNumberedPage("Wikipedia:Good article reassessment/Archive "), which will return "Wikipedia:Good article reassessment/Archive 67" * @private */ async getHighestNumberedPage(prefix) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); let t = new this.mw.Title(prefix); // https://doc.wikimedia.org/mediawiki-core/master/js/#!/api/mw.Title let prefixNoNamespace = t.getMainText; let namespaceNumber = t.getNamespaceId; let api = new this.mw.Api; let params ={ "action": "query", "format": "json", "list": "allpages", "apprefix": prefixNoNamespace, "apnamespace": namespaceNumber, "aplimit": "1", "apdir": "descending" }; let result = await api.post(params); let title = result['query']['allpages'][0]['title']; return title; } /** * @private */ pushStatus(statusToAdd) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); this.$(`#GARCloserTool-Status`).show; this.$(`#GARCloserTool-Status > p`).append(' ' + statusToAdd); } /** * @private */ shouldRunOnThisPageQuickChecks { if ( arguments.length !== 0 ) throw new Error('Incorrect # of arguments'); // don't run when not viewing articles let action = this.mw.config.get('wgAction'); if ( action != 'view' ) return false; // don't run when viewing diffs let isDiff = this.mw.config.get('wgDiffNewId'); if ( isDiff ) return false; let isDeletedPage = ( ! this.mw.config.get('wgCurRevisionId') ); if ( isDeletedPage ) return false; // always run in Novem's sandbox if ( this.garPageTitle === 'User:Novem_Linguae/sandbox' ) return true; return true; } /** * @private */ isGASubPage(title) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); return Boolean(title.match(/\/GA\d{1,2}$/)); } } // === modules/GARCloserHTMLGenerator.js ====================================================== class GARCloserHTMLGenerator { getHTML { if ( arguments.length !== 0 ) throw new Error('Incorrect # of arguments'); return ` border: 1px solid black; padding: 1em; margin-bottom: 1em; } margin-top: 0; } text-decoration: underline; } margin-top: 1.5em; margin-bottom: 1.5em; line-height: 1.5em; } display: none; } .GARCloserTool-ErrorNotice { color: red; font-weight: bold; } height: auto; } GAR Closer Tool Closing message
 * 1) GARCloserTool {
 * 1) GARCloserTool h2 {
 * 1) GARCloserTool strong {
 * 1) GARCloserTool p {
 * 1) GARCloserTool-Status {
 * 1) GARCloserTool textarea {

If you leave this blank, it will default to "Keep" or "Delist" <textarea id="GARCloser-Message" rows="4"> <button id="GARCloser-Keep">Keep <button id="GARCloser-Delist">Delist Processing... `; } } // === modules/GARCloserWikicodeGenerator.js ====================================================== class GARCloserWikicodeGenerator { processKeepForGARPage(garPageWikicode, message, isCommunityAssessment) { if ( arguments.length !== 3 ) throw new Error('Incorrect # of arguments'); return this.processGARPage(garPageWikicode, message, isCommunityAssessment, 'Kept.', 'green'); } processKeepForTalkPage(wikicode, garPageTitle, talkPageTitle) { if ( arguments.length !== 3 ) throw new Error('Incorrect # of arguments'); wikicode = this.removeTemplate('GAR/link', wikicode); wikicode = this.convertGATemplateToArticleHistoryIfPresent(talkPageTitle, wikicode); wikicode = this.updateArticleHistory('keep', wikicode, garPageTitle); return wikicode; } makeCommunityAssessmentLogEntry(garTitle, wikicode, newArchive, archiveTitle) { if ( arguments.length !== 4 ) throw new Error('Incorrect # of arguments'); let output = ``; if ( newArchive ) { let archiveNumber = this.getArchiveNumber(archiveTitle); output += `{| class="messagebox" {{Template:Process header green | title= Good article reassessment | section = (archive) | previous = (${archiveNumber-1}) | next =(Page ${archiveNumber+1}) | shortcut = | notes= }} `; } else { output += wikicode; } output += `\n{{${garTitle}}}` return output; } setGARArchiveTemplate(newArchiveTitle, wikicode) { if ( arguments.length !== 2 ) throw new Error('Incorrect # of arguments'); let archiveNumber = this.getArchiveNumber(newArchiveTitle); return wikicode.replace(/^\d{1,}/, archiveNumber); } /** * @param {'keep'|'delist'} keepOrDelist */ makeScriptLogEntryToAppend(username, keepOrDelist, reviewTitle, garRevisionID, talkRevisionID, articleRevisionID, gaListRevisionID, garLogRevisionID, garArchiveTemplateRevisionID, error) { if ( arguments.length !== 10 ) throw new Error('Incorrect # of arguments'); let textToAppend = `\n* `; if ( error ) { textToAppend += ` ERROR: ${error}. ` } let keepOrDelistPastTense = this.getKeepOrDelistPastTense(keepOrDelist); textToAppend += `${username} ${keepOrDelistPastTense} ${reviewTitle} at. `; if ( garRevisionID ) { textToAppend += `[Atop]`; } if ( garRevisionID ) { textToAppend += `[Talk]`; } if ( articleRevisionID ) { textToAppend += `[Article]`; } if ( gaListRevisionID ) { textToAppend += `[List]`; } if ( garLogRevisionID ) { textToAppend += `[Log]`; } if ( garArchiveTemplateRevisionID ) { textToAppend += `[Tmpl]`; } return textToAppend; } processDelistForGARPage(garPageWikicode, message, isCommunityAssessment) { if ( arguments.length !== 3 ) throw new Error('Incorrect # of arguments'); return this.processGARPage(garPageWikicode, message, isCommunityAssessment, 'Delisted.', 'red'); } processDelistForTalkPage(wikicode, garPageTitle, talkPageTitle) { if ( arguments.length !== 3 ) throw new Error('Incorrect # of arguments'); wikicode = this.removeTemplate('GAR/link', wikicode); wikicode = this.convertGATemplateToArticleHistoryIfPresent(talkPageTitle, wikicode); wikicode = this.updateArticleHistory('delist', wikicode, garPageTitle); wikicode = this.removeGAStatusFromWikiprojectBanners(wikicode); return wikicode; } processDelistForArticle(wikicode) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); wikicode = wikicode.replace(/\{\{ga icon\}\}\n?/i, ''); wikicode = wikicode.replace(/\{\{ga article\}\}\n?/i, ''); wikicode = wikicode.replace(/\{\{good article\}\}\n?/i, ''); return wikicode; } processDelistForGAList(wikicode, articleToRemove) { if ( arguments.length !== 2 ) throw new Error('Incorrect # of arguments'); let regex = new RegExp(`'{0,3}"?\\[\\[${this.regExEscape(articleToRemove)}.*\\]\\]"?'{0,3}\\n`); wikicode = wikicode.replace(regex, ''); return wikicode; } /** * @private */ processGARPage(garPageWikicode, message, isCommunityAssessment, defaultText, atopColor) { if ( arguments.length !== 5 ) throw new Error('Incorrect # of arguments'); message = this.setMessageIfEmpty(defaultText, message); message = this.addSignatureIfMissing(message); let messageForAtop = this.getMessageForAtop(isCommunityAssessment, message); let result = this.placeATOP(garPageWikicode, messageForAtop, atopColor); if ( isCommunityAssessment ) { result = this.replaceGARCurrentWithGARResult(message, result); } return result; } /** * @private */ addSignatureIfMissing(message) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); if ( ! message.includes('~') ) { message += ' ~'; } return message; } /** * @private */ setMessageIfEmpty(defaultText, message) { if ( arguments.length !== 2 ) throw new Error('Incorrect # of arguments'); if ( message === '' ) { message = defaultText; } return message; } /** * @private */ getMessageForAtop(isCommunityAssessment, message) { if ( arguments.length !== 2 ) throw new Error('Incorrect # of arguments'); let messageForAtop = message; if ( isCommunityAssessment ) { messageForAtop = ''; } return messageForAtop; } /** * and {{GAR/result}} are templates used in community reassessment GARs. The first needs to be swapped for the second when closing community reassessment GARs. * @private */ replaceGARCurrentWithGARResult(message, wikicode) { if ( arguments.length !== 2 ) throw new Error('Incorrect # of arguments'); message = message.replace(/ ?~/g, ''); return wikicode.replace(/\{\{GAR\/current\}\}/i, `{{subst:GAR/result|result=${this.escapeTemplateParameter(message)}}} ~`); } /** * @private */ escapeTemplateParameter(parameter) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); // TODO: This needs repair. Should only escape the below if they are not inside of a template. Should not escape them at all times. Commenting out for now. // parameter = parameter.replace(/\|/g, '{{!}}'); // parameter = parameter.replace(/=/g, '{{=}}'); return parameter; } /** * Takes a Wikipedia page name with a number on the end, and returns that number. * @private */ getArchiveNumber(title) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); return parseInt(title.match(/\d{1,}$/)); } /** * @private */ placeATOP(wikicode, result, color) { if ( arguments.length !== 3 ) throw new Error('Incorrect # of arguments'); let colorCode = ''; switch ( color ) { case 'green': colorCode = 'g'; break; case 'red': colorCode = 'r'; break; } // place top piece after first H2 or H3, if it exists let resultText = result ? `\n| result = ${result}\n` : ''; let prependText = `{{atop${colorCode}${resultText}}}`; let hasH2OrH3 = wikicode.match(/^===?[^=]+===?$/m); if ( hasH2OrH3 ) { wikicode = wikicode.replace(/^(.*?===?[^=]+===?\n)(.*)$/s, '$1' + prependText + '\n$2'); } else { wikicode = prependText + "\n" + wikicode; } // place bottom piece at end let appendText = ``; wikicode = wikicode.trim; wikicode += `\n${appendText}\n`; return wikicode; } /** * CC BY-SA 4.0, coolaj86, https://stackoverflow.com/a/6969486/3480193 * @private */ regExEscape(string) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); return string.replace(/[.*+?^${}|[\]\\]/g, '\\$&'); // $& means the whole matched string } /** * @private */ removeTemplate(templateName, wikicode) { if ( arguments.length !== 2 ) throw new Error('Incorrect # of arguments'); let regex = new RegExp(`\\{\\{${this.regExEscape(templateName)}[^\\}]*\\}\\}\\n`, 'i'); return wikicode.replace(regex, ''); } /** * @private */ regexGetFirstMatchString(regex, haystack) { if ( arguments.length !== 2 ) throw new Error('Incorrect # of arguments'); let matches = haystack.match(regex); if ( matches !== null && matches[1] !== undefined ) { return matches[1]; } return null; } /** * There's a template that some people use instead of. If this is present, replace it with. * @private */ convertGATemplateToArticleHistoryIfPresent(talkPageTitle, wikicode) { if ( arguments.length !== 2 ) throw new Error('Incorrect # of arguments'); let hasArticleHistory = Boolean(wikicode.match(/\{\{Article ? history([^\}]*)\}\}/gi)); let gaTemplateWikicode = this.regexGetFirstMatchString(/(\{\{GA[^\}]*\}\})/i, wikicode); if ( ! hasArticleHistory && gaTemplateWikicode ) { // delete {{ga}} template wikicode = wikicode.replace(/\{\{GA[^\}]*\}\}\n?/i, ''); wikicode = wikicode.trim; // parse its parameters // example: |21:00, 12 March 2017 (UTC)|topic=Sports and recreation|page=1|oldid=769997774 let parameters = this.getParametersFromTemplateWikicode(gaTemplateWikicode); // if no page specified, assume page is 1. so then the good article review link will be parsed as /GA1 let noPageSpecified = parameters['page'] === undefined; if ( noPageSpecified ) { parameters['page'] = 1; } let topicString = ''; if ( parameters['topic'] !== undefined ) { topicString = `\n|topic = ${parameters['topic']}`; } else if ( parameters['subtopic'] !== undefined ) { // subtopic is an alias only used in {{ga}}, it is not used in {{article history}} topicString = `\n|topic = ${parameters['subtopic']}`; } let oldIDString = ''; if ( parameters['oldid'] !== undefined ) { oldIDString = `\n|action1oldid = ${parameters['oldid']}`; } // insert {{article history}} template let addToTalkPageAboveWikiProjects = `{{Article history }}`; wikicode = this.addToTalkPageAboveWikiProjects(wikicode, addToTalkPageAboveWikiProjects); } return wikicode; } /** * Adds wikicode right above {{WikiProject X}} or {{WikiProject Banner Shell}} if present, or first ==Header== if present, or at bottom of page. Treat {{Talk:abc/GA1}} as a header. * @private */ addToTalkPageAboveWikiProjects(talkPageWikicode, wikicodeToAdd) { if ( arguments.length !== 2 ) throw new Error('Incorrect # of arguments'); if ( ! talkPageWikicode ) { return wikicodeToAdd; } // Find first WikiProject or WikiProject banner shell template let wikiProjectLocation = false; let dictionary = ['wikiproject', 'wpb', 'wpbs', 'wpbannershell', 'wp banner shell', 'bannershell', 'scope shell', 'project shell', 'multiple wikiprojects', 'football']; for ( let value of dictionary ) { let location = talkPageWikicode.toUpperCase.indexOf('{{' + value.toUpperCase); // case insensitive if ( location !== -1 ) { // if this location is higher up than the previous found location, overwrite it if ( wikiProjectLocation === false || wikiProjectLocation > location ) { wikiProjectLocation = location; } } } // Find first heading let headingLocation = talkPageWikicode.indexOf('=='); // Find first {{Talk:abc/GA1}} template let gaTemplateLocation = this.preg_position(new RegExp(`\\{\\{[^\\}]*\\/GA\\d{1,2}\\}\\}`, 'gis'), talkPageWikicode); // Set insert location let insertPosition; if ( wikiProjectLocation !== false ) { insertPosition = wikiProjectLocation; } else if ( headingLocation !== -1 ) { insertPosition = headingLocation; } else if ( gaTemplateLocation !== false ) { insertPosition = gaTemplateLocation; } else { insertPosition = talkPageWikicode.length; // insert at end of page } // if there's a {{Talk:abc/GA1}} above a heading, adjust for this if ( headingLocation !== -1 && gaTemplateLocation !== false && gaTemplateLocation < insertPosition ) { insertPosition = gaTemplateLocation; } // If there's excess newlines in front of the insert location, delete the newlines let deleteTopPosition = false; let deleteBottomPosition = false; let pos = insertPosition <= 0 ? 0 : insertPosition - 1; let i = 1; while ( pos != 0 ) { let char = talkPageWikicode.substr(pos, 1); if ( char == "\n" ) { if ( i != 1 && i != 2 ) { // skip first two \n, those are OK to keep deleteTopPosition = pos; if ( i == 3 ) { deleteBottomPosition = insertPosition; } } insertPosition = pos; // insert position should back up past all \n's. i++; pos--; } else { break; } } if ( deleteTopPosition !== false ) { talkPageWikicode = this.deleteMiddleOfString(talkPageWikicode, deleteTopPosition, deleteBottomPosition); } let lengthOfRightHalf = talkPageWikicode.length - insertPosition; let leftHalf = talkPageWikicode.substr(0, insertPosition); let rightHalf = talkPageWikicode.substr(insertPosition, lengthOfRightHalf); if ( insertPosition == 0 ) { return wikicodeToAdd + "\n" + talkPageWikicode; } else { return leftHalf + "\n" + wikicodeToAdd + rightHalf; } } /** * @param {RegExp} regex * @private */ preg_position(regex, haystack) { if ( arguments.length !== 2 ) throw new Error('Incorrect # of arguments'); let matches = [...haystack.matchAll(regex)]; let hasMatches = matches.length; if ( hasMatches ) { return matches[0]['index']; } return false; } /** * @private */ deleteMiddleOfString(string, deleteStartPosition, deleteEndPosition) { if ( arguments.length !== 3 ) throw new Error('Incorrect # of arguments'); let part1 = string.substr(0, deleteStartPosition); let part2 = string.substr(deleteEndPosition); let final_str = part1 + part2; return final_str; } /** * @returns {Object} Parameters, with keys being equivalent to the template parameter names. Unnamed parameters will be 1, 2, 3, etc. * @private */ getParametersFromTemplateWikicode(wikicodeOfSingleTemplate) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); wikicodeOfSingleTemplate = wikicodeOfSingleTemplate.slice(2, -2); // remove {{ and }} // TODO: explode without exploding | inside of inner templates let strings = wikicodeOfSingleTemplate.split('|'); let parameters = {}; let unnamedParameterCount = 1; let i = 0; for ( let string of strings ) { i++; if ( i == 1 ) { continue; // skip the template name, this is not a parameter } let hasEquals = string.indexOf('='); if ( hasEquals === -1 ) { parameters[unnamedParameterCount] = string; unnamedParameterCount++; } else { let matches = string.match(/^([^=]*)=(.*)/s); // isolate param name and param value by looking for first equals sign let paramName = matches[1].trim.toLowerCase; let paramValue = matches[2].trim; parameters[paramName] = paramValue; } } return parameters; } /** * @param {'keep'|'delist'} keepOrDelist * @private */ updateArticleHistory(keepOrDelist, wikicode, garPageTitle) { if ( arguments.length !== 3 ) throw new Error('Incorrect # of arguments'); let nextActionNumber = this.determineNextActionNumber(wikicode); if ( keepOrDelist !== 'keep' && keepOrDelist !== 'delist' ) { throw new Error('InvalidArgumentException'); } let topic = this.firstTemplateGetParameterValue(wikicode, 'Artricle history', 'topic'); let topicString = ''; if ( ! topic ) { topicString = `\n|topic = ${topic}`; } // https://en.wikipedia.org/wiki/Template:Article_history#How_to_use_in_practice let existingStatus = this.firstTemplateGetParameterValue(wikicode, 'Artricle history', 'currentstatus') wikicode = this.firstTemplateDeleteParameter(wikicode, 'Article history', 'currentstatus'); let currentStatusString = this.getArticleHistoryNewStatus(existingStatus, keepOrDelist); let result = this.getKeepOrDelistPastTense(keepOrDelist); let addToArticleHistory = `|action${nextActionNumber} = GAR addToArticleHistory += currentStatusString + topicString; wikicode = this.firstTemplateInsertCode(wikicode, ['Article history', 'ArticleHistory'], addToArticleHistory); return wikicode; } /** * @private */ getKeepOrDelistPastTense(keepOrDelist) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); switch ( keepOrDelist ) { case 'keep': return 'kept'; case 'delist': return 'delisted'; } } /** * Determine next |action= number in template. This is so we can insert an action. * @private */ determineNextActionNumber(wikicode) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); let i = 1; while ( true ) { let regex = new RegExp(`\\|\\s*action${i}\\s*=`, 'i'); let hasAction = wikicode.match(regex); if ( ! hasAction ) { return i; } i++; } } /** * @private */ firstTemplateGetParameterValue(wikicode, template, parameter) { if ( arguments.length !== 3 ) throw new Error('Incorrect # of arguments'); // TODO: rewrite to be more robust. currently using a simple algorithm that is prone to failure // new algorithm: // find start of template. use regex /i (ignore case) // iterate using loops until end of template found // handle // handle triple {{{ // handle nested let regex = new RegExp(`\\|\\s*${parameter}\\s*=\\s*([^\\n\\|\\}]*)\\s*`, ''); let result = wikicode.match(regex); if ( wikicode.match(regex) === null ) return null; return result[1]; } /** * @private */ getArticleHistoryNewStatus(existingStatus, keepOrDelist) { if ( arguments.length !== 2 ) throw new Error('Incorrect # of arguments'); if ( keepOrDelist === 'keep' ) { return `\n|currentstatus = ${existingStatus}`; } else { return '\n|currentstatus = DGA'; } } /** * @private * @param {Array} templateNameArrayCaseInsensitive */ firstTemplateInsertCode(wikicode, templateNameArrayCaseInsensitive, codeToInsert) { if ( arguments.length !== 3 ) throw new Error('Incorrect # of arguments'); for ( let templateName of templateNameArrayCaseInsensitive ) { let strPosOfEndOfFirstTemplate = this.getStrPosOfEndOfFirstTemplateFound(wikicode, templateName); if ( strPosOfEndOfFirstTemplate !== null ) { let insertPosition = strPosOfEndOfFirstTemplate - 2; // 2 characters from the end, right before }} let result = this.insertStringIntoStringAtPosition(wikicode, `\n${codeToInsert}\n`, insertPosition); return result; } } } /** * CC BY-SA 4.0, jAndy, https://stackoverflow.com/a/4364902/3480193 * @private */ insertStringIntoStringAtPosition(bigString, insertString, position) { return [ bigString.slice(0, position), insertString, bigString.slice(position) ].join(''); } /** * Grabs string position of the END of first {{template}} contained in wikicode. Case insensitive. Returns null if no template found. Handles nested templates. * @private * @returns {int|null} */ getStrPosOfEndOfFirstTemplateFound(wikicode, templateName) { if ( arguments.length !== 2 ) throw new Error('Incorrect # of arguments'); let starting_position = wikicode.toLowerCase.indexOf("{{" + templateName.toLowerCase); if ( starting_position === -1 ) return null; let counter = 0; let length = wikicode.length; for ( let i = starting_position + 2; i < length; i++ ) { let next_two = wikicode.substr(i, 2); if ( next_two == "{{" ) { counter++; continue; } else if ( next_two == "}}" ) { if ( counter == 0 ) { return i + 2; // +2 to account for next_two being }} (2 characters) } else { counter--; continue; } } } return null; } /** * @private */ removeGAStatusFromWikiprojectBanners(wikicode) { if ( arguments.length !== 1 ) throw new Error('Incorrect # of arguments'); return wikicode.replace(/(\|\s*class\s*=\s*)([^\}\|\s]*)/gi, '$1'); } /** * @private */ firstTemplateDeleteParameter(wikicode, template, parameter) { if ( arguments.length !== 3 ) throw new Error('Incorrect # of arguments'); // TODO: rewrite to be more robust. currently using a simple algorithm that is prone to failure let regex = new RegExp(`\\|\\s*${parameter}\\s*=\\s*([^\\n\\|\\}]*)\\s*`, ''); return wikicode.replace(regex, ''); } } }); //
 * [[Image:Filing cabinet icon.svg|50px|Archive]]
 * This is an archive of past discussions. Its contents should be preserved in their current form. If you wish to start a new discussion or revive an old one, please do so on the [{{FULLURL:{{TALKSPACE}}:{{BASEPAGENAME}}}} current talk page].
 * }
 * }
 * currentstatus = GA${topicString}
 * action1 = GAN
 * action1date = ${parameters[1]}
 * action1link = ${talkPageTitle}/GA${parameters['page']}
 * action1result = listed${oldIDString}
 * action${nextActionNumber}date =
 * action${nextActionNumber}link = ${garPageTitle}
 * action${nextActionNumber}result = ${result}`;

/* Novem Linguae/Scripts/UserRightsDiff.js */ // /* A typical user rights log entry might look like this: 11:29, August 24, 2021 ExampleUser1 talk contribs changed group membership for ExampleUser2 from edit filter helper, autopatrolled, extended confirmed user, page mover, new page reviewer, pending changes reviewer, rollbacker and template editor to autopatrolled, extended confirmed user, pending changes reviewer and rollbacker (inactive 1+ years. should you return and require access again please see WP:PERM) (thank) What the heck perms were removed? Hard to tell right? This user script adds a "DIFF" of the perms that were added or removed, on its own line, and highlights it green for added, yellow for removed. [ADDED template editor] [REMOVED edit filter helper, patroller] This script works in Special:UserRights, in Watchlists, and when clicking "rights" in the user script User:BradV/Scripts/SuperLinks.js $(function { /** Don't delete "(none)". Delete all other parentheses and tags. */ function deleteParenthesesAndTags(text) { // delete all U+200E. this character shows up on watchlists, and causes an extra space if not deleted. text = text.replace("\u200E", ); // get rid of 2 layers of nested parentheses. will help with some edge cases. text = text.replace(/\([^]*\([^]*\)[^]*\)/gs, ); // get rid of 1 layer of nested parentheses, except for (none) text = text.replace(/(?!\(none\))\(.*?\){1,}/gs, ); // remove Tag: and anything after it text = text.replace(/ Tags?:.*?$/, ); // cleanup so it's easier to write unit tests (output doesn't have extra spaces in it) text = text.replace(/ {2,}/gs, ' '); text = text.replace(/(\S) ,/gs, '$1,'); text = text.trim; return text; } /** Helpful for preventing issues where the username contains the word "from", which is searched for by a RegEx later. */ function deleteBeginningOfLogEntry(text) { text = text.replace(/^.*changed group membership for .* from /, ' from '); text = text.replace(/^.*was automatically updated from /gs, ' from '); return text; } function permStringToArray(string) { string = string.replace(/^(.*) and (.*?$)/, '$1, $2'); if ( string === '(none)') { return []; } let array = string.split(', ').map(function(str) { str = str.trim; str = str.replace(/[\s.,\/#!$%\^&\*;:{}=\-_`~]{2,}/g, ''); // remove fragments of punctuation. can result when trying to delete nested parentheses. will delete fragments such as " .)" return str; }); return array; } function permArrayToString(array) { array = array.join(', '); return array; } function checkLine { let text = $(this).text; let from, to; try { text = deleteParenthesesAndTags(text); text = deleteBeginningOfLogEntry(text); let matches = / from (.*?) to (.*?)(?: \(.*)?$/.exec(text); from = permStringToArray(matches[1]); to = permStringToArray(matches[2]); } catch(err) { throw new Error("UserRightsDiff.js error. Error was: " + err + ". Input text was: " + $(this).text); } let added = to.filter(x => !from.includes(x)); let removed = from.filter(x => !to.includes(x)); added = added.length > 0 ? ' [ADDED: ' + permArrayToString(added) + '] ' : ''; removed = removed.length > 0 ? ' [REMOVED: ' + permArrayToString(removed) + '] ' : ''; let noChange = added.length === 0 && removed.length === 0 ? ' [NO CHANGE] ' : ''; $(this).append(` ${added} ${removed} ${noChange}`); } function checkLog { $('body').off('DOMNodeInserted'); // prevent infinite loop if ( $('.user-rights-diff').length === 0 ) { // don't run twice on the same page $('.mw-logevent-loglines .mw-logline-rights').each( checkLine ); // Special:UserRights, BradV SuperLinks $('.mw-changeslist-log-rights .mw-changeslist-line-inner').each( checkLine ); // watchlist } $('body').on('DOMNodeInserted', '.mw-logevent-loglines', checkLog);; }; // User:BradV/Scripts/SuperLinks.js $('body').on('DOMNodeInserted', '.mw-logevent-loglines', checkLog); // Special:UserRights checkLog; }); //

/* Enterprisey/AFCRHS.js */ // (function { if (mw.config.get("wgPageName").indexOf('Wikipedia:Articles_for_creation/Redirects_and_categories') === -1) { return; } var afcHelper_RedirectPageName = mw.config.get("wgPageName").replace(/_/g, ' '); var afcHelper_RedirectSubmissions = new Array; var afcHelper_RedirectSections = new Array; var afcHelper_advert = ' (AFCRHS.js)'; var afcHelper_numTotal = 0; var afcHelper_AJAXnumber = 0; var afcHelper_Submissions = new Array; var needsupdate = new Array; var afcHelper_redirectDecline_reasonhash = { 'exists': 'The title you suggested already exists on Wikipedia', 'blank': 'We cannot accept empty submissions', 'notarget': ' A redirect cannot be created unless the target is an existing article. Either you have not specified the target, or the target does not exist', 'unlikely': 'The title you suggested seems unlikely. Could you provide a source showing that it is a commonly used alternate name?', 'notredirect': 'This request is not a redirect request', 'custom': '' }; var afcHelper_categoryDecline_reasonhash = { 'exists': 'The category you suggested already exists on Wikipedia', 'blank': 'We cannot accept empty submissions', 'unlikely': 'It seems unlikely that there are enough pages to support this category', 'notcategory': 'This request is not a category request', 'custom': '' }; function afcHelper_redirect_init { pagetext = afcHelper_getPageText(afcHelper_RedirectPageName); // cleanup the wikipedia links for preventing stuff like https://en.wikipedia.org/w/index.php?diff=576244067&oldid=576221437 pagetext = afcHelper_cleanuplinks(pagetext); // first, strip out the parts before the first section. var section_re = /==.*?==/; pagetext = pagetext.substring(pagetext.search(section_re)); // then split it into the rest of the sections afcHelper_RedirectSections = pagetext.match(/^==.*?==$((\r?\n?)(?!==[^=]).*)*/img); // parse the sections. for (var i = 0; i < afcHelper_RedirectSections.length; i++) { var closed = /(\{\{\s*afc(?!\s+comment)|This is an archived discussion)/i.test(afcHelper_RedirectSections[i]); if (!closed) { // parse. var header = afcHelper_RedirectSections[i].match(section_re)[0]; if (header.search(/Redirect request/i) !== -1) { var wikilink_re = /\[\[(\s*[^=]*?)*?\]\]/g; var links = header.match(wikilink_re); if (!links) continue; for (var l = 0; l < links.length; l++) { links[l] = links[l].replace(/[\[\]]/g, ''); if (links[l].charAt(0) === ':') links[l] = links[l].substring(1); } var re = /Target of redirect:\s*\[\[([^\[\]]*)\]\]/i; re.test(afcHelper_RedirectSections[i]); var to = $.trim(RegExp.$1); var reasonRe = /Reason:[ \t]*?(.+)/i; var reasonMatch = reasonRe.exec(afcHelper_RedirectSections[i]); var reason = reasonMatch && reasonMatch[1].trim ? reasonMatch[1] : null; var sourceRe = /Source.*?:[ \t]*?(.+)/i; var sourceMatch = sourceRe.exec(afcHelper_RedirectSections[i]); var source = sourceMatch && sourceMatch[1].trim ? sourceMatch[1] : null; var submission = { type: 'redirect', from: new Array, section: i, to: to, title: to, reason: reason, source: source }; for (var j = 0; j < links.length; j++) { var sub = { type: 'redirect', to: to, id: afcHelper_numTotal, title: links[j], action: '' }; submission.from.push(sub); afcHelper_Submissions.push(sub); afcHelper_numTotal++; } afcHelper_RedirectSubmissions.push(submission); } else if (header.search(/Category request/i) !== -1) { // Find a wikilink in the header, and assume it's the category to create var category_name = /\[\^\[\+\]\]/.exec(header); if (!category_name) continue; category_name = category_name[0]; category_name = category_name.replace(/[\[\]]/g, ''); category_name = category_name.replace(/Category\s*:\s*/gi, 'Category:'); if (category_name.charAt(0) === ':') category_name = category_name.substring(1); // Figure out the parent categories var request_text = afcHelper_RedirectSections[i].substring(header.length); // We only want categories listed under the "Parent category/categories" heading, // *NOT* any categories listed under "Example pages which belong to this category". var idx_of_parent_heading = request_text.indexOf('Parent category/categories'); if (idx_of_parent_heading >= 0 ) { request_text = request_text.substring(idx_of_parent_heading); } var parent_cats = []; var parent_cat_match = null; var parent_cat_regex = /\[\[\s*:\s*(Category:[^\]\[]*)\]\]/ig; do { parent_cat_match = parent_cat_regex.exec(request_text); if (parent_cat_match) { parent_cats.push(parent_cat_match[1]); } } while (parent_cat_match); var submission = { type: 'category', title: category_name, section: i, id: afcHelper_numTotal, action: '', parents: parent_cats.join(',') }; afcHelper_numTotal++; afcHelper_RedirectSubmissions.push(submission); afcHelper_Submissions.push(submission); } } // end if (!closed) } // end loop over sections // Build the form var $form = $(' Reviewing AfC redirect requests '); displayMessage($form); var $messageDiv = $form.parent; // now layout the text. var afcHelper_Redirect_empty = 1; var ACTIONS = [ {label: 'Accept', value: 'accept'}, {label: 'Decline', value: 'decline'}, {label: 'Comment',value: 'comment'}, {label: 'None', selected: true, value: 'none'} ]; for (var k = 0; k < afcHelper_RedirectSubmissions.length; k++) { if (afcHelper_RedirectSubmissions[k].to !== undefined) var submissionname = afcHelper_RedirectSubmissions[k].to.replace(/\s/g,''); else var submissionname = ""; var $thisSubList = $('<ul>'); var $thisSubListElement = $('<li>'); if (afcHelper_RedirectSubmissions[k].type === 'redirect') { $thisSubListElement.append('Redirect(s) to '); if (!submissionname) { for (var i = afcHelper_RedirectSubmissions[k].from.length - 1; i >= 0; i--) { needsupdate.push({ id: afcHelper_RedirectSubmissions[k].from[i].id, reason: 'notarget' }); } } else if (!afcHelper_RedirectSubmissions[k].to) { for (var i = afcHelper_RedirectSubmissions[k].from.length - 1; i >= 0; i--) { needsupdate.push({ id: afcHelper_RedirectSubmissions[k].from[i].id, reason: 'notredirect' }); } } if (afcHelper_RedirectSubmissions[k] === '' || afcHelper_RedirectSubmissions[k] === ' ') { $thisSubListElement.append('Empty submission \#' + afcHelper_Redirect_empty); afcHelper_Redirect_empty++; } else { if (submissionname.length > 0) { $thisSubListElement.append($('<a>') .attr('href', mw.config.get("wgArticlePath").replace("$1", encodeURIComponent(afcHelper_RedirectSubmissions[k].to))) .attr('target', '_blank') .text(afcHelper_RedirectSubmissions[k].to)); } else { $thisSubListElement.append('no target given: '); } } var $fromList = $('<ul>').appendTo($thisSubListElement); for (var l = 0; l < afcHelper_RedirectSubmissions[k].from.length; l++) { var from = afcHelper_RedirectSubmissions[k].from[l]; var toarticle = from.title; if (toarticle.replace(/\s*/gi, "").length == 0) toarticle = "no title specified, check the request details"; var reasonAndSource = $('<ul>'); if(afcHelper_RedirectSubmissions[k].reason) reasonAndSource.append('<li>Reason: ' + afcHelper_RedirectSubmissions[k].reason + '</li>'); if(afcHelper_RedirectSubmissions[k].source) reasonAndSource.append('<li>Source: ' + afcHelper_RedirectSubmissions[k].source + '</li>'); var googleSearchUrl = 'http://www.google.com/search?q="' + encodeURIComponent(toarticle) + '"+-wikipedia.org'; $fromList.append($('<li>') .append('From: ' + toarticle + ' ( <a href=\'' + googleSearchUrl + '\'" target="_blank">' + 'Google</a> &middot; <a href="https://en.wikipedia.org/wiki/Special:WhatLinksHere/' + encodeURIComponent(toarticle) + '" target="_blank">what links here</a>) ') .append(reasonAndSource) .append($(' ').attr('for', 'afcHelper_redirect_action_' + from.id).text('Action: ')) .append(afcHelper_generateSelectObject('afcHelper_redirect_action_' + from.id, ACTIONS, afcHelper_redirect_makeActionChange(from.id))) .append($(' ').attr('id', 'afcHelper_redirect_extra_' + from.id))); } } else { var subId = afcHelper_RedirectSubmissions[k].id; $thisSubListElement.append('Category submission: ') .append($('<a>') .attr('href', '/wiki/' + afcHelper_RedirectSubmissions[k].title) .attr('title', afcHelper_RedirectSubmissions[k].title) .text(afcHelper_RedirectSubmissions[k].title)) .append(' ') .append($(' ').attr('for', 'afcHelper_redirect_action_' + subId).text('Action: ')) .append(afcHelper_generateSelectObject('afcHelper_redirect_action_' + subId, ACTIONS, afcHelper_redirect_makeActionChange(subId))) .append($(' ').attr('id', 'afcHelper_redirect_extra_' + subId)); } $thisSubList.append($thisSubListElement); $messageDiv.append($thisSubList); } // end loop over sections $messageDiv.append($(' ') .attr('id', 'afcHelper_redirect_done_button') .attr('name', 'afcHelper_redirect_done_button') .text('Done') .click(afcHelper_redirect_performActions)); for (var y = 0; y < needsupdate.length; y++){ $('#afcHelper_redirect_action_'+needsupdate[y].id).attr('value', 'decline'); afcHelper_redirect_onActionChange(needsupdate[y].id); $('#afcHelper_redirect_decline_'+needsupdate[y].id).attr('value', needsupdate[y].reason); } } function afcHelper_redirect_makeActionChange(id) { return function { afcHelper_redirect_onActionChange(id); }; } function afcHelper_redirect_onActionChange(id) { console.log("Entering afcHelper_redirect_onActionChange, id = " + id); var $extra = $("#afcHelper_redirect_extra_" + id); var selectValue = $("#afcHelper_redirect_action_" + id).val; $extra.html(''); // Blank it first if (selectValue === 'accept') { if (afcHelper_Submissions[id].type === 'redirect') { $extra.append('<label for="afcHelper_redirect_from_' + id + '">From: '); $extra.append($(' ') .attr('type', 'text') .attr('name', 'afcHelper_redirect_from_' + id) .attr('id', 'afcHelper_redirect_from_' + id) .attr('value', afcHelper_escapeHtmlChars(afcHelper_Submissions[id].title))); $extra.html($extra.html + ' <label for="afcHelper_redirect_to_' + id + '">To: <input type="text" ' + 'name="afcHelper_redirect_to_' + id + '" id="afcHelper_redirect_to_' + id + '" value="' + afcHelper_escapeHtmlChars(afcHelper_Submissions[id].to) + '" />'); $extra.html($extra.html + ' <label for="afcHelper_redirect_append_' + id + '">Template to append: (<a href="https://en.wikipedia.org/wiki/Wikipedia:TMR" target="_blank">Help</a>) '); $extra.html($extra.html + afcHelper_generateSelect('afcHelper_redirect_append_' + id, [ { label: 'None', selected: true, value: 'none' }, { labelAndValue: 'Frequently used', disabled: true }, { labelAndValue: 'R from alternative language' }, { labelAndValue: 'R from alternative name' }, { labelAndValue: 'R from modification' }, { labelAndValue: 'R to section' }, { labelAndValue: 'R from diacritic' }, { labelAndValue: 'R to diacritic' }, { labelAndValue: 'From – abbreviation, capitalisation, and grammar', disabled: true }, { labelAndValue: 'R from acronym' }, { labelAndValue: 'R from initialism' }, { labelAndValue: 'R from CamelCase' }, { labelAndValue: 'R from miscapitalisation' }, { labelAndValue: 'R from other capitalisation' }, { labelAndValue: 'R from modification' }, { labelAndValue: 'R from plural' }, { label: 'From parts of speach', value: 'From parts of speach', disabled: true }, { labelAndValue: 'R from adjective' }, { labelAndValue: 'R from adverb' }, { labelAndValue: 'R from common noun' }, { labelAndValue: 'R from gerund' }, { labelAndValue: 'R from proper noun' }, { labelAndValue: 'R from verb' }, { labelAndValue: 'From – spelling', disabled: true }, { labelAndValue: 'R from alternative spelling' }, { labelAndValue: 'R from misspelling' }, { labelAndValue: 'R from American English' }, { labelAndValue: 'R from British English' }, { labelAndValue: 'R from ASCII-only' }, { labelAndValue: 'R from diacritic' }, { labelAndValue: 'R from ligature' }, { labelAndValue: 'R from stylization' }, { labelAndValue: 'R from alternative transliteration' }, { labelAndValue: 'R from Wade–Giles romanization' }, { labelAndValue: 'From alternative names, general', disabled: true }, { labelAndValue: 'R from alternative language' }, { labelAndValue: 'R from alternative name' }, { labelAndValue: 'R from former name' }, { labelAndValue: 'R from historic name' }, { labelAndValue: 'R from incomplete name' }, { labelAndValue: 'R from incorrect name' }, { labelAndValue: 'R from letter–word combination' }, { labelAndValue: 'R from long name' }, { labelAndValue: 'R from portmanteau' }, { labelAndValue: 'R from predecessor company name' }, { labelAndValue: 'R from short name' }, { labelAndValue: 'R from sort name' }, { labelAndValue: 'R from less specific name' }, { labelAndValue: 'R from more specific name' }, { labelAndValue: 'R from antonym' }, { labelAndValue: 'R from eponym' }, { labelAndValue: 'R from synonym' }, { labelAndValue: 'R from Roman numerals' }, { labelAndValue: 'From alternative names, geography', disabled: true }, { labelAndValue: 'R from Canadian settlement name' }, { labelAndValue: 'R from name and country' }, { labelAndValue: 'R from city and state' }, { labelAndValue: 'R from city and province' }, { labelAndValue: 'R from more specific geographic name' }, { labelAndValue: 'R from postal abbreviation' }, { labelAndValue: 'R from postal code' }, { labelAndValue: 'R from US postal abbreviation' }, { labelAndValue: 'From alternative names, organisms', disabled: true }, { labelAndValue: 'R from scientific abbreviation' }, { labelAndValue: 'R from scientific name' }, { labelAndValue: 'R from alternative scientific name' }, { labelAndValue: 'R from monotypic taxon' }, { labelAndValue: 'From alternative names, people', disabled: true }, { labelAndValue: 'R from birth name' }, { labelAndValue: 'R from given name' }, { labelAndValue: 'R from married name' }, { labelAndValue: 'R from name with title' }, { labelAndValue: 'R from non-neutral name' }, { labelAndValue: 'R from personal name' }, { labelAndValue: 'R from pseudonym' }, { labelAndValue: 'R from relative' }, { labelAndValue: 'R from spouse' }, { labelAndValue: 'R from surname' }, { labelAndValue: 'From alternative names, technical', disabled: true }, { labelAndValue: 'R from Bluebook abbreviation' }, { labelAndValue: 'R from brand name' }, { labelAndValue: 'R from drug trade name' }, { labelAndValue: 'R from file name' }, { labelAndValue: 'R from Java package name' }, { labelAndValue: 'R from MathSciNet abbreviation' }, { labelAndValue: 'R from molecular formula' }, { labelAndValue: 'R from NLM abbreviation' }, { labelAndValue: 'R from product name' }, { labelAndValue: 'R from slogan' }, { labelAndValue: 'R from symbol' }, { labelAndValue: 'R from systematic abbreviations' }, { labelAndValue: 'R from technical name' }, { labelAndValue: 'R from trademark' }, { labelAndValue: 'From – navigation', disabled: true }, { labelAndValue: 'R from file metadata link' }, { labelAndValue: 'R mentioned in hatnote' }, { labelAndValue: 'R from shortcut' }, { labelAndValue: 'R from template shortcut' }, { labelAndValue: 'From disambiguations', disabled: true }, { labelAndValue: 'R from ambiguous term' }, { labelAndValue: 'R from incomplete disambiguation' }, { labelAndValue: 'R from incorrect disambiguation' }, { labelAndValue: 'R from other disambiguation' }, { labelAndValue: 'R from predictable disambiguation' }, { labelAndValue: 'R from unnecessary disambiguation' }, { labelAndValue: 'From mergers, duplicates, and moves', disabled: true }, { labelAndValue: 'R from duplicated article' }, { labelAndValue: 'R with history' }, { labelAndValue: 'R from merge' }, { labelAndValue: 'R from move' }, { labelAndValue: 'R with old history' }, { labelAndValue: 'From fiction', disabled: true }, { labelAndValue: 'R from fictional character' }, { labelAndValue: 'R from fictional element' }, { labelAndValue: 'R from fictional location' }, { labelAndValue: 'From related info', disabled: true }, { labelAndValue: 'R from album' }, { labelAndValue: 'R from animal' }, { labelAndValue: 'R from book' }, { labelAndValue: 'R from catchphrase' }, { labelAndValue: 'R from domain name' }, { labelAndValue: 'R from top-level domain' }, { labelAndValue: 'R from film' }, { labelAndValue: 'R from gender' }, { labelAndValue: 'R from legislation' }, { labelAndValue: 'R from list topic' }, { labelAndValue: 'R from member' }, { labelAndValue: 'R from person' }, { labelAndValue: 'R from phrase' }, { labelAndValue: 'R from quotation' }, { labelAndValue: 'R from related word' }, { labelAndValue: 'R from school' }, { labelAndValue: 'R from song' }, { labelAndValue: 'R from subtopic' }, { labelAndValue: 'R from team' }, { labelAndValue: 'R from work' }, { labelAndValue: 'R from writer' }, { labelAndValue: 'R from Unicode' }, { labelAndValue: 'To – grammar, punctuation, and spelling', disabled: true }, { labelAndValue: 'R to acronym' }, { labelAndValue: 'R to initialism' }, { labelAndValue: 'R to ASCII-only title' }, { labelAndValue: 'R to diacritic' }, { labelAndValue: 'R to ligature' }, { labelAndValue: 'R to plural' }, { labelAndValue: 'To alternative names', disabled: true }, { labelAndValue: 'R to former name' }, { labelAndValue: 'R to historic name' }, { labelAndValue: 'R to joint biography' }, { labelAndValue: 'R to name with title' }, { labelAndValue: 'R to monotypic taxon' }, { labelAndValue: 'R to scientific name' }, { labelAndValue: 'R to systematic name' }, { labelAndValue: 'R to technical name' }, { labelAndValue: 'To – navigation and disambiguation', disabled: true }, { labelAndValue: 'R to anchor' }, { labelAndValue: 'R to anthroponymy page' }, { labelAndValue: 'R to disambiguation page' }, { labelAndValue: 'R to list entry' }, { labelAndValue: 'R to section' }, { labelAndValue: 'To miscellaneous', disabled: true }, { labelAndValue: 'R to decade' }, { labelAndValue: 'R to related topic' }, { labelAndValue: 'R to subpage' }, { labelAndValue: 'R to subtopic' }, { labelAndValue: 'R to TV episode list entry' }, { label: 'Custom - prompt me', value: 'custom' } ])); } else { // now Categories $extra.html('<label for="afcHelper_redirect_name_' + id + '">Category name: <input type="text" size="100" ' + 'name="afcHelper_redirect_name_' + id + '" id="afcHelper_redirect_name_' + id + '" value="' + afcHelper_escapeHtmlChars(afcHelper_Submissions[id].title) + '" />'); $extra.html($extra.html + ' <label for="afcHelper_redirect_parents_' + id + '">Parent categories (comma-separated): ' + '<input type="text" size="100" id="afcHelper_redirect_parents_' + id + '" name="afcHelper_redirect_parents_' + id + '" value="' + afcHelper_escapeHtmlChars(afcHelper_Submissions[id].parents) + '" />'); $extra.append(' '); $extra.append($(' ', {type: 'checkbox', name: 'afcHelper_redirect_container_' + id, id: 'afcHelper_redirect_container_' + id})); $extra.append('<label for="afcHelper_redirect_container_' + id + '">This is a <a href="/wiki/Wikipedia:Container_category" title="Wikipedia:Container category">container category</a> '); $extra.html($extra.html + ' <input type="checkbox" name="afcHelper_redirect_container_' + id + '"'); } $extra.html($extra.html + ' <label for="afcHelper_redirect_comment_' + id + '">Comment: ' + '<input type="text" size="100" id="afcHelper_redirect_comment_' + id + '" name="afcHelper_redirect_comment_' + id + '"/>'); } else if (selectValue === 'decline') { if (afcHelper_Submissions[id].type === 'redirect') { $extra.html('<label for="afcHelper_redirect_decline_' + id + '">Reason for decline: ' + afcHelper_generateSelect('afcHelper_redirect_decline_' + id, [ { label: 'Already exists', value: 'exists' }, { label: 'Blank request', value: 'blank' }, { label: 'No valid target specified', value: 'notarget' }, { label: 'Unlikely search term', value: 'unlikely' }, { label: 'Not a redirect request', value: 'notredirect' }, { label: 'Custom - reason below', selected: true, value: 'custom' }])); } else { // now Categories $extra.html('<label for="afcHelper_redirect_decline_' + id + '">Reason for decline: ' + afcHelper_generateSelect('afcHelper_redirect_decline_' + id, [{ label: 'Already exists', value: 'exists' }, { label: 'Blank request', value: 'blank' }, { label: 'Unlikely category', value: 'unlikely' }, { label: 'Not a category request', value: 'notcategory' }, { label: 'Custom - reason below', selected: true, value: 'custom' }])); } $extra.html($extra.html + ' <label for="afcHelper_redirect_comment_' + id + '">Comment: ' + '<input type="text" size="100" id="afcHelper_redirect_comment_' + id + '" name="afcHelper_redirect_comment_' + id + '"/>'); } else if (selectValue === 'none'){ // for categories and redirects! $extra.html(''); } else { $extra.html($extra.html + '<label for="afcHelper_redirect_comment_' + id + '">Comment: ' + '<input type="text" size="100" id="afcHelper_redirect_comment_' + id + '" name="afcHelper_redirect_comment_' + id + '"/>'); } } function afcHelper_redirect_performActions { // Load all of the data. for (var i = 0; i < afcHelper_Submissions.length; i++) { var action = $("#afcHelper_redirect_action_" + i).val; afcHelper_Submissions[i].action = action; if (action === 'none') continue; if (action === 'accept') { if (afcHelper_Submissions[i].type === 'redirect') { afcHelper_Submissions[i].title = $("#afcHelper_redirect_from_" + i).val; afcHelper_Submissions[i].to = $("#afcHelper_redirect_to_" + i).val; afcHelper_Submissions[i].append = $("#afcHelper_redirect_append_" + i).val; if (afcHelper_Submissions[i].append === 'custom') { afcHelper_Submissions[i].append = prompt("Please enter the template to append to " + afcHelper_Submissions[i].title + ". Do not include the curly brackets."); } if (afcHelper_Submissions[i].append === 'none' || afcHelper_Submissions[i].append === null) afcHelper_Submissions[i].append = ''; else afcHelper_Submissions[i].append = '\{\{' + afcHelper_Submissions[i].append + '\}\}'; } else { afcHelper_Submissions[i].title = $("#afcHelper_redirect_name_" + i).val; afcHelper_Submissions[i].parents = $("#afcHelper_redirect_parents_" + i).val; afcHelper_Submissions[i].container = $("#afcHelper_redirect_container_" + i).is(':checked'); } } else if (action === 'decline') { afcHelper_Submissions[i].reason = $('#afcHelper_redirect_decline_' + i).val; } afcHelper_Submissions[i].comment = $("#afcHelper_redirect_comment_" + i).val; } // Data loaded. Show progress screen and get WP:AFC/R page text. displayMessage('<ul id="afcHelper_status"></ul><ul id="afcHelper_finish"></ul>'); var addStatus = function ( status ) { $('#afcHelper_status').append( status ); }; $('#afcHelper_finish').html($('#afcHelper_finish').html + '<span id="afcHelper_finished_wrapper"><span id="afcHelper_finished_main" style="display:none"><li id="afcHelper_done">Done (<a href="' + mw.config.get("wgArticlePath").replace("$1", encodeURI(afcHelper_RedirectPageName)) + '?action=purge" title="' + afcHelper_RedirectPageName + '">Reload page</a>)</li> '); pagetext = afcHelper_getPageText(afcHelper_RedirectPageName, addStatus); var totalaccept = 0; var totaldecline = 0; var totalcomment = 0; // traverse the submissions and locate the relevant sections. addStatus('<li>Processing ' + afcHelper_RedirectSubmissions.length + ' submission' + (afcHelper_RedirectSubmissions.length === 1 ? '' : 's') + '...</li>'); for (var i = 0; i < afcHelper_RedirectSubmissions.length; i++) { var sub = afcHelper_RedirectSubmissions[i]; if (pagetext.indexOf(afcHelper_RedirectSections[sub.section]) === -1) { // Someone has modified the section in the mean time. Skip. addStatus('<li>Skipping ' + sub.title + ': Cannot find section. Perhaps it was modified in the mean time?</li>'); continue; } var text = afcHelper_RedirectSections[sub.section]; var startindex = pagetext.indexOf(afcHelper_RedirectSections[sub.section]); var endindex = startindex + text.length; // First deal with cats. These are easy. if (sub.type === 'category') { if (sub.action === 'accept') { var cattext = ''; if (sub.container) { cattext += '\n'; } if (sub.parents !== '') { cattext = sub.parents .split(',') .map(function(cat){ return '\[\[' + cat + '\]\]'; }) .join('\n'); } afcHelper_editPage(sub.title, cattext, 'Created via \[\[WP:AFC|Articles for Creation\]\] (\[\[WP:WPAFC|you can help!\]\])', true); var talktext = '\{\{subst:WPAFC/article|class=Cat\}\}'; var talktitle = sub.title.replace(/Category:/gi, 'Category talk:'); afcHelper_editPage(talktitle, talktext, 'Placing WPAFC project banner', true); var header = text.match(/==[^=]*==/)[0]; text = header + "\n\{\{AfC-c|a\}\}\n" + text.substring(header.length); if (sub.comment !== '') text += '\n*\{\{subst:afc category|accept|2=' + sub.comment + '\}\} \~\~\~\~\n'; else text += '\n*\{\{subst:afc category\}\} \~\~\~\~\n'; text += '\{\{AfC-c|b\}\}\n'; totalaccept++; } else if (sub.action === 'decline') { var header = text.match(/==[^=]*==/)[0]; var reason = afcHelper_categoryDecline_reasonhash[sub.reason]; if (reason === '') reason = sub.comment; else if (sub.comment !== '') reason = reason + ': ' + sub.comment; if (reason === '') { $('afcHelper_status').html($('#afcHelper_status').html + '<li>Skipping ' + sub.title + ': No decline reason specified.</li>'); continue; } text = header + "\n\{\{AfC-c|d\}\}\n" + text.substring(header.length); if (sub.comment === '') text += '\n*\{\{subst:afc category|' + sub.reason + '\}\} \~\~\~\~\n'; else text += '\n*\{\{subst:afc category|decline|2=' + reason + '\}\} \~\~\~\~\n'; text += '\{\{AfC-c|b\}\}\n'; totaldecline++; } else if (sub.action === 'comment') { if (sub.comment !== '') text += '\n\n\{\{afc comment|1=' + sub.comment + ' \~\~\~\~\}\}\n'; totalcomment++; } } else { // redirects...... var acceptcomment = ''; var declinecomment = ''; var othercomment = ''; var acceptcount = 0, declinecount = 0, commentcount = 0, hascomment = false; for (var j = 0; j < sub.from.length; j++) { var redirect = sub.from[j]; if (redirect.action === 'accept') { var redirecttext = '#REDIRECT \[\[' + redirect.to + '\]\]\n' + redirect.append; afcHelper_editPage(redirect.title, redirecttext, 'Redirected page to \[\[' + redirect.to + '\]\] via \[\[WP:AFC|Articles for Creation\]\] (\[\[WP:WPAFC|you can help!\]\])', true); if ((redirect.title.toLowerCase.indexOf('talk:') < 0) && (redirect.title.indexOf('WT:') < 0)) { var talktext = '\{\{subst:WPAFC/redirect\}\}'; var talktitle; if (redirect.title.indexOf(':') >= 0) { if (redirect.title.indexOf('WP:') == 0) { talktitle = redirect.title.replace('WP:', 'WT:'); } else { talktitle = redirect.title.replace(':', ' talk:'); } } else { talktitle = 'Talk:' + redirect.title; } afcHelper_editPage(talktitle, talktext, 'Placing WPAFC project banner', true); } acceptcomment += redirect.title + " &rarr; " + redirect.to; if (redirect.comment !== '') { acceptcomment += ': ' + redirect.comment; hascomment = true; } else { acceptcomment += '. '; } acceptcount++; } else if (redirect.action === 'decline') { var reason = afcHelper_redirectDecline_reasonhash[redirect.reason]; if (reason === '') reason = redirect.comment; else if (redirect.comment !== '') reason = reason + ': ' + redirect.comment; if (reason === '') { $('#afcHelper_status').html($('#afcHelper_status').html + '<li>Skipping ' + redirect.title + ': No decline reason specified.</li>'); continue; } declinecomment += ((redirect.reason === 'blank' || redirect.reason === 'notredirect') ? reason + ". " : redirect.title + " &rarr; " + redirect.to + ": " + reason + ". "); declinecount++; } else if (redirect.action === 'comment') { othercomment += redirect.title + ": " + redirect.comment + ". "; commentcount++; } } var reason = ''; if (acceptcount > 0) reason += '\n*\{\{subst:afc redirect|accept|2=' + acceptcomment + ' Thank you for your contributions to Wikipedia!\}\} \~\~\~\~'; if (declinecount > 0) reason += '\n*\{\{subst:afc redirect|decline|2=' + declinecomment + '\}\} \~\~\~\~'; if (commentcount > 0) reason += '\n*\{\{afc comment|1=' + othercomment + '\~\~\~\~\}\}'; reason += '\n'; if (!hascomment && acceptcount === sub.from.length) { if (acceptcount > 1) reason = '\n*\{\{subst:afc redirect|all\}\} \~\~\~\~\n'; else reason = '\n*\{\{subst:afc redirect\}\} \~\~\~\~\n'; } if (acceptcount + declinecount + commentcount > 0) { if (acceptcount + declinecount === sub.from.length) { // Every request disposed of. Close. var header = text.match(/==[^=]*==/)[0]; if (acceptcount > declinecount) text = header + "\n\{\{AfC-c|a\}\}\n" + text.substring(header.length); else text = header + "\n\{\{AfC-c|d\}\}\n" + text.substring(header.length); text += reason; text += '\{\{AfC-c|b\}\}\n'; } else text += reason + '\n'; } totalaccept += acceptcount; totaldecline += declinecount; totalcomment += commentcount; } pagetext = pagetext.substring(0, startindex) + text + pagetext.substring(endindex); } var summary = "Updating submission status:"; if (totalaccept > 0) summary += " accepting " + totalaccept + " request" + (totalaccept > 1 ? 's' : ''); if (totaldecline > 0) { if (totalaccept > 0) summary += ','; summary += " declining " + totaldecline + " request" + (totaldecline > 1 ? 's' : ''); } if (totalcomment > 0) { if (totalaccept > 0 || totaldecline > 0) summary += ','; summary += " commenting on " + totalcomment + " request" + (totalcomment > 1 ? 's' : ''); } afcHelper_editPage(afcHelper_RedirectPageName, pagetext, summary, false); // Display the "Done" text only after all ajax requests are completed $(document).ajaxStop(function { $("#afcHelper_finished_main").css("display", ""); }); } /** * Gets the text of a page. * @param {string} The title of the page to get. * @param {function} A function that takes a HTML string to report status. */ function afcHelper_getPageText(title, addStatus) { var addStatus = typeof addStatus !== 'undefined' ? addStatus : function ( status ) {}; addStatus('<li id="afcHelper_get' + jqEsc(title) + '">Getting <a href="' + mw.config.get("wgArticlePath").replace("$1", encodeURI(title)) + '" title="' + title + '">' + title + '</a></li>'); var request = { 'action': 'query', 'prop': 'revisions', 'rvprop': 'content', 'format': 'json', 'indexpageids': true, 'titles' : title }; var response = JSON.parse( $.ajax({ url: mw.util.wikiScript('api'), data: request, async: false }) .responseText ); pageid = response['query']['pageids'][0]; if (pageid === "-1") { addStatus('The page <a class="new" href="' + mw.config.get("wgArticlePath").replace("$1", encodeURI(title)) + '" title="' + title + '">' + title + '</a> does not exist'); return ''; } var newtext = response['query']['pages'][pageid]['revisions'][0]['*']; addStatus('<li id="afcHelper_get' + jqEsc(title) + '">Got <a href="' + mw.config.get("wgArticlePath").replace("$1", encodeURI(title)) + '" title="' + title + '">' + title + '</a></li>'); return newtext; } function afcHelper_cleanuplinks(text) { // Convert external links to Wikipedia articles to proper wikilinks var wikilink_re = /(\[){1,2}(?:https?:)?\/\/(en.wikipedia.org\/wiki|enwp.org)\/([^\s\|\]\[]+)(\s|\|)?((?:\[\^\[\*\]\]|[^\]\[])*)(\]){1,2}/gi; var temptext = text; var match; while (match = wikilink_re.exec(temptext)) { var pagename = decodeURI(match[3].replace(/_/g,' ')); var displayname = decodeURI(match[5].replace(/_/g,' ')); if (pagename === displayname) displayname = ''; var replacetext =  + displayname : ) + ''; pagetext = pagetext.replace(match[0],replacetext); } return text; } function afcHelper_generateSelect(title, options) { return afcHelper_generateSelectObject(title, options).prop('outerHTML'); } function afcHelper_generateSelectObject(title, options, onchange) { var $select = $(' ') .attr('name', title) .attr('id', title); if ( onchange !== null ) { $select.change(onchange); } options.forEach( function ( option ) { if ( option.labelAndValue ) { option.value = option.labelAndValue; option.label = option.labelAndValue; } var $option = $( ' ' ) .appendTo( $select ) .val( afcHelper_escapeHtmlChars( option.value) ) .text( option.label ); if ( option.selected ) $option.attr( 'selected', 'selected' ); if ( option.disabled ) $option.attr( 'disabled', 'disabled' ); } ); return $select; } function afcHelper_escapeHtmlChars(original) { return original.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;"); } /** * The old mw.util.jsMessage function before https://gerrit.wikimedia.org/r/#/c/17605/, which * introduced the silly auto-hide function. Also with the original styles. * Add a little box at the top of the screen to inform the user of * something, replacing any previous message. * Calling with no arguments, with an empty string or null will hide the message * Taken from User:Timotheus Canens/displaymessage.js * * @param message {mixed} The DOM-element, jQuery object or HTML-string to be put inside the message box. * @param className {String} Used in adding a class; should be different for each call * to allow CSS/JS to hide different boxes. null = no class used. * @return {Boolean} True on success, false on failure. */ function displayMessage( message, className ){ if ( !arguments.length || message === '' || message === null ) { $( '#display-message' ).empty.hide; return true; // Emptying and hiding message is intended behaviour, return true } else { // We special-case skin structures provided by the software. Skins that // choose to abandon or significantly modify our formatting can just define // an mw-js-message div to start with. var $messageDiv = $( '#display-message' ); if ( !$messageDiv.length ) { $messageDiv = $( '<div id="display-message" style="margin:1em;padding:0.5em 2.5%;border:solid 1px #ddd;background-color:#fcfcfc;font-size: 0.8em"> ' ); if ( mw.util.$content.length ) { mw.util.$content.prepend( $messageDiv ); } else { return false; } } if ( className ) { $messageDiv.prop( 'class', 'display-message-' + className ); } if ( typeof message === 'object' ) { $messageDiv.empty; $messageDiv.append( message ); } else { $messageDiv.html( message ); } $messageDiv.slideDown; return true; } } function jqEsc(expression) { return expression.replace(/[!"#$%&'*+,.\/:;<=>?@\[\\\]^`{|}~ ]/g, ''); } function afcHelper_editPage(title, newtext, summary, createonly, nopatrol) { var wgArticlePath = mw.config.get('wgArticlePath'); summary += afcHelper_advert; $("#afcHelper_finished_wrapper").html('<span id="afcHelper_AJAX_finished_' + afcHelper_AJAXnumber + '" style="display:none">' + $("#afcHelper_finished_wrapper").html + ' '); var func_id = afcHelper_AJAXnumber; afcHelper_AJAXnumber++; $('#afcHelper_status').html($('#afcHelper_status').html + '<li id="afcHelper_edit' + jqEsc(title) + '">Editing <a href="' + wgArticlePath.replace("$1", encodeURI(title)) + '" title="' + title + '">' + title + '</a></li>'); var request = { 'action': 'edit', 'title': title, 'text': newtext, 'summary': summary, }; if (createonly) request.createonly = true; var api = new mw.Api; api.postWithEditToken(request) .done(function ( data ) { if ( data && data.edit && data.edit.result && data.edit.result == 'Success' ) { $('#afcHelper_edit' + jqEsc(title)).html('Saved <a href="' + wgArticlePath.replace("$1", encodeURI(title)) + '" title="' + title + '">' + title + '</a>'); } else { $('#afcHelper_edit' + jqEsc(title)).html('<span class="afcHelper_notice">Edit failed on <a href="' + wgArticlePath.replace("$1", encodeURI(title)) + '" title="' + title + '">' + title + '</a> . Error info: ' + JSON.stringify(data)); window.console && console.error('Edit failed on %s (%s). Error info: %s', wgArticlePath.replace("$1", encodeURI(title)), title, JSON.stringify(data)); } } ) .fail( function ( error ) { if (createonly && error == "articleexists") $('#afcHelper_edit' + jqEsc(title)).html('<span class="afcHelper_notice">Edit failed on <a href="' + wgArticlePath.replace("$1", encodeURI(title)) + '" title="' + title + '">' + title + '</a> . Error info: The article already exists!'); else $('#afcHelper_edit' + jqEsc(title)).html('<span class="afcHelper_notice">Edit failed on <a href="' + wgArticlePath.replace("$1", encodeURI(title)) + '" title="' + title + '">' + title + '</a> . Error info: ' + error); }) .always( function { $("#afcHelper_AJAX_finished_" + func_id).css("display", ''); }); if (!nopatrol) { /* We patrol by default */ if ($('.patrollink').length) { // Extract the rcid token from the "Mark page as patrolled" link on page var patrolhref = $('.patrollink a').attr('href'); var rcid = mw.util.getParamValue('rcid', patrolhref); if (rcid) { $('#afcHelper_status').html($('#afcHelper_status').html + '<li id="afcHelper_patrol' + jqEsc(title) + '">Marking <a href="' + wgArticlePath.replace("$1", encodeURI(title)) + '" title="' + title + '">' + title + ' as patrolled</a></li>'); var patrolrequest = { 'action': 'patrol', 'format': 'json', 'rcid': rcid }; api.postWithToken('patrol', patrolrequest) .done(function ( data ) { if ( data ) { $('#afcHelper_patrol' + jqEsc(title)).html('Marked <a href="' + wgArticlePath.replace("$1", encodeURI(title)) + '" title="' + title + '">' + title + '</a> as patrolled'); } else { $('#afcHelper_patrol' + jqEsc(title)).html('<span class="afcHelper_notice">Patrolling failed on <a href="' + wgArticlePath.replace("$1", encodeURI(title)) + '" title="' + title + '">' + title + '</a> with an unknown error'); window.console && console.error('Patrolling failed on %s (%s) with an unknown error.', wgArticlePath.replace("$1", encodeURI(title)), title); } } ) .fail( function ( error ) { $('#afcHelper_patrol' + jqEsc(title)).html('<span class="afcHelper_notice">Patrolling failed on <a href="' + wgArticlePath.replace("$1", encodeURI(title)) + '" title="' + title + '">' + title + '</a> . Error info: ' + error); }); }  } } } // From http://stackoverflow.com/a/6323598/1757964 function allMatches ( regex, string ) { var matches = [], match; do { match = regex.exec( string ); if ( match ) { matches.push( match ); } } while ( match ); return matches; } mw.loader.using(['mediawiki.api', 'mediawiki.util'], function { // Create portlet link var redirectportletLink = mw.util.addPortletLink('p-cactions', '#', 'Review AFC/R', 'ca-afcrhs', 'Review', 'a'); // Bind click handler $(redirectportletLink).click(function(e) { e.preventDefault; // clear variables for the case somebody is clicking on "review" multiple times afcHelper_RedirectSubmissions.length = 0; afcHelper_RedirectSections.length = 0; afcHelper_numTotal = 0; afcHelper_Submissions.length = 0; needsupdate.length = 0; afcHelper_redirect_init; }); }); }); //

/* Ahecht/Scripts/pageswap.js */ //

/* Enterprisey/delsort.js */ // ( function ( $, mw ) { mw.loader.load( "jquery.chosen" ); mw.loader.load( "mediawiki.ui.input", "text/css" ); var afdcCategories = { "m": "Media and music", "o": "Organization, corporation, or product", "b": "Biographical", "s": "Society topics", "w": "Web or Internet", "g": "Games or sports", "t": "Science and technology", "f": "Fiction and the arts", "p": "Places and transportation", "i": "Indiscernible or unclassifiable topic", "u": "Not sorted yet" }; var ADVERTISEMENT = " (assisted)"; var currentAfdcCat = ""; var currentDelsortCategories = []; if ( mw.config.get( "wgPageName" ).indexOf("Wikipedia:Articles_for_deletion/") != -1 && mw.config.get( "wgPageName" ).indexOf("Wikipedia:Articles_for_deletion/Log/") == -1) { var portletLink = mw.util.addPortletLink("p-cactions", "#", "Delsort", "pt-delsort", "Perform deletion sorting"); // Load list of delsort categories var delsortCategoriesPromise = $.ajax( { url: "https://en.wikipedia.org/w/index.php?action=raw&title=" + encodeURIComponent( "Wikipedia:WikiProject Deletion sorting/Computer-readable.json" ) + "&maxage=86400&smaxage=86400", dataType: "json" } ) $( portletLink ).click( function ( e ) { e.preventDefault; // Validation for new custom fields var validateCustomCat = function ( container ) { var categoryName = container.children( "input" ).first.val; $.getJSON( mw.util.wikiScript("api"), { format: "json", action: "query", prop: "pageprops", titles: "Wikipedia:WikiProject Deletion sorting/" + categoryName } ).done( function ( data ) { var setStatus = function ( status ) { var text = "Not sure"; var imageSrc = "https://upload.wikimedia.org/wikipedia/commons/a/ad/Question_mark_grey.png"; switch( status ) { case "d": text = "Doesn't exist"; imageSrc = "https://upload.wikimedia.org/wikipedia/commons/5/5f/Red_X.svg"; break; case "e": text = "Exists"; imageSrc = "https://upload.wikimedia.org/wikipedia/commons/1/16/Allowed.svg"; break; } container.children( ".category-status" ).empty .append( $( " ", { "src": imageSrc, "style": "padding: 0 5px; width: 20px; height: 20px" } ) ) .append( text ); }; if( data && data.query && data.query.pages ) { if( data.query.pages.hasOwnProperty( "-1" ) ) { setStatus( "d" ); } else { setStatus( "e" ); } } else { setStatus( "n" ); } } ); }; // Define a function to add a new custom field, used below var addCustomField = function ( e ) { $( " " ) .appendTo( "#delsort-td" ) .css( "width", "100%" ) .css( "margin", "0.25em auto" ) .append( $( " " ) .attr( "type", "text" ) .addClass( "mw-ui-input mw-ui-input-inline custom-delsort-field" ) .change( function ( e ) { validateCustomCat( $( this ).parent ); } ) ) .append( $( " " ).addClass( "category-status" ) ) .append( " (" ) .append( $( " ", { "src": "https://upload.wikimedia.org/wikipedia/commons/a/a2/Crystal_128_reload.svg", "style": "width: 15px; height: 15px; cursor: pointer" } ) .click( function ( e ) { validateCustomCat( $( this ).parent ); } ) ) .append( ")" ) .append( $( " " ) .addClass( "mw-ui-button mw-ui-destructive mw-ui-quiet" ) .text( "Remove" ) .click( function { $( this ).parent.remove; } ) ); }; $( "#mw-content-text" ).prepend( '<div style="border: thin solid rgb(197, 197, 197); box-shadow: 0px 3px 8px rgba(0, 0, 0, 0.25); border-radius: 3px; padding: 5px; position: relative;" id="delsort">' + ' Select a deletion sorting category ' + '  ' + '  ' + '  <td style="border-left: solid black thick; padding-left: 10px; vertical-align: top;" id="delsort-td">' + ' <select multiple="multiple" data-placeholder="Select a deletion sorting category..."> ' + ' <button id="add-custom-button" class="mw-ui-button mw-ui-progressive mw-ui-quiet">Add custom ' + ' ' + ' ' + '  ' + '  <button style="position: absolute; top: 5px; right: 5px;" id="close-button" class="mw-ui-button mw-ui-destructive mw-ui-quiet">Close ' + ' ' ); $( "#add-custom-button" ).click( addCustomField ); $( "#close-button" ).click( function { $( "#delsort" ).remove; } ); var afdcHtml = ""; Object.keys( afdcCategories ).forEach( function ( code, i ) { if ( i % 2 === 0 ) afdcHtml += " "; afdcHtml += " <input type='radio' name='afdc' value='" + code + "' id='afdc-" + code + "' /><label for='afdc-" + code + "'>" + afdcCategories[ code ] + " "; if ( i % 2 !== 0 ) afdcHtml += " "; } ); // If there are an odd number of AFDC cats, we need to close off the last row if ( Object.keys( afdcCategories ).length % 2 !== 0 ) afdcHtml += " "; $( "#afdc" ).html( afdcHtml ); // Build the deletion sorting categories delsortCategoriesPromise.done( function ( delsortCategories ) { $.each( delsortCategories, function ( groupName, categories ) { var group = $( " " ) .appendTo( "#delsort select" ) .attr( "label", groupName ); $.each( categories, function ( index, category ) { group.append( $( " " ) .val( category )  .text( category )  .addClass( "delsort-category" ) ); } ); } ); getWikitext( mw.config.get( "wgPageName" ) ).then( function ( wikitext ) { autofillAfdc( wikitext ); // Autofill the delsort box var DELSORT_RE = /:<small class="delsort-notice">(.+?)<\/small>/g; var DELSORT_LIST_RE = /\[\[Wikipedia:WikiProject Deletion sorting\/(.+?)\|.+?\]\]/; var delsortMatch; var delsortListMatch; do { delsortMatch = DELSORT_RE.exec( wikitext ); if( delsortMatch !== null ) { delsortListMatch = DELSORT_LIST_RE.exec( delsortMatch[1] ); if( delsortListMatch !== null ) { currentDelsortCategories.push( delsortListMatch[1] ); var delsortOption = document.querySelector( "option.delsort-category[value='" + delsortListMatch[1] + "']" ); if( delsortOption ) { delsortOption.selected = true; } } } } while( delsortMatch ); // Now that we've updated the underlying, ask Chosen to // update the visible search box $( "#delsort select" ).trigger( "chosen:updated" ); } ); // end getWikitext } ); // end delsortCategoriesPromise // Initialize the special chosen.js select box // (some code stolen from http://stackoverflow.com/a/27445788) $( "#delsort select" ).chosen; $( "#delsort .chzn-container" ).css( "text-align", "left" ); // Add the button that triggers sorting $( "#delsort" ).append( $( " " ) .css( "text-align", "center" ) .append( $( " ") .addClass( "mw-ui-button" ) .addClass( "mw-ui-progressive" ) .attr( "id", "sort-button" ) .text( "Save changes" ) .click( function { // Make a status list $( "#delsort" ).append( $( "<ul> ") .attr( "id", "status" ) ); // Build a list of categories var categories = $( "#delsort select" ).val || []; $( ".custom-delsort-field" ).each( function ( index, element ) { categories.push( $( element ).val ); } ); categories = categories.filter( Boolean ); // remove empty strings categories = removeDups( categories ); // Only allow categories that aren't already there categories = categories.filter( function ( elem ) { return currentDelsortCategories.indexOf( elem ) < 0; } ); // Obtain the target AFDC category, brought to you by http://stackoverflow.com/a/24886483/1757964 var afdcTarget = document.querySelector("input[name='afdc']:checked").value; // Actually do the delsort saveChanges( categories, afdcTarget ); } ) ) ); } ); } // End if ( mw.config.get( "wgPageName" ).indexOf('Wikipedia:Articles_for_deletion/') ... ) /* * Autofills the AFDC radio button group based on the current * page's wikitext */ function autofillAfdc( wikitext ) { var regexMatch = /REMOVE THIS TEMPLATE WHEN CLOSING THIS AfD(?:\|(.*))?}}/.exec( wikitext ); if ( regexMatch ) { var templateParameter = regexMatch[1]; if ( templateParameter ) { currentAfdcCat = templateParameter; if ( templateParameter.length === 1 ) { var currentClass = templateParameter.toLowerCase; $( "#afdc-" + currentClass ).prop( "checked", true ); } } } } /* * Saves the changes to the current discussion page by adding delsort notices (if applicable) and updating the AFDC cat */ function saveChanges( cats, afdcTarget ) { var changingAfdcCat = currentAfdcCat.toLowerCase !== afdcTarget; // Indicate to the user that we're doing some deletion sorting $( "#delsort-table" ).remove; $( "#delsort #sort-button" ) .text( "Sorting " + ( changingAfdcCat ? "and categorizing " : "" ) + "discussion..." ) .prop( "disabled", true ) .fadeOut( 400, function { $( this ).remove; } ); var categoryTitleComponent = ( cats.length === 1 ) ? ( "the \"" + cats[0] + "\" category" ) : ( cats.length + " categories" ); var afdcTitleComponent = changingAfdcCat ? " and categorizing it as " + afdcCategories[ afdcTarget ] : ""; $( "#delsort-title" ) .html( "Sorting discussion into " + categoryTitleComponent + afdcTitleComponent + "<span id=\"delsort-dots\"> " ); // Start the animation, using super-advanced techniques var animationInterval = setInterval( function { $( "#delsort-dots" ).text( $( "#delsort-dots" ).text + "." ); if( $( "#delsort-dots" ).text.length > 3 ) { $( "#delsort-dots" ).text( "" ); } }, 600 ); // Place (a) notification(s) on the discussion and update its AFDC cat var editDiscussionDeferred = postDelsortNoticesAndUpdateAfdc( cats, afdcTarget ); // List the discussion at the DELSORT pages var deferreds = cats.map( listAtDelsort ); // We still have to wait for the discussion to be edited deferreds.push( editDiscussionDeferred ); // When everything's done, say something $.when.apply( $, deferreds ).then( function { // Call the done hook if( window.delsortDoneHook ) { window.delsortDoneHook; } // We're done! $( "#delsort-title" ) .text( "Done " + ( changingAfdcCat ? "updating the discussion's AFDC category and " : "" ) + "sorting discussion into " + categoryTitleComponent + "." ); showStatus( "Done! " + ( changingAfdcCat ? "The discussion's AFDC was updated and it was" : "Discussion was" ) + " sorted into " + categoryTitleComponent + ". (" ) .append( $( "<a>" ) .text( "reload" ) .attr( "href", "#" ) .click( function { document.location.reload( true ); } ) ) .append( ")" ); clearInterval( animationInterval ); } ); } /* * Adds a new status to the status list, and returns the newly-displayed element. */ function showStatus( newStatus ) { return $( "<li>" ) .appendTo( "#delsort ul#status" ) .html( newStatus ); } /* * Adds some notices to the discussion page that this discussion was sorted. */ function postDelsortNoticesAndUpdateAfdc( cats, afdcTarget ) { var changingAfdcCat = currentAfdcCat.toLowerCase !== afdcTarget, deferred = $.Deferred, statusElement = showStatus( "Updating the discussion page..." ); getWikitext( mw.config.get( "wgPageName" ) ).then( function ( wikitext ) { try { statusElement.html( "Processing wikitext..." ); // Process wikitext // First, add delsort notices wikitext += createDelsortNotices( cats ); // Then, update the AFDC category var afdcMatch = wikitext.match( /REMOVE THIS TEMPLATE WHEN CLOSING THIS AfD/ ); if ( afdcMatch && afdcMatch[ 0 ] ) { var afdcMatchIndex = wikitext.indexOf( afdcMatch[ 0 ] ) + afdcMatch[ 0 ].length, charAfterTemplateName = wikitext[ afdcMatchIndex ]; if ( charAfterTemplateName === "}" ) { wikitext = wikitext.slice( 0, afdcMatchIndex ) + "|" + afdcTarget.toUpperCase + wikitext.slice( afdcMatchIndex ); } else if ( charAfterTemplateName === "|" ) { wikitext = wikitext.replace( "|" + currentAfdcCat + "}}", "|" + afdcTarget.toUpperCase + "}}" ); } } statusElement.html( "Processed wikitext. Saving..." ); var catPlural = ( cats.length === 1 ) ? "" : "s"; $.ajax( { url: mw.util.wikiScript( "api" ), type: "POST", dataType: "json", data: { format: "json", action: "edit", title: mw.config.get( "wgPageName" ), summary: "Updating nomination page with notices" + ( changingAfdcCat ? " and new AFDC cat" : "" ) + ADVERTISEMENT, token: mw.user.tokens.get( "csrfToken" ), text: wikitext } } ).done ( function ( data ) { if ( data && data.edit && data.edit.result && data.edit.result == "Success" ) { statusElement.html( cats.length + " notice" + catPlural + " placed on the discussion!" ); if ( changingAfdcCat ) { if ( currentAfdcCat ) { var formattedCurrentAfdcCat = currentAfdcCat.length === 1 ? afdcCategories[ currentAfdcCat.toLowerCase ] : currentAfdcCat; showStatus( "Discussion's AFDC category was changed from " + formattedCurrentAfdcCat + " to " + afdcCategories[ afdcTarget ] + "." ); } else { showStatus( "Discussion categorized under " + afdcCategories[ afdcTarget ] + " with AFDC." ); } } deferred.resolve; } else { statusElement.html( "While editing the current discussion page, the edit query returned an error. =(" ); deferred.reject; } } ).fail ( function { statusElement.html( "While editing the current discussion page, the AJAX request failed." ); deferred.reject; } ); } catch ( e ) { statusElement.html( "While getting the current page content, there was an error." ); console.log( "Current page content request error: " + e.message ); deferred.reject; } } ).fail( function { statusElement.html( "While getting the current content, there was an AJAX error." ); deferred.reject; } ); return deferred; } /* * Turns a list of delsort categories into a number of delsort template notice substitutions. */ function createDelsortNotices( cats ) { if ( Array.isArray(cats) && ! cats.length ) return ''; var appendText = "\n" + cat; } ); return appendText + ""; // string concat to prevent it from being transformed into my signature } /* * Adds a listing at the DELSORT page for the category. */ function listAtDelsort( cat ) { // Make a status element just for this category var statusElement = showStatus( "Listing this discussion at DELSORT/" + cat + "..." ); // Clarify our watchlist behavior for this edit var allowedWatchlistBehaviors = ["watch", "unwatch", "preferences", "nochange"]; var watchlistBehavior = "nochange"; // no watchlist change by default if( window.delsortWatchlist && allowedWatchlistBehaviors.indexOf( window.delsortWatchlist.toLowerCase ) >= 0 ) { watchlistBehavior = window.delsortWatchlist.toLowerCase; } var listTitle = "Wikipedia:WikiProject Deletion sorting/" + cat; // First, get the current wikitext for the DELSORT page return $.getJSON( mw.util.wikiScript("api"), { format: "json", action: "query", prop: "revisions", rvprop: "content", rvslots: "main", rvlimit: 1, titles: listTitle, redirects: "true", formatversion: 2, } ).then( function ( data ) { var wikitext = data.query.pages[0].revisions[0].slots.main.content; var properTitle = data.query.pages[0].title; try { statusElement.html( "Got the DELSORT/" + cat + " listing wikitext, processing..." ); // Actually edit the content to include the new listing var newDelsortContent = wikitext.replace("directly below this line -->", "directly below this line -->\n\{\{" + mw.config.get("wgPageName") + "\}\}"); // Then, replace the DELSORT listing with the new content $.ajax( { url: mw.util.wikiScript( "api" ), type: "POST", dataType: "json", data: { format: "json", action: "edit", title: properTitle, summary: "Listing " + mw.config.get("wgPageName") + "" + ADVERTISEMENT, token: mw.user.tokens.get( "csrfToken" ), text: newDelsortContent, watchlist: watchlistBehavior } } ).done ( function ( data ) { if ( data && data.edit && data.edit.result && data.edit.result == "Success" ) { statusElement.html( "Listed page at <a href=" + mw.util.getUrl( listTitle ) + ">the " + cat + " deletion sorting list</a>!" ); } else { statusElement.html( "While listing at DELSORT/" + cat + ", the edit query returned an error. =(" ); } } ).fail ( function { statusElement.html( "While listing at DELSORT/" + cat + ", the ajax request failed." ); } ); } catch ( e ) { statusElement.html( "While getting the DELSORT/" + cat + " content, there was an error." ); console.log( "DELSORT content request error: " + e.message ); //console.log( "DELSORT content request response: " + JSON.stringify( data ) ); } } ).fail( function { statusElement.html( "While getting the DELSORT/" + cat + " content, there was an AJAX error." ); } ); } /** * Gets the wikitext of a page with the given title (namespace required). */ function getWikitext( title ) { return $.getJSON( mw.util.wikiScript("api"), { format: "json", action: "query", prop: "revisions", rvprop: "content", rvslots: "main", rvlimit: 1, titles: title, formatversion: 2, } ).then( function ( data ) { return data.query.pages[0].revisions[0].slots.main.content; } ); } /** * Removes duplicates from an array. */ function removeDups( arr ) { var obj = {}; for( var i = 0; i < arr.length; i++ ) { obj[arr[i]] = 0; } return Object.keys( obj ); } }( jQuery, mediaWiki ) ); //