User: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("FAMEPedia:Articles_for_deletion/") != -1 &&        mw.config.get( "wgPageName" ).indexOf("FAMEPedia: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.famepedia.org/w/index.php?action=raw&title=" + encodeURIComponent( "FAMEPedia: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: "FAMEPedia: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( '' + ' Select a deletion sorting category ' + '   ' + '      ' + '      ' + '           ' + '          Add custom ' + '      ' + '    ' + '  ' + '  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 += " " + 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>/g; var DELSORT_LIST_RE = /\[\[FAMEPedia: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( $( " ")                                                   .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('FAMEPedia: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 + " " );

// 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( $( "" ) .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 $( "" ) .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 ) { var appendText = ""; cats.forEach( function ( cat ) {           appendText += "\n\{\{subst:Delsort|" + cat + "|\~\~\~\~\}\}";        } ); return appendText; }   /*     * 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 = "FAMEPedia: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 the " + cat + " deletion sorting list!" ); } 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 ) ); //