MediaWiki:Gadget-MassRollback.js
/*********************************************************************************************\
MassRollback (一括巻き戻しスクリプト)
利用者の投稿記録上でツールボックスに一括巻き戻し用リンクを追加します。
可能な設定:
* ボット巻き戻し (管理者のみ)
* 利用者名を隠した巻き戻し
* 巻き戻し対象ページをウォッチリストに追加
作者: User:Dragoniez
\*********************************************************************************************/
//
$(function() {
// ******************************************** タイプ定義 ********************************************
/**
* @typedef RollbackParams
* @property {string} summary
* @property {boolean} markbot
* @property {string} watchlist
* @property {string} [watchlistexpiry]
* @property {string} tags
*/
// ******************************************** 初期化 ********************************************
// 投稿記録上でのみスクリプトを使用
if (mw.config.get('wgCanonicalSpecialPageName') !== 'Contributions') return;
// ページ上にvisibleな巻き戻しリンクがあるか確認
/** @type {JQuery<HTMLSpanElement>} @readonly */
var $rbspans = $('.mw-rollback-link:visible');
if (!$rbspans.length) return;
// 依存モジュールのロード後一括巻き戻し用のポートレットリンクを作成
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.ui'], addPortletLink);
// ******************************************** 主要関数 ********************************************
/** @type {mw.Api} @readonly */
var api;
/** @type {HTMLLIElement|null} @readonly */
var portlet;
/**
* 一括巻き戻し用のポートレットリンクを作成する関数
* @returns {void}
*/
function addPortletLink() {
api = new mw.Api();
portlet = mw.util.addPortletLink('p-tb', '#', '一括巻き戻し' , 't-mr', '投稿記録の一括巻き戻し');
if (!portlet) return;
var $dialog;
portlet.addEventListener('click', function(e) {
e.preventDefault();
if ($dialog) {
$dialog.dialog('open');
} else {
$dialog = createDialog();
}
});
}
/**
* 一括巻き戻し用のダイアログを作成する関数
* @returns {JQuery<HTMLDivElement>}
*/
function createDialog() {
/**
* ラベル付きのチェックボックスを作成する関数
* @param {HTMLElement} appendTo
* @param {string} id
* @param {string} labeltext
* @returns {HTMLInputElement}
*/
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 checkbox;
};
// ダイアログコンテナの作成
var dialog = document.createElement('div');
dialog.id = 'mr-dialog';
dialog.title = 'Mass Rollback';
// ヘッダーを追加
var h2 = document.createElement('h2');
h2.textContent = '一括巻き戻し';
dialog.appendChild(h2);
// 「ボット巻き戻し」チェックボックスを追加
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.checked = true; // "markbotedits" の利用者権限があればボックスをチェック
} else {
// @ts-ignore
markBot.parentElement.style.display = 'none'; // 無ければコンテナdiv自体を隠す
}
// 「利用者名を隠す」チェックボックスを追加
var hideUsername = createLabeledCheckbox(dialog, 'mr-hideusername', '利用者名を隠す');
// 「対象ページをウォッチリストに追加」チェックボックスを追加
var watchlist = createLabeledCheckbox(dialog, 'mr-watchlist', '対象ページをウォッチリストに追加');
// 期限設定用ドロップダウンの作成
var ul = document.createElement('ul');
ul.style.display = 'none';
var li = document.createElement('li');
li.appendChild(document.createTextNode('期限: '));
ul.appendChild(li);
var watchlistExpiry = document.createElement('select');
watchlistExpiry.id = 'mr-watchlist-expiry';
li.appendChild(watchlistExpiry);
var options = [
{value: 'infinity', text: '無期限'},
{value: '1 week', text: '1週間'},
{value: '1 month', text: '1か月'},
{value: '3 months', text: '3か月'},
{value: '6 months', text: '6か月'},
{value: '1 year', text: '1年'},
{value: '3 years', text: '3年'}
];
options.forEach(function(obj) {
var opt = document.createElement('option');
opt.value = obj.value;
opt.textContent = obj.text;
watchlistExpiry.appendChild(opt);
});
// @ts-ignore
watchlist.parentElement.appendChild(ul);
watchlist.addEventListener('change', function() {
$(ul).toggle(); // watchlistチェックボックスのチェック状態が変わるごとにドロップダウンを表示/非表示
});
// 作成した要素をDOMに追加しダイアログ化
var $dialog = $(dialog);
$dialog.dialog({
resizable: false,
height: 'auto',
width: 'auto',
minWidth: 515,
minHeight: 175,
modal: true,
buttons: [{
text: '実行',
click: function() { // 「実行」ボタンクリック時
// 設定を取得
/** @type {RollbackParams} */
var params = {
summary: hideUsername.checked ? '$1 による ID: $3 ($4) の版へ巻き戻し' : ,
markbot: markBot.checked,
watchlist: watchlist.checked ? 'watch' : 'nochange',
tags: mw.config.get('wgDBname') === 'testwiki' ? 'testtag' : 'MassRollback'
};
if (watchlist.checked) params.watchlistexpiry = watchlistExpiry.value;
// 用がなくなるセレクタを除去し一括巻き戻しを実行
$dialog.dialog('destroy').remove();
if (portlet) portlet.remove();
massRollback(params);
}
}, {
text: '閉じる',
click: function() {
$dialog.dialog('close');
}
}]
});
return $dialog;
}
/**
* 一括巻き戻しを実行する関数
* @param {RollbackParams} params
*/
function massRollback(params) {
$rbspans.each(function(i, rbs) { // 全ての巻き戻しリンクをループ
// spanタグ内部のアンカーを取得
var rblink = rbs.querySelector('a');
if (!rblink) return;
// hrefと巻き戻し対象ページのタイトル、ユーザー名を取得
var href = rblink.href;
var title = mw.util.getParamValue('title', href);
var user = mw.util.getParamValue('from', href);
// 巻き戻しリンクの内部要素をスピナーに置換
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);
// の巻き戻しリンクがあれば除去
var rbBotLink = rbs.nextElementSibling;
if (rbBotLink && rbBotLink.classList.contains('mw-rollback-link-bot')) {
rbBotLink.remove();
}
// 巻き戻しの実行
rollback(title, user, params).then(function(err) {
// 結果が返ってきたら、巻き戻しリンクの内部要素にその結果を表示
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');
});
});
}
/**
* 単一の巻き戻しを実行する関数
* @param {string} title
* @param {string} user
* @param {object} params
* @returns {JQueryPromise}
*/
function rollback(title, user, params) {
return api.rollback(title, user, params)
.then(function() {
return;
}).catch(function(code, err) {
console.log(err);
return code;
});
}
// *********************************************************************************************
});
//