MediaWiki:Gadget-MassRollback.js

お知らせ: 保存した後、ブラウザのキャッシュをクリアしてページを再読み込みする必要があります。

多くの WindowsLinux のブラウザ

  • Ctrl を押しながら F5 を押す。

Mac における Safari

  • Shift を押しながら、更新ボタン をクリックする。

Mac における ChromeFirefox

  • Cmd Shift を押しながら R を押す。

詳細についてはWikipedia:キャッシュを消すをご覧ください。

/*********************************************************************************************\

    MassRollback

    On a user's contributions, add a portlet link for mass rollback to the toolbox. When the
    link is hit, a dialog will pop up, and the script user can configure three options:

     * Mark edits as bot edits (exclusive to sysops, g-sysops, g-rollbackers, and stewards)
     * Hide username in the rollback summary
     * Add target pages to watchlist
    
    When the 'execute' button is hit on the dialog, all visible rollback links on the page
    will be resolved at one fell swoop in accordance with these configurations.

    @link https://ja.wikipedia.org/wiki/Help:MassRollback
    @author [[User:Dragoniez]]

\*********************************************************************************************/
//<nowiki>

$(function() {

// *********************************************** INITIALIZATION ***********************************************

/** @readonly */
var MR = 'MassRollback';

// Use script only on Special:Contributions
if (mw.config.get('wgCanonicalSpecialPageName') !== 'Contributions') return;

// Find visible rollback links
/** @type {JQuery<HTMLSpanElement>} @readonly */
var $rbspans = $('.mw-rollback-link:visible');
if (!$rbspans.length) return;

// Load dependent modules and create a portlet link
/** @type {mw.Api} @readonly */
var api;
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.ui'], function() {
    api = new mw.Api();
    addPortletLink();
});

// *********************************************** MAIN FUNCTIONS ***********************************************

/** @type {HTMLLIElement|null} @readonly */
var portlet;

/** Create a portlet link to open the main dialog. */
function addPortletLink() {

    portlet = mw.util.addPortletLink('p-tb', '#', '一括巻き戻し' , 't-mr', '投稿記録の一括巻き戻し');
    if (!portlet) return;

    var $dialog;
    portlet.addEventListener('click', function(e) {
        e.preventDefault();
        if (!$dialog) {
            $dialog = createDialog();
        } else {
            $dialog.dialog('open');
        }
    });

}

/**
 * Create a MassRollback dialog.
 * @returns {JQuery<HTMLDivElement>}
 */
function createDialog() {

    /**
     * Create a checkbox with a label on its right.
     * @param {HTMLElement} appendTo
     * @param {string} id
     * @param {string} labeltext
     * @returns {{wrapper: HTMLDivElement; checkbox: HTMLInputElement; label: HTMLLabelElement;}}
     */
    var createLabeledCheckbox = function(appendTo, id, labeltext) {

        var wrapper = document.createElement('div');

        var checkbox = document.createElement('input');
        checkbox.type = 'checkbox';
        checkbox.id = id;
        checkbox.style.marginRight = '0.5em';

        var label = document.createElement('label');
        label.htmlFor = id;
        label.textContent = labeltext;

        wrapper.appendChild(checkbox);
        wrapper.appendChild(label);
        appendTo.appendChild(wrapper);

        return {wrapper: wrapper, checkbox: checkbox, label: label};

    };

    // Dialog container and heading
    var dialog = document.createElement('div');
    dialog.id = 'mr-dialog';
    dialog.title = MR;
    var h2 = document.createElement('h2');
    h2.textContent = '一括巻き戻し';
    dialog.appendChild(h2);

    // MarkBot checkbox
    var markBot = createLabeledCheckbox(dialog, 'mr-bot', 'ボット巻き戻し');
    // @ts-ignore
    var allowedMarkBot = mw.config.get('wgUserGroups').concat(mw.config.get('wgGlobalGroups')).some(function(group) {
        return ['sysop', 'global-sysop', 'global-rollbacker', 'steward'].indexOf(group) !== -1;
    });
    if (allowedMarkBot) {
        markBot.checkbox.checked = true; // Check the box if the current user has the "markbotedits" right
    } else {
        markBot.wrapper.style.display = 'none'; // If not, hide the wrapper div
    }

    // HideUsername checkbox
    var hideUsername = createLabeledCheckbox(dialog, 'mr-hideusername', '利用者名を隠す');

    // AddToWatchlist checkbox
    var watchlist = createLabeledCheckbox(dialog, 'mr-watchlist', '対象ページをウォッチリストに追加');

    // Expiry selector dropdown
    var ul = document.createElement('ul');
    ul.style.display = 'none';
    watchlist.wrapper.appendChild(ul);
    var li = document.createElement('li');
    li.appendChild(document.createTextNode('期限: '));
    ul.appendChild(li);
    var watchlistExpiry = document.createElement('select');
    watchlistExpiry.id = 'mr-watchlist-expiry';
    watchlistExpiry.innerHTML =
        '<option value="infinity">無期限</option>' +
        '<option value="1 week">1週間</option>' +
        '<option value="1 month">1か月</option>' +
        '<option value="3 months">3か月</option>' +
        '<option value="6 months">6か月</option>' +
        '<option value="1 year">1年</option>';
    li.appendChild(watchlistExpiry);
    watchlist.checkbox.addEventListener('change', function() {
        $(ul).toggle();
    });

    // 'Dialogize' the created HTML
    var $dialog = $(dialog);
    $dialog.dialog({
        resizable: false,
        height: 'auto',
        width: 'auto',
        minWidth: 515,
        minHeight: 175,
        modal: true,
        buttons: [{
            text: '実行',
            click: function() { // Execute button

                // Get parameters
                /** @type {RollbackParams} */
                var params = {
                    summary: hideUsername.checkbox.checked ? '$1 による ID: $3 ($4) の版へ[[H:RV|巻き戻し]]' : '',
                    markbot: markBot.checkbox.checked,
                    watchlist: watchlist.checkbox.checked ? 'watch' : 'nochange',
                    watchlistexpiry: watchlist.checkbox.checked ? watchlistExpiry.value : undefined,
                    tags: mw.config.get('wgDBname') === 'jawiki' ? MR : undefined
                };

                // Remove elements that won't be reused and execute mass rollback
                $dialog.dialog('destroy').remove();
                if (portlet) portlet.remove();
                massRollback(params);

            }
        }, {
            text: '閉じる',
            click: function() { // Close button
                $dialog.dialog('close');
            }
        }]
    });

    return $dialog;

}

/**
 * @typedef RollbackParams
 * @property {string} summary
 * @property {boolean} markbot
 * @property {"nochange"|"preferences"|"unwatch"|"watch"} watchlist
 * @property {string} [watchlistexpiry]
 * @property {string} [tags]
 */
/**
 * Execute mass rollback.
 * @param {RollbackParams} params
 */
function massRollback(params) {

    $rbspans.each(function(i, rbs) {

        // Get anchor in the rollback span
        var rblink = rbs.querySelector('a');
        if (!rblink) return;

        // Get pagetitle and username for rollback
        var href = rblink.href;
        var title = mw.util.getParamValue('title', href);
        var user = mw.util.getParamValue('from', href);

        // Replace the elements in the rollback span with a spinner icon
        var processing = document.createElement('img');
        processing.src = 'https://upload.wikimedia.org/wikipedia/commons/4/42/Loading.gif';
        processing.style.cssText = 'vertical-align: middle; height: 1em; border: 0;';
        rbs.replaceChildren(processing);

        // Remove a rollback link added by [[MediaWiki:Gadget-rollbackBot.js]], if there's any
        var rbBotLink = rbs.nextElementSibling;
        if (rbBotLink && rbBotLink.classList.contains('mw-rollback-link-bot')) {
            rbBotLink.remove();
        }

        // Execute rollback on this link
        rollback(title, user, params).then(function(err) {
            // When done, show result in the span
            rbs.innerHTML = '';
            rbs.appendChild(document.createTextNode('['));
            var msg = document.createElement('span');
            if (err) {
                msg.textContent = '巻き戻し失敗 (' + err + ')';
                msg.style.backgroundColor = 'lightpink';
            } else {
                msg.textContent = '巻き戻し成功';
                msg.style.backgroundColor = 'lightgreen';
            }
            rbs.appendChild(msg);
            rbs.appendChild(document.createTextNode(']'));
            rbs.classList.remove('mw-rollback-link');
            rbs.classList.add('mr-rollback-link-resolved');
        });

    });

}

/**
 * Execute a single rollback.
 * @param {string} title
 * @param {string} user
 * @param {RollbackParams} params
 * @returns {JQueryPromise<string|undefined>} Error code on failure, otherwise undefined
 */
function rollback(title, user, params) {
    return api.rollback(title, user, params)
        .then(function() {
            return undefined;
        }).catch(function(code, err) {
            console.log(MR, err);
            return code;
        });
}

// **********************************************************************************************

});
//</nowiki>