MediaWiki:Gadget-MassProtect.js
$('#mp-level-move, #mp-level-upload').find('.mp-level-dropdown').children('option[value="' + selectedVal + '"]').prop('selected', true);
});
// 保護期間ドロップダウン
$(document).off('change', '#mp-level-edit-expiry-select').on('change', '#mp-level-edit-expiry-select', function() {
if ($('#mp-level-enableadditionaloptions').is(':checked')) return;
var selectedVal = $(this).find('option').filter(':selected').val();
$('#mp-level-move-expiry-select, #mp-level-upload-expiry-select').find('option[value="' + selectedVal + '"]').prop('selected', true);
});
// カスタム編集保護期間テキストボックス
$(document).off('input', '#mp-level-edit-expiry-custom-input').on('input', '#mp-level-edit-expiry-custom-input', function() {
if ($('#mp-level-enableadditionaloptions').is(':checked')) return;
$('#mp-level-move-expiry-custom-input, #mp-level-upload-expiry-custom-input').val($(this).val());
});
// 保護期間設定ドロップダウンで「その他の期間」以外が選択されたら「その他の期間」用のテキストボックスを白紙化
$(document).off('change', '.mp-level-expiry-dropdown').on('change', '.mp-level-expiry-dropdown', function() {
var selectedVal = $(this).find('option').filter(':selected').val();
var acrossTheBoard = !$('#mp-level-enableadditionaloptions').is(':checked') && $(this).prop('id') === 'mp-level-edit-expiry-select';
var $target = acrossTheBoard ? $('.mp-level-expiry-custom').not('#mp-level-create-expiry-custom-input') : $(this).closest('fieldset').find('.mp-level-expiry-custom');
if (selectedVal) $target.val();
});
// 「その他の期間」テキストボックスにカーソルが当てられたら自動的に期間を変更
$(document).off('click', '.mp-level-expiry-custom').on('click', '.mp-level-expiry-custom', function() {
var $expiry =  $(this).closest('fieldset').find('.mp-level-expiry-dropdown');
$expiry.find('option[value=""]').prop('selected', true);
$expiry.trigger('change');
});
// ------------------------------------------------------------------------------------------------------------
// 「保護対象ページをウォッチリストに追加」がチェックされたらドロップダウンを表示
$(document).off('change', '#mp-watchlist').on('change', '#mp-watchlist', function() {
var displayType = $(this).is(':checked') ? 'block' : 'none';
$('#mp-watchlist-expiry').css('display', displayType);
});
// 「保護タグを自動添付」がチェックされたらドロップダウンを表示、「保護依頼タグを自動除去」を対応して有効化・無効化
$(document).off('change', '#mp-tag').on('change', '#mp-tag', function() {
var checked = $(this).is(':checked');
$('#mp-tag-list').css('display', checked ? 'block' : 'none');
$('#mp-rfptag').prop('disabled', checked);
if (checked) $('#mp-rfptag').prop('checked', true); // チェックされた時には「保護依頼タグを自動除去」をチェック
});
// 保護タグドロップダウンのオプションコントロール
$(document).off('change', '#mp-tag-template').on('change', '#mp-tag-template', function() {
/********************************************************\
Template:Ppの引数一覧
template         action                  small
pp-(meta)        edit, move, upload      def=no
dispute          edit, move, upload      X
vandalism        edit, move              def=no
(semi-indef)     ----------              ---------
template         X                       def=yes
permanent        X                       def=yes
move             X                       def=no
move-vandalism   X                       def=no
move-dispute     X                       X
office           X                       X
reset            X                       X
office-dmca      X                       X
\********************************************************/
// セレクタ
var $action = $('#mp-tag-action'),
$actionBlank = $('#mp-tag-action-blank'),
$actionEdit = $('#mp-tag-action-edit'), // 未使用
$actionMove = $('#mp-tag-action-move'), // 未使用
$actionUpload = $('#mp-tag-action-upload'),
$small = $('#mp-tag-small'),
$smallBlank = $('#mp-tag-small-blank'),
$smallYes = $('#mp-tag-small-yes'),
$smallNo = $('#mp-tag-small-no');
// リセット (全て表示 + actionとsmallは空オプションを選択)
$action.prop('disabled', false);
$action.children('option').prop('hidden', false);
$small.prop('disabled', false);
$small.children('option').prop('hidden', false);
$actionBlank.prop('selected', true);
$smallBlank.prop('selected', true);
// オプションコントロール
switch ($(this).children('option').filter(':selected').prop('id')) {
case 'mp-tag-template-meta':
$smallNo.prop('hidden', true);
break;
case 'mp-tag-template-dispute':
$small.prop('disabled', true);
break;
case 'mp-tag-template-vandalism':
$actionUpload.prop('hidden', true);
$smallNo.prop('hidden', true);
$smallYes.prop('selected', true); // pp-vamdalism は一番よく使うため yes をデフォルトに
break;
case 'mp-tag-template-template':
case 'mp-tag-template-permanent':
$action.prop('disabled', true);
$smallYes.prop('hidden', true);
break;
case 'mp-tag-template-move':
case 'mp-tag-template-move-vandalism':
$action.prop('disabled', true);
$smallNo.prop('hidden', true);
break;
case 'mp-tag-template-move-dispute':
case 'mp-tag-template-office':
case 'mp-tag-template-reset':
case 'mp-tag-template-office-dmca':
$action.prop('disabled', true);
$small.prop('disabled', true);
}
});
/**
* 保護設定の詳細を取得する関数
* @returns {Array<{type: string, level: string, expiry: string}>} 例: [{"type": "edit", "level": "autoconfirmed", "expiry": "2022-09-24T18:32:15Z"}, ...]
*/
function getProtectionSettings() {
var params = [];
var actions = ['create', 'edit', 'move', 'upload'];
/* --------------------------------------------------------------
作成する配列 (例)
[
{
"type": "edit",
"level": "autoconfirmed",
"expiry": "2022-09-24T18:32:15Z"
},
{
"type": "move",
"level": "autoconfirmed",
"expiry": "2022-09-24T18:32:15Z"
}
]
----------------------------------------------------------------- */
// 保護レベル設定用の  タグをループ
var skip = $('#mp-level-division-select').children('option').filter(':selected').val() === '未作成ページ' ? [1, 2, 3] : [0];
$('.mp-level-dropdown').each(function(i) {
if ($.inArray(i, skip) !== -1) return;
var level = $(this).children('option').filter(':selected').val(); // , 'autoconfirmed', 'extendedconfirmed', 'sysop'
if (level) {
var $parent = $(this).closest('fieldset');
var expiryOption = $parent.find('.mp-level-expiry-dropdown').find('option').filter(':selected').val();
var expiryCustom = $parent.find('.mp-level-expiry-custom').val().replace(/\u200e/g, ).trim();
var expiry = expiryOption ? expiryOption : expiryCustom;
params.push({
type: actions[i],
level: level,
expiry: expiry
});
}
});
return params;
}
/**
* 保護実行前の最終確認 + 必要情報を取得する関数
* @returns pages: Array, levels: Array, expiry: Array, reason: string, cascade: boolean, watchlist: string, watchlistexpiry: string, tag: string
*/
function editPrep() {
// 保護対象ページを取得
var targetPages = getTargetPages();
if (targetPages.length === 0) return alert('保護対象ページが入力されていません');
// 保護レベルを取得
var protectionSettings = getProtectionSettings();
if (protectionSettings.length === 0) {
return alert('保護レベルが設定されていません');
// if (!confirm('対象ページの保護を全て解除します。よろしいですか?')) return;
}
var levels = [], expiries = [];
protectionSettings.forEach(function(obj) {
levels.push(obj.type + '=' + obj.level);
expiries.push(obj.expiry);
});
// アップロード保護のみ、かつ保護対象ページにファイル名前空間ではないページが含まれている場合を検出
var protectNonFiles = targetPages.some(function(el) { return !el.match(/^(ファイル|file|画像|image):/i); });
if (protectNonFiles && levels.length === 1 && levels[0].indexOf('upload=') !== -1) {
return alert('アップロード保護対象にファイル名前空間ではないページが含まれています');
}
// 保護理由を取得
var reasonOption = $('#mp-reason-dropdown').find('option').filter(':selected').val(),
reasonOption2 = $('#mp-reason-dropdown2').find('option').filter(':selected').val(),
reasonCustom = $('#mp-reason-custom').val().replace(/\u200e/g, ).trim();
if (!(reasonOption + reasonOption2 + reasonCustom)) {
if (!confirm('保護理由が入力されていません。このまま実行しますか?')) return;
}
reasonOption = reasonOption ? reasonOption + ': ' : ; // 保護理由ドロップダウンの値が空でなければ語末に ': ' を追加
reasonOption2 = reasonOption2 ? reasonOption2 + ': ' : ;
var reason = (reasonOption + reasonOption2 + reasonCustom).replace(/: $/, ); // 全ての理由を組み合わせ、語末に ': ' がある場合は除去
// 保護タグを取得
var tag = ;
var newPageProtection = protectionSettings.some(function(obj) { return obj.type === 'create'; });
if ($('#mp-tag').is(':checked') && !newPageProtection) { // 「保護タグを自動添付」がチェックされており、作成保護でなければ
var tagName = $('#mp-tag-template').children('option').filter(':selected').val(),
tagAction = $('#mp-tag-action').children('option').filter(':selected').val(),
tagSmall = $('#mp-tag-small').children('option').filter(':selected').val();
tag = (tagName + tagAction + tagSmall) ?  + tagName + tagAction + tagSmall +  : ;
}
if (!tag && !newPageProtection && $('#mp-rfptag').is(':checked')) tag = 'rm';
// オブジェクトを return
return {
pages: targetPages,
levels: levels,
expiry: expiries,
reason: reason,
cascade: $('#mp-cascade').prop('checked'),
watchlist: $('#mp-watchlist').is(':checked') ? 'watch' : 'nochange',
watchlistexpiry: $('#mp-watchlist-expiry').find('select').children('option').filter(':selected').val(),
tag: tag
};
}
// ボタンクリック時に一括保護を実行
$(document).off('click', '#mp-submit').on('click', '#mp-submit', function() {
// 保護設定を取得
var ep = editPrep();
if (!ep) return;
// 「保護を実行」ボタンを無効化
$(this).prop('disabled', true);
// 進捗状態を表示
$('#mp-protection-settings').append( // 「保護を実行」ボタンの下にフィールドを作成
'<fieldset>' +
'<legend>進捗</legend>' +
'</fieldset>'
);
ep.pages.forEach(function(page) { // 保護対象ページの全てをループ
$('#mp-progress').append(
'<p>' +
+ page + ' ' +
'保護: ' + MassProtect.image.doing + ' ' +
(ep.tag ? 'タグ: ' + MassProtect.image.doing +  : ) +
'</p>'
);
});
/**
* 画面の最下部に追加される進捗状態が見える位置までスクロールダウンする関数
* @param {number} pos スクロール位置のY座標
*/
var scrollDown = function(pos) {
var i = window.scrollY;
var int = setInterval(function() {
window.scrollTo(0, i);
i += 20;
if (i >= pos) clearInterval(int);
}, 10);
};
scrollDown($('#mp-progress').prop('offsetTop'));
// 保護を実行
var protectCnt = 0;
protectPages();
/**
* ページの保護を実行する関数 (意図的にコールバックで順番に実行)
*/
function protectPages() {
// 保護対象ページのタイトルを取得
var pagetitle = ep.pages[protectCnt];
// 既存の保護設定チェックがオンである、かつ既に保護が掛けられている場合確認メッセージを表示
var confirmOverriding = $('#mp-confirmoverriding').is(':checked');
if (confirmOverriding) {
// 現在の保護設定をチェック
getProtectionStatus(pagetitle).then(function(obj) {
// 保護されていれば
if (obj && obj.protection) {
var msg =
'<p>' +
'「' + pagetitle +'」は既に保護されています ' +
' 保護者: ' + obj.executer +
' 保護日時: ' + obj.timestamp.replace(/Z$/, ) +
' 現在時刻: ' + new Date().toJSON().replace(/\.\d{3}Z$/, ) +
' 保護設定: ' + obj.protection.overview.join(', ') +
' 保護理由: ' + obj.reason +
'  保護設定を上書きしますか?' +
'</p>';
// 確認メッセージを表示
dconfirm(msg).then(function(confirmed) {
if (confirmed) { // 続行
protectPagesInnerProcedure();
} else { // 中止
updateProgress(pagetitle, 'protect', '上書き防止', true);
if (ep.tag) updateProgress(pagetitle, 'tag', '保護中止', true);
protectCnt++;
if (ep.pages[protectCnt]) protectPages();
}
});
// 保護されていない場合はそのまま保護処理を実行
} else {
protectPagesInnerProcedure();
}
});
// 既存の保護設定チェックがオフの場合、そのまま保護処理を実行
} else {
protectPagesInnerProcedure();
}
function protectPagesInnerProcedure() {
// APIリクエストのパラメータを設定
var levels = JSON.parse(JSON.stringify(ep.levels)),
expiry = JSON.parse(JSON.stringify(ep.expiry));
var uploadProtection = levels.some(function(el) { return el.indexOf('upload=') !== -1; });
if (!pagetitle.match(/^(ファイル|file|画像|image):/i) && uploadProtection) { // ファイル名前空間ではないページはアップロード保護不可
levels.pop(); // 「upload=XXX」パラメータを除去
expiry.pop();
}
var params = {
action: 'protect',
title: pagetitle,
protections: levels.join('|'),
expiry: expiry.join('|'),
reason: ep.reason,
watchlist: ep.watchlist,
token: mw.user.tokens.get('csrfToken'),
format: 'json'
};
if (ep.cascade) params.cascade = true; // なぜか false で保護リクエストを投げてもカスケード保護されるため true の場合のみパラメータを追加
if (ep.watchlist === 'watch' && ep.watchlistexpiry) params.watchlistexpiry = ep.watchlistexpiry;
// 保護リクエストを送信
$.post(apiEndpoint, params, function(res) {
var succeeded;
// 何らかの理由で保護に失敗
if (res.error) {
// 進捗を「保護: 失敗 (エラーの詳細)」に更新
updateProgress(pagetitle, 'protect', res.error.info);
// 不明なエラー
} else if (!res || !res.protect) {
// 進捗を「保護: 失敗 (不明なエラー)」に更新
updateProgress(pagetitle, 'protect', false);
// 成功
} else {
// 進捗を「保護: 成功」に更新
updateProgress(pagetitle, 'protect', true);
succeeded = true;
}
// 次の保護に移行するために保護対象ページ配列のインデックスを加算 (pasteTagの前に更新する必要があるためこの位置での更新必須)
protectCnt++;
// タグ添付をする場合は