User:Enterprisey/req-helper.js

From Wikipedia, the free encyclopedia
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
//<nowiki>
( function ( $, mw ) {
    if ( mw.config.get( 'wgAction' ) === 'view' &&
         mw.config.get( 'wgPageName' ).indexOf('Wikipedia:Requested_articles') === 0 &&
         mw.config.get( 'wgPageName' ) !== "Wikipedia:Requested_articles" &&
         !$( "#mw-diff-ntitle1" ).length &&
         !$( "#mw-revision-info" ).length ) {
        
        // Load and display form
        mw.loader.using( [ "mediawiki.api", "mediawiki.util" ] ).then( function () {
            $.getJSON(
                mw.util.wikiScript('api'),
                {
                    format: 'json',
                    action: 'query',
                    prop: 'revisions',
                    rvprop: 'content',
                    rvlimit: 1,
                    titles: "User:Enterprisey/req-helper.js/tpl-panel.js"
                }
            ).done( function ( data ) {
                var pageId = Object.keys(data.query.pages)[0];
                var panelHtml = data.query.pages[pageId].revisions[0]['*'];
                $( "#jump-to-nav" ).after( panelHtml );
                
                $( "#req-helper-panel input.view-option" ).click( updateRequestActions );
                $( "#req-helper-panel input[type='radio']" ).click( updateRequestDisplay );
                
                // Enable save button if there are requests marked for deletion
                $( "#mw-content-text" ).on( "click", "a.delete-action", updateSaveChangesButton );
                
                // Save handler
                $( "#save-changes" ).click( function ( event ) {
                    $( "#save-changes" ).prop( "disabled", true );
                    editPage( function ( wikitext ) {
                        $( ".marked-for-deletion" ).each( function ( index, element ) {
                            var pageTitle = $( element ).children( "a" ).first().text();
                            wikitext = removeRequest( wikitext, pageTitle );
                        } );
                        return wikitext;
                    } );
                } ); // end save handler
                
                // Mark blue handler
                $( "#mark-bluelinks" ).click( function ( event ) {
                    forEachRequest( function ( element ) {
                        
                        var elemClass = $( element ).attr( "class" );
                        if( $( "#also-mark-redirects" ).is( ":checked" ) ) {
                            if( elemClass !== "mw-redirect" && elemClass ) return;
                        } else {
                            if( elemClass ) return;
                        }
                        
                        if( !$( element ).parent().hasClass( "marked-for-deletion" ) ) {
                            $( element ).parent().addClass( "marked-for-deletion" );
                        }

                        // Ensure that the text of the action link is correct
                        if( $( element ).parent().hasClass( "marked-for-deletion" ) ) {
                            $( element ).parent().find( ".delete-action" ).text( "undelete" );
                        }
                    } );
                    updateSaveChangesButton();
                } ); // end mark handler
                
                // Create action links, initially hidden
                forEachRequest( function ( link ) {
                    $( "<span>" )
                        .insertAfter( link )
                        .addClass( "request-actions" )
                        .css( "margin-left", "0.25em" )
                        .append( $( "<span>", { "text": "( ", "class": "action-parenthesis" } ) )
                        .append( $( "<a>" )
                            .text( "search" )
                            .addClass( "search-action" )
                            .attr( "href", "https://www.google.com/search?q=" + $( link ).text().replace( / /, "+" ) ) )
                        .append( $( "<span>", { "text": " | ", "class": "action-separator" } ) )
                        .append( $( "<a>" )
                            .text( "delete" )
                            .addClass( "delete-action" )
                            .click( function ( e ) {
                                $( link ).parent().toggleClass( "marked-for-deletion" );
                                if( $( this ).text() === "delete" ) {
                                    $( this ).text( "undelete" );
                                } else {
                                    $( this ).text( "delete" );
                                }
                            } ) )
                        .append( $( "<span>", { "text": " )", "class": "action-parenthesis" } ) );
                } ); // end action link creation loop
                
                // Make a style element to be used by updateRequestActions
                $( "#req-helper-panel" ).append( $( "<style>", { "id": "visibility-css" } ) );
                updateRequestActions();
                
                updatePageIssues();
            } ); // end form-loaded handler
        } ); // end mw.loader.using handler
    } // end initial if block

    /**
     * Removes a request with the given page title from the given wikitext.
     * @returns The wikitext, with the specified request removed.
     */
    function removeRequest ( wikitext, pageTitle ) {

        // Sanitize the page title so we can put it in a regex
        // (regex from http://stackoverflow.com/a/3561711)
        pageTitle = pageTitle.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');

        [
            "\\*\\s?\\[\\[" + pageTitle + "\\]\\].*?\n",
            "\\*\\s?\\{\\{req\\|" + pageTitle + "\\}\\}.*?\n"
        ].forEach( function ( regex ) {
            regex = new RegExp( regex, "i" );
            wikitext = wikitext.replace( regex, "" );
        } );
        return wikitext;
    }
    
    function updateSaveChangesButton () {
        var numMarkedRequests = $( ".marked-for-deletion" ).length;
        $( "#save-changes" ).prop( "disabled", numMarkedRequests === 0 );
        $( "#save-changes" ).text( numMarkedRequests ? ( "Delete " + numMarkedRequests + " request" + ( ( numMarkedRequests === 1 ) ? "" : "s" ) ) : "Save changes" );
    }
    
    // Takes a callback that takes a *link* object
    function forEachRequest ( callback ) {

        // Precompute a list of navboxes
        var navboxes = arrayFromNodeList( document.getElementsByClassName( "navbox" ) );
        var links = arrayFromNodeList( document.querySelectorAll( "#mw-content-text a" ) );

        for( var i = 0; i < links.length; i++ ) {
            var element = links[i];
            
            // Test to make sure it's an actual request
            if( navboxes.some( function ( navbox ) { return $.contains( navbox, element ); } ) ) continue;
            if( $( element ).children( ".tocnumber" ).length ) continue;
            if( $( element ).parent().prop( "tagName" ) !== "LI" ) continue;
            if( $( element ).parents( "#toc" ).length ) continue;
            if( $( element ).parent().children( "a" ).first().text() !== $( element ).text() ) continue;
            
            // Run callback
            callback( element );
        }
    }
    
    function updateRequestDisplay () {
        var noSourcesAction = $( "input[name=no-sources]:checked" ).val(),
            someSourcesAction = $( "input[name=some-sources]:checked" ).val();
        forEachRequest( function ( link ) {
            // Highlight articles based on # of provided refs
            var listElement = $( link ).parent();
            if( listElement.children( ".external" ).length === 0 ) {
                if( noSourcesAction === "hide" ) {
                    listElement.hide();
                } else {
                    listElement.show();
                    listElement.css( "background-color",  ( $( "input[name=no-sources]:checked" ).val() === "highlight" ) ? "#FEE" : "#FFF" );
                }
            } else {
                if( someSourcesAction === "hide" ) {
                    listElement.hide();
                } else {
                    listElement.show();
                    listElement.css( "background-color",  ( $( "input[name=some-sources]:checked" ).val() === "highlight" ) ? "#EFE" : "#FFF" );
                }
            }
        } );
    }
    
    function updateRequestActions () {
        var showSearch = $( "#show-search-option" ).is( ":checked" ),
            showDelete = $( "#show-delete-option" ).is( ":checked" );
            
        // Make sure we aren't destroying any deletion state
        if( !showDelete && $( ".marked-for-deletion" ).length !== 0 ) {
            if( window.confirm( "You've marked some requests for deletion. " +
				"Are you sure you want to exit mark-for-deletion mode without deleting the requests?" ) ) {
                
                // Wipe deletion state
                $( "a.delete-action" ).text( "delete" );
                $( "li.marked-for-deletion" ).toggleClass( "marked-for-deletion" );
                $( "#save-changes" ).prop( "disabled", true );
            } else {
                
                // The user is indecisive; get out
                $( "#show-delete-option" ).prop( "checked", true );
                return;
            }
        }
        
        var newVisibilityCss = "";
        if( !showSearch && !showDelete ) newVisibilityCss += "span.action-parenthesis{display:none;}";
        if( !showSearch ) newVisibilityCss += "a.search-action{display:none;}";
        if( !showDelete ) newVisibilityCss += "a.delete-action{display:none;}";
        if( !showSearch || !showDelete ) newVisibilityCss += "span.action-separator{display:none;}";
        $( "#visibility-css" ).html( newVisibilityCss );
    }
    
    function updatePageIssues () {
    	$( "#page-issues" ).empty().append( "<ul></ul>" );
    	var addIssue = function ( issue ) { return $( "<li>" ).html( issue ).appendTo( "#page-issues ul" ); };
        $.getJSON(
            mw.util.wikiScript('api'),
            {
                format: 'json',
                action: 'query',
                prop: 'revisions',
                rvprop: 'content',
                rvlimit: 1,
                titles: mw.config.get( 'wgPageName' )
            }
        ).done( function ( data ) {
            var pageId = Object.keys(data.query.pages)[0];
            var wikitext = data.query.pages[pageId].revisions[0]['*'];
            
            // Regex counter from http://stackoverflow.com/a/4009768
            var refs = ( wikitext.match( /<ref>/g ) || [] ).length;
            if( refs ) {
                addIssue( refs + " &lt;ref&gt; tag" + ( ( refs === 1 ) ? "" : "s" ) );
            }
            
            var noLinkReqs = [];
            forEachRequest( function ( element ) {
            	if( [ "<a ", "<i>" ].indexOf( $( element ).parent().html().substring( 0, 3 ) ) === -1 ) {
            	    //console.log( [ "<a>", "<i>" ].indexOf( $( element ).parent().html().substring( 0, 3 ) ) + ", " + $( element ).parent().html().substring( 0, 3 ) );
            		noLinkReqs.push( $( element ).parent().html() );
            	}
            } );
            if( noLinkReqs.length ) {
                addIssue( noLinkReqs.length + " req" + ( ( noLinkReqs.length === 1 ) ? "" : "s" ) + " without a link" )
                    .append( $( "<a>", { "text": "(highlight)", "role": "button", "style": "margin-left: 0.75ch" } )
                        .click( function ( event ) {
                            forEachRequest( function ( element ) {
                                if( noLinkReqs.indexOf( $( element ).parent().html() ) !== -1 ) {
                                	$( element ).parent().css( "background-color", "#FCC" );
                                }
                            } );
                            $( this ).remove();
                        } ) );
            }
            
            var timestamps = ( wikitext.match( /\(UTC\)/g ) || [] ).length;
            if( timestamps ) {
            	addIssue( timestamps + " timestamp" + ( ( timestamps === 1 ) ? "" : "s" ) );
            }
            
            // Set state overall
            if( $( "#page-issues ul li").length ) {
                $( "#page-issues" ).css( "background-color", "#FEE" );
            } else {
                $( "#page-issues" ).css( { "vertical-align": "center",
                                           "text-align": "center" } );
                $( "#page-issues" ).css( "background-color", "#EFE" );
                $( "#page-issues" ).text( "(no issues)" );
            }
        } ); // End get-page-wikitext handler
    }
    
    function editPage ( processWikitext ) {
        $( "#req-helper-panel table").append( "<tr><td colspan=5><ul id='req-status'></ul></td></tr>" );
        var status = function ( newStatus ) { return $( "<li> ").appendTo( "#req-helper-panel ul#req-status" ).text( newStatus ); };

        status( $( ".marked-for-deletion" ).length + " item(s) to delete." );

        var wikitext;
        var getTextStatus = status( "Getting wikitext..." );
        $.getJSON(
            mw.util.wikiScript('api'),
            {
                format: 'json',
                action: 'query',
                prop: 'revisions',
                rvprop: 'content',
                rvlimit: 1,
                titles: mw.config.get( 'wgPageName' )
            }
        ).done( function ( data ) {
            var pageId = Object.keys(data.query.pages)[0];
            var wikitext = data.query.pages[pageId].revisions[0]['*'];
            getTextStatus.text( "Got wikitext." );
            wikitext = processWikitext( wikitext );
            status( "Processed wikitext." );
            var saveStatus = status( "Saving page..." );
            var plural = ( $( ".marked-for-deletion" ).length === 1 ) ? "" : "s";
            ( new mw.Api() ).postWithEditToken( {
                action: "edit",
                title: mw.config.get( 'wgPageName' ),
                summary: "Removing " + $( ".marked-for-deletion" ).length + " request" + plural + " ([[User:APerson/req-helper|req-helper]])",
                text: wikitext
            } ).done ( function ( data ) {
                if( data && data.edit && data.edit.result && data.edit.result == 'Success' ) {
                    saveStatus.html( "<b>Saved page!</b> (" )
                        .append( $( "<a>" )
                            .text( "reload" )
                            .attr( "href", "#" )
                            .click( function () { document.location.reload( true ); } ) )
                        .append( ")" );
                } else {
                    saveStatus.text( "There was an error saving the page." );
                    console.log( "Page save result: " + JSON.stringify( data ) );
                }
            } );
        } ); // end get-current-page-wikitext handler
    }

    // Silly utility function, from https://stackoverflow.com/a/5242912/1757964
    function arrayFromNodeList( nl ) {
        var arr = [];
        for ( var i = 0, ref = arr.length = nl.length; i < ref; i++ ) {
            arr[i] = nl[i];
        }
        return arr;
    }
}( jQuery, mediaWiki ) );
//</nowiki>