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.

// vim: ts=4 sw=4 et ai

( function () {

    var wikitextCache = {};

    function getWikitext( pageName, revision ) {

        if( wikitextCache revision  ) {

            return $.when( wikitextCache revision  );

        }

        return $.getJSON(

            mw.config.get( 'wgScriptPath' ) + '/api.php',

            {

                format: "json",

                action: "query",

                prop: "revisions",

                rvprop: "content",

                rvslots: "main",

                rvlimit: 1,

                rvstartid: revision,

                titles: pageName,

                formatversion: 2,

            }

        ).then( function ( data ) {

            var revObj = data.query.pages Object.keys( data.query.pages )[0 ].revisions0];

            var wikitext = revObj.slots.main.content;

            wikitextCache revision  = wikitext;

            return wikitext;

        } );

    }



    function lineNumFromLineNumCell( cell ) {

        return parseInt( cell.textContent.replace( /,/g, "" ).match( /Line (\d+):/ )[1 );

    }



    function findLineNumberRowIdx( diffTable, direction, rowIdx ) {

        var rows = Array.prototype.slice.call( diffTable.rows );

        var searchRows;

        if( direction === "next" ) {

            searchRows = rows.slice( rowIdx );

        } else {

            searchRows = rows.slice( 0, rowIdx );

            searchRows.reverse();

        }

        return searchRows.find( function ( row ) {

            return row.cells0].className === "diff-lineno";

        } );

    }



    // I'm probably gonna keep finding off-by-one errors in this code until the heat death of the universe

    function showMore( revision, pageName, direction, rowWithButton, numRowsToShow, targetRow, diffTable ) {

        var rows = Array.prototype.slice.call( diffTable.rows );

        var buttonRowIdx = rows.indexOf( rowWithButton );

        if( direction === "next" ) {

            targetRow = findLineNumberRowIdx( diffTable, "previous", buttonRowIdx );

        }

        var currLineNum = lineNumFromLineNumCell( targetRow.cells1 );

        var targetRowIdx = Array.prototype.indexOf.call( diffTable.rows, targetRow );

        var currRowIdx = direction === "next" ? buttonRowIdx - 1 : targetRowIdx + 1;

        if( direction === "next" ) {

            currLineNum += buttonRowIdx - rows.indexOf( targetRow ) - 2;

        }

        return getWikitext( pageName, revision ).then( function ( wikitext ) {

            var lines = wikitext.split( "\n" );

            

            var startLineIdx, endLineIdx;

            switch( direction ) {

                case "next":

                    startLineIdx = currLineNum + 1;

                    endLineIdx = currLineNum + numRowsToShow + 1;



                    var nextLineNumberRow = findLineNumberRowIdx( diffTable, "next", buttonRowIdx );

                    if( nextLineNumberRow ) {

                        var limitLineNum = lineNumFromLineNumCell( nextLineNumberRow.cells1 ) - 1;

                        endLineIdx = Math.min( endLineIdx, limitLineNum );

                    }

                    break;

                case "previous":

                    startLineIdx = currLineNum - numRowsToShow - 1;

                    endLineIdx = currLineNum - 1;



                    var prevLineNumberRow = findLineNumberRowIdx( diffTable, "previous", buttonRowIdx );

                    if( prevLineNumberRow ) {

                        var prevLineNumberRowIdx = rows.indexOf( prevLineNumberRow );

                        var limitLineNum = lineNumFromLineNumCell( prevLineNumberRow.cells1 ) + ( buttonRowIdx - prevLineNumberRowIdx ) - 2;

                        startLineIdx = Math.max( startLineIdx, limitLineNum );

                    }

                    break;

            }

            startLineIdx = Math.max( 0, startLineIdx );

            endLineIdx = Math.min( lines.length - 1, endLineIdx );

            if( endLineIdx - startLineIdx === 0 ) {

                return true; // no more work can be done

            }

            var actualNumLines = endLineIdx - startLineIdx;

            var linesToShow = lines.slice( startLineIdx, endLineIdx );

            if( direction === "previous" ) {

                linesToShow.reverse();

            }

            var insertionRow = currRowIdx + +( direction === "next" );

            for( var lineCounter = 0; lineCounter < actualNumLines; lineCounter++ ) {

                var currLine = "<div>" + linesToShow lineCounter ].replace( /</g, "&lt;" ) + "</div>";

                var newRow = diffTable.insertRow( insertionRow );

                newRow.insertCell( 0 ).appendChild( document.createTextNode( "." ) );

                var newCell = newRow.insertCell( 1 );

                newCell.className = "diff-context";

                newCell.innerHTML = currLine;

                newRow.insertCell( 2 ).appendChild( document.createTextNode( "." ) );

                var newCell2 = newRow.insertCell( 3 );

                newCell2.className = "diff-context";

                newCell2.innerHTML = currLine;

            }

            if( direction === "previous" ) {

                targetRow.cells0].textContent = "Line " + ( currLineNum - actualNumLines ) + ":";

                targetRow.cells1].textContent = "Line " + ( currLineNum - actualNumLines ) + ":";

            }

            mw.hook( "diff-update" ).fire( diffTable );

            if( actualNumLines < numRowsToShow ) {

                return true; // we're done

            }

            return false;

        } );

    }



    function makeDropdown() {

        var sel = document.createElement( "select" );

        function add( num ) {

            var opt = document.createElement( "option" );

            opt.value = num;

            opt.textContent = num;

            sel.appendChild( opt );

        }

        add( 1 );

        add( 10 );

        add( 50 );

        add( 100 );

        //add( "all" );

        return sel;

    }



    function addLinks( diffTable ) {

        if( !diffTable.querySelector ) {



            // Assume it's a jquery object

            diffTable = diffTable.get( 0 );

        }



        if( diffTable.getElementsByClassName( "diff-context-container" ).length > 0 ) {



            // We already ran on this diff

            return;

        }



        function makeRow( index, direction, targetRow ) {

            var newRow = diffTable.insertRow( index );

            newRow.className = "context " + direction;

            var newCell = newRow.insertCell( 0 );

            newCell.setAttribute( "colspan", "4" );

            var container = document.createElement( "div" );

            container.className = "diff-context-container";

            container.appendChild( document.createTextNode( "Show " + direction + " " ) );

            var dropdown = makeDropdown();

            container.appendChild( dropdown );

            container.appendChild( document.createTextNode( " lines: " ) );

            var go = document.createElement( "button" );

            go.textContent = "Go";

            var revision = mw.config.get( "wgDiffNewId" );

            var pageName = mw.config.get( "wgPageName" );



            if( diffTable.parentNode && diffTable.parentNode.dataset.mwRevid ) {

                // User contribs page or normal watchlist or history page

                revision = diffTable.parentNode.dataset.mwRevid;

                if( pageName.indexOf( "Special:Contributions" ) === 0 ) {

                    pageName = diffTable.parentNode.querySelector( "a.mw-contributions-title" ).textContent;

                }

            } else if( diffTable.parentNode && diffTable.parentNode.className === "mw-enhanced-rc-nested" ) {

                // Enhanced ("show all changes") watchlist, when multiple revisions are shown for one page

                revision = diffTable.parentNode.parentNode.dataset.mwRevid;

                pageName = diffTable.parentNode.dataset.targetPage;

            } else if( diffTable.id.substring( diffTable.id.length - 7 ) === "display" && diffTable.previousElementSibling.dataset.mwRevid ) {

                // Enhanced watchlist, only one revision shown for a page

                revision = diffTable.previousElementSibling.dataset.mwRevid;

                pageName = diffTable.previousElementSibling.getElementsByClassName( "mw-changeslist-line-inner" )[0].dataset.targetPage;

            }



            go.addEventListener( "click", function ( evt ) {

                var numRowsToShow = parseInt( dropdown.value );

                showMore( revision, pageName, direction, newRow, numRowsToShow, targetRow, diffTable ).then( function ( done ) {

                    if( done ) {

                        this.disabled = true;

                    }

                }.bind( this ) );

                evt.preventDefault();

            }, true );



            container.appendChild( go );

            newCell.appendChild( container );

        }



        var firstTime = true;

        for( var rowIdx = 0, rowCount = diffTable.rows.length; rowIdx < rowCount; rowIdx++ ) {

            var row = diffTable.rowsrowIdx];

            if( row.cells0].className !== "diff-lineno" ) continue;

            if( !firstTime ) {

                makeRow( rowIdx, "next", row );

                rowIdx++;

            } else {

                firstTime = false;

            }

            makeRow( rowIdx, "previous", row );

            rowIdx++;

        }

        var lineNumCells = diffTable.querySelectorAll( "td.diff-lineno" );

        if( lineNumCells.length ) {

            var lastLineNumRow = Array.prototype.slice.call( lineNumCells, -1 )[0].parentNode;

            makeRow( -1, "next", lastLineNumRow );

        }

    }



    $( function () {

        var diffTable = document.querySelector( "table.diff" );

        if( mw.config.get( "wgDiffNewId" ) && diffTable && diffTable.rows.length > 2 ) {

            mw.loader.addStyleTag( "tr.context td { text-align: center; }" );

            mw.loader.addStyleTag( "tr.context td div.diff-context-container { display: inline-block; border: thin solid #ddd; padding: 0.1em 0.5em; }" );

            addLinks( diffTable );

        }

        mw.hook( "wikipage.diff" ).add( addLinks );

        mw.hook( "new-diff-table" ).add( addLinks );

    } );

} )();