コンテンツにスキップ

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

削除された内容 追加された内容
v1.2: 同じページ上で2度目の報告を行おうとすると無効トークンが返るバグを修正
m v1.3.2: cl
(同じ利用者による、間の2版が非表示)
2行目: 2行目:
* Name: RFD Helper *
* Name: RFD Helper *
* Author: Dragoniez *
* Author: Dragoniez *
* Version: 1.2 *
* Version: 1.3.2 *
****************************/
****************************/
//<nowiki>
//<nowiki>


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

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


/* Config
/* Config
52行目: 54行目:
// DebugMode
// DebugMode
const debuggingMode = {
const debuggingMode = {
'library': false,
library: false,
'portletlinkText': false,
portletlinkText: false,
'targetPage': false,
targetPage: false,
'scriptAd': false,
scriptAd: false,
'causeIntentionalError': false
causeIntentionalError: false
};
};
const library = debuggingMode.library ?
const library = debuggingMode.library ?
68行目: 70行目:
const REDIRS = {}; // {source: goal, source2: goal2}...
const REDIRS = {}; // {source: goal, source2: goal2}...
const btns = [{ // Buttons of the dialog
const btns = [{ // Buttons of the dialog
'text': '依頼',
text: '依頼',
'click': submitRequest
click: submitRequest
}, {
}, {
'text': 'プレビュー',
text: 'プレビュー',
'click': preview
click: preview
}, {
}, {
'text': '閉じる',
text: '閉じる',
'click': function() {
click: function() {
$(this).dialog('close');
$(this).dialog('close');
}
}
169行目: 171行目:
// Show dialog
// Show dialog
$('#rfdh-dialog').dialog({
$('#rfdh-dialog').dialog({
'resizable': false,
dialogClass: 'rfdh-dialog-main',
'height': 'auto',
resizable: false,
'width': 'auto',
height: 'auto',
'modal': true,
width: 'auto',
'open': function() {
modal: true,
dragoLib.dialogCSS(rfdhConfig.headerColor, rfdhConfig.backgroundColor, rfdhConfig.fontSize);
open: function() {
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);
},
},
'buttons': btns
buttons: btns
});
});


187行目: 190行目:
// Error storage
// Error storage
const problems = {
const problems = {
'redirects': '',
redirects: '',
'conjunction': '',
conjunction: '',
'text': ''
text: ''
};
};


202行目: 205行目:
var vote2 = $vote2.children('option').filter(':selected').val();
var vote2 = $vote2.children('option').filter(':selected').val();
if (vote && vote2) {
if (vote && vote2) {
if (!delimiter) problems['conjunction'] = '・依頼者票の中間表現';
if (!delimiter) problems.conjunction = '・依頼者票の中間表現';
} else if ((vote && !vote2) || (!vote && !vote2)) {
} else if ((vote && !vote2) || (!vote && !vote2)) {
if (delimiter) {
if (delimiter) {
219行目: 222行目:


// 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)
var reqText = dragoLib.trim2($('#rfdh-reason-input').val());
var reqText = dragoLib.trim($('#rfdh-reason-input').val());
if (!reqText) problems.text = '・依頼文';
if (!reqText) problems.text = '・依頼文';
$('#rfdh-reason-input').val(reqText);
$('#rfdh-reason-input').val(reqText);
239行目: 242行目:
// Return values once (Go on to API query)
// Return values once (Go on to API query)
return {
return {
'redirects': redirects,
redirects: redirects,
'reqText': reqText,
reqText: reqText,
'summary': generateSummary(dragoLib.getSection5('依頼'), redirects.length)
summary: generateSummary(dragoLib.getSection5('依頼'), redirects.length)
};
};


255行目: 258行目:
return new Promise(function(resolve) {
return new Promise(function(resolve) {
new mw.Api().get({
new mw.Api().get({
'action': 'query',
action: 'query',
'titles': redirectsArr.join('|'), // Max 50
titles: redirectsArr.join('|'), // Max 50
'redirects': 1,
redirects: 1,
'formatversion': 2
formatversion: 2
}).then(function(res) {
}).then(function(res) {
var resRdr, redirect;
var resRdr, redirect;
280行目: 283行目:
});
});
});
});
}
};


const redirectsArr = JSON.parse(JSON.stringify(redirects)); // Prevent pass-by-reference (the variable will be spliced)
const redirectsArr = JSON.parse(JSON.stringify(redirects)); // Prevent pass-by-reference (the variable will be spliced)
290行目: 293行目:
return await Promise.all(queries);
return await Promise.all(queries);


}
};


// Return reqText and summary as an object
// Return reqText and summary as an object
311行目: 314行目:
reqText = tlRfdForRequest.join('') + reqText;
reqText = tlRfdForRequest.join('') + reqText;
return {
return {
'reqText': reqText,
reqText: reqText,
'redirects': redirects,
redirects: redirects,
'summary': ep.summary,
summary: ep.summary,
'needQuery': needQuery
needQuery: needQuery
};
};


320行目: 323行目:


function generateSummary(section, redirectsCnt) {
function generateSummary(section, redirectsCnt) {
var summary = dragoLib.trim2($('#rfdh-summary-input').val());
var summary = dragoLib.trim($('#rfdh-summary-input').val());
summary = summary ? summary : '+' + redirectsCnt;
summary = summary ? summary : '+' + redirectsCnt;
summary = `/*${section}*/ ${summary}${scriptAd}`;
summary = `/*${section}*/ ${summary}${scriptAd}`;
367行目: 370行目:
$('body').append(previewDiv);
$('body').append(previewDiv);
$('#rfdh-preview-dialog').dialog({
$('#rfdh-preview-dialog').dialog({
'height': 'auto',
dialogClass: 'rfdh-dialog-preview',
'width': $('#content').width() * 0.8,
height: 'auto',
'modal': true,
width: $('#content').width() * 0.8,
'open': async function(){
modal: true,
open: async function(){


// Initialize the design of the dialog
// Initialize the design of the dialog
dragoLib.dialogCSS(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
// Update REDIRS and get text and summary to preview
406行目: 410行目:


},
},
'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');
const $input = $('#rfdh-redirectlist-input');
var inputVal = dragoLib.trim2($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) {
444行目: 448行目:
$dialog
$dialog
.append(`<p id="rfdh-editting">準備中${dragoLib.toggleLoadingSpinner('add')}</p>`)
.append(`<p id="rfdh-editting">準備中${dragoLib.toggleLoadingSpinner('add')}</p>`)
.dialog({'buttons': []})
.dialog({buttons: []})
.find('form').css('display', 'none');
.find('form').css('display', 'none');


451行目: 455行目:
$('#rfdh-editting').prop('innerHTML', '<span style="color: MediumVioletRed;">転送先不明のリダイレクトを検出しました</span>');
$('#rfdh-editting').prop('innerHTML', '<span style="color: MediumVioletRed;">転送先不明のリダイレクトを検出しました</span>');
$dialog.dialog({
$dialog.dialog({
'buttons': [{
buttons: [{
'text': 'プレビュー',
text: 'プレビュー',
'click': preview
click: preview
}, {
}, {
'text': '続行',
text: '続行',
'click': function() {
click: function() {
$(this).dialog({'buttons': []});
$(this).dialog({'buttons': []});
submitRequest2(ep);
submitRequest2(ep);
}
}
}, {
}, {
'text': '戻る',
text: '戻る',
'click': function() {
click: function() {
$(this).find('form').css('display', 'block');
$(this).find('form').css('display', 'block');
$(this).dialog({'buttons': btns});
$(this).dialog({'buttons': btns});
468行目: 472行目:
}
}
}, {
}, {
'text': '中止',
text: '中止',
'click': function() {
click: function() {
$(this).remove();
$(this).remove();
}
}
485行目: 489行目:


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


// Get section
// Get section title
const section = dragoLib.getSection5('依頼');
var sectiontitle = dragoLib.getSection5('依頼');
ep.section = section;
ep.section = sectiontitle;


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


// Check for duplicate requests
// Check for duplicate requests
505行目: 509行目:
const redirectsEscaped = dragoLib.escapeRegExp(ep.redirects.join('|'));
const redirectsEscaped = dragoLib.escapeRegExp(ep.redirects.join('|'));
const redirectRegExp = new RegExp(`(?:${redirectsEscaped})`);
const redirectRegExp = new RegExp(`(?:${redirectsEscaped})`);
const templates = dragoLib.findTemplates(wikitext, 'rfd'); // Extract RFD templates
const templates = dragoLib.findTemplates(lr.content, 'rfd'); // Extract RFD templates
if (templates.length !== 0) {
if (templates.length !== 0) {
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
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
525行目: 529行目:
var mtch;
var mtch;
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
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(section, mtch.length); // Re-generate edit summary
ep.summary = generateSummary(sectiontitle, mtch.length); // Re-generate edit summary


// Edit page
// Edit page
const token = debuggingMode.causeIntentionalError ? '' : undefined;
const result = await new mw.Api().post({
action: 'edit',
const result = await dragoLib.editPage(RFDR, '\n\n' + ep.reqText, 'appendtext', ts.baseTS, ts.curTS, sectNum, ep.summary, token);
title: RFDR,
switch(result) {
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
case true: // Edit succeeded
editDone(ep, true);
editDone(ep, true);
545行目: 566行目:
/**
/**
* @param {*} ep
* @param {*} ep
* @param {*} type 'dr' when request is cancelled because of duplicates, 'ts' when timestamps failed to be fetched, 'sect' when section number
* @param {*} 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
*/
*/
559行目: 580行目:
showCopyButton = false;
showCopyButton = false;
break;
break;
case 'ts':
case 'lr':
$msg.prop('innerHTML', '<span style="color: MediumVioletRed;">失敗: 報告先の最新版が取得できませんでした</span>');
$msg.prop('innerHTML', '<span style="color: MediumVioletRed;">失敗: 報告先の最新版が取得できませんでした</span>');
break;
break;
580行目: 601行目:
const $dialog = $('#rfdh-dialog');
const $dialog = $('#rfdh-dialog');
$dialog.dialog({
$dialog.dialog({
'buttons': [{
buttons: [{
'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': '閉じる',
text: '閉じる',
'click': function() {
click: function() {
$(this).dialog('close');
$(this).dialog('close');
if (mw.config.get('wgPageName') === RFDR) location.reload(true);
if (mw.config.get('wgPageName') === RFDR) location.reload(true);
613行目: 634行目:
function getRedirectSources() {
function getRedirectSources() {
const $input = $('#rfdh-redirectlist-input');
const $input = $('#rfdh-redirectlist-input');
var inputVal = dragoLib.trim2($input.val());
var inputVal = dragoLib.trim($input.val());
if (!inputVal) return [];
if (!inputVal) return [];
inputVal = inputVal.split('\n');
inputVal = inputVal.split('\n');

2022年10月31日 (月) 09:26時点における版

/****************************
 * Name: RFD Helper         *
 * Author: Dragoniez        *
 * Version: 1.3.2           *
 ****************************/
//<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

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

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

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

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

$.when(
    $.getScript(library),
    mw.loader.using('jquery.ui'),
    $.ready
).then(function() {
    $('head').append( // <style> for the dialog
        '<style>' +
        '   .rfdh-needmargin {' +
        '       margin: 0.5em 0;' +
        '   }' +
        '   .rfdh-textarea {' +
        '       width: 100%;' +
        '       box-sizing: border-box;' +
        '   }' +
        '   .rfdh-preview-notarget {' +
        `       background: ${rfdhConfig.headerColor};` +
        '   }' +
        '</style>'
    );
    if (dragoLib.inGroup('autoconfirmed') && mw.config.get('wgAction') !== 'edit') {
        $(mw.util.addPortletLink(rfdhConfig.portletlinkPosition, '#', portletlinkText, 'ca-rfdh', 'リダイレクトの削除依頼を提出', null, '#ca-move')).click(openDialog);
    }
});

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

function openDialog(e) {
    e.preventDefault();

    const 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>';

    const 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" class="rfdh-textarea" 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">' +
                        votes +
    '               </select>' +
    '               <select id="rfdh-votedelimiter-select">' +
    '                   <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" class="rfdh-textarea" 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" class="rfdh-textarea" 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);
        },
        buttons: btns
    });

}

// Check whether the necessary fileds are filleld
function editPrep1() {

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

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

    // Votes
    const $vote = $('#rfdh-vote-select'), $delimiter = $('#rfdh-votedelimiter-select'), $vote2 = $('#rfdh-vote2-select');
    var vote = $vote.children('option').filter(':selected').val();
    var delimiter = $delimiter.children('option').filter(':selected').val();
    var 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 = '';
    }
    const 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 (let key in problems) {
        if (problems[key]) {
            proceed = false;
            msg += '\n' + problems[key];
        }
    }
    if (!proceed) {
        alert(msg);
        return;
    }

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

}

// Add RFD templates to reqText
async function editPred2(ep) {

    // Anonymous function to get redirect targets and update REDIRS
    const getRredirectTargets = async function(redirects) {   

        const queryRedirects = function(redirectsArr) {
            return new Promise(function(resolve) {
                new mw.Api().get({
                    action: 'query',
                    titles: redirectsArr.join('|'), // Max 50
                    redirects: 1,
                    formatversion: 2
                }).then(function(res) {
                    var resRdr, redirect;
                    if (res && (resRdr = res.query)) {
                        if (resRdr = resRdr.redirects) {
                            for (let i = 0; i < resRdr.length; i++) {
                                redirect = resRdr[i];
                                REDIRS[redirect.from] = redirect.to;
                            }
                        } else { // If the API doesn't return res.query.redirects, the queried titles are all non-redirects
                            for (let i = 0; i < redirectsArr.length; i++) {
                                redirect = redirectsArr[i];
                                REDIRS[redirect] = undefined;
                            }
                        }
                    }
                    resolve();
                }).catch(function(code, err) {
                    console.log(err.error.info);
                    resolve();
                });
            });
        };

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

    };

    // Return reqText and summary as an object
    const tlRfd = '* {{RFD|SOURCE|GOAL}}\n', tlRfdForRequest = [];
    var redirects = ep.redirects, reqText = ep.reqText, needQuery = false;
    for (let i = 0; i < redirects.length; i++) {
        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;
        }
    }
    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
    };

}

function generateSummary(section, redirectsCnt) {
    var summary = dragoLib.trim($('#rfdh-summary-input').val());
    summary = summary ? summary : '+' + redirectsCnt;
    summary = `/*${section}*/ ${summary}${scriptAd}`;
    return summary;
}

function preview() {

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

    // Preview dialog contour
    const 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: async function(){

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

            // Update REDIRS and get text and summary to preview
            ep = await editPred2(ep);

            // Convert text on the dialog to html
            const parsed = await dragoLib.getParsedHtml(ep.reqText, ep.summary);
            if (parsed) {

                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: [{
            text: '閉じる',
            click: function(){
                if ($('#rfdh-preview-checkbox').is(':checked')) {
                    const $input = $('#rfdh-redirectlist-input');
                    var inputVal = dragoLib.trim($input.val());
                    inputVal = inputVal.split('\n');
                    inputVal = inputVal.filter(function(item) {
                        return REDIRS[item]; // Remove the undefined
                    });
                    $input.val(inputVal.join('\n'));
                }
                $(this).dialog('close');
                $('#rfdh-dialog')
                    .dialog({'buttons': btns})
                    .find('form').css('display', 'block');
            }
        }]
    });

}

async function submitRequest() {

    if (!dragoLib.inGroup('autoconfirmed')) { // Just in case
        $('#rfdh-dialog, #ca-rfdh').remove();
        return;
    }

    const $dialog = $('#rfdh-dialog');

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

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

    ep = await editPred2(ep);
    if (ep.needQuery) { // If any redirect target failed to be fetched
        $('#rfdh-editting').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-editting').remove();
                }
            }, {
                text: '中止',
                click: function() {
                    $(this).remove();
                }
            }]
        });
    } else {
        submitRequest2(ep);
    }

}

async function submitRequest2(ep) {

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

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

    // Get section title
    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
    const duplicateRequests = [];
    const redirectsEscaped = dragoLib.escapeRegExp(ep.redirects.join('|'));
    const redirectRegExp = new RegExp(`(?:${redirectsEscaped})`);
    const templates = dragoLib.findTemplates(lr.content, 'rfd'); // Extract RFD templates
    if (templates.length !== 0) {    
        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;
            if (mtch = tl.split('|')[1].match(redirectRegExp)) {
                mtch = mtch[0].replace(/_/g, ' ');
                if ($.inArray(mtch, duplicateRequests) === -1) duplicateRequests.push(mtch);
            }
        }
    }

    // Update ep
    const drEscaped = dragoLib.escapeRegExp(duplicateRequests.join('|'));
    const rqTxtRegExp = new RegExp('\\* \\{{2}RFD\\|(?:' + drEscaped + ')\\|.+\\}{2}\\n', 'g');
    if (ep.reqText.match(rqTxtRegExp)) {
        ep.reqText = ep.reqText.replace(rqTxtRegExp, ''); // Remove all duplicates from the report text
        ep.updated = true; // Create a new property in ep which signals that reqText has been updated
    }
    var mtch;
    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

    // 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 {*} 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) {

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

    switch(type) {
        case 'dr':
            $msg.prop('innerHTML', '<span style="color: MediumVioletRed;">中止: 指定されたリダイレクトは既に全て依頼されています</span>');
            showCopyButton = 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;
            showCopyButton = false;
            break;
        case false:
            $msg.prop('innerHTML', '<span style="color: MediumSeaGreen;">失敗: ページの編集段階で不明なエラーが発生しました</span>');
            break;
        default:
            $msg.prop('innerHTML', `<span style="color: MediumVioletRed;">失敗: ${type}</span>`);
            break;
    }

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

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

}

/**
 * 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
$(document).off('click', '#rfdh-redirectlist-cleanup').on('click', '#rfdh-redirectlist-cleanup', function() {
    getRedirectSources();
});

// Expand/shrink the summary textarea when the checkbox is (un)checked
$(document).off('click', '#rfdh-summary-checkbox').on('click', '#rfdh-summary-checkbox', function() {
    const $textarea = $('#rfdh-summary-input');
    if ($(this).is(':checked')) {
        let redirectsCnt, summary;
        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>