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.

// forked from https://en.wikipedia.org/?title=User:%CE%A3/Testing_facility/Archiver.js&oldid=1003561411

$.when( mw.loader.using(['mediawiki.util','mediawiki.api']), $.ready).done( function () {

    if (mw.config.get("wgNamespaceNumber") % 2 == 0 && mw.config.get("wgNamespaceNumber") != 4) {

        // not a talk page and not project namespace

        return;

    }

    if (mw.config.get("wgNamespaceNumber") == -1) {

        // is a special page

        return;

    }



    mw.util.addCSS(".arky-selected-section { background-color:#D9E9FF } .arky-selected-section .arky-span a { font-weight:bold }");



    var sectionCodepointOffsets = new Object();

    var wikiText = "";

    var revStamp; // The timestamp when we originally got the page contents - we pass it to the "edit" API call for edit conflict detection



    var portletLink = mw.util.addPortletLink("p-cactions", "#", "ØCA", "pt-oeca", "Enter/exit the archival process", null, null);

    var archiveButton = $(document.createElement("button"));

    $(portletLink).click(function(e) {

        $(".arky-selected-section").removeClass('.arky-selected-section');

        $(".arky-span").toggle();

        $("#arky-archive-button").toggle();

    });



    archiveButton.html("archive all the selected threads")

        .attr("id", 'arky-archive-button')

        .css("position", 'sticky')

        .css("bottom", 0)

        .css("width", '100%')

        .css("font-size", '200%');

    $(document.body).append(archiveButton);

    archiveButton.toggle();

    archiveButton.click(function(e) {

        // returns `s` without the substring starting at `start` and ending at `end`

        function cut(s, start, end) {

            return s.substr(0, start) + s.substring(end);

        }

        var selectedSections = $(".arky-selected-section .arky-span").map(function() {

            return $(this).data("section");

        }).toArray();

        if (selectedSections.length === 0) {

            return alert("No threads selected, aborting");

        }



        var archivePageName = prompt("Archiving " + selectedSections.length + " threads: where should we move them to? (e.g. Wikipedia:Sandbox/Archive 1)", mw.config.get("wgPageName"));

        if (!archivePageName || archivePageName == mw.config.get("wgPageName")) {

            return alert("No archive target selected, aborting");

        }



        // codepointToUtf16Idx maps codepoint idx (i.e. MediaWiki index into page text) to utf-16 idx (i.e. JavaScript index into wikiText)

        var codepointToUtf16Idx = {};



        // Initialize "important" (= either a section start or end) values to 0

        selectedSections.forEach(function(n) {

            codepointToUtf16IdxsectionCodepointOffsetsn].start = 0;

            codepointToUtf16IdxsectionCodepointOffsetsn].end = 0;

        });

        codepointToUtf16IdxInfinity = Infinity; // Because sometimes we'll have Infinity as an "end" value



        // fill in our mapping from codepoints (MediaWiki indices) to utf-16 (i.e. JavaScript).

        // yes, this loops through every character in the wikitext. very unfortunate.

        var codepointPos = 0;

        for (var utf16Pos = 0; utf16Pos < wikiText.length; utf16Pos++, codepointPos++) {

            if (codepointToUtf16Idx.hasOwnProperty(codepointPos)) {

                codepointToUtf16IdxcodepointPos = utf16Pos;

            }



            if ((0xD800 <= wikiText.charCodeAt(utf16Pos)) && (wikiText.charCodeAt(utf16Pos) <= 0xDBFF)) {

                // high surrogate! utf16Pos goes up by 2, but codepointPos goes up by only 1.

                utf16Pos++; // skip the low surrogate

            }

        }



        var newTextForArchivePage = selectedSections.map(function(n) {

            return wikiText.substring(

                codepointToUtf16IdxsectionCodepointOffsetsn].start],

                codepointToUtf16IdxsectionCodepointOffsetsn].end

            );

        }).join("");



        selectedSections.reverse(); // go in reverse order so that we don't invalidate the offsets of earlier sections

        var newWikiText = wikiText;

        selectedSections.forEach(function(n) {

            newWikiText = cut(

                newWikiText,

                codepointToUtf16IdxsectionCodepointOffsetsn].start],

                codepointToUtf16IdxsectionCodepointOffsetsn].end

            );

        });



        console.log("archive this:" + newTextForArchivePage);

        console.log("revised page:" + newWikiText);

        var pluralizedThreads = selectedSections.length + ' thread' + ((selectedSections.length === 1) ? '' : 's');

        new mw.Api().postWithToken("csrf", {

            action: 'edit',

            title: mw.config.get("wgPageName"),

            text: newWikiText,

            summary: "Removing " + pluralizedThreads + ", will be on [[" + archivePageName + "]]",

            basetimestamp: revStamp,

            starttimestamp: revStamp

        }).done(function(res1) {

            alert("Successfully removed threads from talk page");

            console.log(res1);

            new mw.Api().postWithToken("csrf", {action: 'edit', title: archivePageName, appendtext: "\n" + newTextForArchivePage, summary: "Adding " + pluralizedThreads + " from [[" + mw.config.get("wgPageName") + "]]"})

                .done(function(res2) {

                    alert("Successfully added threads to archive page");

                })

                .fail(function(res2) {

                    alert("failed to add threads to archive page. manual inspection needed.");

                })

                .always(function(res2) {

                    console.log(res2);

                    window.location.reload();

                });

            })

            .fail(function(res1) {

                alert("failed to remove threads from talk page. aborting archive process.");

                console.log(res1);

                window.location.reload();

            });

    }); // end of archiveButton click handler



    // grab page sections and wikitext so we can add the "archive" links to appropriate sections

    new mw.Api().get({action: 'parse', page: mw.config.get("wgPageName")}).done(function(parseApiResult) {

        new mw.Api().get({action: 'query', pageids: mw.config.get("wgArticleId"), prop: 'revisions'], rvprop: 'content', 'timestamp']}).done(function(revisionsApiResult) {

            var rv;

            rv = revisionsApiResult.query.pagesmw.config.get("wgArticleId")].revisions0];

            wikiText = rv"*"];

            revStamp = rv'timestamp'];

        });



        var validSections = {};



        $(parseApiResult.parse.sections)

            // For sections transcluded from other pages, s.index will look

            // like T-1 instead of just 1. Remove those.

            .filter(function(i, s) { return s.index == parseInt(s.index) })



            .each(function(i, s) { validSectionss.index = s });



        for (var i in validSections) {

            i = parseInt(i);

            // What MediaWiki calls "byteoffset" is actually a codepoint offset!! Drat!!

            sectionCodepointOffsetsi = {

                start: validSectionsi].byteoffset,

                end: validSections.hasOwnProperty(i+1)?validSectionsi+1].byteoffset:Infinity

            };

        }

        $("#mw-content-text").find(":header").find("span.mw-headline").each(function(i, title) {

            var header, headerLevel, editSection, sectionNumber;

            header = $(this).parent();

            headerLevel = header.prop("tagName").substr(1, 1) * 1; // wtf javascript

            editSection = header.find(".mw-editsection"); // 1st child

            var editSectionLink = header.find(".mw-editsection a:last");

            var sectionNumber = undefined;



            if (editSectionLink0]) {

                // Note: href may not be set.

                var sectionNumberMatch = editSectionLink.attr("href") && editSectionLink.attr("href").match(/&section=(\d+)/);

                if (sectionNumberMatch) {

                    sectionNumber = sectionNumberMatch1];

                }

            }

            // if the if statement fails, it might be something like <h2>not a real section</h2>

            if (validSections.hasOwnProperty(sectionNumber)){

                $(editSection0]).append(

                    "&nbsp;",

                    $("<span>", { "class": "arky-span" })

                    .css({'display':'none'})

                    .data({'header-level': headerLevel, 'section': sectionNumber})

                    .append(

                        $('<span>', { 'class': 'mw-editsection-bracket' }).text('['),

                        $('<a>')

                        .text('archive')

                        .click(function(){

                            var parentHeader = $(this).parents(':header');

                            parentHeader.toggleClass('arky-selected-section');



                            // now, click all sub-sections of this section

                            var isThisSectionSelected = parentHeader.hasClass('arky-selected-section');

                            var thisHeaderLevel = $(this).parents('.arky-span').data('header-level');



                            // starting from the current section, loop through each section

                            var allArchiveSpans = $('.arky-span');

                            var currSectionIdx = allArchiveSpans.index($(this).parents('.arky-span'));

                            for(var i = currSectionIdx + 1; i < allArchiveSpans.length; i++) {

                                if($(allArchiveSpansi]).data('header-level') <= thisHeaderLevel) {

                                    // if this isn't a subsection, quit

                                    break;

                                }

                                var closestHeader = $(allArchiveSpansi]).parents(':header');

                                if(closestHeader.hasClass('arky-selected-section') != isThisSectionSelected) {

                                    // if this section needs toggling, toggle it

                                    closestHeader.toggleClass('arky-selected-section');

                                }

                            }



                            // finally, update button

                            $('#arky-archive-button')

                                .prop('disabled', !$('.arky-selected-section').length)

                                .text('archive ' + $('.arky-selected-section').length + ' selected thread' +

                                    (($('.arky-selected-section').length === 1) ? '' : 's'));

                        }),

                        $('<span>', { 'class': 'mw-editsection-bracket' }).text(']')

                    ));

            }

        });

    });

});