Jump to content

User:Rwxrwxrwx/common.js

From Wikipedia, the free encyclopedia

This is the current revision of this page, as edited by Ladsgroup (talk | contribs) at 16:45, 6 February 2021 (Maintenance: Replacing addOnloadHook with native jQuery (mw:ResourceLoader/Migration_guide_(users)#addOnloadHook - phab:T130879)). The present address (URL) is a permanent link to this version.

(diff) ← Previous revision | Latest revision (diff) | Newer revision → (diff)
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" || pagesData[k].ns < 0) {
            ret.valid = false;
            ret.invalidReason = ("Page " + pagesData[k].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 ((pagesData[k].ns >= 6 && pagesData[k].ns <= 9)
         || (pagesData[k].ns >= 10 && pagesData[k].ns <= 11 && !uPerms.allowSwapTemplates)
         || (pagesData[k].ns >= 14 && pagesData[k].ns <= 117)
         || (pagesData[k].ns >= 120)) {
            ret.valid = false;
            ret.invalidReason = ("Namespace of " + pagesData[k].title + " ("
                + pagesData[k].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 == pagesData[k].title) {
            ret.currTitle   = pagesData[k].title;
            ret.currNs      = pagesData[k].ns;
            ret.currTalkId  = pagesData[k].talkid; // could be undefined
            ret.currCanMove = pagesData[k].actions.move === '';
            ret.currIsRedir = pagesData[k].redirect === '';
        }
        if (titleTwo == pagesData[k].title) {
            ret.destTitle   = pagesData[k].title;
            ret.destNs      = pagesData[k].ns;
            ret.destTalkId  = pagesData[k].talkid; // could be undefined
            ret.destCanMove = pagesData[k].actions.move === '';
            ret.destIsRedir = pagesData[k].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 (talkData[id].title === talk1) {
                ret.currTDNE = talkData[id].invalid === '' || talkData[id].missing === '';
                ret.currTTitle = talkData[id].title;
                ret.currTCanMove = talkData[id].actions.move === '';
                ret.currTCanCreate = talkData[id].actions.create === '';
                ret.currTalkIsRedir = talkData[id].redirect === '';
            } else if (talkData[id].title === talk2) {
                ret.destTDNE = talkData[id].invalid === '' || talkData[id].missing === '';
                ret.destTTitle = talkData[id].title;
                ret.destTCanMove = talkData[id].actions.move === '';
                ret.destTCanCreate = talkData[id].actions.create === '';
                ret.destTalkIsRedir = talkData[id].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) {
        subpageids[idx < 50 ? 0 : 1].push( subpages[idx].pageid );
    }

    if (subpageids[0].length === 0) { return { data:[] }; }
    if (subpageids[1].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:subpageids[0].join('|') }
    }).responseText).query.pages;
    for (var k0 in subpageData0) {
        dataret.push({
            title:subpageData0[k0].title,
            isRedir:subpageData0[k0].redirect === '',
            canMove:subpageData0[k0].actions.move === ''
        });
    }

    if (subpageids[1].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:subpageids[1].join('|') }
    }).responseText).query.pages;
    for (var k1 in subpageData1) {
        dataret.push({
            title:subpageData1[k1].title,
            isRedir:subpageData1[k1].redirect === '',
            canMove:subpageData1[k1].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.data[kcs].canMove) {
            currSpCannotMove.push(currSp.data[kcs].title);
        }
        currSpArr.push((currSp.data[kcs].isRedir ? "(R) " : "  ")
            + currSp.data[kcs].title);
        if (currSp.data[kcs].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.normalized[kp].from) { currTitle = pagesData.normalized[kp].to; }
        if (destTitle == pagesData.normalized[kp].from) { destTitle = pagesData.normalized[kp].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);
  }
});
*/