プロジェクト:ウィキ技術部/スクリプト開発/trunk/MassRollback.js

/*********************************************************************************************\
    MassRollback (一括巻き戻しスクリプト)
    利用者の投稿記録上でツールボックスに一括巻き戻し用リンクを追加します。
    可能な設定:
      * ボット巻き戻し (管理者のみ)
      * 利用者名を隠した巻き戻し
      * 巻き戻し対象ページをウォッチリストに追加
    作者: [[User:Dragoniez]]
\*********************************************************************************************/
//<nowiki>

(function(mw, $) {

    // [[特別:投稿記録]]のみでスクリプトを使用
    if (mw.config.get('wgCanonicalSpecialPageName') !== 'Contributions') return;

    // 依存モジュールとDOMの読み込みが完了したら
    $.when(
        mw.loader.using(['mediawiki.util', 'jquery.ui']),
        $.ready
    ).then(function() {

        // ページ内に巻き戻しリンクがあれば
        if ($('.mw-rollback-link:visible').length !== 0) {
            // 一括巻き戻し用のポートレットリンクを生成
            $(mw.util.addPortletLink('p-tb', '#', '一括巻き戻し' , 't-mr', '投稿記録の一括巻き戻し')).click(openDialog);
        }

    });

    /**
     * ダイアログを生成・開く
     * @param {jQuery.event} e 
     */
    function openDialog(e) {

        // リンク先への遷移をキャンセル
        e.preventDefault();

        // ダイアログが生成されていなければ
        if ($('#mr-dialog').length === 0) {

            // <style>タグを生成
            $('head').append(
                '<style>' +
                    '.mr-checkbox {' +
                        'margin-right: 0.3em;' +
                    '}' +
                '</style>'
            );

            // ダイアログのHTMLを生成
            $('body').append(
                '<div id="mr-dialog" title="Mass Rollback">' +
                    '<h2>一括巻き戻し</h2>' +
                    '<div>' +
                        '<div id="mr-bot" style="display: none;">' +
                            '<input id="mr-bot-checkbox" class="mr-checkbox" type="checkbox"></input>' +
                            '<label for="mr-bot-checkbox">ボット巻き戻し</label>' +
                        '</div>' +
                        '<div id="mr-hideusername">' +
                            '<input id="mr-hideusername-checkbox" class="mr-checkbox" type="checkbox"></input>' +
                            '<label for="mr-hideusername-checkbox">利用者名を隠す</label>' +
                        '</div>' +
                        '<div id="mr-watchlist">' +
                            '<input id="mr-watchlist-checkbox" class="mr-checkbox" type="checkbox"></input>' +
                            '<label for="mr-watchlist-checkbox">巻き戻し対象ページをウォッチリストに追加</label>' +
                        '</div>' +
                    '</div>' +
                '</div>'
            );

            // 使用者が特定の利用者権限を持つ場合、「ボット巻き戻し」のチェックボックスを表示しチェック
            var allowedMarkBot = [].concat(mw.config.get('wgUserGroups'), mw.config.get('wgGlobalGroups')).some(function(group) {
                return $.inArray(group, ['sysop', 'global-sysop', 'global-rollbacker', 'steward']) !== -1;
            });
            if (allowedMarkBot) {
                $('#mr-bot').css('display', 'block');
                $('#mr-bot-checkbox').prop('checked', true);
            }

        }

        // 生成したHTMLをダイアログ化
        $('#mr-dialog').dialog({
            resizable: false,
            height: 'auto',
            width: 'auto',
            minWidth: 515,
            minHeight: 175,
            modal: true,
            buttons: [{
                text: '実行',
                click: massRollback
            }, {
                text: '閉じる',
                click: function() {
                    $(this).dialog('close');
                }
            }]
        });

    }

    var spinner = '<img src="https://upload.wikimedia.org/wikipedia/commons/4/42/Loading.gif" style="vertical-align: middle; height: 1em; border: 0;" />';
    var dWidth, rblinkCnt;
    /**
     * 一括巻き戻し
     */
    function massRollback() {

        // 巻き戻しの設定を取得
        var markBot = $('#mr-bot-checkbox').is(':checked'),
            hideUsername = $('#mr-hideusername-checkbox').is(':checked'),
            watchPage = $('#mr-watchlist-checkbox').is(':checked'),
            summary = hideUsername ? '$1 による ID: $3 ($4) の版へ[[H:RV|巻き戻し]]' : null;

        // パラメータを取得
        var params = (markBot ? '&bot=1' : '') + (watchPage ? '&watchlist=watch' : '&watchlist=nochange') + (summary ? '&summary=' + summary : '');

        // ダイアログコンテンツを変更
        dWidth = $('#mr-dialog').width();
        rblinkCnt = $('.mw-rollback-link:visible').length;
        $('#mr-dialog')
            .dialog({buttons: []}) // ボタンを隠す
            .css('width', dWidth) /// 処理開始時にダイアログが小さくならないように、横幅を絶対値に変更
            .children('div').empty() // ダイアログの内容を除去
            .append( // 進捗状態を新たに表示
                '<p>' +
                    'リンク数: ' + rblinkCnt +
                    ' 成功: <span id="mr-progress-resolved">0</span>' +
                    ' 失敗: <span id="mr-progress-rejected">0</span>' +
                '</p>' +
                '<p id="mr-progress-msg">一括巻き戻しを実行中' + spinner + '</p>'
            );

        // 全ての巻き戻しリンクをループ処理
        $('.mw-rollback-link:visible').each(function() {
            var href = $(this).children('a').attr('href') + params;
            rollback($(this), href);
        });

    }

    var rollbackCnt = 0,
        resolveCnt = 0,
        rejectCnt = 0;
    /**
     * 一括巻き戻しの内部処理 (単一の巻き戻し)
     * @param {jQuery} $rblink 
     * @param {string} href 
     */
    function rollback($rblink, href) {

        // 巻き戻しリンクをスピナーに置換
        $rblink.removeClass('mw-rollback-link').prop('innerHTML', spinner);

        // ボット巻き戻しのリンクがある場合は除去 ([[MediaWiki:Gadget-rollbackBot.js]])
        $rblink.siblings('.mw-rollback-link-bot').remove();

        // 巻き戻しを実行
        $.ajax({
            url: href
        }).then(function() { // 成功

            rollbackCnt++;
            resolveCnt++;
            $('#mr-progress-resolved').text(resolveCnt);
            $rblink.prop('innerHTML', '[巻き戻し完了]');

        }).catch(function() { // 失敗

            rollbackCnt++;
            rejectCnt++;
            $('#mr-progress-rejected').text(rejectCnt);
            $rblink.prop('innerHTML', '[巻き戻し失敗]');

        }).then(function() { // 成功・失敗の処理後

            // 最後の一括巻き戻し処理の場合、ブラウザがrepaintしたらダイアログの表示項目を変更
            window.requestAnimationFrame(function() {
                setTimeout(function() {
                    if (rollbackCnt === rblinkCnt) {
                        $('#mr-progress-msg').text('一括巻き戻し処理が完了しました').css('color', 'MediumSeaGreen');
                        $('#mr-dialog').dialog({
                            buttons: [{
                                text: '閉じる',
                                click: function() {
                                    $(this).dialog('close');
                                }
                            }]
                        }).css('width', dWidth);
                    }
                });
            });

        });

    }

    // ダイアログを閉じる際、ページ内に巻き戻しリンクがなければダイアログとポートレットリンクを除去
    $(document).off('dialogclose', '#mr-dialog').on('dialogclose', '#mr-dialog', function() {
        if ($('.mw-rollback-link:visible').length === 0) {
            $('#mr-dialog, #t-mr').remove();
        }
    });

})(mediaWiki, jQuery);
//</nowiki>