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> foo
( function ( $, mw ) {
    var TIMESTAMP_REGEX = /\(UTC\)$/m;

    /**
     * This function converts any (index-able) iterable into a list.
     */
    function iterableToList( nl ) {
        var arr = new Array( nl.length );
        for(var i=-1,l=nl.length;++i!==l;arr[i]=nl[i]);
        return arr;
    }

    /**
     * When there's a panel being shown, this function sets the status
     * in the panel.
     */
    function setStatus ( status, callback ) {
        var statusElement = $( "#reply-dialog-status" );
        statusElement.fadeOut( function () {
            statusElement.html( status ).fadeIn( callback );
        } )
    }

    /**
     * Using the text in #reply-dialog-field, add a reply to the
     * current page.
     */
    function doReply( context, level ) {
        var wikitext;
        $.getJSON(
            mw.util.wikiScript('api'),
            {
                format: 'json',
                action: 'query',
                prop: 'revisions',
                rvprop: 'content',
                rvlimit: 1,
                titles: mw.config.get( 'wgPageName' )
            }
        ).done( function ( data ) {
            try {
                var pageId = Object.keys(data.query.pages)[0];
                wikitext = data.query.pages[pageId].revisions[0]['*'];
                var reply = document.getElementById( "reply-dialog-field" ).value;
                var newWikitext = wikitext.replace( context, context + "\n" + ":".repeat( level + 1 ) + reply + " ~~~~" );
                setStatus( "Generating wikitext..." );

                $.ajax( {
                    url: mw.util.wikiScript( 'api' ),
                    type: 'POST',
                    dataType: 'json',
                    data: {
                        format: 'json',
                        action: 'edit',
                        title: mw.config.get( 'wgPageName' ),
                        summary: "Replying with reply-dialog (testing)",
                        token: mw.user.tokens.get( 'editToken' ),
                        text: newWikitext
                    }
                } ).done ( function ( data ) {
                    if ( data && data.edit && data.edit.result && data.edit.result == 'Success' ) {
                        setStatus( "Reply saved! (<a href='javascript:window.location.reload(true)' class='reload'>Reload</a>)" );
                    } else {
                        setStatus( "While saving, the edit query returned an error. =(" );
                    }
                } ).fail ( function() {
                    setStatus( "While saving, the AJAX request failed." );
                } );
            } catch ( e ) {
                setStatus( "While getting the wikitext, there was an error." );
                console.log( "Content request error: " + e.message );
                console.log( "Content request response: " + JSON.stringify( data ) );
            }
        } ).fail( function () {
            setStatus( "While getting the wikitext, there was an AJAX error." );
        } );
    }

    /**
     * Adds a "(reply)" link after the provided text node.
     */
    function attachLinkAfterTextNode( node, level ) {

        // Verify that this text node ends with a timestamp
        if( !TIMESTAMP_REGEX.test( node.textContent ) ) return;

        // Construct new link
        var newLinkWrapper = document.createElement( "span" );
        newLinkWrapper.className = "reply-dialog-wrapper";
        var newLink = document.createElement( "a" );
        newLink.href = "#";
        newLink.appendChild( document.createTextNode( "reply" ) );
        newLink.addEventListener( "click", function ( evt ) {

            // Create panel
            var panelEl = document.createElement( "div" );
            panelEl.style = "border: thin gray solid; padding: 5px; box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);";
            panelEl.innerHTML = "<input type='text' id='reply-dialog-field' class='mw-ui-input' /><button id='reply-dialog-button' class='mw-ui-button mw-ui-constructive'>Reply</button><span id='reply-dialog-status'></span>";
            node.parentNode.insertBefore( panelEl, newLinkWrapper.nextSibling );

            // Button event listener
            document.getElementById( "reply-dialog-button" )
                    .addEventListener( "click", function () { doReply( node.textContent, level ); } );

            // Cancel default event handler
            evt.preventDefault();
            return false;
        } );
        newLinkWrapper.appendChild( document.createTextNode( "(" ) );
        newLinkWrapper.appendChild( newLink );
        newLinkWrapper.appendChild( document.createTextNode( ")" ) );

        // Insert new link into DOM
        var parent = node.parentNode;
        parent.insertBefore( newLinkWrapper, node.nextSibling );
    }

    /**
     * Uses attachLinkAfterTextNode to add a reply link after every
     * timestamp on the page.
     */
    function attachLinks () {
        var mainContent = document.querySelector( "#mw-content-text .mw-parser-output" );
        var contentEls = mainContent.children;

        // Loop until we get a header
        var headerIndex = 0;
        for( headerIndex = 0; headerIndex < contentEls.length; headerIndex++ ) {
            if( contentEls[ headerIndex ].tagName.toLowerCase() === "h2" ) break;
        }

        if( headerIndex === contentEls.length ) {
            console.log( "Hit end of loop!" );
            return;
        }

        // Main recursive parsing function
        function parseNode( node, currLevel ) {

            // Detect if it's a text node
            if( node.nodeType === 3 ) {
                attachLinkAfterTextNode( node, currLevel );
                return;
            } else if( /p|dl|dd|ul|li/.test( node.tagName.toLowerCase() ) ) {
                if( /dl|ul/.test( node.tagName.toLowerCase() ) ) {
                    currLevel++;
                }
                iterableToList( node.childNodes ).map( function( node ) {
                    parseNode( node, currLevel );
                } );
            }
        }

        contentEls = iterableToList( contentEls ).slice( headerIndex + 1);
        contentEls.map( function ( el ) { parseNode( el, 0 ); } );
    }

    function onReady () {
        attachLinks();
    }

    var currNamespace = mw.config.get( "wgNamespaceNumber" );
    if ( currNamespace % 2 === 1 || currNamespace === 4 ) {
        mw.loader.load( "mediawiki.ui.input", "text/css" );
        mw.loader.using( "mediawiki.util" ).then( function () {
            $( document ).ready( onReady );
        } );
    }
}( jQuery, mediaWiki ) );
//</nowiki>