コンテンツにスキップ

英文维基 | 中文维基 | 日文维基 | 草榴社区

「利用者:Dragoniez/scripts/RFD Helper.js」の版間の差分

削除された内容 追加された内容
m v1.3.2: cl
m typo
(同じ利用者による、間の1版が非表示)
2行目: 2行目:
* Name: RFD Helper *
* Name: RFD Helper *
* Author: Dragoniez *
* Author: Dragoniez *
* Version: 1.3.2 *
* Version: 1.4.0 *
****************************/
****************************/
//<nowiki>
//<nowiki>


// ******************** CONFIGS ********************
// **************************************** CONFIGS ****************************************


// var {dragoLib} = require('../dragoLib/dragoLib.js');
// var {dragoLib} = require('../dragoLib/dragoLib.js');
46行目: 46行目:
}
}


// ******************** SCRIPT BODY ********************
// **************************************** SCRIPT BODY ****************************************


(function() { // Create a function scope
(function() { // Create a function scope


// Run the script only if the user is autoconfirmed and the page is not an edit page
// ************************* VARIABLES *************************
if (mw.config.get('wgUserGroups').indexOf('autoconfirmed') === -1 || ['edit', 'submit'].indexOf(mw.config.get('wgAction')) !== -1) return;

// ********************************************* VARIABLES *********************************************


// DebugMode
// DebugMode
const debuggingMode = {
var debuggingMode = {
library: false,
library: false,
portletlinkText: false,
portletlinkText: false,
targetPage: false,
targetPage: false,
scriptAd: false,
scriptAd: false,
causeIntentionalError: false
causeIntentionalError: false,
fillFields: false // Fill the redirect source and reason fields
};
};
const library = debuggingMode.library ?
var library = debuggingMode.library ?
'http://127.0.0.1:5500/dragoLib/dragoLib.js' :
'http://127.0.0.1:5500/dragoLib/dragoLib.js' :
'//ja-two.iwiki.icu/w/index.php?title=User:Dragoniez/scripts/dragoLib.js&action=raw&ctype=text/javascript';
'//ja-two.iwiki.icu/w/index.php?title=User:Dragoniez/scripts/dragoLib.js&action=raw&ctype=text/javascript';
const portletlinkText = debuggingMode.portletlinkText ? 'リダイレクトの削除依頼β' : 'リダイレクトの削除依頼';
var portletlinkText = debuggingMode.portletlinkText ? 'リダイレクトの削除依頼β' : 'リダイレクトの削除依頼';
const RFDR = debuggingMode.targetPage ? '利用者:Dragoniez/test3' : 'Wikipedia:リダイレクトの削除依頼/受付';
var RFDR = debuggingMode.targetPage ? '利用者:Dragoniez/test3' : 'Wikipedia:リダイレクトの削除依頼/受付';
const scriptAd = ' ([[User:Dragoniez/scripts/RFD Helper|' + (debuggingMode.scriptAd ? 'RFD Helper Dev]])' : 'RFD Helper]])');
var scriptAd = ' ([[User:Dragoniez/scripts/RFD Helper|' + (debuggingMode.scriptAd ? 'RFD Helper Dev]])' : 'RFD Helper]])');


// Buttons of the dialog
// Others
var btns = [{
const REDIRS = {}; // {source: goal, source2: goal2}...
const btns = [{ // Buttons of the dialog
text: '依頼',
text: '依頼',
click: submitRequest
click: submitRequest
82行目: 85行目:
}];
}];


// ************************* DOM READY FUNCTION *************************
// ********************************************* DOM READY FUNCTION *********************************************


$.when(
$.when(
$.getScript(library),
$.getScript(library),
mw.loader.using('jquery.ui'),
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.ui']),
$.ready
$.ready
).then(function() {
).then(function() {

$('head').append( // <style> for the dialog
// Add <style>
$('head').append(
'<style>' +
'<style>' +
' .rfdh-needmargin {' +
'.rfdh-needmargin {' +
' margin: 0.5em 0;' +
'margin: 0.5em 0;' +
' }' +
'}' +
' .rfdh-textarea {' +
'.rfdh-dialog-main textarea {' +
' width: 100%;' +
'box-sizing: border-box;' +
' box-sizing: border-box;' +
'width : 100%;' +
' }' +
'}' +
' .rfdh-preview-notarget {' +
'.rfdh-dialog-main input[type="checkbox"],' +
'.rfdh-dialog-preview input[type="checkbox"] {' +
` background: ${rfdhConfig.headerColor};` +
' }' +
'margin-right: 0.5em;' +
'}' +
'.rfdh-preview-notarget {' +
'background: ' + rfdhConfig.headerColor + ';' +
'}' +
'</style>'
'</style>'
);
);

if (dragoLib.inGroup('autoconfirmed') && mw.config.get('wgAction') !== 'edit') {
// Create a portletlink for the main dialog
$(mw.util.addPortletLink(rfdhConfig.portletlinkPosition, '#', portletlinkText, 'ca-rfdh', 'リダイレクトの削除依頼を提出', null, '#ca-move')).click(openDialog);
$(mw.util.addPortletLink(rfdhConfig.portletlinkPosition, '#', portletlinkText, 'ca-rfdh', 'リダイレクトの削除依頼を提出', null, '#ca-move')).click(openDialog);
}

});
});


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


/** Create and open the main dialog when the portletlink is hit */
function openDialog(e) {
function openDialog(e) {
e.preventDefault();
e.preventDefault();


const votes =
var votes =
'<option value="">なし</option>' +
'<option value="">なし</option>' +
'<option value="{{AFD|削除}}">削除</option>' +
'<option value="{{AFD|削除}}">削除</option>' +
126行目: 137行目:
'<option value="{{AFD|履歴統合}}">履歴統合</option>';
'<option value="{{AFD|履歴統合}}">履歴統合</option>';


const dialogHtml =
var dialogHtml =
'<div id="rfdh-dialog" title="RFD Helper" style="max-height: 80vh; min-width: 515px;">' +
'<div id="rfdh-dialog" title="RFD Helper" style="max-height: 80vh; min-width: 515px;">' +
' <div id="rfdh-dialog-header">' +
'<div id="rfdh-dialog-header">' +
' <h2>リダイレクトの削除依頼</h2>' +
'<h2>リダイレクトの削除依頼</h2>' +
' </div>' +
'</div>' +
' <div id="rfdh-dialog-body">' +
'<div id="rfdh-dialog-body">' +
' <form>' +
'<form>' +
' <div id="rfdh-redirectlist-div">' +
'<div id="rfdh-redirectlist-div">' +
' <label for="rfdh-redirectlist-input">リダイレクト元 (ページごとに改行)</label>' +
'<label for="rfdh-redirectlist-input">リダイレクト元 (ページごとに改行)</label>' +
' <textarea id="rfdh-redirectlist-input" class="rfdh-textarea" rows="8"></textarea>' +
'<textarea id="rfdh-redirectlist-input" rows="8"></textarea>' +
' <input id="rfdh-redirectlist-cleanup" class="rfdh-needmargin" type="button" value="整形" style="margin-right: 0.5em;">' +
'<input id="rfdh-redirectlist-cleanup" class="rfdh-needmargin" type="button" value="整形" style="margin-right: 0.5em;">' +
' <span>(余分な改行および重複項目を除去)</span>' +
'<span>(余分な改行および重複項目を除去)</span>' +
' </div>' +
'</div>' +
' <div id="rfdh-vote-div" class="rfdh-needmargin">' +
'<div id="rfdh-vote-div" class="rfdh-needmargin">' +
' <label for="rfdh-vote-select">依頼者票 <span style="font-size: smaller;">(複数選択する場合のみ中間表現必須)</span></label><br>' +
'<label for="rfdh-vote-select">依頼者票 <span style="font-size: smaller;">(複数選択する場合のみ中間表現必須)</span></label><br>' +
' <select id="rfdh-vote-select">' +
'<select id="rfdh-vote-select" style="margin-right: 0.3em;">' +
votes +
votes +
' </select>' +
'</select>' +
' <select id="rfdh-votedelimiter-select">' +
'<select id="rfdh-votedelimiter-select" style="margin-right: 0.3em;">' +
' <option></option>' +
'<option></option>' +
' <option value="および">および</option>' +
'<option value="および">および</option>' +
' <option value="または">または</option>' +
'<option value="または">または</option>' +
' </select>' +
'</select>' +
' <select id="rfdh-vote2-select">' +
'<select id="rfdh-vote2-select">' +
votes +
votes +
' </select>' +
'</select>' +
' </div>' +
'</div>' +
' <div id="rfdh-reason-div" class="rfdh-needmargin">' +
'<div id="rfdh-reason-div" class="rfdh-needmargin">' +
' <label for="rfdh-reason-input">依頼文 <span style="font-size: smaller;">(署名不要)</span></label>' +
'<label for="rfdh-reason-input">依頼文 <span style="font-size: smaller;">(署名不要)</span></label>' +
' <textarea id="rfdh-reason-input" class="rfdh-textarea" rows="3"></textarea>' +
'<textarea id="rfdh-reason-input" rows="3"></textarea>' +
' </div>' +
'</div>' +
' <div id="rfdh-summary-div" class="rfdh-needmargin">' +
'<div id="rfdh-summary-div" class="rfdh-needmargin">' +
' <input id="rfdh-summary-checkbox" type="checkbox">' +
'<input id="rfdh-summary-checkbox" type="checkbox">' +
' <label for="rfdh-summary-checkbox">要約を指定</label>' +
'<label for="rfdh-summary-checkbox">要約を指定</label>' +
' <textarea id="rfdh-summary-input" class="rfdh-textarea" rows="3" style="display: none;"></textarea>' +
'<textarea id="rfdh-summary-input" rows="3" style="display: none;"></textarea>' +
' </div>' +
'</div>' +
' </form>' +
'</form>' +
' </div>' +
'</div>' +
'</div>';
'</div>';


179行目: 190行目:
dragoLib.dialogCSS($('.rfdh-dialog-main'), rfdhConfig.headerColor, rfdhConfig.backgroundColor, rfdhConfig.fontSize);
dragoLib.dialogCSS($('.rfdh-dialog-main'), rfdhConfig.headerColor, rfdhConfig.backgroundColor, rfdhConfig.fontSize);
$('#rfdh-vote-select').children('option').eq(1).prop('selected', true);
$('#rfdh-vote-select').children('option').eq(1).prop('selected', true);
if (debuggingMode.fillFields) {
$('#rfdh-redirectlist-input').val(['利用者:Dragoniez', 'User:Dragoniez/AN Reporter'].join('\n'));
$('#rfdh-reason-input').val('Test.');
}
},
},
buttons: btns
buttons: btns
185行目: 200行目:
}
}


/**
// Check whether the necessary fileds are filleld
* Check whether the necessary fileds are filled, and get neccesary information to submit an RFD
function editPrep1() {
* @returns {{redirects: Array, reqText: string, summary: string}|undefined}
* [IMPORTANT] reqText doesn't contain RFD templates. The object needs to be updated by updateEp().
*/
function editPrep() {


// Error storage
// Error storage
const problems = {
var problems = {
redirects: '',
redirects: '',
conjunction: '',
conjunction: '',
196行目: 215行目:


// Redirect sources
// Redirect sources
const redirects = getRedirectSources();
var redirects = getRedirectSources();
if (redirects.length === 0) problems.redirects = '・リダイレクト元';
if (redirects.length === 0) problems.redirects = '・リダイレクト元';


// Votes
// Votes
const $vote = $('#rfdh-vote-select'), $delimiter = $('#rfdh-votedelimiter-select'), $vote2 = $('#rfdh-vote2-select');
var $vote = $('#rfdh-vote-select'),
$delimiter = $('#rfdh-votedelimiter-select'),
$vote2 = $('#rfdh-vote2-select'),
var vote = $vote.children('option').filter(':selected').val();
vote = $vote.children('option').filter(':selected').val(),
var delimiter = $delimiter.children('option').filter(':selected').val();
delimiter = $delimiter.children('option').filter(':selected').val(),
var vote2 = $vote2.children('option').filter(':selected').val();
vote2 = $vote2.children('option').filter(':selected').val();
if (vote && vote2) {
if (vote && vote2) {
if (!delimiter) problems.conjunction = '・依頼者票の中間表現';
if (!delimiter) problems.conjunction = '・依頼者票の中間表現';
212行目: 233行目:
}
}
} else if (!vote && vote2) {
} else if (!vote && vote2) {
$vote.children(`option[value=${vote2}]`).prop('selected', true); // Move vote2 to vote
$vote.children('option[value=' + vote2 + ']').prop('selected', true); // Move vote2 to vote
$delimiter.children('option').eq(0).prop('selected', true); // Blank delimiter
$delimiter.children('option').eq(0).prop('selected', true); // Blank delimiter
$vote2.children('option').eq(0).prop('selected', true); // Blank vote2
$vote2.children('option').eq(0).prop('selected', true); // Blank vote2
219行目: 240行目:
vote2 = '';
vote2 = '';
}
}
const votetext = vote + delimiter + vote2;
var votetext = vote + delimiter + vote2;


// Get text for the request (at this point RFD templates are not included)
// Get text for the request (at this point RFD templates are not included)
228行目: 249行目:


// Check the necessary fields
// Check the necessary fields
var msg = '以下の項目に入力の不備があります:', proceed = true;
var msg = '以下の項目に入力の不備があります:',
proceed = true;
for (let key in problems) {
for (var key in problems) {
if (problems[key]) {
if (problems[key]) {
proceed = false;
proceed = false;
235行目: 257行目:
}
}
}
}
if (!proceed) {
if (!proceed) return alert(msg);
alert(msg);
return;
}


// Return values once (Go on to API query)
// Return values once (Go on to API query)
249行目: 268行目:
}
}


/**
// Add RFD templates to reqText
* Check and update the redirect list textarea, and return its content as an array without duplicates
async function editPred2(ep) {
* @returns {Array} [redirsrc1, redirsrc2...]
*/
function getRedirectSources() {
var $input = $('#rfdh-redirectlist-input');
var inputVal = dragoLib.trim($input.val());
if (!inputVal) return [];
inputVal = inputVal.split('\n');
inputVal = inputVal.filter(function(item, index) {
return item !== '' && inputVal.indexOf(item) === index;
});
$input.val(inputVal.join('\n'));
return inputVal;
}


/**
// Anonymous function to get redirect targets and update REDIRS
* Generate a summary for the RFD
const getRredirectTargets = async function(redirects) {
* @param {string} sectiontitle
* @param {number|string} redirectsCnt
* @returns
*/
function generateSummary(sectiontitle, redirectsCnt) {
var summary = dragoLib.trim($('#rfdh-summary-input').val());
summary = summary ? summary : '+' + redirectsCnt;
summary = '/*' + sectiontitle + '*/' + summary + scriptAd;
return summary;
}


/**
const queryRedirects = function(redirectsArr) {
* Get redirect targets and add an array of objects to ep
return new Promise(function(resolve) {
* @param {{}} ep
new mw.Api().get({
* @returns {jQuery.Promise<{reqRFD: Array<{template: string, source: string, goal: string|undefined}>}>} Added to ep as a property:
action: 'query',
* The passed ep object doesn't need to be substituted
titles: redirectsArr.join('|'), // Max 50
*/
redirects: 1,
function updateEp(ep) {
formatversion: 2
}).then(function(res) {
var def = new $.Deferred();

var resRdr, redirect;
var query = function(redirSrcArr) {
if (res && (resRdr = res.query)) {
var deferred = new $.Deferred();
if (resRdr = resRdr.redirects) {
new mw.Api().get({
for (let i = 0; i < resRdr.length; i++) {
redirect = resRdr[i];
action: 'query',
titles: redirSrcArr.join('|'), // Max 50
REDIRS[redirect.from] = redirect.to;
}
redirects: 1,
formatversion: 2
} else { // If the API doesn't return res.query.redirects, the queried titles are all non-redirects
}).then(function(res) {
for (let i = 0; i < redirectsArr.length; i++) {
redirect = redirectsArr[i];
var resRdr, resNom;
if (!res || !res.query || !(resRdr = res.query.redirects)) return deferred.resolve();
REDIRS[redirect] = undefined;
}
if (resNom = res.query.normalized) { // Namespace prefixes are translated in the response (e.g. User: => 利用者:)
resNom.forEach(function(objN) { // Loop through all the translations
resRdr.some(function(objR) {
if (objN.to === objR.from) {
objR.from = objN.from; // Translate back to the inputted page name
return true;
}
}
}
});
resolve();
}).catch(function(code, err) {
console.log(err.error.info);
resolve();
});
});
});
}
};
deferred.resolve(resRdr);
}).catch(function(code, err) {

deferred.resolve(console.log(err.error.info));
const redirectsArr = JSON.parse(JSON.stringify(redirects)); // Prevent pass-by-reference (the variable will be spliced)
const queries = [];
});
return deferred.promise();
while(redirectsArr.length !== 0) {
queries.push(queryRedirects(redirectsArr.slice(0, 50)));
redirectsArr.splice(0, 50);
}
return await Promise.all(queries);

};
};


var apilimit = dragoLib.apiHighLimit() ? 500 : 50;
// Return reqText and summary as an object
var deferreds = [];
const tlRfd = '* {{RFD|SOURCE|GOAL}}\n', tlRfdForRequest = [];
var redirects = ep.redirects, reqText = ep.reqText, needQuery = false;
var redirSrcArr = JSON.parse(JSON.stringify(ep.redirects));
for (let i = 0; i < redirects.length; i++) {
while (redirSrcArr.length) {
deferreds.push(query(redirSrcArr.splice(0, apilimit)));
if (!REDIRS[redirects[i]]) { // If the object doesn't know the target of any redirect, it can be retrieved from the API
needQuery = true;
break;
}
}
}
$.when.apply($, deferreds).then(function() {
if (needQuery) {
needQuery = false;
await getRredirectTargets(redirects);
}
for (let i = 0; i < redirects.length; i++) {
if (!REDIRS[redirects[i]]) needQuery = true;
tlRfdForRequest.push(tlRfd.replace('SOURCE', redirects[i]).replace('GOAL', REDIRS[redirects[i]]));
}
reqText = tlRfdForRequest.join('') + reqText;
return {
reqText: reqText,
redirects: redirects,
summary: ep.summary,
needQuery: needQuery
};


// Convert the response object to an array of objects
}
var args = arguments;
var res = Object.keys(args).filter(function(key) { return args[key]; }).map(function(key) { return args[key]; });
res = res.concat.apply([], res);


// Create an array of objects that stores RFD templates to be added to reqText
function generateSummary(section, redirectsCnt) {
var RFD = '* {{RFD|SOURCE|GOAL}}\n';
var summary = dragoLib.trim($('#rfdh-summary-input').val());
var reqRFD = ep.redirects.reduce(function(acc, src) {
summary = summary ? summary : '+' + redirectsCnt;
var goal = res.filter(function(obj) { return obj.from === src; });
summary = `/*${section}*/ ${summary}${scriptAd}`;
goal = goal[0] && goal[0].to ? goal[0].to : undefined;
return summary;
acc.push({
template: dragoLib.replaceAll(RFD, 'SOURCE', src, 'GOAL', goal ? goal : 'undefined'),
source: src,
goal: goal
});
return acc;
}, []);

// Update ep
ep.reqRFD = reqRFD;
def.resolve(ep);

});

return def.promise();
}
}


/** Preview the RFD to be sumitted */
function preview() {
function preview() {


// Check if the necessary fields are filled and get edit information
// Check if the necessary fields are filled and get edit information
var ep = editPrep1();
var ep = editPrep();
if (!ep) return;
if (!ep) return;


// Preview dialog contour
// Preview dialog contour
const previewDiv =
var previewDiv =
'<div id="rfdh-preview-dialog" title="RFD Helper Preview" style="max-height: 80vh;">' +
'<div id="rfdh-preview-dialog" title="RFD Helper Preview" style="max-height: 80vh;">' +
' <div id="rfdh-preview-header" style="padding: 0.5em;">' +
'<div id="rfdh-preview-header" style="padding: 0.5em;">' +
' <p id="rfdh-preview-loading">' +
'<p id="rfdh-preview-loading">' +
' プレビューを読み込み中' + dragoLib.toggleLoadingSpinner('add') +
'プレビューを読み込み中' + dragoLib.toggleLoadingSpinner('add') +
' </p>' +
'</p>' +
' <div id="rfdh-preview-warning" style="display: none;">' +
'<div id="rfdh-preview-warning" style="display: none;">' +
' <p>' +
'<p>' +
' 転送先不明の項目は以下のいずれかに起因します:' +
'転送先不明の項目は以下のいずれかに起因します:' +
' </p>' +
'</p>' +
' <ol>' +
'<ol>' +
' <li>リダイレクト元のページ名が間違っている</li>' +
'<li>リダイレクト元のページ名が間違っている</li>' +
' <li>リダイレクト元として入力されたページがリダイレクトではない</li>' +
'<li>リダイレクト元として入力されたページがリダイレクトではない</li>' +
' <li>リダイレクト先の取得時に通信に失敗した</li>' +
'<li>リダイレクト先の取得時に通信に失敗した</li>' +
' </ol>' +
'</ol>' +
' </div>' +
'</div>' +
' </div>' +
'</div>' +
' <div id="rfdh-preview-body" style="display: none; font-size: 1.1em; padding-top: 1em; border-top: 1px solid silver;">' +
'<div id="rfdh-preview-body" style="display: none; font-size: 1.1em; padding-top: 1em; border-top: 1px solid silver;">' +
' <div id="rfdh-preview-text" style="border: 1px solid silver; padding: 0.2em 0.5em; background: white;">' +
'<div id="rfdh-preview-text" style="border: 1px solid silver; padding: 0.2em 0.5em; background: white;">' +
// previewHtml
// previewHtml
' </div>' +
'</div>' +
' <div id="rfdh-preview-summary" style="margin-top: 0.8em; border: 1px solid silver; padding: 0.2em 0.5em; background: white;">' +
'<div id="rfdh-preview-summary" style="margin-top: 0.8em; border: 1px solid silver; padding: 0.2em 0.5em; background: white;">' +
// summaryHtml
// summaryHtml
' </div>' +
'</div>' +
' <div id="rfdh-preview-checkbox-div" style="margin: 0.5em 0; display: none;">' +
'<div id="rfdh-preview-checkbox-div" style="margin: 0.5em 0; display: none;">' +
' <input id="rfdh-preview-checkbox" type="checkbox">' +
'<input id="rfdh-preview-checkbox" type="checkbox">' +
' <label for="rfdh-preview-checkbox">プレビューを閉じる際に転送先不明の項目を自動的にリダイレクト元リストから除去</label>' +
'<label for="rfdh-preview-checkbox">プレビューを閉じる際に転送先不明の項目を自動的にリダイレクト元リストから除去</label>' +
' </div>' +
'</div>' +
' </div>' +
'</div>' +
'</div>';
'</div>';


374行目: 414行目:
width: $('#content').width() * 0.8,
width: $('#content').width() * 0.8,
modal: true,
modal: true,
open: async function(){
open: function() {


// Initialize the design of the dialog
// Initialize the design of the dialog
dragoLib.dialogCSS($('.rfdh-dialog-preview'), rfdhConfig.headerColor, rfdhConfig.backgroundColor, rfdhConfig.fontSize);
dragoLib.dialogCSS($('.rfdh-dialog-preview'), rfdhConfig.headerColor, rfdhConfig.backgroundColor, rfdhConfig.fontSize);


// Update REDIRS and get text and summary to preview
// Preview
ep = await editPred2(ep);
updateEp(ep).then(function() { // Add 'reqRFD' to ep


dragoLib.getParsedHtml(mergeReqText(ep), ep.summary).then(function(parsed) { // Convert reqText to HTML
// Convert text on the dialog to html

const parsed = await dragoLib.getParsedHtml(ep.reqText, ep.summary);
if (parsed) {
if (parsed) {
if (ep.reqRFD.some(function(obj) { return !obj.goal; })) {
$('#rfdh-preview-checkbox-div').css('display', 'block').children('input').prop('checked', true);
}
var previewHtml = parsed.htmltext;
var summaryHtml = parsed.htmlsummary.replace(/API/g, RFDR);
$('#rfdh-preview-text').append(previewHtml);
$('#rfdh-preview-text a').each(function() { // Loop through all <a> tags in preview
if ($(this).text() === 'undefined') $(this).addClass('rfdh-preview-notarget').text('転送先不明'); // Highlight if undefined
});
$('#rfdh-preview-summary').append(summaryHtml);
$('.autocomment a').css('color', 'gray'); // Change color of section spec in summary
$('#rfdh-preview-dialog a').attr('target', '_blank'); // Open all links on a new tab
$('#rfdh-preview-body').css('display', 'block');
$('#rfdh-preview-loading').remove();
$('#rfdh-preview-warning').css('display', 'block');
dragoLib.centerDialog('#rfdh-preview-dialog');
} else {
$('#rfdh-preview-loading').text('プレビューの読み込みに失敗しました').css('color', 'MediumVioletRed');
dragoLib.centerDialog('#rfdh-preview-dialog');
setTimeout(function() {
$('#rfdh-preview-dialog').dialog('close');
}, 5000);
}


if (ep.needQuery) $('#rfdh-preview-checkbox-div').css('display', 'block').children('input').prop('checked', true);
const previewHtml = parsed.htmltext;
const summaryHtml = parsed.htmlsummary.replace(/API/g, RFDR);
$('#rfdh-preview-text').append(previewHtml);
$('#rfdh-preview-text a').each(function() { // Loop through all <a> tags in preview
if ($(this).text() === 'undefined') $(this).addClass('rfdh-preview-notarget').text('転送先不明'); // Highlight if undefined
});
});
$('#rfdh-preview-summary').append(summaryHtml);
});
$('.autocomment a').css('color', 'gray'); // Change color of section spec in summary
$('#rfdh-preview-dialog a').attr('target', '_blank'); // Open all links on a new tab
$('#rfdh-preview-body').css('display', 'block');
$('#rfdh-preview-loading').remove();
$('#rfdh-preview-warning').css('display', 'block');
dragoLib.centerDialog('#rfdh-preview-dialog');

} else {
$('#rfdh-preview-loading').text('プレビューの読み込みに失敗しました').css('color', 'MediumVioletRed');
dragoLib.centerDialog('#rfdh-preview-dialog');
setTimeout(function(){
$('#rfdh-preview-dialog').dialog('close');
}, 5000);
}


},
},
buttons: [{
buttons: [{
text: '閉じる',
text: '閉じる',
click: function(){
click: function() {
if ($('#rfdh-preview-checkbox').is(':checked')) {
if ($('#rfdh-preview-checkbox').is(':checked')) {
const $input = $('#rfdh-redirectlist-input');
var $input = $('#rfdh-redirectlist-input');
var inputVal = dragoLib.trim($input.val());
var inputVal = dragoLib.trim($input.val());
inputVal = inputVal.split('\n');
inputVal = inputVal.split('\n');
inputVal = inputVal.filter(function(item) {
inputVal = inputVal.filter(function(item) {
return REDIRS[item]; // Remove the undefined
return ep.reqRFD.some(function(obj) { return item === obj.source && obj.goal; }); // Remove the undefined
});
});
$input.val(inputVal.join('\n'));
$input.val(inputVal.join('\n'));
424行目: 467行目:
$(this).dialog('close');
$(this).dialog('close');
$('#rfdh-dialog')
$('#rfdh-dialog')
.dialog({'buttons': btns})
.dialog({buttons: btns})
.find('form').css('display', 'block');
.find('form').css('display', 'block');
$('#rfdh-editing').remove();
}
}
}]
}]
432行目: 476行目:
}
}


/**
async function submitRequest() {
* Merge ep.reqRFD and ep.reqText into a single text

* @param {{}} ep The object must contain a 'reqRFD' and 'reqText' property
if (!dragoLib.inGroup('autoconfirmed')) { // Just in case
* @returns {string}
$('#rfdh-dialog, #ca-rfdh').remove();
*/
return;
function mergeReqText(ep) {
}
return ep.reqRFD.map(function(obj) { return obj.template; }).join('') + ep.reqText;
}


/** Submit RFD: 1st procedure (get redirect goals) */
const $dialog = $('#rfdh-dialog');
function submitRequest() {


// Check if the necessary fields are filled
// Check if the necessary fields are filled
var ep = editPrep1();
var ep = editPrep();
if (!ep) return;
if (!ep) return;


// Hide the form and buttons and show a message
// Hide the form and buttons and show a message
var $dialog = $('#rfdh-dialog');
$dialog
$dialog
.append(`<p id="rfdh-editting">準備中${dragoLib.toggleLoadingSpinner('add')}</p>`)
.append('<p id="rfdh-editing">準備中' + dragoLib.toggleLoadingSpinner('add') + '</p>')
.dialog({buttons: []})
.dialog({buttons: []})
.find('form').css('display', 'none');
.find('form').css('display', 'none');


updateEp(ep).then(function() {
ep = await editPred2(ep);

if (ep.needQuery) { // If any redirect target failed to be fetched
if (ep.reqRFD.some(function(obj) { return !obj.goal; })) { // If any redirect target failed to be fetched
$('#rfdh-editting').prop('innerHTML', '<span style="color: MediumVioletRed;">転送先不明のリダイレクトを検出しました</span>');
$('#rfdh-editing').prop('innerHTML', '<span style="color: MediumVioletRed;">転送先不明のリダイレクトを検出しました</span>');
$dialog.dialog({
buttons: [{
$dialog.dialog({
text: 'プレビュー',
buttons: [{
click: preview
text: 'プレビュー',
}, {
click: preview
text: '続行',
}, {
click: function() {
text: '続行',
$(this).dialog({'buttons': []});
click: function() {
submitRequest2(ep);
$(this).dialog({buttons: []});
}
submitRequest2(ep);
}, {
}
text: '戻る',
}, {
click: function() {
text: '戻る',
$(this).find('form').css('display', 'block');
click: function() {
$(this).dialog({'buttons': btns});
$(this).find('form').css('display', 'block');
$('#rfdh-editting').remove();
$(this).dialog({buttons: btns});
}
$('#rfdh-editing').remove();
}, {
}
text: '中止',
}, {
click: function() {
text: '中止',
$(this).remove();
click: function() {
}
$(this).remove();
}]
}
});
}]
} else {
});
submitRequest2(ep);
} else {
submitRequest2(ep);
}
}

});


}
}


/**
async function submitRequest2(ep) {
* Submit RFD: 2nd procedure (edit)
* @param {{}} ep
* @returns
*/
function submitRequest2(ep) {


$('#rfdh-editting').prop('innerHTML', '依頼を提出しています' + dragoLib.toggleLoadingSpinner('add'));
$('#rfdh-editing').prop('innerHTML', '依頼を提出しています' + dragoLib.toggleLoadingSpinner('add'));


// Get timestamps
// Get timestamps
var lr = await dragoLib.getLatestRevision(RFDR);
dragoLib.getLatestRevision(RFDR).then(function(lr) {
if (!lr) return editDone(ep, 'ts');
lr = lr[0];


if (!lr) return editDone(ep, 'lr');
// Get section title
lr = lr[0];
var sectiontitle = dragoLib.getSection5('依頼');
ep.section = sectiontitle;


// Get the content of the section to which the request will be submitted
// Re-get the title of the section to get just in case
var sections = dragoLib.parseContentBySection(lr.content);
var sectiontitle = dragoLib.getSection5('依頼');
ep.section = sectiontitle;
sections = sections.filter(function(obj) {
return obj.title === sectiontitle;
});
if (sections.length === 0) return editDone(ep, 'sect');
var sectNum = sections[0].index;


// Get the content of the section to which the request will be submitted
// Check for duplicate requests
var sections = dragoLib.parseContentBySection(lr.content);
const duplicateRequests = [];
sections = sections.filter(function(obj) {
const redirectsEscaped = dragoLib.escapeRegExp(ep.redirects.join('|'));
return obj.title === sectiontitle;
const redirectRegExp = new RegExp(`(?:${redirectsEscaped})`);
});
const templates = dragoLib.findTemplates(lr.content, 'rfd'); // Extract RFD templates
if (templates.length !== 0) {
if (sections.length === 0) return editDone(ep, 'sect');
var sectNum = sections[0].index;
for (const tl of templates) { // Loop through all the extracted RFD templates and check if the 1st parameter contains the redirect(s) to be RFD-ed

let mtch;
// Check for duplicate requests
if (mtch = tl.split('|')[1].match(redirectRegExp)) {
mtch = mtch[0].replace(/_/g, ' ');
var templates = dragoLib.findTemplates(lr.content, 'RFD'); // Extract RFD templates
var duplicates = [];
if ($.inArray(mtch, duplicateRequests) === -1) duplicateRequests.push(mtch);
}
if (templates.length !== 0) {
// duplicates = templates.filter(function(tpml) {
// var params = dragoLib.getTemplateParams(tpml);
// var param1 = params.filter(function(el) { return el.match(/^1\s*=/); });
// param1 = param1.length !== 0 ? param1[0].replace(/^1\s*=/, '').trim() : params[0];
// var isDuplicate = ep.redirects.some(function(title) { return dragoLib.isSameTitle(title, param1); });
// return isDuplicate;
// });
duplicates = templates.reduce(function(acc, tpml) { // Get the redirect sources of duplicate RFD templates
var params = dragoLib.getTemplateParams(tpml);
var param1 = params.filter(function(el) { return el.match(/^1\s*=/); });
var paramUnnamed = params.filter(function(el) { return !el.match(/^[^\s]+\s*=/); });
param1 = param1.length !== 0 ? param1[0].replace(/^1\s*=/, '').trim() : paramUnnamed[0] ? paramUnnamed[0] : null;
if (!param1) return acc;
var isDuplicate = ep.redirects.some(function(title) { return dragoLib.isSameTitle(title, param1); });
if (isDuplicate && acc.indexOf(param1) === -1) acc.push(param1);
return acc;
}, []);
}
}
ep.duplicates = duplicates;
}
var reqRFD = ep.reqRFD.filter(function(obj) {
return !duplicates.some(function(title) { return dragoLib.isSameTitle(title, obj.source); });
});
if (reqRFD.length === 0) return editDone(ep, 'dr'); // All the redirects have already been reported as RFD candidates


// Update ep
// Edit page
var params = {
const drEscaped = dragoLib.escapeRegExp(duplicateRequests.join('|'));
action: 'edit',
const rqTxtRegExp = new RegExp('\\* \\{{2}RFD\\|(?:' + drEscaped + ')\\|.+\\}{2}\\n', 'g');
title: RFDR,
if (ep.reqText.match(rqTxtRegExp)) {
section: sectNum,
ep.reqText = ep.reqText.replace(rqTxtRegExp, ''); // Remove all duplicates from the report text
appendtext: '\n\n' + mergeReqText(ep),
ep.updated = true; // Create a new property in ep which signals that reqText has been updated
basetimestamp: lr.basetimestamp,
}
starttimestamp: lr.curtimestamp,
var mtch;
summary: ep.summary
if (!(mtch = ep.reqText.match(/\* {{RFD\|/g))) return editDone(ep, 'dr'); // If reqText has no RFD template in it, all the redirects have already been reported
};
ep.summary = generateSummary(sectiontitle, mtch.length); // Re-generate edit summary
dragoLib.editPage(params, debuggingMode.causeIntentionalError).then(function(result) {
switch (result) {
case true: // Edit succeeded
return editDone(ep, true);
case false: // Edit failed with an unknown error
return editDone(ep, false);
default: // Edit failed with a known error ('result' stores error info)
return editDone(ep, result);
}
});


// Edit page
const result = await new mw.Api().post({
action: 'edit',
title: RFDR,
section: sectNum,
appendtext: '\n\n' + ep.reqText,
basetimestamp: lr.basetimestamp,
starttimestamp: lr.curtimestamp,
summary: ep.summary,
token: debuggingMode.causeIntentionalError ? '' : mw.user.tokens.get('csrfToken'),
format: 'json'
}).then(function(res) {
if (res && res.edit) {
if (res.edit.result === 'Success') return true;
}
return false;
}).catch(function(code, err) {
return err.error.info;
});
});

switch (result) {
case true: // Edit succeeded
editDone(ep, true);
return;
case false: // Edit failed with an unknown error
editDone(ep, false);
return;
default: // Edit failed with a known error ('result' stores error info)
editDone(ep, result);
}


}
}


/**
/**
* @param {*} ep
* @param {{}} ep
* @param {*} type 'dr' when request is cancelled because of duplicates, 'lr' when the latest revision failed to be fetched, 'sect' when section number
* @param {string|boolean} type 'dr' when request is cancelled because of duplicates, 'lr' when the latest revision failed to be fetched, 'sect' when section number
* failed to be fetcehd, true when edit succeeded, false when unexpected error occurred on edit, errcode when edit failed
* failed to be fetcehd, true when edit succeeded, false when unexpected error occurred on edit, errcode when edit failed
*/
*/
function editDone(ep, type) {
function editDone(ep, type) {


const drText = ep.updated ? ' (重複依頼分は除去されました)' : '';
var drText = ep.duplicates.length !== 0 ? ' (' + ep.duplicates.length + '件の重複依頼分は除去されました)' : '',
const $msg = $('#rfdh-editting');
$msg = $('#rfdh-editing'),
var editFailed = true, showCopyButton = true;
editFailed = true;


switch(type) {
switch (type) {
case 'dr':
case 'dr':
$msg.prop('innerHTML', '<span style="color: MediumVioletRed;">中止: 指定されたリダイレクトは既に全て依頼されています</span>');
$msg.prop('innerHTML', '<span style="color: MediumVioletRed;">中止: 指定されたリダイレクトは既に全て依頼されています</span>');
showCopyButton = false;
editFailed = false;
break;
break;
case 'lr':
case 'lr':
587行目: 638行目:
break;
break;
case true:
case true:
$msg.prop('innerHTML', `<span style="color: MediumSeaGreen;">成功: 依頼が完了しました${drText}</span>`);
$msg.prop('innerHTML', '<span style="color: MediumSeaGreen;">成功: 依頼が完了しました' + drText + '</span>');
editFailed = false;
editFailed = false;
showCopyButton = false;
break;
break;
case false:
case false:
595行目: 645行目:
break;
break;
default:
default:
$msg.prop('innerHTML', `<span style="color: MediumVioletRed;">失敗: ${type}</span>`);
$msg.prop('innerHTML', '<span style="color: MediumVioletRed;">失敗: ' + type + '</span>');
break;
}
}


const $dialog = $('#rfdh-dialog');
var $dialog = $('#rfdh-dialog');
var btnsAfterEdit = [];
$dialog.dialog({
var onThePage = mw.config.get('wgPageName') === RFDR;
buttons: [{
if (!onThePage) {
btnsAfterEdit.push({
text: '報告先',
text: '報告先',
click: function() {
click: function() {
window.open(mw.util.getUrl(RFDR + '#' + ep.section), '_blank');
window.open(mw.util.getUrl(RFDR + '#' + ep.section), '_blank');
}
}
}, {
});
}
text: '閉じる',
btnsAfterEdit.push({
click: function() {
$(this).dialog('close');
text: '閉じる',
click: function() {
if (mw.config.get('wgPageName') === RFDR) location.reload(true);
}
$dialog.dialog('close');
if (onThePage) location.reload(true);
}]
}
});
});
$dialog.dialog({ buttons: btnsAfterEdit });
if (editFailed && showCopyButton) {
if (editFailed) {
$dialog.append(
$dialog.append(
'<label for="rfdh-editfailed">手動編集用:</label>' +
'<label for="rfdh-editfailed">手動編集用</label>' +
'<input id="rfdh-editfailed" type="button" style="margin-left: 0.5em;" value="依頼文をコピー">'
'<textarea id="rfdh-editfailed" rows="5" disabled></textarea>'
);
);
$('#rfdh-editfailed').val(mergeReqText(ep));
}
}

$('#rfdh-editfailed').click(function() {
dragoLib.copyToClipboard(ep.reqText);
alert('コピーしました');
});


}
}


// ********************************************* EVENT HANDLERS *********************************************
/**
* Check and update the redirect list textarea, and return its content as an array without duplicates
* @returns {Array}
*/
function getRedirectSources() {
const $input = $('#rfdh-redirectlist-input');
var inputVal = dragoLib.trim($input.val());
if (!inputVal) return [];
inputVal = inputVal.split('\n');
inputVal = inputVal.filter(function(item, index) {
return item !== '' && inputVal.indexOf(item) === index;
});
$input.val(inputVal.join('\n'));
return inputVal;
}

// ************************* EVENT HANDLERS *************************


// Clean up the redirect list textarea when the button is hit
// Clean up the redirect list textarea when the button is hit
$(document).off('click', '#rfdh-redirectlist-cleanup').on('click', '#rfdh-redirectlist-cleanup', function() {
$(document).off('click', '#rfdh-redirectlist-cleanup').on('click', '#rfdh-redirectlist-cleanup', getRedirectSources);
getRedirectSources();
});


// Expand/shrink the summary textarea when the checkbox is (un)checked
// Expand/shrink the summary textarea when the checkbox is (un)checked
$(document).off('click', '#rfdh-summary-checkbox').on('click', '#rfdh-summary-checkbox', function() {
$(document).off('click', '#rfdh-summary-checkbox').on('click', '#rfdh-summary-checkbox', function() {
const $textarea = $('#rfdh-summary-input');
var $textarea = $('#rfdh-summary-input');
var redirectsCnt, summary;
if ($(this).is(':checked')) {
if ($(this).is(':checked')) {
let redirectsCnt, summary;
summary = (redirectsCnt = getRedirectSources().length) === 0 ? '' : '+' + redirectsCnt;
summary = (redirectsCnt = getRedirectSources().length) === 0 ? '' : '+' + redirectsCnt;
$textarea.css('display', 'inline-block').val(summary);
$textarea.css('display', 'inline-block').val(summary);
668行目: 699行目:
$(this).remove();
$(this).remove();
});
});

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


})();
})();

2022年11月5日 (土) 16:34時点における版

/****************************
 * Name: RFD Helper         *
 * Author: Dragoniez        *
 * Version: 1.4.0           *
 ****************************/
//<nowiki>

// **************************************** CONFIGS ****************************************

// var {dragoLib} = require('../dragoLib/dragoLib.js');

/* Config
rfdhConfig: {
    headerColor: '#FEC493',
    backgroundColor: '#FFF0E4',
    portletlinkPosition: 'skin-dependent',
    fontSize: 'skin-dependent'
}                                               */

if (typeof rfdhConfig === 'undefined') var rfdhConfig = {};
if (!rfdhConfig.headerColor) rfdhConfig.headerColor = '#FEC493';
if (!rfdhConfig.backgroundColor) rfdhConfig.backgroundColor = '#FFF0E4';
if (!rfdhConfig.portletlinkPosition) {
    if (mw.config.get('skin') === 'minerva') {
        rfdhConfig.portletlinkPosition = 'p-personal';
    } else {
        rfdhConfig.portletlinkPosition = 'p-cactions';
    }
}
if (!rfdhConfig.fontSize) {
    switch(mw.config.get('skin')) {
        case 'vector':
        case 'vector-2022':
        case 'minerva':
            rfdhConfig.fontSize = '80%';
            break;
        case 'monobook':
            rfdhConfig.fontSize = '110%';
            break;
        case 'timeless':
            rfdhConfig.fontSize = '90%';
            break;
        default:
            rfdhConfig.fontSize = '80%';
    }
}

// **************************************** SCRIPT BODY ****************************************

(function() { // Create a function scope

// Run the script only if the user is autoconfirmed and the page is not an edit page
if (mw.config.get('wgUserGroups').indexOf('autoconfirmed') === -1 || ['edit', 'submit'].indexOf(mw.config.get('wgAction')) !== -1) return;

// ********************************************* VARIABLES *********************************************

// DebugMode
var debuggingMode = {
    library: false,
    portletlinkText: false,
    targetPage: false,
    scriptAd: false,
    causeIntentionalError: false,
    fillFields: false // Fill the redirect source and reason fields
};
var library = debuggingMode.library ?
                'http://127.0.0.1:5500/dragoLib/dragoLib.js' :
                '//ja-two.iwiki.icu/w/index.php?title=User:Dragoniez/scripts/dragoLib.js&action=raw&ctype=text/javascript';
var portletlinkText = debuggingMode.portletlinkText ? 'リダイレクトの削除依頼β' : 'リダイレクトの削除依頼';
var RFDR = debuggingMode.targetPage ? '利用者:Dragoniez/test3' : 'Wikipedia:リダイレクトの削除依頼/受付';
var scriptAd = ' ([[User:Dragoniez/scripts/RFD Helper|' + (debuggingMode.scriptAd ? 'RFD Helper Dev]])' : 'RFD Helper]])');

// Buttons of the dialog
var btns = [{
    text: '依頼',
    click: submitRequest
}, {
    text: 'プレビュー',
    click: preview
}, {
    text: '閉じる',
    click: function() {
        $(this).dialog('close');
    }
}];

// ********************************************* DOM READY FUNCTION *********************************************

$.when(
    $.getScript(library),
    mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.ui']),
    $.ready
).then(function() {

     // Add <style>
    $('head').append(
        '<style>' +
            '.rfdh-needmargin {' +
                'margin: 0.5em 0;' +
            '}' +
            '.rfdh-dialog-main textarea {' +
                'box-sizing: border-box;' +
                'width : 100%;' +
            '}' +
            '.rfdh-dialog-main input[type="checkbox"],' +
            '.rfdh-dialog-preview input[type="checkbox"] {' +
                'margin-right: 0.5em;' +
            '}' +
            '.rfdh-preview-notarget {' +
                'background: ' + rfdhConfig.headerColor + ';' +
            '}' +
        '</style>'
    );

    // Create a portletlink for the main dialog
    $(mw.util.addPortletLink(rfdhConfig.portletlinkPosition, '#', portletlinkText, 'ca-rfdh', 'リダイレクトの削除依頼を提出', null, '#ca-move')).click(openDialog);

});

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

/** Create and open the main dialog when the portletlink is hit */
function openDialog(e) {
    e.preventDefault();

    var votes =
    '<option value="">なし</option>' +
    '<option value="{{AFD|削除}}">削除</option>' +
    '<option value="{{AFD|全削除}}">全削除</option>' +
    '<option value="{{AFD|即時削除}}">即時削除</option>' +
    '<option value="{{AFD|全即時削除}}">全即時削除</option>' +
    '<option value="{{AFD|緊急削除}}">緊急削除</option>' +
    '<option value="{{AFD|緊急即時削除}}">緊急即時削除</option>' +
    '<option value="{{AFD|一部}}">一部削除/存続</option>' +
    '<option value="{{AFD|存続}}">存続</option>' +
    '<option value="{{AFD|全存続}}">全存続</option>' +
    '<option value="{{AFD|履歴統合}}">履歴統合</option>';

    var dialogHtml =
    '<div id="rfdh-dialog" title="RFD Helper" style="max-height: 80vh; min-width: 515px;">' +
        '<div id="rfdh-dialog-header">' +
            '<h2>リダイレクトの削除依頼</h2>' +
        '</div>' +
        '<div id="rfdh-dialog-body">' +
            '<form>' +
                '<div id="rfdh-redirectlist-div">' +
                    '<label for="rfdh-redirectlist-input">リダイレクト元 (ページごとに改行)</label>' +
                    '<textarea id="rfdh-redirectlist-input" rows="8"></textarea>' +
                    '<input id="rfdh-redirectlist-cleanup" class="rfdh-needmargin" type="button" value="整形" style="margin-right: 0.5em;">' +
                    '<span>(余分な改行および重複項目を除去)</span>' +
                '</div>' +
                '<div id="rfdh-vote-div" class="rfdh-needmargin">' +
                    '<label for="rfdh-vote-select">依頼者票 <span style="font-size: smaller;">(複数選択する場合のみ中間表現必須)</span></label><br>' +
                    '<select id="rfdh-vote-select" style="margin-right: 0.3em;">'    +
                        votes +
                    '</select>' +
                    '<select id="rfdh-votedelimiter-select" style="margin-right: 0.3em;">' +
                        '<option></option>' +
                        '<option value="および">および</option>' +
                        '<option value="または">または</option>' +
                    '</select>' +
                    '<select id="rfdh-vote2-select">'   +
                        votes +
                    '</select>' +
                '</div>' +
                '<div id="rfdh-reason-div" class="rfdh-needmargin">' +
                    '<label for="rfdh-reason-input">依頼文 <span style="font-size: smaller;">(署名不要)</span></label>' +
                    '<textarea id="rfdh-reason-input" rows="3"></textarea>' +
                '</div>' +
                '<div id="rfdh-summary-div" class="rfdh-needmargin">' +
                    '<input id="rfdh-summary-checkbox" type="checkbox">' +
                    '<label for="rfdh-summary-checkbox">要約を指定</label>' +
                    '<textarea id="rfdh-summary-input" rows="3" style="display: none;"></textarea>' +
                '</div>' +
            '</form>' +
        '</div>' +
    '</div>';

    // Add the frame div to the page
    $('body').append(dialogHtml);

    // Show dialog
    $('#rfdh-dialog').dialog({
        dialogClass: 'rfdh-dialog-main',
        resizable: false,
        height: 'auto',
        width: 'auto',
        modal: true,
        open: function() {
            dragoLib.dialogCSS($('.rfdh-dialog-main'), rfdhConfig.headerColor, rfdhConfig.backgroundColor, rfdhConfig.fontSize);
            $('#rfdh-vote-select').children('option').eq(1).prop('selected', true);
            if (debuggingMode.fillFields) {
                $('#rfdh-redirectlist-input').val(['利用者:Dragoniez', 'User:Dragoniez/AN Reporter'].join('\n'));
                $('#rfdh-reason-input').val('Test.');
            }
        },
        buttons: btns
    });

}

/**
 * Check whether the necessary fileds are filled, and get neccesary information to submit an RFD
 * @returns {{redirects: Array, reqText: string, summary: string}|undefined}
 * [IMPORTANT] reqText doesn't contain RFD templates. The object needs to be updated by updateEp().
 */
function editPrep() {

    // Error storage
    var problems = {
        redirects: '',
        conjunction: '',
        text: ''
    };

    // Redirect sources
    var redirects = getRedirectSources();
    if (redirects.length === 0) problems.redirects = '・リダイレクト元';

    // Votes
    var $vote = $('#rfdh-vote-select'),
        $delimiter = $('#rfdh-votedelimiter-select'),
        $vote2 = $('#rfdh-vote2-select'),
        vote = $vote.children('option').filter(':selected').val(),
        delimiter = $delimiter.children('option').filter(':selected').val(),
        vote2 = $vote2.children('option').filter(':selected').val();
    if (vote && vote2) {
        if (!delimiter) problems.conjunction = '・依頼者票の中間表現';
    } else if ((vote && !vote2) || (!vote && !vote2)) {
        if (delimiter) {
            $delimiter.children('option').eq(0).prop('selected', true); // Blank delimiter
            delimiter = '';
        }
    } else if (!vote && vote2) {
        $vote.children('option[value=' + vote2 + ']').prop('selected', true); // Move vote2 to vote
        $delimiter.children('option').eq(0).prop('selected', true); // Blank delimiter
        $vote2.children('option').eq(0).prop('selected', true); // Blank vote2
        vote = vote2;
        delimiter = '';
        vote2 = '';
    }
    var votetext = vote + delimiter + vote2;

    // Get text for the request (at this point RFD templates are not included)
    var reqText = dragoLib.trim($('#rfdh-reason-input').val());
    if (!reqText) problems.text = '・依頼文';
    $('#rfdh-reason-input').val(reqText);
    reqText = '** ' + votetext + ' ' + reqText + (reqText.substring(reqText.length - 4) === '~~~~' ? '' : '--~~~~');

    // Check the necessary fields
    var msg = '以下の項目に入力の不備があります:',
        proceed = true;
    for (var key in problems) {
        if (problems[key]) {
            proceed = false;
            msg += '\n' + problems[key];
        }
    }
    if (!proceed) return alert(msg);

    // Return values once (Go on to API query)
    return {
        redirects: redirects,
        reqText: reqText,
        summary: generateSummary(dragoLib.getSection5('依頼'), redirects.length)
    };

}

/**
 * Check and update the redirect list textarea, and return its content as an array without duplicates
 * @returns {Array} [redirsrc1, redirsrc2...]
 */
function getRedirectSources() {
    var $input = $('#rfdh-redirectlist-input');
    var inputVal = dragoLib.trim($input.val());
    if (!inputVal) return [];
    inputVal = inputVal.split('\n');
    inputVal = inputVal.filter(function(item, index) {
        return item !== '' && inputVal.indexOf(item) === index;
    });
    $input.val(inputVal.join('\n'));
    return inputVal;
}

/**
 * Generate a summary for the RFD
 * @param {string} sectiontitle 
 * @param {number|string} redirectsCnt 
 * @returns 
 */
function generateSummary(sectiontitle, redirectsCnt) {
    var summary = dragoLib.trim($('#rfdh-summary-input').val());
    summary = summary ? summary : '+' + redirectsCnt;
    summary = '/*' + sectiontitle + '*/' + summary + scriptAd;
    return summary;
}

/**
 * Get redirect targets and add an array of objects to ep
 * @param {{}} ep 
 * @returns {jQuery.Promise<{reqRFD: Array<{template: string, source: string, goal: string|undefined}>}>} Added to ep as a property:
 * The passed ep object doesn't need to be substituted
 */
function updateEp(ep) {
    var def = new $.Deferred();

    var query = function(redirSrcArr) {
        var deferred = new $.Deferred();
        new mw.Api().get({
            action: 'query',
            titles: redirSrcArr.join('|'), // Max 50
            redirects: 1,
            formatversion: 2
        }).then(function(res) {
            var resRdr, resNom;
            if (!res || !res.query || !(resRdr = res.query.redirects)) return deferred.resolve();
            if (resNom = res.query.normalized) { // Namespace prefixes are translated in the response (e.g. User: => 利用者:)
                resNom.forEach(function(objN) { // Loop through all the translations
                    resRdr.some(function(objR) {
                        if (objN.to === objR.from) {
                            objR.from = objN.from; // Translate back to the inputted page name
                            return true;
                        }
                    });
                });
            }
            deferred.resolve(resRdr);
        }).catch(function(code, err) {
            deferred.resolve(console.log(err.error.info));
        });
        return deferred.promise();
    };

    var apilimit = dragoLib.apiHighLimit() ? 500 : 50;
    var deferreds = []; 
    var redirSrcArr = JSON.parse(JSON.stringify(ep.redirects));
    while (redirSrcArr.length) {
        deferreds.push(query(redirSrcArr.splice(0, apilimit)));
    }
    $.when.apply($, deferreds).then(function() {

        // Convert the response object to an array of objects
        var args = arguments;
        var res = Object.keys(args).filter(function(key) { return args[key]; }).map(function(key) { return args[key]; });
        res = res.concat.apply([], res);

        // Create an array of objects that stores RFD templates to be added to reqText
        var RFD = '* {{RFD|SOURCE|GOAL}}\n';
        var reqRFD = ep.redirects.reduce(function(acc, src) {
            var goal = res.filter(function(obj) { return obj.from === src; });
            goal = goal[0] && goal[0].to ? goal[0].to : undefined;
            acc.push({
                template: dragoLib.replaceAll(RFD, 'SOURCE', src, 'GOAL', goal ? goal : 'undefined'),
                source: src,
                goal: goal
            });
            return acc;
        }, []);

        // Update ep
        ep.reqRFD = reqRFD;
        def.resolve(ep);

    });

    return def.promise();
}

/** Preview the RFD to be sumitted */
function preview() {

    // Check if the necessary fields are filled and get edit information
    var ep = editPrep();
    if (!ep) return;

    // Preview dialog contour
    var previewDiv =
    '<div id="rfdh-preview-dialog" title="RFD Helper Preview" style="max-height: 80vh;">' +
        '<div id="rfdh-preview-header" style="padding: 0.5em;">' +
            '<p id="rfdh-preview-loading">' +
                'プレビューを読み込み中' + dragoLib.toggleLoadingSpinner('add') +
            '</p>' +
            '<div id="rfdh-preview-warning" style="display: none;">' +
                '<p>' +
                    '転送先不明の項目は以下のいずれかに起因します:' +
                '</p>' +
                '<ol>' +
                    '<li>リダイレクト元のページ名が間違っている</li>' +
                    '<li>リダイレクト元として入力されたページがリダイレクトではない</li>' +
                    '<li>リダイレクト先の取得時に通信に失敗した</li>' +
                '</ol>' +
            '</div>' +
        '</div>' +
        '<div id="rfdh-preview-body" style="display: none; font-size: 1.1em; padding-top: 1em; border-top: 1px solid silver;">' +
            '<div id="rfdh-preview-text" style="border: 1px solid silver; padding: 0.2em 0.5em; background: white;">' +
                // previewHtml
            '</div>' +
            '<div id="rfdh-preview-summary" style="margin-top: 0.8em; border: 1px solid silver; padding: 0.2em 0.5em; background: white;">' +
                // summaryHtml
            '</div>' +
            '<div id="rfdh-preview-checkbox-div" style="margin: 0.5em 0; display: none;">' +
                '<input id="rfdh-preview-checkbox" type="checkbox">' +
                '<label for="rfdh-preview-checkbox">プレビューを閉じる際に転送先不明の項目を自動的にリダイレクト元リストから除去</label>' +
            '</div>' +
        '</div>' +
    '</div>';

    // Show preview dialog
    $('body').append(previewDiv);
    $('#rfdh-preview-dialog').dialog({
        dialogClass: 'rfdh-dialog-preview',
        height: 'auto',
        width: $('#content').width() * 0.8,
        modal: true,
        open: function() {

            // Initialize the design of the dialog
            dragoLib.dialogCSS($('.rfdh-dialog-preview'), rfdhConfig.headerColor, rfdhConfig.backgroundColor, rfdhConfig.fontSize);

            // Preview 
            updateEp(ep).then(function() { // Add 'reqRFD' to ep

                dragoLib.getParsedHtml(mergeReqText(ep), ep.summary).then(function(parsed) { // Convert reqText to HTML

                    if (parsed) {
                        if (ep.reqRFD.some(function(obj) { return !obj.goal; })) {
                            $('#rfdh-preview-checkbox-div').css('display', 'block').children('input').prop('checked', true);
                        }
                        var previewHtml = parsed.htmltext;
                        var summaryHtml = parsed.htmlsummary.replace(/API/g, RFDR);
                        $('#rfdh-preview-text').append(previewHtml);
                        $('#rfdh-preview-text a').each(function() { // Loop through all <a> tags in preview
                            if ($(this).text() === 'undefined') $(this).addClass('rfdh-preview-notarget').text('転送先不明'); // Highlight if undefined
                        });
                        $('#rfdh-preview-summary').append(summaryHtml);
                        $('.autocomment a').css('color', 'gray'); // Change color of section spec in summary
                        $('#rfdh-preview-dialog a').attr('target', '_blank'); // Open all links on a new tab
                        $('#rfdh-preview-body').css('display', 'block');
                        $('#rfdh-preview-loading').remove();
                        $('#rfdh-preview-warning').css('display', 'block');
                        dragoLib.centerDialog('#rfdh-preview-dialog');
                    } else {
                        $('#rfdh-preview-loading').text('プレビューの読み込みに失敗しました').css('color', 'MediumVioletRed');
                        dragoLib.centerDialog('#rfdh-preview-dialog');
                        setTimeout(function() {
                            $('#rfdh-preview-dialog').dialog('close');
                        }, 5000);
                    }

                });
            });

        },
        buttons: [{
            text: '閉じる',
            click: function() {
                if ($('#rfdh-preview-checkbox').is(':checked')) {
                    var $input = $('#rfdh-redirectlist-input');
                    var inputVal = dragoLib.trim($input.val());
                    inputVal = inputVal.split('\n');
                    inputVal = inputVal.filter(function(item) {
                        return ep.reqRFD.some(function(obj) { return item === obj.source && obj.goal; }); // Remove the undefined
                    });
                    $input.val(inputVal.join('\n'));
                }
                $(this).dialog('close');
                $('#rfdh-dialog')
                    .dialog({buttons: btns})
                    .find('form').css('display', 'block');
                $('#rfdh-editing').remove();
            }
        }]
    });

}

/**
 * Merge ep.reqRFD and ep.reqText into a single text
 * @param {{}} ep The object must contain a 'reqRFD' and 'reqText' property
 * @returns {string}
 */
function mergeReqText(ep) {
    return ep.reqRFD.map(function(obj) { return obj.template; }).join('') + ep.reqText;
}

/** Submit RFD: 1st procedure (get redirect goals) */
function submitRequest() {

    // Check if the necessary fields are filled
    var ep = editPrep();
    if (!ep) return;

    // Hide the form and buttons and show a message
    var $dialog = $('#rfdh-dialog');
    $dialog
        .append('<p id="rfdh-editing">準備中' + dragoLib.toggleLoadingSpinner('add') + '</p>')
        .dialog({buttons: []})
        .find('form').css('display', 'none');

    updateEp(ep).then(function() {

        if (ep.reqRFD.some(function(obj) { return !obj.goal; })) { // If any redirect target failed to be fetched
            $('#rfdh-editing').prop('innerHTML', '<span style="color: MediumVioletRed;">転送先不明のリダイレクトを検出しました</span>');
            $dialog.dialog({
                buttons: [{
                    text: 'プレビュー',
                    click: preview
                }, {
                    text: '続行',
                    click: function() {
                        $(this).dialog({buttons: []});
                        submitRequest2(ep);
                    }
                }, {
                    text: '戻る',
                    click: function() {
                        $(this).find('form').css('display', 'block');
                        $(this).dialog({buttons: btns});
                        $('#rfdh-editing').remove();
                    }
                }, {
                    text: '中止',
                    click: function() {
                        $(this).remove();
                    }
                }]
            });
        } else {
            submitRequest2(ep);
        }

    });

}

/**
 * Submit RFD: 2nd procedure (edit)
 * @param {{}} ep 
 * @returns
 */
function submitRequest2(ep) {

    $('#rfdh-editing').prop('innerHTML', '依頼を提出しています' + dragoLib.toggleLoadingSpinner('add'));

    // Get timestamps
    dragoLib.getLatestRevision(RFDR).then(function(lr) {

        if (!lr) return editDone(ep, 'lr');
        lr = lr[0];

        // Re-get the title of the section to get just in case
        var sectiontitle = dragoLib.getSection5('依頼');
        ep.section = sectiontitle;

        // Get the content of the section to which the request will be submitted
        var sections = dragoLib.parseContentBySection(lr.content);
        sections = sections.filter(function(obj) {
            return obj.title === sectiontitle;
        });
        if (sections.length === 0) return editDone(ep, 'sect');
        var sectNum = sections[0].index;

        // Check for duplicate requests
        var templates = dragoLib.findTemplates(lr.content, 'RFD'); // Extract RFD templates
        var duplicates = [];
        if (templates.length !== 0) {
            // duplicates = templates.filter(function(tpml) {
            //     var params = dragoLib.getTemplateParams(tpml);
            //     var param1 = params.filter(function(el) { return el.match(/^1\s*=/); });
            //     param1 = param1.length !== 0 ? param1[0].replace(/^1\s*=/, '').trim() : params[0];
            //     var isDuplicate = ep.redirects.some(function(title) { return dragoLib.isSameTitle(title, param1); });
            //     return isDuplicate;
            // });
            duplicates = templates.reduce(function(acc, tpml) { // Get the redirect sources of duplicate RFD templates
                var params = dragoLib.getTemplateParams(tpml);
                var param1 = params.filter(function(el) { return el.match(/^1\s*=/); });
                var paramUnnamed = params.filter(function(el) { return !el.match(/^[^\s]+\s*=/); });
                param1 = param1.length !== 0 ? param1[0].replace(/^1\s*=/, '').trim() : paramUnnamed[0] ? paramUnnamed[0] : null;
                if (!param1) return acc;
                var isDuplicate = ep.redirects.some(function(title) { return dragoLib.isSameTitle(title, param1); });
                if (isDuplicate && acc.indexOf(param1) === -1) acc.push(param1);
                return acc;
            }, []);
        }
        ep.duplicates = duplicates;
        var reqRFD = ep.reqRFD.filter(function(obj) {
            return !duplicates.some(function(title) { return dragoLib.isSameTitle(title, obj.source); });
        });
        if (reqRFD.length === 0) return editDone(ep, 'dr'); // All the redirects have already been reported as RFD candidates

        // Edit page
        var params = {
            action: 'edit',
            title: RFDR,
            section: sectNum,
            appendtext: '\n\n' + mergeReqText(ep),
            basetimestamp: lr.basetimestamp,
            starttimestamp: lr.curtimestamp,
            summary: ep.summary
        };
        dragoLib.editPage(params, debuggingMode.causeIntentionalError).then(function(result) {
            switch (result) {
                case true: // Edit succeeded
                    return editDone(ep, true);
                case false: // Edit failed with an unknown error
                    return editDone(ep, false);
                default: // Edit failed with a known error ('result' stores error info)
                    return editDone(ep, result);
            }
        });

    });

}

/**
 * @param {{}} ep
 * @param {string|boolean} type 'dr' when request is cancelled because of duplicates, 'lr' when the latest revision failed to be fetched, 'sect' when section number
 * failed to be fetcehd, true when edit succeeded, false when unexpected error occurred on edit, errcode when edit failed 
 */
function editDone(ep, type) {

    var drText = ep.duplicates.length !== 0 ? ' (' + ep.duplicates.length + '件の重複依頼分は除去されました)' : '',
        $msg = $('#rfdh-editing'),
        editFailed = true;

    switch (type) {
        case 'dr':
            $msg.prop('innerHTML', '<span style="color: MediumVioletRed;">中止: 指定されたリダイレクトは既に全て依頼されています</span>');
            editFailed = false;
            break;
        case 'lr':
            $msg.prop('innerHTML', '<span style="color: MediumVioletRed;">失敗: 報告先の最新版が取得できませんでした</span>');
            break;
        case 'sect':
            $msg.prop('innerHTML', '<span style="color: MediumVioletRed;">失敗: 報告先のセクション情報が取得できませんでした</span>');
            break;
        case true:
            $msg.prop('innerHTML', '<span style="color: MediumSeaGreen;">成功: 依頼が完了しました' + drText + '</span>');
            editFailed = false;
            break;
        case false:
            $msg.prop('innerHTML', '<span style="color: MediumSeaGreen;">失敗: ページの編集段階で不明なエラーが発生しました</span>');
            break;
        default:
            $msg.prop('innerHTML', '<span style="color: MediumVioletRed;">失敗: ' + type + '</span>');
    }

    var $dialog = $('#rfdh-dialog');
    var btnsAfterEdit = [];
    var onThePage = mw.config.get('wgPageName') === RFDR;
    if (!onThePage) {
        btnsAfterEdit.push({
            text: '報告先',
            click: function() {
                window.open(mw.util.getUrl(RFDR + '#' + ep.section), '_blank');
            }
        });
    }
    btnsAfterEdit.push({
        text: '閉じる',
        click: function() {
            $dialog.dialog('close');
            if (onThePage) location.reload(true);
        }
    });
    $dialog.dialog({ buttons: btnsAfterEdit });
    if (editFailed) {
        $dialog.append(
            '<label for="rfdh-editfailed">手動編集用</label>' +
            '<textarea id="rfdh-editfailed" rows="5" disabled></textarea>'
        );
        $('#rfdh-editfailed').val(mergeReqText(ep));
    }

}

// ********************************************* EVENT HANDLERS *********************************************

// Clean up the redirect list textarea when the button is hit
$(document).off('click', '#rfdh-redirectlist-cleanup').on('click', '#rfdh-redirectlist-cleanup', getRedirectSources);

// Expand/shrink the summary textarea when the checkbox is (un)checked
$(document).off('click', '#rfdh-summary-checkbox').on('click', '#rfdh-summary-checkbox', function() {
    var $textarea = $('#rfdh-summary-input');
    var redirectsCnt, summary;
    if ($(this).is(':checked')) {
        summary = (redirectsCnt = getRedirectSources().length) === 0 ? '' : '+' + redirectsCnt;
        $textarea.css('display', 'inline-block').val(summary);
    } else {
        $textarea.css('display', 'none').val('');
    }
    dragoLib.centerDialog('#rfdh-dialog');
});

// Remove the dialog html when closed
$(document).off('dialogclose', '#rfdh-dialog, #rfdh-preview-dialog').on('dialogclose', '#rfdh-dialog, #rfdh-preview-dialog', function() {
    $(this).remove();
});

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

})();
//</nowiki>