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(/§ion=(\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(']')

));

}

});

});

});