User:Enterprisey/archiver.js

// forked from https://en.wikipedia.org/w/index.php?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) {           codepointToUtf16Idx[sectionCodepointOffsets[n].start] = 0;            codepointToUtf16Idx[sectionCodepointOffsets[n].end] = 0;        }); codepointToUtf16Idx[Infinity] = 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)) { codepointToUtf16Idx[codepointPos] = 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( codepointToUtf16Idx[sectionCodepointOffsets[n].start], codepointToUtf16Idx[sectionCodepointOffsets[n].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, codepointToUtf16Idx[sectionCodepointOffsets[n].start], codepointToUtf16Idx[sectionCodepointOffsets[n].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.pages[mw.config.get("wgArticleId")].revisions[0]; 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) { validSections[s.index] = s });

for (var i in validSections) { i = parseInt(i); // What MediaWiki calls "byteoffset" is actually a codepoint offset!! Drat!! sectionCodepointOffsets[i] = { start: validSections[i].byteoffset, end: validSections.hasOwnProperty(i+1)?validSections[i+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 (editSectionLink[0]) { // Note: href may not be set. var sectionNumberMatch = editSectionLink.attr("href") && editSectionLink.attr("href").match(/&section=(\d+)/); if (sectionNumberMatch) { sectionNumber = sectionNumberMatch[1]; }           }            // if the if statement fails, it might be something like not a real section if (validSections.hasOwnProperty(sectionNumber)){ $(editSection[0]).append(                   " ",                    $(" ", { "class": "arky-span" })                    .css({'display':'none'})                    .data({'header-level': headerLevel, 'section': sectionNumber})                    .append( $(' ', { 'class': 'mw-editsection-bracket' }).text('['), $('') .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($(allArchiveSpans[i]).data('header-level') <= thisHeaderLevel) { // if this isn't a subsection, quit break; }                               var closestHeader = $(allArchiveSpans[i]).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')); }),                       $(' ', { 'class': 'mw-editsection-bracket' }).text(']')                    ));            }        }); }); });