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.

//================================================================================

// hide rollback links; see [[WP:Customizing_watchlists#rollback]]

//================================================================================

$('span.mw-rollback-link').remove();



//================================================================================

// quick script for round-robin page moves, from [[User:Andy M. Wang/pageswap.js]]

//================================================================================



// [[WP:PM/C#4]] round-robin history swap

// by [[User:Andy M. Wang]]

// 1.4.2016.1101



$(document).ready(function() {

    "use strict";

    var currNs = mw.config.get("wgNamespaceNumber");

    if (currNs < 0 || currNs >= 120

            || (currNs >=  6 && currNs <= 9)

            || (currNs >= 14 && currNs <= 99))

        return; // special/other page





/**

 * If user is able to perform swaps

 */

function checkUserPermissions() {

    var ret = {};

    ret.canSwap = true;

    var reslt = JSON.parse($.ajax({ url:mw.util.wikiScript('api'), async:false,

        error: function (jsondata) {

            alert("Swapping pages unavailable."); return ret; },

        data: { action:'query', format:'json', meta:'userinfo', uiprop:'rights' }

    }).responseText).query.userinfo;



    // check userrights for suppressredirect and move-subpages

    var rightslist = reslt.rights;

    ret.canSwap =

            $.inArray('suppressredirect', rightslist) > -1

         && $.inArray('move-subpages', rightslist) > -1;

    ret.allowSwapTemplates =

            $.inArray('templateeditor', rightslist) > -1;



    return ret;

}



    mw.util.addPortletLink("p-cactions", "#",

        "Swap", "ca-swappages", "Perform a revision history swap / round-robin move");

    $("#ca-swappages").click(function(e) {

        var userPermissions = checkUserPermissions();

        if (!userPermissions.canSwap) {

            alert("User rights insufficient for action."); return;

        }

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

        var destTitle = prompt("Swap \"" + (currTitle.replace(/_/g, ' ')) + "\" with:");



/**

 * Given namespace data, title, title namespace, returns expected title of page

 * Along with title without prefix

 * Precondition, title, titleNs is a subject page!

 */

function getTalkPageName(nsData, title, titleNs) {

    var ret = {};

    var prefixLength = nsData'' + titleNs]['*'].length === 0

        ? 0 : nsData'' + titleNs]['*'].length + 1;

    ret.titleWithoutPrefix = title.substring(prefixLength, title.length);

    ret.talkTitle = nsData'' + (titleNs + 1)]['*' + ':'

        + ret.titleWithoutPrefix;

    return ret;

}



/**

 * Given two (normalized) titles, find their namespaces, if they are redirects,

 * if have a talk page, whether the current user can move the pages, suggests

 * whether movesubpages should be allowed, whether talk pages need to be checked

 */

function swapValidate(titleOne, titleTwo, pagesData, nsData, uPerms) {

    var ret = {};

    ret.valid = true;

    if (titleOne === null || titleTwo === null || pagesData === null) {

        ret.valid = false;

        ret.invalidReason = "Unable to validate swap.";

        return ret;

    }



    ret.allowMoveSubpages = true;

    ret.checkTalk = true;

    var count = 0;

    for (var k in pagesData) {

        ++count;

        if (k == "-1" || pagesDatak].ns < 0) {

            ret.valid = false;

            ret.invalidReason = ("Page " + pagesDatak].title + " does not exist.");

            return ret;

        }

        // enable only in ns 0..5,12,13,118,119 (Main,Talk,U,UT,WP,WT,H,HT,D,DT)

        if ((pagesDatak].ns >= 6 && pagesDatak].ns <= 9)

         || (pagesDatak].ns >= 10 && pagesDatak].ns <= 11 && !uPerms.allowSwapTemplates)

         || (pagesDatak].ns >= 14 && pagesDatak].ns <= 117)

         || (pagesDatak].ns >= 120)) {

            ret.valid = false;

            ret.invalidReason = ("Namespace of " + pagesDatak].title + " ("

                + pagesDatak].ns + ") not supported.\n\nLikely reasons:\n"

                + "- Names of pages in this namespace relies on other pages\n"

                + "- Namespace features heavily-transcluded pages\n"

                + "- Namespace involves subpages: swaps produce many redlinks\n"

                + "\n\nIf the move is legitimate, consider a careful manual swap.");

            return ret;

        }

        if (titleOne == pagesDatak].title) {

            ret.currTitle   = pagesDatak].title;

            ret.currNs      = pagesDatak].ns;

            ret.currTalkId  = pagesDatak].talkid; // could be undefined

            ret.currCanMove = pagesDatak].actions.move === '';

            ret.currIsRedir = pagesDatak].redirect === '';

        }

        if (titleTwo == pagesDatak].title) {

            ret.destTitle   = pagesDatak].title;

            ret.destNs      = pagesDatak].ns;

            ret.destTalkId  = pagesDatak].talkid; // could be undefined

            ret.destCanMove = pagesDatak].actions.move === '';

            ret.destIsRedir = pagesDatak].redirect === '';

        }

    }



    if (!ret.valid) return ret;

    if (!ret.currCanMove) {

        ret.valid = false;

        ret.invalidReason = ('' + ret.currTitle + " is immovable. Aborting");

        return ret;

    }

    if (!ret.destCanMove) {

        ret.valid = false;

        ret.invalidReason = ('' + ret.destTitle + " is immovable. Aborting");

        return ret;

    }

    if (ret.currNs % 2 !== ret.destNs % 2) {

        ret.valid = false;

        ret.invalidReason = "Namespaces don't match: one is a talk page.";

        return ret;

    }

    if (count !== 2) {

        ret.valid = false;

        ret.invalidReason = "Pages have the same title. Aborting.";

        return ret;

    }

    ret.currNsAllowSubpages = nsData'' + ret.currNs].subpages !== '';

    ret.destNsAllowSubpages = nsData'' + ret.destNs].subpages !== '';



    // if same namespace (subpages allowed), if one is subpage of another,

    // disallow movesubpages

    if (ret.currTitle.startsWith(ret.destTitle + '/')

            || ret.destTitle.startsWith(ret.currTitle + '/')) {

        if (ret.currNs !== ret.destNs) {

            ret.valid = false;

            ret.invalidReason = "Strange.\n" + ret.currTitle + " in ns "

                + ret.currNs + "\n" + ret.destTitle + " in ns " + ret.destNs

                + ". Disallowing.";

            return ret;

        }



        ret.allowMoveSubpages = ret.currNsAllowSubpages;

        if (!ret.allowMoveSubpages)

            ret.addlInfo = "One page is a subpage. Disallowing move-subpages";

    }



    if (ret.currNs % 2 === 1) {

        ret.checkTalk = false; // no need to check talks, already talk pages

    } else { // ret.checkTalk = true;

        var currTPData = getTalkPageName(nsData, ret.currTitle, ret.currNs);

        ret.currTitleWithoutPrefix = currTPData.titleWithoutPrefix;

        ret.currTalkName = currTPData.talkTitle;

        var destTPData = getTalkPageName(nsData, ret.destTitle, ret.destNs);

        ret.destTitleWithoutPrefix = destTPData.titleWithoutPrefix;

        ret.destTalkName = destTPData.talkTitle;

        // possible: ret.currTalkId undefined, but subject page has talk subpages

    }



    return ret;

}



/**

 * Given two talk page titles (may be undefined), retrieves their pages for comparison

 * Assumes that talk pages always have subpages enabled.

 * Assumes that pages are not identical (subject pages were already verified)

 * Assumes namespaces are okay (subject pages already checked)

 * (Currently) assumes that the malicious case of subject pages

 *   not detected as subpages and the talk pages ARE subpages

 *   (i.e. A and A/B vs. Talk:A and Talk:A/B) does not happen / does not handle

 * Returns structure indicating whether move talk should be allowed

 */

function talkValidate(checkTalk, talk1, talk2) {

    var ret = {};

    ret.allowMoveTalk = true;

    if (!checkTalk) { return ret; } // currTitle destTitle already talk pages

    if (talk1 === undefined || talk2 === undefined) {

        alert("Unable to validate talk. Disallowing movetalk to be safe");

        ret.allowMoveTalk = false;

        return ret;

    }

    ret.currTDNE = true;

    ret.destTDNE = true;

    ret.currTCanCreate = true;

    ret.destTCanCreate = true;

    var talkTitleArr = talk1, talk2];

    if (talkTitleArr.length !== 0) {

        var talkData = JSON.parse($.ajax({ url:mw.util.wikiScript('api'), async:false,

            error: function (jsondata) {

                alert("Unable to get info on talk pages."); return ret; },

            data: { action:'query', format:'json', prop:'info',

                intestactions:'move|create', titles:talkTitleArr.join('|') }

        }).responseText).query.pages;

        for (var id in talkData) {

            if (talkDataid].title === talk1) {

                ret.currTDNE = talkDataid].invalid === '' || talkDataid].missing === '';

                ret.currTTitle = talkDataid].title;

                ret.currTCanMove = talkDataid].actions.move === '';

                ret.currTCanCreate = talkDataid].actions.create === '';

                ret.currTalkIsRedir = talkDataid].redirect === '';

            } else if (talkDataid].title === talk2) {

                ret.destTDNE = talkDataid].invalid === '' || talkDataid].missing === '';

                ret.destTTitle = talkDataid].title;

                ret.destTCanMove = talkDataid].actions.move === '';

                ret.destTCanCreate = talkDataid].actions.create === '';

                ret.destTalkIsRedir = talkDataid].redirect === '';

            } else {

                alert("Found pageid not matching given ids."); return {};

            }

        }

    }



    ret.allowMoveTalk = (ret.currTCanCreate && ret.currTCanMove)

        && (ret.destTCanCreate && ret.destTCanMove);

    return ret;

}



/**

 * Given existing title (not prefixed with "/"), optionally searching for talk,

 *   finds subpages (incl. those that are redirs) and whether limits are exceeded

 * As of 2016-08, uses 2 api get calls to get needed details:

 *   whether the page can be moved, whether the page is a redirect

 */

function getSubpages(nsData, title, titleNs, isTalk) {

    if ((!isTalk) && nsData'' + titleNs].subpages !== '') { return { data:[] }; }

    var titlePageData = getTalkPageName(nsData, title, titleNs);

    var subpages = JSON.parse($.ajax({ url:mw.util.wikiScript('api'), async:false,

        error: function (jsondata) {

            return { error:"Unable to search for subpages. They may exist" }; },

        data: { action:'query', format:'json', list:'allpages',

            apnamespace:(isTalk ? (titleNs + 1) : titleNs),

            apfrom:(titlePageData.titleWithoutPrefix + '/'),

            apto:(titlePageData.titleWithoutPrefix + '0'),

            aplimit:101 }

    }).responseText).query.allpages;



    // put first 50 in first arr (need 2 queries due to api limits)

    var subpageids = [[],[]];

    for (var idx in subpages) {

        subpageidsidx < 50 ? 0 : 1].push( subpagesidx].pageid );

    }



    if (subpageids0].length === 0) { return { data:[] }; }

    if (subpageids1].length === 51) { return { error:"100+ subpages. Aborting" }; }

    var dataret = [];

    var subpageData0 = JSON.parse($.ajax({ url:mw.util.wikiScript('api'), async:false,

        error: function (jsondata) {

            return { error:"Unable to fetch subpage data." }; },

        data: { action:'query', format:'json', prop:'info', intestactions:'move|create',

            pageids:subpageids0].join('|') }

    }).responseText).query.pages;

    for (var k0 in subpageData0) {

        dataret.push({

            title:subpageData0k0].title,

            isRedir:subpageData0k0].redirect === '',

            canMove:subpageData0k0].actions.move === ''

        });

    }



    if (subpageids1].length === 0) { return { data:dataret }; }

    var subpageData1 = JSON.parse($.ajax({ url:mw.util.wikiScript('api'), async:false,

        error: function (jsondata) {

            return { error:"Unable to fetch subpage data." }; },

        data: { action:'query', format:'json', prop:'info', intestactions:'move|create',

            pageids:subpageids1].join('|') }

    }).responseText).query.pages;

    for (var k1 in subpageData1) {

        dataret.push({

            title:subpageData1k1].title,

            isRedir:subpageData1k1].redirect === '',

            canMove:subpageData1k1].actions.move === ''

        });

    }

    return { data:dataret };

}



/**

 * Prints subpage data given retrieved subpage information returned by getSubpages

 * Returns a suggestion whether movesubpages should be allowed

 */

function printSubpageInfo(basepage, currSp) {

    var ret = {};

    var currSpArr = [];

    var currSpCannotMove = [];

    var redirCount = 0;

    for (var kcs in currSp.data) {

        if (!currSp.datakcs].canMove) {

            currSpCannotMove.push(currSp.datakcs].title);

        }

        currSpArr.push((currSp.datakcs].isRedir ? "(R) " : "  ")

            + currSp.datakcs].title);

        if (currSp.datakcs].isRedir)

            redirCount++;

    }



    if (currSpArr.length > 0) {

        alert((currSpCannotMove.length > 0

                ? "Disabling move-subpages.\n"

                + "The following " + currSpCannotMove.length + " (of "

                + currSpArr.length + ") total subpages of "

                + basepage + " CANNOT be moved:\n\n  "

                + currSpCannotMove.join("\n  ") + '\n\n'

            : (currSpArr.length + " total subpages of " + basepage + ".\n"

            + (redirCount !== 0 ? ('' + redirCount + " redirects, labeled (R)\n") : '')

            + '\n' + currSpArr.join('\n'))));

    }



    ret.allowMoveSubpages = currSpCannotMove.length === 0;

    ret.noNeed = currSpArr.length === 0;

    return ret;

}



/**

 * After successful page swap, post-move cleanup:

 * Make talk page redirect

 * TODO more reasonable cleanup/reporting as necessary

 * vData.(curr|dest)IsRedir

 */

function doPostMoveCleanup(movedTalk, movedSubpages, vData, vTData) {

    if (movedTalk && vTData.currTDNE && confirm("Create redirect "

            + vData.currTalkName + " → " + vData.destTalkName + " if possible?")) {

        // means that destination talk now is redlinked TODO

    } else if (movedTalk && vTData.destTDNE && confirm("Create redirect "

            + vData.destTalkName + " → " + vData.currTalkName + " if possible?")) {

        // curr talk now is redlinked TODO

    }

}





/**

 * Swaps the two pages (given all prerequisite checks)

 * Optionally moves talk pages and subpages

 */

function swapPages(titleOne, titleTwo, moveReason, intermediateTitlePrefix,

        moveTalk, moveSubpages, vData, vTData) {

    if (titleOne === null || titleTwo === null

            || moveReason === null || moveReason === '') {

        alert("Titles are null, or move reason given was empty. Swap not done");

        return false;

    }



    var intermediateTitle = intermediateTitlePrefix + titleOne;

    var pOne = { action:'move', from:titleTwo, to:intermediateTitle,

        reason:"[[WP:PM/C#4|Round-robin history swap]] step 1 using [[User:Andy M. Wang/pageswap|pageswap]]",

        watchlist:"unwatch", noredirect:1 };

    var pTwo = { action:'move', from:titleOne, to:titleTwo,

        reason:moveReason,

        watchlist:"unwatch", noredirect:1 };

    var pTre = { action:'move', from:intermediateTitle, to:titleOne,

        reason:"[[WP:PM/C#4|Round-robin history swap]] step 3 using [[User:Andy M. Wang/pageswap|pageswap]]",

        watchlist:"unwatch", noredirect:1 };

    if (moveTalk) {

        pOne.movetalk = 1; pTwo.movetalk = 1; pTre.movetalk = 1;

    }

    if (moveSubpages) {

        pOne.movesubpages = 1; pTwo.movesubpages = 1; pTre.movesubpages = 1;

    }



    new mw.Api().postWithToken("csrf", pOne).done(function (reslt1) {

    new mw.Api().postWithToken("csrf", pTwo).done(function (reslt2) {

    new mw.Api().postWithToken("csrf", pTre).done(function (reslt3) {

        alert("Moves completed successfully.\n"

        	+ "Please create new red-linked talk pages/subpages if there are incoming links\n"

        	+ "  (check your contribs for \"Talk:\" redlinks),\n"

            + "  correct any moved redirects, and do post-move cleanup if necessary.");

        //doPostMoveCleanup(moveTalk, moveSubpages, vData, vTData);

    }).fail(function (reslt3) {

        alert("Fail on third move " + intermediateTitle + " → " + titleOne);

    });

    }).fail(function (reslt2) {

        alert("Fail on second move " + titleOne + " → " + titleTwo);

    });

    }).fail(function (reslt1) {

        alert("Fail on first move " + titleTwo + " → " + intermediateTitle);

    });

}



/**

 * Given two titles, normalizes, does prerequisite checks for talk/subpages,

 * prompts user for config before swapping the titles

 */

function roundrobin(uPerms, currNs, currTitle, destTitle, intermediateTitlePrefix) {

    // get ns info (nsData.query.namespaces)

    var nsData = JSON.parse($.ajax({ url:mw.util.wikiScript('api'), async:false,

        error: function (jsondata) { alert("Unable to get info about namespaces"); },

        data: { action:'query', format:'json', meta:'siteinfo', siprop:'namespaces' }

    }).responseText).query.namespaces;



    // get page data, normalize titles

    var relevantTitles = currTitle + "|" + destTitle;

    var pagesData = JSON.parse($.ajax({ url:mw.util.wikiScript('api'), async:false,

        error: function (jsondata) {

            alert("Unable to get info about " + currTitle + " or " + destTitle);

        },

        data: { action:'query', format:'json', prop:'info', inprop:'talkid',

            intestactions:'move|create', titles:relevantTitles }

    }).responseText).query;



    for (var kp in pagesData.normalized) {

        if (currTitle == pagesData.normalizedkp].from) { currTitle = pagesData.normalizedkp].to; }

        if (destTitle == pagesData.normalizedkp].from) { destTitle = pagesData.normalizedkp].to; }

    }

    // validate namespaces, not identical, can move

    var vData = swapValidate(currTitle, destTitle, pagesData.pages, nsData, uPerms);

    if (!vData.valid) { alert(vData.invalidReason); return; }

    if (vData.addlInfo !== undefined) { alert(vData.addlInfo); }



    // subj subpages

    var currSp = getSubpages(nsData, vData.currTitle, vData.currNs, false);

    if (currSp.error !== undefined) { alert(currSp.error); return; }

    var currSpFlags = printSubpageInfo(vData.currTitle, currSp);

    var destSp = getSubpages(nsData, vData.destTitle, vData.destNs, false);

    if (destSp.error !== undefined) { alert(destSp.error); return; }

    var destSpFlags = printSubpageInfo(vData.destTitle, destSp);



    var vTData = talkValidate(vData.checkTalk, vData.currTalkName, vData.destTalkName);



    // future goal: check empty subpage DESTINATIONS on both sides (subj, talk)

    //   for create protection. disallow move-subpages if any destination is salted

    var currTSp = getSubpages(nsData, vData.currTitle, vData.currNs, true);

    if (currTSp.error !== undefined) { alert(currTSp.error); return; }

    var currTSpFlags = printSubpageInfo(vData.currTalkName, currTSp);

    var destTSp = getSubpages(nsData, vData.destTitle, vData.destNs, true);

    if (destTSp.error !== undefined) { alert(destTSp.error); return; }

    var destTSpFlags = printSubpageInfo(vData.destTalkName, destTSp);



    var noSubpages = currSpFlags.noNeed && destSpFlags.noNeed

        && currTSpFlags.noNeed && destTSpFlags.noNeed;

    // If one ns disables subpages, other enables subpages, AND HAS subpages,

    //   consider abort. Assume talk pages always safe (TODO fix)

    var subpageCollision = (vData.currNsAllowSubpages && !destSpFlags.noNeed)

        || (vData.destNsAllowSubpages && !currSpFlags.noNeed);



    var moveTalk = false;

    // TODO: count subpages and make restrictions?

    if (vData.checkTalk && vTData.allowMoveTalk) {

        moveTalk     = confirm("Move talk page(s)? (OK for yes, Cancel for no)");

    } else if (vData.checkTalk) {

        alert("Disallowing moving talk. "

            + (!vTData.currTCanCreate ? (vData.currTalkName + " is create-protected")

            : (!vTData.destTCanCreate ? (vData.destTalkName + " is create-protected")

            : "Talk page is immovable")));

    }



    var moveSubpages = false;

    // TODO future: currTSpFlags.allowMoveSubpages && destTSpFlags.allowMoveSubpages

    // needs to be separate check. If talk subpages immovable, should not affect subjspace

    if (!subpageCollision && !noSubpages && vData.allowMoveSubpages

            && (currSpFlags.allowMoveSubpages && destSpFlags.allowMoveSubpages)

            && (currTSpFlags.allowMoveSubpages && destTSpFlags.allowMoveSubpages)) {

        moveSubpages = confirm("Move subpages? (OK for yes, Cancel for no)");

    } else if (subpageCollision) {

        alert("One namespace does not have subpages enabled. Disallowing move subpages");

    }



    var moveReason = prompt("Move reason:");

    var confirmString = "Round-robin configuration:\n  "

        + currTitle + " → " + destTitle + "\n    : " + moveReason

        + "\n      with movetalk:" + moveTalk + ", movesubpages:" + moveSubpages

        + "\n\nProceed? (Cancel to abort)";



    if (confirm(confirmString)) {

        swapPages(currTitle, destTitle, moveReason, intermediateTitlePrefix,

            moveTalk, moveSubpages, vData, vTData);

    }

}

        return roundrobin(userPermissions, currNs, currTitle, destTitle, "Draft:Move/");

    });

});



/*

//================================================================================

// watchlist sorter, from [[User:Misza13/watchlistSorter.js]]

//================================================================================



$(function(){

  if (location.href.indexOf('Special:Watchlist') == -1) return; //Are we on a watchlist?

  //days = document.getElementById('bodyContent').getElementsByTagName('ul');

  days = document.evaluate( //Hell knows how it works - found in "Dive into Greasemonkey"

    "//ul[@class='special']",

    document,

    null,

    XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,

    null);

  for (d = 0; d < days.snapshotLength; d++) { //For each day

    day = days.snapshotItem(d);

    newday = document.createElement('ul'); //This will replace the old listing

    while ((diffs = day.getElementsByTagName('li')).length > 0) { //Are there any diffs left?

      //Try to extract the namespace

      As = diffs[0].getElementsByTagName('a');

      if (As[0].innerHTML == 'diff')

        pagename = As[2].innerHTML;

      else

        pagename = As[1].innerHTML;

      if (pagename.indexOf(':') == -1)

        namespace = 'Main';

      else

        namespace = pagename.split(':')[0]; //This will fail for articles which contain ":" in name

      hdrs = newday.getElementsByTagName('h5'); //Get the list of namespace headers

      hdr = null;

      for (j=0; j<hdrs.length; j++) //Find the header

        if (hdrs[j].innerHTML==namespace) {

          hdr = hdrs[j]; break;

        }

      if (hdr==null) { //Not found? Make a new one!

        hdr = document.createElement('h5');

        hdr.innerHTML = namespace;

        newday.appendChild(hdr);

        namespacesub = document.createElement('ul');

        namespacesub.className = "special";

        newday.appendChild(namespacesub);

      }

      hdr.nextSibling.appendChild(diffs[0]); //Move the diff

    }

    newday.appendChild(document.createElement('hr')); //For readablility

    day.parentNode.replaceChild(newday,day);

  }

});

*/
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.

//================================================================================

// hide rollback links; see [[WP:Customizing_watchlists#rollback]]

//================================================================================

$('span.mw-rollback-link').remove();



//================================================================================

// quick script for round-robin page moves, from [[User:Andy M. Wang/pageswap.js]]

//================================================================================



// [[WP:PM/C#4]] round-robin history swap

// by [[User:Andy M. Wang]]

// 1.4.2016.1101



$(document).ready(function() {

    "use strict";

    var currNs = mw.config.get("wgNamespaceNumber");

    if (currNs < 0 || currNs >= 120

            || (currNs >=  6 && currNs <= 9)

            || (currNs >= 14 && currNs <= 99))

        return; // special/other page





/**

 * If user is able to perform swaps

 */

function checkUserPermissions() {

    var ret = {};

    ret.canSwap = true;

    var reslt = JSON.parse($.ajax({ url:mw.util.wikiScript('api'), async:false,

        error: function (jsondata) {

            alert("Swapping pages unavailable."); return ret; },

        data: { action:'query', format:'json', meta:'userinfo', uiprop:'rights' }

    }).responseText).query.userinfo;



    // check userrights for suppressredirect and move-subpages

    var rightslist = reslt.rights;

    ret.canSwap =

            $.inArray('suppressredirect', rightslist) > -1

         && $.inArray('move-subpages', rightslist) > -1;

    ret.allowSwapTemplates =

            $.inArray('templateeditor', rightslist) > -1;



    return ret;

}



    mw.util.addPortletLink("p-cactions", "#",

        "Swap", "ca-swappages", "Perform a revision history swap / round-robin move");

    $("#ca-swappages").click(function(e) {

        var userPermissions = checkUserPermissions();

        if (!userPermissions.canSwap) {

            alert("User rights insufficient for action."); return;

        }

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

        var destTitle = prompt("Swap \"" + (currTitle.replace(/_/g, ' ')) + "\" with:");



/**

 * Given namespace data, title, title namespace, returns expected title of page

 * Along with title without prefix

 * Precondition, title, titleNs is a subject page!

 */

function getTalkPageName(nsData, title, titleNs) {

    var ret = {};

    var prefixLength = nsData'' + titleNs]['*'].length === 0

        ? 0 : nsData'' + titleNs]['*'].length + 1;

    ret.titleWithoutPrefix = title.substring(prefixLength, title.length);

    ret.talkTitle = nsData'' + (titleNs + 1)]['*' + ':'

        + ret.titleWithoutPrefix;

    return ret;

}



/**

 * Given two (normalized) titles, find their namespaces, if they are redirects,

 * if have a talk page, whether the current user can move the pages, suggests

 * whether movesubpages should be allowed, whether talk pages need to be checked

 */

function swapValidate(titleOne, titleTwo, pagesData, nsData, uPerms) {

    var ret = {};

    ret.valid = true;

    if (titleOne === null || titleTwo === null || pagesData === null) {

        ret.valid = false;

        ret.invalidReason = "Unable to validate swap.";

        return ret;

    }



    ret.allowMoveSubpages = true;

    ret.checkTalk = true;

    var count = 0;

    for (var k in pagesData) {

        ++count;

        if (k == "-1" || pagesDatak].ns < 0) {

            ret.valid = false;

            ret.invalidReason = ("Page " + pagesDatak].title + " does not exist.");

            return ret;

        }

        // enable only in ns 0..5,12,13,118,119 (Main,Talk,U,UT,WP,WT,H,HT,D,DT)

        if ((pagesDatak].ns >= 6 && pagesDatak].ns <= 9)

         || (pagesDatak].ns >= 10 && pagesDatak].ns <= 11 && !uPerms.allowSwapTemplates)

         || (pagesDatak].ns >= 14 && pagesDatak].ns <= 117)

         || (pagesDatak].ns >= 120)) {

            ret.valid = false;

            ret.invalidReason = ("Namespace of " + pagesDatak].title + " ("

                + pagesDatak].ns + ") not supported.\n\nLikely reasons:\n"

                + "- Names of pages in this namespace relies on other pages\n"

                + "- Namespace features heavily-transcluded pages\n"

                + "- Namespace involves subpages: swaps produce many redlinks\n"

                + "\n\nIf the move is legitimate, consider a careful manual swap.");

            return ret;

        }

        if (titleOne == pagesDatak].title) {

            ret.currTitle   = pagesDatak].title;

            ret.currNs      = pagesDatak].ns;

            ret.currTalkId  = pagesDatak].talkid; // could be undefined

            ret.currCanMove = pagesDatak].actions.move === '';

            ret.currIsRedir = pagesDatak].redirect === '';

        }

        if (titleTwo == pagesDatak].title) {

            ret.destTitle   = pagesDatak].title;

            ret.destNs      = pagesDatak].ns;

            ret.destTalkId  = pagesDatak].talkid; // could be undefined

            ret.destCanMove = pagesDatak].actions.move === '';

            ret.destIsRedir = pagesDatak].redirect === '';

        }

    }



    if (!ret.valid) return ret;

    if (!ret.currCanMove) {

        ret.valid = false;

        ret.invalidReason = ('' + ret.currTitle + " is immovable. Aborting");

        return ret;

    }

    if (!ret.destCanMove) {

        ret.valid = false;

        ret.invalidReason = ('' + ret.destTitle + " is immovable. Aborting");

        return ret;

    }

    if (ret.currNs % 2 !== ret.destNs % 2) {

        ret.valid = false;

        ret.invalidReason = "Namespaces don't match: one is a talk page.";

        return ret;

    }

    if (count !== 2) {

        ret.valid = false;

        ret.invalidReason = "Pages have the same title. Aborting.";

        return ret;

    }

    ret.currNsAllowSubpages = nsData'' + ret.currNs].subpages !== '';

    ret.destNsAllowSubpages = nsData'' + ret.destNs].subpages !== '';



    // if same namespace (subpages allowed), if one is subpage of another,

    // disallow movesubpages

    if (ret.currTitle.startsWith(ret.destTitle + '/')

            || ret.destTitle.startsWith(ret.currTitle + '/')) {

        if (ret.currNs !== ret.destNs) {

            ret.valid = false;

            ret.invalidReason = "Strange.\n" + ret.currTitle + " in ns "

                + ret.currNs + "\n" + ret.destTitle + " in ns " + ret.destNs

                + ". Disallowing.";

            return ret;

        }



        ret.allowMoveSubpages = ret.currNsAllowSubpages;

        if (!ret.allowMoveSubpages)

            ret.addlInfo = "One page is a subpage. Disallowing move-subpages";

    }



    if (ret.currNs % 2 === 1) {

        ret.checkTalk = false; // no need to check talks, already talk pages

    } else { // ret.checkTalk = true;

        var currTPData = getTalkPageName(nsData, ret.currTitle, ret.currNs);

        ret.currTitleWithoutPrefix = currTPData.titleWithoutPrefix;

        ret.currTalkName = currTPData.talkTitle;

        var destTPData = getTalkPageName(nsData, ret.destTitle, ret.destNs);

        ret.destTitleWithoutPrefix = destTPData.titleWithoutPrefix;

        ret.destTalkName = destTPData.talkTitle;

        // possible: ret.currTalkId undefined, but subject page has talk subpages

    }



    return ret;

}



/**

 * Given two talk page titles (may be undefined), retrieves their pages for comparison

 * Assumes that talk pages always have subpages enabled.

 * Assumes that pages are not identical (subject pages were already verified)

 * Assumes namespaces are okay (subject pages already checked)

 * (Currently) assumes that the malicious case of subject pages

 *   not detected as subpages and the talk pages ARE subpages

 *   (i.e. A and A/B vs. Talk:A and Talk:A/B) does not happen / does not handle

 * Returns structure indicating whether move talk should be allowed

 */

function talkValidate(checkTalk, talk1, talk2) {

    var ret = {};

    ret.allowMoveTalk = true;

    if (!checkTalk) { return ret; } // currTitle destTitle already talk pages

    if (talk1 === undefined || talk2 === undefined) {

        alert("Unable to validate talk. Disallowing movetalk to be safe");

        ret.allowMoveTalk = false;

        return ret;

    }

    ret.currTDNE = true;

    ret.destTDNE = true;

    ret.currTCanCreate = true;

    ret.destTCanCreate = true;

    var talkTitleArr = talk1, talk2];

    if (talkTitleArr.length !== 0) {

        var talkData = JSON.parse($.ajax({ url:mw.util.wikiScript('api'), async:false,

            error: function (jsondata) {

                alert("Unable to get info on talk pages."); return ret; },

            data: { action:'query', format:'json', prop:'info',

                intestactions:'move|create', titles:talkTitleArr.join('|') }

        }).responseText).query.pages;

        for (var id in talkData) {

            if (talkDataid].title === talk1) {

                ret.currTDNE = talkDataid].invalid === '' || talkDataid].missing === '';

                ret.currTTitle = talkDataid].title;

                ret.currTCanMove = talkDataid].actions.move === '';

                ret.currTCanCreate = talkDataid].actions.create === '';

                ret.currTalkIsRedir = talkDataid].redirect === '';

            } else if (talkDataid].title === talk2) {

                ret.destTDNE = talkDataid].invalid === '' || talkDataid].missing === '';

                ret.destTTitle = talkDataid].title;

                ret.destTCanMove = talkDataid].actions.move === '';

                ret.destTCanCreate = talkDataid].actions.create === '';

                ret.destTalkIsRedir = talkDataid].redirect === '';

            } else {

                alert("Found pageid not matching given ids."); return {};

            }

        }

    }



    ret.allowMoveTalk = (ret.currTCanCreate && ret.currTCanMove)

        && (ret.destTCanCreate && ret.destTCanMove);

    return ret;

}



/**

 * Given existing title (not prefixed with "/"), optionally searching for talk,

 *   finds subpages (incl. those that are redirs) and whether limits are exceeded

 * As of 2016-08, uses 2 api get calls to get needed details:

 *   whether the page can be moved, whether the page is a redirect

 */

function getSubpages(nsData, title, titleNs, isTalk) {

    if ((!isTalk) && nsData'' + titleNs].subpages !== '') { return { data:[] }; }

    var titlePageData = getTalkPageName(nsData, title, titleNs);

    var subpages = JSON.parse($.ajax({ url:mw.util.wikiScript('api'), async:false,

        error: function (jsondata) {

            return { error:"Unable to search for subpages. They may exist" }; },

        data: { action:'query', format:'json', list:'allpages',

            apnamespace:(isTalk ? (titleNs + 1) : titleNs),

            apfrom:(titlePageData.titleWithoutPrefix + '/'),

            apto:(titlePageData.titleWithoutPrefix + '0'),

            aplimit:101 }

    }).responseText).query.allpages;



    // put first 50 in first arr (need 2 queries due to api limits)

    var subpageids = [[],[]];

    for (var idx in subpages) {

        subpageidsidx < 50 ? 0 : 1].push( subpagesidx].pageid );

    }



    if (subpageids0].length === 0) { return { data:[] }; }

    if (subpageids1].length === 51) { return { error:"100+ subpages. Aborting" }; }

    var dataret = [];

    var subpageData0 = JSON.parse($.ajax({ url:mw.util.wikiScript('api'), async:false,

        error: function (jsondata) {

            return { error:"Unable to fetch subpage data." }; },

        data: { action:'query', format:'json', prop:'info', intestactions:'move|create',

            pageids:subpageids0].join('|') }

    }).responseText).query.pages;

    for (var k0 in subpageData0) {

        dataret.push({

            title:subpageData0k0].title,

            isRedir:subpageData0k0].redirect === '',

            canMove:subpageData0k0].actions.move === ''

        });

    }



    if (subpageids1].length === 0) { return { data:dataret }; }

    var subpageData1 = JSON.parse($.ajax({ url:mw.util.wikiScript('api'), async:false,

        error: function (jsondata) {

            return { error:"Unable to fetch subpage data." }; },

        data: { action:'query', format:'json', prop:'info', intestactions:'move|create',

            pageids:subpageids1].join('|') }

    }).responseText).query.pages;

    for (var k1 in subpageData1) {

        dataret.push({

            title:subpageData1k1].title,

            isRedir:subpageData1k1].redirect === '',

            canMove:subpageData1k1].actions.move === ''

        });

    }

    return { data:dataret };

}



/**

 * Prints subpage data given retrieved subpage information returned by getSubpages

 * Returns a suggestion whether movesubpages should be allowed

 */

function printSubpageInfo(basepage, currSp) {

    var ret = {};

    var currSpArr = [];

    var currSpCannotMove = [];

    var redirCount = 0;

    for (var kcs in currSp.data) {

        if (!currSp.datakcs].canMove) {

            currSpCannotMove.push(currSp.datakcs].title);

        }

        currSpArr.push((currSp.datakcs].isRedir ? "(R) " : "  ")

            + currSp.datakcs].title);

        if (currSp.datakcs].isRedir)

            redirCount++;

    }



    if (currSpArr.length > 0) {

        alert((currSpCannotMove.length > 0

                ? "Disabling move-subpages.\n"

                + "The following " + currSpCannotMove.length + " (of "

                + currSpArr.length + ") total subpages of "

                + basepage + " CANNOT be moved:\n\n  "

                + currSpCannotMove.join("\n  ") + '\n\n'

            : (currSpArr.length + " total subpages of " + basepage + ".\n"

            + (redirCount !== 0 ? ('' + redirCount + " redirects, labeled (R)\n") : '')

            + '\n' + currSpArr.join('\n'))));

    }



    ret.allowMoveSubpages = currSpCannotMove.length === 0;

    ret.noNeed = currSpArr.length === 0;

    return ret;

}



/**

 * After successful page swap, post-move cleanup:

 * Make talk page redirect

 * TODO more reasonable cleanup/reporting as necessary

 * vData.(curr|dest)IsRedir

 */

function doPostMoveCleanup(movedTalk, movedSubpages, vData, vTData) {

    if (movedTalk && vTData.currTDNE && confirm("Create redirect "

            + vData.currTalkName + " → " + vData.destTalkName + " if possible?")) {

        // means that destination talk now is redlinked TODO

    } else if (movedTalk && vTData.destTDNE && confirm("Create redirect "

            + vData.destTalkName + " → " + vData.currTalkName + " if possible?")) {

        // curr talk now is redlinked TODO

    }

}





/**

 * Swaps the two pages (given all prerequisite checks)

 * Optionally moves talk pages and subpages

 */

function swapPages(titleOne, titleTwo, moveReason, intermediateTitlePrefix,

        moveTalk, moveSubpages, vData, vTData) {

    if (titleOne === null || titleTwo === null

            || moveReason === null || moveReason === '') {

        alert("Titles are null, or move reason given was empty. Swap not done");

        return false;

    }



    var intermediateTitle = intermediateTitlePrefix + titleOne;

    var pOne = { action:'move', from:titleTwo, to:intermediateTitle,

        reason:"[[WP:PM/C#4|Round-robin history swap]] step 1 using [[User:Andy M. Wang/pageswap|pageswap]]",

        watchlist:"unwatch", noredirect:1 };

    var pTwo = { action:'move', from:titleOne, to:titleTwo,

        reason:moveReason,

        watchlist:"unwatch", noredirect:1 };

    var pTre = { action:'move', from:intermediateTitle, to:titleOne,

        reason:"[[WP:PM/C#4|Round-robin history swap]] step 3 using [[User:Andy M. Wang/pageswap|pageswap]]",

        watchlist:"unwatch", noredirect:1 };

    if (moveTalk) {

        pOne.movetalk = 1; pTwo.movetalk = 1; pTre.movetalk = 1;

    }

    if (moveSubpages) {

        pOne.movesubpages = 1; pTwo.movesubpages = 1; pTre.movesubpages = 1;

    }



    new mw.Api().postWithToken("csrf", pOne).done(function (reslt1) {

    new mw.Api().postWithToken("csrf", pTwo).done(function (reslt2) {

    new mw.Api().postWithToken("csrf", pTre).done(function (reslt3) {

        alert("Moves completed successfully.\n"

        	+ "Please create new red-linked talk pages/subpages if there are incoming links\n"

        	+ "  (check your contribs for \"Talk:\" redlinks),\n"

            + "  correct any moved redirects, and do post-move cleanup if necessary.");

        //doPostMoveCleanup(moveTalk, moveSubpages, vData, vTData);

    }).fail(function (reslt3) {

        alert("Fail on third move " + intermediateTitle + " → " + titleOne);

    });

    }).fail(function (reslt2) {

        alert("Fail on second move " + titleOne + " → " + titleTwo);

    });

    }).fail(function (reslt1) {

        alert("Fail on first move " + titleTwo + " → " + intermediateTitle);

    });

}



/**

 * Given two titles, normalizes, does prerequisite checks for talk/subpages,

 * prompts user for config before swapping the titles

 */

function roundrobin(uPerms, currNs, currTitle, destTitle, intermediateTitlePrefix) {

    // get ns info (nsData.query.namespaces)

    var nsData = JSON.parse($.ajax({ url:mw.util.wikiScript('api'), async:false,

        error: function (jsondata) { alert("Unable to get info about namespaces"); },

        data: { action:'query', format:'json', meta:'siteinfo', siprop:'namespaces' }

    }).responseText).query.namespaces;



    // get page data, normalize titles

    var relevantTitles = currTitle + "|" + destTitle;

    var pagesData = JSON.parse($.ajax({ url:mw.util.wikiScript('api'), async:false,

        error: function (jsondata) {

            alert("Unable to get info about " + currTitle + " or " + destTitle);

        },

        data: { action:'query', format:'json', prop:'info', inprop:'talkid',

            intestactions:'move|create', titles:relevantTitles }

    }).responseText).query;



    for (var kp in pagesData.normalized) {

        if (currTitle == pagesData.normalizedkp].from) { currTitle = pagesData.normalizedkp].to; }

        if (destTitle == pagesData.normalizedkp].from) { destTitle = pagesData.normalizedkp].to; }

    }

    // validate namespaces, not identical, can move

    var vData = swapValidate(currTitle, destTitle, pagesData.pages, nsData, uPerms);

    if (!vData.valid) { alert(vData.invalidReason); return; }

    if (vData.addlInfo !== undefined) { alert(vData.addlInfo); }



    // subj subpages

    var currSp = getSubpages(nsData, vData.currTitle, vData.currNs, false);

    if (currSp.error !== undefined) { alert(currSp.error); return; }

    var currSpFlags = printSubpageInfo(vData.currTitle, currSp);

    var destSp = getSubpages(nsData, vData.destTitle, vData.destNs, false);

    if (destSp.error !== undefined) { alert(destSp.error); return; }

    var destSpFlags = printSubpageInfo(vData.destTitle, destSp);



    var vTData = talkValidate(vData.checkTalk, vData.currTalkName, vData.destTalkName);



    // future goal: check empty subpage DESTINATIONS on both sides (subj, talk)

    //   for create protection. disallow move-subpages if any destination is salted

    var currTSp = getSubpages(nsData, vData.currTitle, vData.currNs, true);

    if (currTSp.error !== undefined) { alert(currTSp.error); return; }

    var currTSpFlags = printSubpageInfo(vData.currTalkName, currTSp);

    var destTSp = getSubpages(nsData, vData.destTitle, vData.destNs, true);

    if (destTSp.error !== undefined) { alert(destTSp.error); return; }

    var destTSpFlags = printSubpageInfo(vData.destTalkName, destTSp);



    var noSubpages = currSpFlags.noNeed && destSpFlags.noNeed

        && currTSpFlags.noNeed && destTSpFlags.noNeed;

    // If one ns disables subpages, other enables subpages, AND HAS subpages,

    //   consider abort. Assume talk pages always safe (TODO fix)

    var subpageCollision = (vData.currNsAllowSubpages && !destSpFlags.noNeed)

        || (vData.destNsAllowSubpages && !currSpFlags.noNeed);



    var moveTalk = false;

    // TODO: count subpages and make restrictions?

    if (vData.checkTalk && vTData.allowMoveTalk) {

        moveTalk     = confirm("Move talk page(s)? (OK for yes, Cancel for no)");

    } else if (vData.checkTalk) {

        alert("Disallowing moving talk. "

            + (!vTData.currTCanCreate ? (vData.currTalkName + " is create-protected")

            : (!vTData.destTCanCreate ? (vData.destTalkName + " is create-protected")

            : "Talk page is immovable")));

    }



    var moveSubpages = false;

    // TODO future: currTSpFlags.allowMoveSubpages && destTSpFlags.allowMoveSubpages

    // needs to be separate check. If talk subpages immovable, should not affect subjspace

    if (!subpageCollision && !noSubpages && vData.allowMoveSubpages

            && (currSpFlags.allowMoveSubpages && destSpFlags.allowMoveSubpages)

            && (currTSpFlags.allowMoveSubpages && destTSpFlags.allowMoveSubpages)) {

        moveSubpages = confirm("Move subpages? (OK for yes, Cancel for no)");

    } else if (subpageCollision) {

        alert("One namespace does not have subpages enabled. Disallowing move subpages");

    }



    var moveReason = prompt("Move reason:");

    var confirmString = "Round-robin configuration:\n  "

        + currTitle + " → " + destTitle + "\n    : " + moveReason

        + "\n      with movetalk:" + moveTalk + ", movesubpages:" + moveSubpages

        + "\n\nProceed? (Cancel to abort)";



    if (confirm(confirmString)) {

        swapPages(currTitle, destTitle, moveReason, intermediateTitlePrefix,

            moveTalk, moveSubpages, vData, vTData);

    }

}

        return roundrobin(userPermissions, currNs, currTitle, destTitle, "Draft:Move/");

    });

});



/*

//================================================================================

// watchlist sorter, from [[User:Misza13/watchlistSorter.js]]

//================================================================================



$(function(){

  if (location.href.indexOf('Special:Watchlist') == -1) return; //Are we on a watchlist?

  //days = document.getElementById('bodyContent').getElementsByTagName('ul');

  days = document.evaluate( //Hell knows how it works - found in "Dive into Greasemonkey"

    "//ul[@class='special']",

    document,

    null,

    XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,

    null);

  for (d = 0; d < days.snapshotLength; d++) { //For each day

    day = days.snapshotItem(d);

    newday = document.createElement('ul'); //This will replace the old listing

    while ((diffs = day.getElementsByTagName('li')).length > 0) { //Are there any diffs left?

      //Try to extract the namespace

      As = diffs[0].getElementsByTagName('a');

      if (As[0].innerHTML == 'diff')

        pagename = As[2].innerHTML;

      else

        pagename = As[1].innerHTML;

      if (pagename.indexOf(':') == -1)

        namespace = 'Main';

      else

        namespace = pagename.split(':')[0]; //This will fail for articles which contain ":" in name

      hdrs = newday.getElementsByTagName('h5'); //Get the list of namespace headers

      hdr = null;

      for (j=0; j<hdrs.length; j++) //Find the header

        if (hdrs[j].innerHTML==namespace) {

          hdr = hdrs[j]; break;

        }

      if (hdr==null) { //Not found? Make a new one!

        hdr = document.createElement('h5');

        hdr.innerHTML = namespace;

        newday.appendChild(hdr);

        namespacesub = document.createElement('ul');

        namespacesub.className = "special";

        newday.appendChild(namespacesub);

      }

      hdr.nextSibling.appendChild(diffs[0]); //Move the diff

    }

    newday.appendChild(document.createElement('hr')); //For readablility

    day.parentNode.replaceChild(newday,day);

  }

});

*/

Videos

Youtube | Vimeo | Bing

Websites

Google | Yahoo | Bing

Encyclopedia

Google | Yahoo | Bing

Facebook