コンテンツにスキップ

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

削除された内容 追加された内容
v4.8: LTA名称の取得中にスピナーが表示される機能を追加ほか(スピナーが消えてから報告先の選択を行えば、WP:AN/Sへの報告時にLTA名称が必ず正常に表示されます)
v5.0: グローバルブロックとグローバルロックの検知機能を追加
2行目: 2行目:
* AN Reporter (ANR)
* AN Reporter (ANR)
* Author: Dragoniez
* Author: Dragoniez
* Version: 4.8
* Version: 5.0
*************************************/
*************************************/
//<nowiki>
//<nowiki>
51行目: 51行目:
replaced = replaced.split(arguments[i]).join(arguments[i + 1]);
replaced = replaced.split(arguments[i]).join(arguments[i + 1]);
}
}

}
}
return replaced;
return replaced;
132行目: 132行目:
let Logids = {}; // Object to store usernames and their corresponding logids
let Logids = {}; // Object to store usernames and their corresponding logids
let checkBlockStatusBeforeEdit = true;
let checkBlockStatusBeforeEdit = true;
let checkDuplicateReportsBeforeEdit = true;
let checkDuplicateReportsBeforeEdit = true;


// Add ANR tab
// Add ANR tab
162行目: 162行目:
'border-radius: 10%;' ;
'border-radius: 10%;' ;
/* Experimental design
/* Experimental design
const lsCSS = // Eliminate gap between inline-blocks
const lsCSS = // Eliminate gap between inline-blocks
'letter-spacing: -1em;' +
'letter-spacing: -1em;' +
'white-space: nowrap' ;
'white-space: nowrap' ;
168行目: 168行目:
'letter-spacing: normal' ;
'letter-spacing: normal' ;
*/
*/

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

// Username input
// Username input
let userHtml =
let userHtml =
//<div class="anr-user-div">
//<div id="anr-user-div">
` <div id="anr-user1-input-div">` +
` <div id="anr-user1-input-div">` +
` <label for="anr-user1-input" style="${labelCSS}">利用者</label>` +
` <label for="anr-user1-input" style="${labelCSS}">利用者</label>` +
211行目: 212行目:
// The whole html contour
// The whole html contour
const modalHtml =
const modalHtml =
`<div class="anr-modal-dialog" title="AN Reporter" style="max-height: 80vh;">` +
`<div id="anr-modal-dialog" title="AN Reporter" style="max-height: 80vh;">` +
` <div class="anr-modal-header">` +
` <div id="anr-modal-header">` +
` <h2>利用者を報告</h2>` +
` <h2>利用者を報告</h2>` +
` </div>` +
` </div>` +
` <div class="anr-modal-body">` +
` <div id="anr-modal-body">` +
` <form>` +
` <form>` +
` <div class="anr-target-div" style="${marginCSS}">` +
` <div id="anr-target-div" style="${marginCSS}">` +
` <label for="anr-target-options" id="anr-target-options-label" style="${labelCSS}">報告先</label>` +
` <label for="anr-target-options" id="anr-target-options-label" style="${labelCSS}">報告先</label>` +
` <select id="anr-target-options" style="${siCSS}">` +
` <select id="anr-target-options" style="${siCSS}">` +
225行目: 226行目:
` <option>${AN3RR}</option>` +
` <option>${AN3RR}</option>` +
` </select>` +
` </select>` +
` <div class="anr-target-pagelink-div" style="display: none;">` +
` <div id="anr-target-pagelink-div" style="display: none;">` +
` <label class="anr-emptylabel" for="anr-target-pagelink" style="${labelCSS}"></label>` +
` <label class="anr-emptylabel" for="anr-target-pagelink" style="${labelCSS}"></label>` +
` <a id="anr-target-pagelink" href="" target="_blank">報告先を確認</a>` +
` <a id="anr-target-pagelink" href="" target="_blank">報告先を確認</a>` +
` </div>` +
` </div>` +
` </div>` +
` </div>` +
` <div class="anr-section-i-div" style="${marginCSS} display: none;">` +
` <div id="anr-section-i-div" style="${marginCSS} display: none;">` +
` <label for="anr-section-i-select" style="${labelCSS}">節</label>` +
` <label for="anr-section-i-select" style="${labelCSS}">節</label>` +
` <select id="anr-section-i-select" style="${siCSS}">` +
` <select id="anr-section-i-select" style="${siCSS}">` +
241行目: 242行目:
` </select>` +
` </select>` +
` </div>` +
` </div>` +
` <div class="anr-section-s-div" style="${marginCSS} display: none;">` +
` <div id="anr-section-s-div" style="${marginCSS} display: none;">` +
` <label for="anr-section-s-select" style="${labelCSS}">節</label>` +
` <label for="anr-section-s-select" style="${labelCSS}">節</label>` +
` <select id="anr-section-s-select" style="${siCSS}">` +
` <select id="anr-section-s-select" style="${siCSS}">` +
256行目: 257行目:
` </select>` +
` </select>` +
` </div>` +
` </div>` +
` <div class="anr-user-div" style="${marginCSS}">` +
` <div id="anr-user-div" style="${marginCSS}">` +
userHtml +
userHtml +
` <div class="anr-btn-div">` +
` <div id="anr-btn-div">` +
` <button type="button" class="anr-addBtn" style="${btnCSS}">追加</button>` +
` <button type="button" id="anr-addBtn" style="${btnCSS}">追加</button>` +
` </div>` +
` </div>` +
` </div>` +
` </div>` +
` <div class="anr-predefinedreasons-div" style="${marginCSS} display: none;">` +
` <div id="anr-predefinedreasons-div" style="${marginCSS} display: none;">` +
` <label for="anr-predefinedreasons-select" style="${labelCSS}">定型文</label>` +
` <label for="anr-predefinedreasons-select" style="${labelCSS}">定型文</label>` +
` <select id="anr-predefinedreasons-select">` +
` <select id="anr-predefinedreasons-select">` +
270行目: 271行目:
` </select>` +
` </select>` +
` </div>` +
` </div>` +
` <div class="anr-reason-div" style="${marginCSS}">` +
` <div id="anr-reason-div" style="${marginCSS}">` +
` <label for="anr-reason-text" style="${labelCSS}">理由</label>` +
` <label for="anr-reason-text" style="${labelCSS}">理由</label>` +
` <textarea id="anr-reason-text" rows="6" style="width: 100%"></textarea>` +
` <textarea id="anr-reason-text" rows="6" style="width: 100%"></textarea>` +
` </div>` +
` </div>` +
` <div class="anr-summary-div" style="${marginCSS}">` +
` <div id="anr-summary-div" style="${marginCSS}">` +
` <input id="anr-summary-checkbox" type="checkbox">` +
` <input id="anr-summary-checkbox" type="checkbox">` +
` <label for="anr-summary-checkbox">要約を指定</label>` +
` <label for="anr-summary-checkbox">要約を指定</label>` +
` <textarea id="anr-summary-text" rows="3" style="width: 100%; display: none;"></textarea>` +
` <textarea id="anr-summary-text" rows="3" style="width: 100%; display: none;"></textarea>` +
` </div>` +
` </div>` +
` <div class="anr-checkbox-div" style="${marginCSS}">` +
` <div id="anr-checkbox-div" style="${marginCSS}">` +
` <input checked id="anr-blockstatus-checkbox" type="checkbox">` +
` <input checked id="anr-blockstatus-checkbox" type="checkbox">` +
` <label for="anr-blockstatus-checkbox">報告前にブロック状態をチェック</label>` +
` <label for="anr-blockstatus-checkbox">報告前にブロック状態をチェック</label>` +
292行目: 293行目:
// Add the frame div to the page
// Add the frame div to the page
$('body').append(modalHtml);
$('body').append(modalHtml);

// Show dialog
// Show dialog
$('.anr-modal-dialog').dialog({
$('#anr-modal-dialog').dialog({
'resizable': false,
'resizable': false,
'height': 'auto',
'height': 'auto',
320行目: 321行目:
// Initialize the design of the dialog
// Initialize the design of the dialog
dialogCSS();
dialogCSS();

// Show VIP list
// Show VIP list
VIPList();
getVipList();


// Show the select box for predefined reasons if they're predefined by the user
// Show the select box for predefined reasons if they're predefined by the user
343行目: 344行目:
}
}
}
}

// Exit function if the current user is on his/her own page or username has remained undefined or null
// 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')) {
if (!username || username === mw.config.get('wgUserName')) return;
return;
}


/* Initialize the username input and type dropdown
/* Initialize the username input and type dropdown
441行目: 440行目:
}
}
// Function to change the CSS of the dialog and to get predifined reasons
// Function to change the CSS of the dialog
function dialogCSS() {
function dialogCSS() {

// CSS for the dialog
$('.ui-dialog-content, .ui-dialog-buttonpane, .ui-corner-all, .ui-draggable, .ui-resizable').css('background', '#FFF0E4');
$('.ui-dialog-content, .ui-dialog-buttonpane, .ui-corner-all, .ui-draggable, .ui-resizable').css('background', '#FFF0E4');
$('.ui-button').css({
$('.ui-button').css({
452行目: 449行目:
$('.ui-dialog-titlebar, .ui-dialog-titlebar-close').attr('style', 'background: #FEC493 !important;');
$('.ui-dialog-titlebar, .ui-dialog-titlebar-close').attr('style', 'background: #FEC493 !important;');
$('.ui-dialog').css('font-size', fSize);
$('.ui-dialog').css('font-size', fSize);

}
}


471行目: 467行目:
}
}
$reasons.children('optgroup').append(reasonsArr.join(''));
$reasons.children('optgroup').append(reasonsArr.join(''));
$('.anr-predefinedreasons-div').css('display', 'block');
$('#anr-predefinedreasons-div').css('display', 'block');


} else { // If the fixed reasons are NOT prepared as an array
} else { // If the fixed reasons are NOT prepared as an array
481行目: 477行目:


// WP:VIP list (for copy to clipboard)
// WP:VIP list (for copy to clipboard)
async function VIPList() {
function getVipList() {
return new Promise(function(resolve) {
new mw.Api().get({
new mw.Api().get({
'action': 'parse',
'action': 'parse',
'page': 'Wikipedia:進行中の荒らし行為',
'page': VIP,
'prop': 'sections',
'prop': 'sections',
'formatversion': 2
'formatversion': 2
}).then(function(res){
}).then(function(res){


if (res && res.parse) {
if (res && res.parse) {


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


if (vipList.length === 0) {
if (vipList.length === 0) {
resolve(mw.log.error('VIP list: There\'s no VIP to fetch.'));
return mw.log.error('VIP list: There\'s no VIP to fetch.');
} else {
} else {


// Show the VIP list on the dialog
// Show the VIP list on the dialog
const VIPListHtml =
const vipListHtml =
'<div class="anr-viplist-div" style="width: 100%;">' +
'<div class="anr-viplist-div" style="width: 100%;">' +
` <label for="anr-viplist-select" style="${labelCSS}">VIP</label>` +
` <label for="anr-viplist-select" style="${labelCSS}">VIP</label>` +
` <select id="anr-viplist-select">` +
` <select id="anr-viplist-select">` +
' <optgroup style="display: none;">' + // Adjust font size
' <optgroup style="display: none;">' + // Adjust font size
' <option selected disabled hidden>コピーする場合は選択してください</option>' +
' <option selected disabled hidden>コピーする場合は選択してください</option>' +
vipList.join('') +
vipList.join('') +
' </optgroup>' +
' </optgroup>' +
' </select>' +
' </select>' +
'</div>';
'</div>';
$('.anr-predefinedreasons-div').before(VIPListHtml);
$('#anr-predefinedreasons-div').before(vipListHtml);
$('#anr-viplist-select').css('width', $('#anr-target-options').width()).select2();
$('#anr-viplist-select').css('width', $('#anr-target-options').width()).select2();
resolve();


}

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


}).catch(function(){
} else {
resolve(mw.log.error('VIP list: Failed to retrieve data from the API.'));
return mw.log.error('VIP list: The API returned an unresolvable object.');
});
}

}).catch(function(){
return mw.log.error('VIP list: Failed to retrieve data from the API.');
});
});
}
}
572行目: 566行目:


// Check if at least one username is given and get users to report (and their UserAN types)
// Check if at least one username is given and get users to report (and their UserAN types)
let users = [];
let users = [], types = [], duplicates = [];
let types = [];
let duplicates = [];
let usernameInInput = '';
let selectedType = '';
let tempUsername = ''; // An escape hatch for username (for t=logid)
let tempUsername = ''; // An escape hatch for username (for t=logid)


587行目: 577行目:
} else { // if selector is found
} else { // if selector is found


usernameInInput = $(`#anr-user${i}-input`).val().trim2();
const usernameInInput = $(`#anr-user${i}-input`).val().trim2();
selectedType = $(`#anr-user${i}-select`).children('option').filter(':selected').text();
const selectedType = $(`#anr-user${i}-select`).children('option').filter(':selected').text();


if (usernameInInput !== '') { // if input is not empty
if (usernameInInput !== '') { // if input is not empty
643行目: 633行目:
// Update the target section for cases in which the date has changed since the date-dependent section was chosen
// 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}日新規報告$/) !== null) {
if (sectionToEdit.match(/^\d{4}年\d{1,2}月\d{1,2}日 - \d{1,2}日新規報告$/) !== null) {
sectionToEdit = getSectionI(false);
const sectionIDate = getSectionI(false);
$('#anr-section-i-options-date').text(getSectionI(false));
sectionToEdit = sectionIDate;
$('#anr-section-i-options-date').text(sectionIDate);
}
}


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


704行目: 695行目:
const editSummarySection = '/*' + sectionToEdit + '*/';
const editSummarySection = '/*' + sectionToEdit + '*/';


let editSummary =
const editSummary =
$('#anr-summary-text').val().trim2() === '' ?
$('#anr-summary-text').val().trim2() === '' ?
editSummarySection + genEditSummary().replace(' - ', '') + scriptAd:
editSummarySection + genEditSummary().replace(' - ', '') + scriptAd:
739行目: 730行目:


// Check if the necessary fields are filled and get edit information
// Check if the necessary fields are filled and get edit information
let ep = editPrep();
const ep = editPrep();
if (ep.rqFieldsEmpty) {
if (ep.rqFieldsEmpty) {
alert('必須項目が入力・選択されていません'); // Show error and cancel the preview
alert('必須項目が入力・選択されていません'); // Show error and cancel the preview
747行目: 738行目:
// If the inputs have duplicates in them
// If the inputs have duplicates in them
if (ep.duplicates.length !== 0) {
if (ep.duplicates.length !== 0) {
const confirmMsg =

'以下の利用者について、重複入力がある可能性があります。\n\n' + ep.duplicates.join(', ') + '\n\n' +
let confirmMsg =
'以下の利用者について、重複入力がある可能性があります。\n\n' +
ep.duplicates.join(', ') + '\n\n' +
'プレビューを続行する場合は OK を、フォームに戻る場合は Cancel を押してください';
'プレビューを続行する場合は OK を、フォームに戻る場合は Cancel を押してください';


if (confirm(confirmMsg) === false) { // If cancelled
if (confirm(confirmMsg) === false) return;
return;
}

}
}
762行目: 748行目:
const ANSMisc = `<a href="${mw.util.getUrl('WP:AN/S#OTH')}" target="_blank">WP:AN/S#その他</a>`;
const ANSMisc = `<a href="${mw.util.getUrl('WP:AN/S#OTH')}" target="_blank">WP:AN/S#その他</a>`;
const previewDiv =
const previewDiv =
'<div class="anr-preview-dialog" title="AN Reporter Preview">' +
'<div id="anr-preview-dialog" title="AN Reporter Preview">' +
' <div id="anr-preview-header" style="padding: 0.5em;">' +
' <div id="anr-preview-header" style="padding: 0.5em;">' +
' <p id="anr-preview-loading">' +
' <p id="anr-preview-loading">' +
785行目: 771行目:
// Show preview dialog
// Show preview dialog
$('body').append(previewDiv);
$('body').append(previewDiv);
$('.anr-preview-dialog').dialog({
$('#anr-preview-dialog').dialog({
'height': 'auto',
'height': 'auto',
'width': $('#content').width() * 0.8,
'width': $('#content').width() * 0.8,
799行目: 785行目:
if (parsed) {
if (parsed) {


let previewHtml = parsed.htmltext;
const previewHtml = parsed.htmltext;
let summaryHtml = parsed.htmlsummary.replaceAll2('API', ep.pageToEdit);
const summaryHtml = parsed.htmlsummary.replaceAll2('API', ep.pageToEdit);
$('#anr-preview-text').append(previewHtml);
$('#anr-preview-text').append(previewHtml);
$('#anr-preview-summary').append(summaryHtml);
$('#anr-preview-summary').append(summaryHtml);
$('.autocomment a').css('color', 'gray'); // Change color of section spec in summary
$('.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-dialog a').attr('target', '_blank'); // Open all links on a new tab
$('#anr-preview-body').css('display', 'block');
$('#anr-preview-body').css('display', 'block');
$('#anr-preview-loading').remove();
$('#anr-preview-loading').remove();
812行目: 798行目:
$('#anr-preview-loading').text('プレビューの読み込みに失敗しました').css('color', 'MediumVioletRed');
$('#anr-preview-loading').text('プレビューの読み込みに失敗しました').css('color', 'MediumVioletRed');
setTimeout(function(){
setTimeout(function(){
$('.anr-preview-dialog').dialog('close');
$('#anr-preview-dialog').dialog('close');
}, 5000);
}, 5000);
}
}
828行目: 814行目:


/**
/**
* Convert wikitext to its HTML format
* Function to convert wikitext to its HTML format
* @param {String} wikitext The wikitext to convert
* @param {String} wikitext The wikitext to convert
* @param {String} wikisummary The summary to convert
* @param {String} wikisummary The summary to convert
866行目: 852行目:
// If the inputs have duplicates in them
// If the inputs have duplicates in them
if (ep.duplicates.length !== 0) {
if (ep.duplicates.length !== 0) {

const confirmMsg =
const confirmMsg =
'以下の利用者について、重複入力がある可能性があります。\n\n' +
'以下の利用者について、重複入力がある可能性があります。\n\n' + ep.duplicates.join(', ') + '\n\n' +
'プレビューを続行する場合は OK を、フォームに戻る場合は Cancel を押してください';
ep.duplicates.join(', ') + '\n\n' +
'報告を続行する場合は OK を、フォームに戻る場合は Cancel を押してください';
if (confirm(confirmMsg) === false) { // If cancelled
return;
}


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


// Variables for edit
// Variables for edit
const $dialog = $('.anr-modal-dialog');
const $dialog = $('#anr-modal-dialog');
const dWidth = $dialog.width();
const dWidth = $dialog.width();
let msgEditing = '<div class="anr-editing" />';
let msgEditing = '<div class="anr-editing" />';
let msgDone = ''; // Message to show when edit attempt is done
let msgDone = ''; // Message to show when edit attempt is done
const wikiPagename = ep.pageToEdit + '#' + ep.sectionToEdit; // The wiki pagename for link
const wikiPagename = ep.pageToEdit + '#' + ep.sectionToEdit; // The wiki pagename for link
let editFailed = false; // Boolean value to pass to function when edit attempt is done


// Change dialog content
// Change dialog content
$dialog.dialog('option', 'width', dWidth); // Set an absolute width
$dialog.dialog('option', 'width', dWidth); // Set an absolute width
$dialog.find('form').css('display', 'none'); // Hide dialog content
$dialog.find('form').css('display', 'none'); // Hide dialog content
$dialog.dialog({'buttons': [] }); // Hide the button
$dialog.dialog({'buttons': [] }); // Hide the buttons
$dialog.append(msgEditing);
$dialog.append(msgEditing);


951行目: 931行目:


if ($(`#anr-user${i}-input`).length === 0) { // if selector is not found
if ($(`#anr-user${i}-input`).length === 0) { // if selector is not found

break; // exit for
break; // exit for

} else { // if selector is found
} else { // if selector is found


1,010行目: 990行目:
});
});
});
});
}
}
const parsed = await getWikitextAndSectionInfo();
const parsed = await getWikitextAndSectionInfo();
let sections = parsed.sections; // Array of objects
let sections = parsed.sections; // Array of objects
1,022行目: 1,002行目:


// The transcluded '新規依頼' sections on WP:AN/S are duplicate names, hence shouldn't be included in the array
// The transcluded '新規依頼' sections on WP:AN/S are duplicate names, hence shouldn't be included in the array
if (sections[i].index.indexOf('T') === -1) { // If the section is not loaded from another page
if (sections[i].index.indexOf('T') === -1) { // If the section isn't a transcluded one


sectiontitles.push(sections[i].line); // Get section titles
sectiontitles.push(sections[i].line); // Get section titles

switch(sections[i].level) { // Get equal-enclosed section headers
switch(sections[i].level) { // Get equal-enclosed section headers
case 2:
case 2:
1,047行目: 1,027行目:
}
}
}
}

// The sections in which to search for duplicate reports
// The sections in which to search for duplicate reports
let tarSections;
let tarSections;
1,099行目: 1,079行目:
let regex = new RegExp(`={2,5}\\s*(?:${sectionsPiped})\\s*={2,5}`, 'g');
let regex = new RegExp(`={2,5}\\s*(?:${sectionsPiped})\\s*={2,5}`, 'g');
let sectionContent = wikitext.split(regex); // Array of the content of each section, without section headers
let sectionContent = wikitext.split(regex); // Array of the content of each section, without section headers

// Re-add to sectionContent the '== TITLE ==' header that's been removed by the split() above
// Re-add to sectionContent the '== TITLE ==' header that's been removed by the split() above
for (let i = 0; i < sectionContent.length; i++) {
for (let i = 0; i < sectionContent.length; i++) {
1,175行目: 1,155行目:
logid = undefined; // Reset before the next iteration
logid = undefined; // Reset before the next iteration
}
}

// Extract UserAN templates and find duplicate reports
// Extract UserAN templates and find duplicate reports
let templates = [];
let templates = [];
1,182行目: 1,162行目:
for (let i = sectionContent.length -1; i >= 0; i--) { // Loop through all the relevant section texts
for (let i = sectionContent.length -1; i >= 0; i--) { // Loop through all the relevant section texts

/* Extract UserAN templates from the section text
/* Extract UserAN templates from the section text
* This returns an array of the occurrences of UserAN, e.g. [{{UserAN|t=UNL|ウィキ助}}, ...] */
* This returns an array of the occurrences of UserAN, e.g. [{{UserAN|t=UNL|ウィキ助}}, ...] */
1,188行目: 1,168行目:
if (templates.length !== 0) { // If the section text contains at least one UserAN
if (templates.length !== 0) { // If the section text contains at least one UserAN

for (let j = templates.length -1; j >= 0; j--) { // Loop through all the occurences of UserAN
for (let j = templates.length -1; j >= 0; j--) { // Loop through all the occurences of UserAN
if (stringContainsElementInArray(templates[j], reportees)) { // If there's a duplciate report
if (stringContainsElementInArray(templates[j], reportees)) { // If there's a duplciate report
1,200行目: 1,180行目:
sectionContent.splice(i, 1);
sectionContent.splice(i, 1);
}
}

} else { // If the section text has no UserAN
} else { // If the section text has no UserAN

sectionContent.splice(i, 1); // Remove the section text from the array
sectionContent.splice(i, 1); // Remove the section text from the array

}
}
duplicateFound = false; // Reset
duplicateFound = false; // Reset
1,251行目: 1,231行目:


}
}

}
}


// Function to get the latest revision of the administrator's noticeboard
// Function to get the latest revision of the administrator's noticeboard
async function getLastestRevision() {
async function getLastestRevision() {

return new Promise(function(resolve) {
return new Promise(function(resolve) {


1,262行目: 1,241行目:
`<p>最新版を取得しています${toggleLoadingSpinner('add')}</p>`;
`<p>最新版を取得しています${toggleLoadingSpinner('add')}</p>`;
$('.anr-editing').append(msgEditing);
$('.anr-editing').append(msgEditing);

new mw.Api().get({
new mw.Api().get({
'action': 'query',
'action': 'query',
1,269行目: 1,248行目:
'curtimestamp': true,
'curtimestamp': true,
'formatversion': 2
'formatversion': 2
}).done(function(res){
}).then(function(res){


if (res && res.query && res.query.pages) { // If the latest revision is successfully retrieved
if (res && res.query && res.query.pages) { // If the latest revision is successfully retrieved
1,275行目: 1,254行目:


// Get the timestamps of the latest revision and the API query
// Get the timestamps of the latest revision and the API query
let baseTS = res.query.pages[0].revisions[0].timestamp; // The TS of the latest revision
const baseTS = res.query.pages[0].revisions[0].timestamp; // The TS of the latest revision
let curTS = res.curtimestamp; // The TS of the API query
const curTS = res.curtimestamp; // The TS of the API query


// Update message on the dialog
// Update message on the dialog
1,298行目: 1,277行目:
$('.anr-editing').append(msgDone);
$('.anr-editing').append(msgDone);
editFailed = true;
editDone($dialog, true, wikiPagename);
editDone($dialog, editFailed, wikiPagename);
resolve();
resolve();


1,310行目: 1,288行目:


});
});

});
});
}
}
1,316行目: 1,295行目:
async function getSectionNumber() {
async function getSectionNumber() {
return new Promise(function(resolve) {
return new Promise(function(resolve) {

new mw.Api().get({
new mw.Api().get({
'action': 'parse',
'action': 'parse',
'page': ep.pageToEdit,
'page': ep.pageToEdit,
'formatversion': 2
'formatversion': 2
}).done(function(res){
}).then(function(res){


if (res && res.parse && res.parse.sections) { // If the section list is successfully retrieved
if (res && res.parse && res.parse.sections) { // If the section list is successfully retrieved
1,332行目: 1,311行目:
// Return a section number if the section is found, undefined if not
// Return a section number if the section is found, undefined if not
let sectionNum = sectionsAPI[ep.sectionToEdit];
const sectionNum = sectionsAPI[ep.sectionToEdit];
if (sectionNum === undefined) { // If section title in the dropdown is not found
if (sectionNum === undefined) { // If section title in the dropdown is not found


1,364行目: 1,343行目:
async function getTextToReplace(sectionNum) {
async function getTextToReplace(sectionNum) {
return new Promise(function(resolve) {
return new Promise(function(resolve) {

// Get the text of the latest revision
// Get the text of the latest revision
new mw.Api().get({
new mw.Api().get({
1,372行目: 1,351行目:
'prop': 'wikitext',
'prop': 'wikitext',
'formatversion': 2
'formatversion': 2
}).done(function(res){
}).then(function(res){


if (res && res.parse) {
if (res && res.parse) {
1,383行目: 1,362行目:


// Get the whole text to append
// Get the whole text to append
let wikitextObtained = res.parse.wikitext;
const wikitextObtained = res.parse.wikitext;
let wholeTextToSubmit;
let wholeTextToSubmit;
const delimiter = '<!-- ◆';
const delimiter = '<!-- ◆';
1,416行目: 1,395行目:
$('.anr-editing').append(msgDone);
$('.anr-editing').append(msgDone);


editFailed = true;
editDone($dialog, true, wikiPagename);
editDone($dialog, editFailed, wikiPagename);
resolve();
resolve();


1,459行目: 1,437行目:
'type': 'POST',
'type': 'POST',
success: function(res) {
success: function(res) {

// If the edit was successful
// If the edit was successful
if (res && res.edit && res.edit.result == 'Success') {
if (res && res.edit && res.edit.result == 'Success') {

// Show message
// Show message
toggleLoadingSpinner('remove');
toggleLoadingSpinner('remove');
$('.anr-editing').append(`<p style="color: MediumSeaGreen">報告が完了しました</p>`);
$('.anr-editing').append(`<p style="color: MediumSeaGreen">報告が完了しました</p>`);
editDone($dialog, editFailed, wikiPagename);
editDone($dialog, false, wikiPagename);
return;


// If the edit failed
// If the edit failed
1,482行目: 1,459行目:
$('.anr-editing').append(msgDone);
$('.anr-editing').append(msgDone);


editFailed = true;
editDone($dialog, true, wikiPagename);
editDone($dialog, editFailed, wikiPagename);
return;


// If unknown error occurs
// If unknown error occurs
1,496行目: 1,471行目:
$('.anr-editing').append(msgDone);
$('.anr-editing').append(msgDone);


editFailed = true;
editDone($dialog, true, wikiPagename);
editDone($dialog, editFailed, wikiPagename);
return;


}
}
1,504行目: 1,477行目:
}
}
});
});

}
}


1,532行目: 1,505行目:
'text': '戻る',
'text': '戻る',
'click': function(){
'click': function(){
$('.anr-modal-dialog form').css('display', 'block');
$('#anr-modal-dialog form').css('display', 'block');
$('.anr-editing').remove();
$('.anr-editing').remove();
$dialog.dialog({
$dialog.dialog({
1,601行目: 1,574行目:
'text': '戻る',
'text': '戻る',
'click': function(){
'click': function(){
$('.anr-modal-dialog form').css('display', 'block');
$('#anr-modal-dialog form').css('display', 'block');
$('.anr-editing').remove();
$('.anr-editing').remove();
$dialog.dialog({
$dialog.dialog({
1,630行目: 1,603行目:


async function reportUsersFinalProcedure() {
async function reportUsersFinalProcedure() {

const rev = await getLastestRevision();
const rev = await getLastestRevision();
if (rev === undefined) {
if (rev === undefined) {
1,659行目: 1,632行目:
*/
*/
function previewDuplicateReports(wikitext, reportees) {
function previewDuplicateReports(wikitext, reportees) {

// Show logids in parentheses
// Show logids in parentheses
for (let i = 0; i < reportees.length; i++) {
for (let i = 0; i < reportees.length; i++) {
1,668行目: 1,641行目:


// Create dialog
// Create dialog
let duplicateReportPreviewDiv =
const duplicateReportPreviewDiv =
'<div class="anr-drpreview-dialog" title="AN Reporter Duplicate Report Preview" style="max-height: 80vh;">' +
'<div id="anr-drpreview-dialog" title="AN Reporter Duplicate Report Preview" style="max-height: 80vh;">' +
' <div id="anr-drpreview-header" style="padding: 0.5em;">' +
' <div id="anr-drpreview-header" style="padding: 0.5em;">' +
' <p id="anr-drpreview-loading">' +
' <p id="anr-drpreview-loading">' +
1,687行目: 1,660行目:


// Show preview dialog
// Show preview dialog
$('.anr-drpreview-dialog').dialog({
$('#anr-drpreview-dialog').dialog({
'height': 'auto',
'height': 'auto',
'width': $('#content').width() * 0.8,
'width': $('#content').width() * 0.8,
1,700行目: 1,673行目:
const wikitextInHtml = await convertWikitextToHtmlFormat(wikitext, '');
const wikitextInHtml = await convertWikitextToHtmlFormat(wikitext, '');
if (wikitextInHtml) {
if (wikitextInHtml) {

$('#anr-drpreview-body').append(wikitextInHtml.htmltext);
$('#anr-drpreview-body').append(wikitextInHtml.htmltext);
$('.anr-drpreview-dialog a').attr('target', '_blank'); // Open all links on a new tab
$('#anr-drpreview-dialog a').attr('target', '_blank'); // Open all links on a new tab
$('#anr-drpreview-body').css('display', 'block');
$('#anr-drpreview-body').css('display', 'block');
$('#anr-drpreview-loading').remove();
$('#anr-drpreview-loading').remove();
1,710行目: 1,683行目:
$('#anr-drpreview-loading').text('読み込みに失敗しました').css('color', 'MediumVioletRed');
$('#anr-drpreview-loading').text('読み込みに失敗しました').css('color', 'MediumVioletRed');
setTimeout(function(){
setTimeout(function(){
$('.anr-drpreview-dialog').dialog('close');
$('#anr-drpreview-dialog').dialog('close');
}, 10000);
}, 10000);
}
}

},
},
'buttons': [{
'buttons': [{
1,733行目: 1,706行目:
$('.anr-editing').append(msgDone);
$('.anr-editing').append(msgDone);
editFailed = true;
editDone($dialog, true, wikiPagename);
editDone($dialog, editFailed, wikiPagename);
}
}


1,763行目: 1,735行目:
$('.anr-editing').append(msgDone);
$('.anr-editing').append(msgDone);


editFailed = true;
editDone($dialog, true, wikiPagename);
editDone($dialog, editFailed, wikiPagename);
}
}


1,789行目: 1,760行目:
* textSplit = ['', 'UserAN|t=none|', 'Logid|123456789|', 'User|EXAMPLE}}|s=}}}} - ', 'UserAN|USER}} is the same.']
* textSplit = ['', 'UserAN|t=none|', 'Logid|123456789|', 'User|EXAMPLE}}|s=}}}} - ', 'UserAN|USER}} is the same.']
*/
*/

if (textSplit.length === 0) { // If the text has no tempalte in it
if (textSplit.length === 0) { // If the text has no tempalte in it

return templates; // Return an empty array
return templates; // Return an empty array

} else { // If the text has some templates in it
} else { // If the text has some templates in it


let nest = []; // Array to store element numbers of the split segments that involving nested templates
let nest = []; // Array to store element numbers of the split segments involving nested templates


for (let i = 1; i < textSplit.length; i++) { // Loop through all elements (but arr[0]) in the split array
for (let i = 1; i < textSplit.length; i++) { // Loop through all elements (but arr[0]) in the split array

let TLCloseCnt = (textSplit[i].match(/\}\}/g) || []).length; // Get the number of '}}' in the split segment
let TLCloseCnt = (textSplit[i].match(/\}\}/g) || []).length; // Get the number of '}}' in the split segment
let temp = ''; // Temporary escape hatch
let temp = ''; // Temporary escape hatch

switch(true) {
switch(true) {

case TLCloseCnt === 0: // If the split segment doesn't have any '}}' (= it nests another template)
case TLCloseCnt === 0: // If the split segment doesn't have any '}}' (= it nests another template)

nest.push(i); // Push the element number into the array
nest.push(i); // Push the element number into the array
break;
break;

case TLCloseCnt === 1: // If the split segment itself is the whole of a template
case TLCloseCnt === 1: // If the split segment itself is the whole of a template

temp = '{{' + textSplit[i].split('}}')[0] + '}}';
temp = '{{' + textSplit[i].split('}}')[0] + '}}';
if (!isInArray(temp, templates)) {
if (!isInArray(temp, templates)) {
1,817行目: 1,788行目:
}
}
break;
break;

/* Explanation
/* Explanation
* If textSplit[3] ( = 'User|EXAMPLE}}|s=}}}} - '), .split('}}') returns
* If textSplit[3] ( = 'User|EXAMPLE}}|s=}}}} - '), .split('}}') returns
* ['User|EXAMPLE', '|s=', '', ' - ']. Thus '{{' + splitArr[0] +'}}' returns '{{User|EXAMPLE}}'.
* ['User|EXAMPLE', '|s=', '', ' - ']. Thus '{{' + splitArr[0] +'}}' returns '{{User|EXAMPLE}}'.
*/
*/

case TLCloseCnt > 1: // If templates are nested
case TLCloseCnt > 1: // If templates are nested

for (let j = 0; j < TLCloseCnt; j++) { // Loop through all the nests
for (let j = 0; j < TLCloseCnt; j++) { // Loop through all the nests

if (j === 0) { // The innermost template
if (j === 0) { // The innermost template

temp = '{{' + textSplit[i].split('}}')[j] + '}}'; // Same as when TLCloseCnt === 1
temp = '{{' + textSplit[i].split('}}')[j] + '}}'; // Same as when TLCloseCnt === 1
if (!isInArray(temp, templates)) {
if (!isInArray(temp, templates)) {
templates.push(temp);
templates.push(temp);
}
}

} else { // Nesting templates
} else { // Nesting templates


1,839行目: 1,810行目:
nest.pop();
nest.pop();
let curSegSplit = textSplit[i].split('}}'); // Array of the current segment split by '}}'
let curSegSplit = textSplit[i].split('}}'); // Array of the current segment split by '}}'

temp = '{{' + textSplit.slice(elNum, i).join('{{') + '{{' + curSegSplit.slice(0, j +1).join('}}') + '}}';
temp = '{{' + textSplit.slice(elNum, i).join('{{') + '{{' + curSegSplit.slice(0, j +1).join('}}') + '}}';
if (!isInArray(temp, templates)) {
if (!isInArray(temp, templates)) {
templates.push(temp);
templates.push(temp);
}
}

}
}
}
}
break;
break;

default:
default:
// Do nothing
// Do nothing
}
}

}
}
//console.log('Templates in the section:');
//console.log('Templates in the section:');
//console.log(templates);
//console.log(templates);

// Check if the optional parameter is specified
// Check if the optional parameter is specified
if (templateName !== undefined && templates.length !== 0) {
if (templateName !== undefined && templates.length !== 0) {
1,870行目: 1,841行目:
//console.log('UserAN occurrences in the section:');
//console.log('UserAN occurrences in the section:');
//console.log(templates);
//console.log(templates);

return templates;
return templates;
}
}
1,903行目: 1,874行目:
}
}


// Action for when edit is done (in any way)
/**
* Action for when edit is done (in any way)
* @param {*} $dialog
* @param {Boolean} editFailed
* @param {String} wikiPagename
*/
function editDone($dialog, editFailed, wikiPagename) {
function editDone($dialog, editFailed, wikiPagename) {


// Get the page name without a section specifier
// Get the page name without a section specifier
let tarPage = wikiPagename.split('#')[0];
let tarPage = wikiPagename.split('#')[0];

// Buttons to show on the dialog when the edit attempt is done
// Buttons to show on the dialog when the edit attempt is done
let btns = [];
let btns = [];
1,953行目: 1,929行目:
'buttons': btns
'buttons': btns
});
});

}
}


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

let inputRemains = true;
let inputRemains = true;
let i = 1;
let i = 1;
let arrOfContribs = [];
let arrOfContribs = [];
let contribs = '';
let contribs, type, reportee;
let type = '';
let reportee = '';


// Check content of all inputs into which usernames are typed
// Check content of all inputs into which usernames are typed
1,977行目: 1,951行目:


if (reportee !== '') { // Skip if the input value is a null string
if (reportee !== '') { // Skip if the input value is a null string

// Get appropriate links depending on the UserAN type
// Get appropriate links depending on the UserAN type
switch(type) {
switch(type) {
2,024行目: 1,998行目:


// Reset dialog when closed
// Reset dialog when closed
$(document).off('dialogclose', '.anr-modal-dialog, .anr-preview-dialog, .anr-drpreview-dialog')
$(document)
.off('dialogclose', '#anr-modal-dialog, #anr-preview-dialog, #anr-drpreview-dialog')
.on('dialogclose', '.anr-modal-dialog, .anr-preview-dialog, .anr-drpreview-dialog', function() {
.on('dialogclose', '#anr-modal-dialog, #anr-preview-dialog, #anr-drpreview-dialog',
function() {
$(this).remove();
$(this).remove();
});
});
2,035行目: 2,011行目:
switch(selectedTar) {
switch(selectedTar) {
case ANI:
case ANI:
$('.anr-section-i-div').css('display', 'block');
$('#anr-section-i-div').css('display', 'block');
$('.anr-section-s-div').css('display', 'none');
$('#anr-section-s-div').css('display', 'none');
$('#anr-section-i-options-date').text(getSectionI(false));
$('#anr-section-i-options-date').text(getSectionI(false));
$('#anr-section-i-select').css({'width': $(this).width()});
$('#anr-section-i-select').css({'width': $(this).width()});
$('.anr-target-pagelink-div').css('display', 'block');
$('#anr-target-pagelink-div').css('display', 'block');
$('#anr-target-pagelink').attr('href', mw.util.getUrl(ANI));
$('#anr-target-pagelink').attr('href', mw.util.getUrl(ANI));
break;
break;
case ANS:
case ANS:
$('.anr-section-i-div').css('display', 'none');
$('#anr-section-i-div').css('display', 'none');
$('.anr-section-s-div').css('display', 'block');
$('#anr-section-s-div').css('display', 'block');
$('#anr-section-s-select').select2({'width': $(this).width()});
$('#anr-section-s-select').select2({'width': $(this).width()});
//$('#select2-anr-section-s-select-container').attr('style', rlsCSS);
//$('#select2-anr-section-s-select-container').attr('style', rlsCSS);
$('.anr-target-pagelink-div').css('display', 'block');
$('#anr-target-pagelink-div').css('display', 'block');
$('#anr-target-pagelink').attr('href', mw.util.getUrl(ANS));
$('#anr-target-pagelink').attr('href', mw.util.getUrl(ANS));
break;
break;
case AN3RR:
case AN3RR:
$('.anr-section-i-div').css('display', 'none');
$('#anr-section-i-div').css('display', 'none');
$('.anr-section-s-div').css('display', 'none');
$('#anr-section-s-div').css('display', 'none');
$('.anr-target-pagelink-div').css('display', 'block');
$('#anr-target-pagelink-div').css('display', 'block');
$('#anr-target-pagelink').attr('href', mw.util.getUrl(AN3RR));
$('#anr-target-pagelink').attr('href', mw.util.getUrl(AN3RR));
break;
break;
2,060行目: 2,036行目:


// Add section name to the '報告先' link when section is specified
// Add section name to the '報告先' link when section is specified
$(document).off('change', '#anr-section-i-select, #anr-section-s-select')
$(document)
.off('change', '#anr-section-i-select, #anr-section-s-select')
.on('change', '#anr-section-i-select, #anr-section-s-select', function(){
.on('change', '#anr-section-i-select, #anr-section-s-select',
function(){
let tarSection = '';
let tarSection = '', tarPage = '';
let tarPage = '';
if ($(this).attr('id') === 'anr-section-i-select') {
if ($(this).attr('id') === 'anr-section-i-select') {
tarPage = ANI;
tarPage = ANI;
2,078行目: 2,055行目:


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


const selectID = '#' + e.target.id; // #anr-userX-select
const selectID = '#' + e.target.id; // #anr-userX-select
2,088行目: 2,065行目:
const idlinkDivID = selectID.replace('select', 'idlink-div'); // #anr-userX-idlink-div
const idlinkDivID = selectID.replace('select', 'idlink-div'); // #anr-userX-idlink-div
const idlinkID = selectID.replace('select', 'idlink'); // #anr-userX-idlink
const idlinkID = selectID.replace('select', 'idlink'); // #anr-userX-idlink

switch(valSelected) {
switch(valSelected) {
case 'UNL':
case 'UNL':
2,120行目: 2,097行目:


// When username is typed in, change dropdown options for UserAN types
// 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){
$(document).off('input', '#anr-user-div :text').on('input', '#anr-user-div :text', function(e){


const inputID = '#' + e.target.id; // #anr-userX-input
const inputID = '#' + e.target.id; // #anr-userX-input
2,126行目: 2,103行目:


});
});

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

const checkboxID = '#' + e.target.id; // #anr-userX-checkbox
const checkboxID = '#' + e.target.id; // #anr-userX-checkbox
const selectID = checkboxID.replace('checkbox', 'select'); // #anr-userX-select
const selectID = checkboxID.replace('checkbox', 'select'); // #anr-userX-select
2,136行目: 2,113行目:
const idlinkID = checkboxID.replace('checkbox', 'idlink'); // #anr-userX-idlink
const idlinkID = checkboxID.replace('checkbox', 'idlink'); // #anr-userX-idlink
const idlinkDivID = checkboxID.replace('checkbox', 'idlink-div'); // #anr-userX-idlink-div
const idlinkDivID = checkboxID.replace('checkbox', 'idlink-div'); // #anr-userX-idlink-div

if ($(checkboxID).is(':checked')) { // if the checkbox is checked
if ($(checkboxID).is(':checked')) { // if the checkbox is checked

// Function to update type dropdown
// Function to update type dropdown
let updateDropdown = function(logid) {
let updateDropdown = function(logid) {
2,151行目: 2,128行目:
toggleBlockStatusLink(inputID, true, true);
toggleBlockStatusLink(inputID, true, true);
}
}

if (Logids[inputVal] !== undefined) {
if (Logids[inputVal] !== undefined) {

$(inputID).val(Logids[inputVal]); // if the object knows the logid for the user, retrieve the data
$(inputID).val(Logids[inputVal]); // if the object knows the logid for the user, retrieve the data
updateDropdown(Logids[inputVal]);
updateDropdown(Logids[inputVal]);

} else {
} else {

// if the object doesn't know the logid for the user, ask the API
// if the object doesn't know the logid for the user, ask the API
async function logidApi(){
async function logidApi(){

// Get logid from the API
// Get logid from the API
let logid = await getLogid(inputVal);
const logid = await getLogid(inputVal);


setTimeout(function(){ // Deliberately setting lag to prevent bugs
setTimeout(function(){ // Deliberately setting lag to prevent bugs

// Check the obtained logid
// Check the obtained logid
if (logid === undefined) { // If undefined is returned, reject the checking of the checkbox
if (logid === undefined) { // If undefined is returned, reject the checking of the checkbox
2,176行目: 2,153行目:
// Set the logid to the input
// Set the logid to the input
$(inputID).val(logid);
$(inputID).val(logid);

// Push username and logid into object if it doesn't have them
// Push username and logid into object if it doesn't have them
if (Logids[inputVal] === undefined) {
if (Logids[inputVal] === undefined) {
2,192行目: 2,169行目:
}
}
logidApi();
logidApi();

}
}

} else { // if the checkbox is unchecked
} else { // if the checkbox is unchecked

if (Logids[inputVal] !== undefined) {
if (Logids[inputVal] !== undefined) {

$(inputID).val(Logids[inputVal]); // if the object knows the username for the logid, retrieve the data
$(inputID).val(Logids[inputVal]); // if the object knows the username for the logid, retrieve the data
$(selectID).children('.anr-opt-UNL').prop('hidden', false).prop('selected', true);
$(selectID).children('.anr-opt-UNL').prop('hidden', false).prop('selected', true);
2,208行目: 2,185行目:
$(idlinkDivID).css('display', 'none');
$(idlinkDivID).css('display', 'none');
toggleBlockStatusLink(inputID, false, false);
toggleBlockStatusLink(inputID, false, false);

} else {
} else {

alert('エラー\n\nLogidにはアカウント作成記録以外のものも含まれるため、logidからユーザー名への変換機能は実装していません。' +
alert('エラー\n\nLogidにはアカウント作成記録以外のものも含まれるため、logidからユーザー名への変換機能は実装していません。' +
'テキストボックス下のリンク先からユーザー名を取得するか、手動入力してください。なお、ユーザー名からlogidへの変換が行われた' +
'テキストボックス下のリンク先からユーザー名を取得するか、手動入力してください。なお、ユーザー名からlogidへの変換が行われた' +
2,216行目: 2,193行目:
$(checkboxID).prop('checked', true);
$(checkboxID).prop('checked', true);
}
}

}
}


2,223行目: 2,200行目:
// When the 'add' button is hit, add another input layer
// When the 'add' button is hit, add another input layer
let userCnt = 1;
let userCnt = 1;
$('.anr-addBtn').click(function(){
$('#anr-addBtn').click(function(){


userCnt++;
userCnt++;
let replaceTar = new RegExp(`${userCnt-1}-`, 'g');
let replaceTar = new RegExp(`${userCnt-1}-`, 'g');
userHtml = userHtml.replace(replaceTar, `${userCnt}-`); // 1 → 2, 2 → 3 and so forth
userHtml = userHtml.replace(replaceTar, `${userCnt}-`); // 1 → 2, 2 → 3 and so forth
$('.anr-btn-div').before(userHtml);
$('#anr-btn-div').before(userHtml);
$(`#anr-user${userCnt}-input-div`).css('margin-top', '0.2em');
$(`#anr-user${userCnt}-input-div`).css('margin-top', '0.2em');


2,234行目: 2,211行目:


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


// When the summary checkbox is (un)checked
// When the summary checkbox is (un)checked
$(document).off('change', '#anr-summary-checkbox').on('change', '#anr-summary-checkbox', function(){
$(document).off('change', '#anr-summary-checkbox').on('change', '#anr-summary-checkbox', function(){


let $textarea = $('#anr-summary-text');
const $textarea = $('#anr-summary-text');


if ($(this).is(':checked')) { // Box is checked
if ($(this).is(':checked')) { // Box is checked
2,295行目: 2,272行目:
function getSectionI(last){
function getSectionI(last){


let d = new Date();
const d = new Date();
if (last) {
if (last) {
let subtract = 5;
let subtract = 5;
2,305行目: 2,282行目:
d.setDate(d.getDate() - subtract);
d.setDate(d.getDate() - subtract);
}
}
let sectionName;


let sectionName;
switch(true) {
switch(true) {
case (1 <= d.getDate() && d.getDate() <= 5):
case (1 <= d.getDate() && d.getDate() <= 5):
2,327行目: 2,304行目:
break;
break;
default:
default:
undefined;
}
}
return sectionName;
return sectionName;
2,334行目: 2,310行目:
// Function to get today's date
// Function to get today's date
function today() {
function today() {
let d = new Date();
const d = new Date();
return d.getMonth()+1 + '月' + d.getDate() + '日';
return d.getMonth()+1 + '月' + d.getDate() + '日';
}
}
2,346行目: 2,322行目:
'ususers': username,
'ususers': username,
'formatversion': 2
'formatversion': 2
}).done(function(res){
}).then(function(res){
resolve(res.query.users[0].userid !== undefined); // True if the user exists and false if not
resolve(res.query.users[0].userid !== undefined); // True if the user exists and false if not
});
});
2,356行目: 2,332行目:
function updateTypeDropdown(inputID) {
function updateTypeDropdown(inputID) {
let tarVal = $(inputID).val().trim2(); // The value typed into the input
const tarVal = $(inputID).val().trim2(); // The value typed into the input
let selectID = inputID.replace('input', 'select'); // #anr-userX-select
const selectID = inputID.replace('input', 'select'); // #anr-userX-select
let checkboxDivID = inputID.replace('input', 'checkbox-div'); // #anr-userX-checkbox-div
const checkboxDivID = inputID.replace('input', 'checkbox-div'); // #anr-userX-checkbox-div
let checkboxID = inputID.replace('input', 'checkbox'); // #anr-userX-checkbox
const checkboxID = inputID.replace('input', 'checkbox'); // #anr-userX-checkbox
let idlinkDivID = inputID.replace('input', 'idlink-div'); // #anr-userX-idlink-div
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
clearTimeout(updateTypeDropdownTimeout); // Run the async function only once when there's been no input change for 0.35 seconds
2,372行目: 2,348行目:
$(idlinkDivID).css('display', 'none'); // Hide logid/diff link
$(idlinkDivID).css('display', 'none'); // Hide logid/diff link
toggleBlockStatusLink(inputID, true, false);
toggleBlockStatusLink(inputID, true, false);

} else { // if the field is filled
} else { // if the field is filled


2,415行目: 2,391行目:


}
}

}
}

}, 350);
}, 350);
}
}
2,430行目: 2,406行目:


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


if (forceHide && convertLogid) {
if (forceHide && convertLogid) {
2,448行目: 2,424行目:


}
}

// Check the block status of the user, and if blocked, update the bsLink, or else, hide the link
// Check the block status of the user, and if blocked, update the bsLink, or else, hide the link
let blocked = await getBlocked([username]);
const blocked = await getBlocked([username]);

if (blocked.length !== 0) { // If the user typed into the input is blocked
if (blocked.length !== 0) { // If the user typed into the input is blocked


2,463行目: 2,439行目:
}
}


return;
}
}


2,470行目: 2,444行目:
async function getBlocked(namesArr) {
async function getBlocked(namesArr) {


let users = [];
let users = [], ips = [], blocked = [];
let ips = [];
let blocked = [];


// Sort names to users and IPs
// Sort names to users and IPs
2,483行目: 2,455行目:
}
}


// Check who's blocked
// Check who's (b)locked
if (users.length !== 0 && ips.length !== 0) { // If namesArr contains both users and IPs
if (users.length !== 0 && ips.length !== 0) { // If namesArr contains both users and IPs

blocked = blocked.concat(await getBlockedUsers(users), await getBlockedIps(ips));
// Check local block status
blocked = blocked.concat(
await getBlockedUsers(users),
await getBlockedIps(ips)
);

// Remove users/IPs that are already in the array 'blocked' (to make the code faster)
for (let i = users.length -1; i >= 0; i--) {
if (isInArray(users[i], blocked)) {
users.splice(i ,1);
}
}
for (let i = ips.length -1; i >= 0; i--) {
if (isInArray(ips[i], blocked)) {
ips.splice(i ,1);
}
}

// Check global (b)lock status
blocked = blocked.concat(
await getGloballyLockedUsers(users),
await getGloballyBlockedIps(ips)
);

} else if (users.length !== 0) { // If namesArr only contains users
} else if (users.length !== 0) { // If namesArr only contains users

// Check local block status
blocked = blocked.concat(await getBlockedUsers(users));
blocked = blocked.concat(await getBlockedUsers(users));

// Remove users that are already in the array 'blocked' (to make the code faster)
for (let i = users.length -1; i >= 0; i--) {
if (isInArray(users[i], blocked)) {
users.splice(i ,1);
}
}

// Check global lock status
blocked = blocked.concat(await getGloballyLockedUsers(users));

} else if (ips.length !== 0) { // If namesArr only contains IPs
} else if (ips.length !== 0) { // If namesArr only contains IPs

// Check local block status
blocked = blocked.concat(await getBlockedIps(ips));
blocked = blocked.concat(await getBlockedIps(ips));

// Remove IPs that are already in the array 'blocked' (to make the code faster)
for (let i = ips.length -1; i >= 0; i--) {
if (isInArray(ips[i], blocked)) {
ips.splice(i ,1);
}
}

// Check global block status
blocked = blocked.concat(await getGloballyBlockedIps(ips));

} else {
} else {
return blocked; // [], empty array
// Do nothing
}
}


// Sort the blocked in the original order
return blocked;
for (i = namesArr.length -1; i >= 0; i--) {
if (!isInArray(namesArr[i], blocked)) { // If not blocked
namesArr.splice(i, 1); // Remove the name from the array
}
}

return namesArr;


}
}
2,508行目: 2,523行目:
async function getBlockedUsers(usersArr) {
async function getBlockedUsers(usersArr) {


if (usersArr.length === 0) return [];
let blockedArr = [];
let blockedArr = [];


// API query
return new Promise(function(resolve) {
return new Promise(function(resolve) {
new mw.Api().get({
new mw.Api().post({
'action': 'query',
'action': 'query',
'list': 'blocks',
'list': 'blocks',
2,519行目: 2,534行目:
'bkprop': 'user',
'bkprop': 'user',
'formatversion': 2
'formatversion': 2
}).done(function(res){
}).then(function(res){


let blockstatusApi = res.query.blocks;
const blockstatusApi = res.query.blocks;


if (blockstatusApi.length === 0) { // If none of the users is blocked
if (blockstatusApi.length === 0) { // If none of the users is blocked
2,538行目: 2,553行目:
});
});
});
});

}
}


// Function to get an array of blocked IPs from an array of random IPs
/**
* Function to get an array of locked users from an array of random users
* @param {Array} usersArr
* @returns {Array}
*/
async function getGloballyLockedUsers(usersArr) {
if (usersArr.length === 0) return [];
let lockedArr = [];
for (let i = 0; i < usersArr.length; i++) {
const locked = await userIsLocked(usersArr[i]);
if (locked) {
lockedArr.push(usersArr[i]);
}
if (i === usersArr.length -1) {
return lockedArr;
}
}
}

/**
* Function to check if a user is globally locked
* @param {String} user
* @returns {Boolean}
*/
async function userIsLocked(user) {
return new Promise(function(resolve) {
new mw.Api().get({
action: 'query',
list: 'globalallusers',
agulimit: 1,
agufrom: user,
aguto: user,
aguprop: 'lockinfo'
}).then(function(res) {
if (res.query.globalallusers.length === 0) {
// If the length is 0, then we couldn't find the global user
resolve(false);
}
// If the 'locked' field is present, then the user is locked
resolve(res.query.globalallusers[0].locked !== undefined);
});
});
}

/**
* Function to get an array of locally blocked IPs from an array of random IPs
* @param {Array} ipsArr
* @returns {Array}
*/
async function getBlockedIps(ipsArr) {
async function getBlockedIps(ipsArr) {
if (ipsArr.length === 0) return [];
let blockedArr = [];
let blockedArr = [];
let blocked;
for (let i = 0; i < ipsArr.length; i++) {
for (let i = 0; i < ipsArr.length; i++) {
blocked = await ipIsBlocked(ipsArr[i]);
const blocked = await ipIsBlocked(ipsArr[i]);
if (blocked) {
if (blocked) {
blockedArr.push(ipsArr[i]);
blockedArr.push(ipsArr[i]);
2,555行目: 2,619行目:
}
}


/**
// Function to check if a given IP is blocked (true if the IP is blocked and false if not)
* Function to check if a given IP is locally blocked
* @param {String} ip
* @returns {Boolean}
*/
async function ipIsBlocked(ip) {
async function ipIsBlocked(ip) {
return new Promise(function(resolve) {
return new Promise(function(resolve) {
new mw.Api().get({
new mw.Api().get({
'action': 'query',
'action': 'query',
2,566行目: 2,633行目:
'bkprop': 'user', // Get the blocked IP: Could be a range; Currently this value is not used in the function
'bkprop': 'user', // Get the blocked IP: Could be a range; Currently this value is not used in the function
'formatversion': 2
'formatversion': 2
}).done(function(res){
}).then(function(res){
resolve(res.query.blocks.length !== 0);
resolve(res.query.blocks.length !== 0);
});
});
});
}


/**
* Function to get an array of globally blocked IPs from an array of random IPs
* @param {Array} ipsArr
* @returns {Array}
*/
async function getGloballyBlockedIps(ipsArr) {
if (ipsArr.length === 0) return [];
let blockedArr = [];
for (let i = 0; i < ipsArr.length; i++) {
const blocked = await ipIsGloballyBlocked(ipsArr[i]);
if (blocked) {
blockedArr.push(ipsArr[i]);
}
if (i === ipsArr.length -1) {
return blockedArr;
}
}
}

/**
* Function to check if a given IP is globally blocked
* @param {String} ip
* @returns {Boolean}
*/
async function ipIsGloballyBlocked(ip) {
return new Promise(function(resolve) {
new mw.Api().get({
'action': 'query',
'list': 'globalblocks',
'bgip': ip,
'bglimit': 1,
'bgprop': 'address'
}).then(function(res){
resolve(res.query.globalblocks.length !== 0);
});
});
});
}
}
2,583行目: 2,687行目:
'lelimit': 1,
'lelimit': 1,
'formatversion': 2
'formatversion': 2
}).done(function(res){
}).then(function(res){
if (res.query.logevents.length === 0) { // If empty array is returned (=if no logevent exists)
if (res.query.logevents.length === 0) { // If empty array is returned (=if no logevent exists)
resolve();
resolve();

2022年4月10日 (日) 13:08時点における版

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

// Run the script only after Select2, jQuery UI, and the DOM are loaded
$.when(
    $.getScript('https://cdn.jsdelivr.net/npm/[email protected]/dist/js/select2.min.js'),
    mw.loader.using('jquery.ui'),
    $.ready
).then(function(){

    // Load CSS source for Select2
    $('head')
    .append($('<link />')
        .attr({
            'rel': 'stylesheet',
            'href': 'https://cdn.jsdelivr.net/npm/[email protected]/dist/css/select2.min.css'
        })
    );

    // Run the script only if the user is autoconfirmed and the page is not an edit page
    if (userIsInGroup('autoconfirmed') && mw.config.get('wgAction') !== 'edit') {

        // ********** UTILITY FUNCTIONS **********

        /**
         * String method to get rid of the U+200E space, in addition to the function of $.trim()
         * @returns {String}
         */
        String.prototype.trim2 = function() {
            return this.replace(/\u200e/g, '').trim();
        };

        /**
         * String method (alternative) to replace all occurences of a string with another
         * (takes a replacer and a replacee as arguments)
         * @returns {String}
         */
        String.prototype.replaceAll2 = function() {
            let replaced = '';
            if (arguments.length %2 !== 0) {
                return new Error('SyntaxError: replaceAll2 takes an even number of arguments.');
            } else {
                for (let i = 0; i < arguments.length; i = i + 2) {
                    if (i === 0) {
                        replaced = this.split(arguments[i]).join(arguments[i + 1]);
                    } else {
                        replaced = replaced.split(arguments[i]).join(arguments[i + 1]);
                    }

                }
                return replaced;
            }
        };

        // ********** PREPARATIONS BEFORE OPENING THE DIALOG **********

        // 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
        };
        if (debugMode.scriptAd) {
            var scriptAd = ' ([[User:Dragoniez/AN Reporter|AN Reporter Experimental]])';
        } else {
            var scriptAd = ' ([[User:Dragoniez/AN Reporter|AN Reporter]])';
        }
        if (debugMode.portletLink) {
            var portletLinkText = '報告β';
        } else {            
            var portletLinkText = '報告';
        }

        // Skin-dependent options
        let btnPosition; // Where to place the '報告' link
        let fSize; // Font size of the elements on the dialog
        let s2fSize; // Font size of Select2 dropdown options
        switch(mw.config.get('skin')) {
            case 'vector':
            case 'vector-2022':
                btnPosition = 'p-views';
                fSize = '80%';
                s2fSize = '0.9em';
                break;
            case 'minerva':
                btnPosition = 'p-personal';
                fSize = '80%';
                s2fSize = '0.9em';
                break;
            case 'monobook':
                btnPosition = 'p-cactions';
                fSize = '110%';
                s2fSize = '1.03em';
                break;
            case 'timeless':
                btnPosition = 'p-cactions';
                fSize = '90%';
                s2fSize = '0.94em';
                break;
            default:
                btnPosition = 'p-cactions';
                fSize = '80%';
                s2fSize = '0.9em';
        }

        // Default 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: ${s2fSize};
                   margin: 0;
                }
                .select2-container, .select2-selection--single {
                    height: auto !important;
                }
            </style>`
        );

        // Variables
        let Logids = {}; // Object to store usernames and their corresponding logids
        let checkBlockStatusBeforeEdit = true;
        let checkDuplicateReportsBeforeEdit = true;

        // Add ANR tab
        $(mw.util.addPortletLink(btnPosition, '#', portletLinkText, 'ca-anr', '管理者伝言板に利用者を報告', null, '#ca-move'))
        .click(function(e){

            // Cancel event that redirects the user to the href destination
            e.preventDefault();

            // ********** DIALOG CREATION **********

            // CSS
            const labelCSS = 
                'display: inline-block;' + 
                'width: 8ch;' ;
            const marginCSS = 
                'margin: 1em 0;' ;
            const siCSS = // For select and input
                'border: 1px solid #d3d3d3;' +
                'border-radius: 1%;' +
                'background-color: white;' +
                'padding: 2px 4px;' ;
            const btnCSS = // For button
                'color: black;' +
                'font-weight: normal;' +
                'border: 1px solid #d3d3d3;' +
                'background-color: white;' +
                'padding: 0.2em 0.5em;' +
                'border-radius: 10%;' ;
            /* Experimental design
            const lsCSS = // Eliminate gap between inline-blocks
                'letter-spacing: -1em;' +
                'white-space: nowrap' ;
            const rlsCSS = // Reset line-spacing in child elements
                'letter-spacing: normal' ;
            */

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

            // Username input
            let userHtml =
            //<div id="anr-user-div">
            `    <div id="anr-user1-input-div">` +
            `        <label for="anr-user1-input" style="${labelCSS}">利用者</label>` +
            `        <input id="anr-user1-input" style="width: 34ch; ${siCSS}">` +
            `        <select disabled id="anr-user1-select" style="${siCSS}">` +
            `            <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" style="${labelCSS}"></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" style="${labelCSS}"></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" style="${labelCSS}"></label>` +
            `        <a id="anr-user1-blockstatus" href="" target="_blank" style="color: MediumVioletRed;">ブロックあり</a>` +
            `    </div>`;
            //</div>

            // 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" style="${marginCSS}">` +
            `               <label for="anr-target-options" id="anr-target-options-label" style="${labelCSS}">報告先</label>` +
            `               <select id="anr-target-options" style="${siCSS}">` +
            `                   <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" for="anr-target-pagelink" style="${labelCSS}"></label>` +
            `                   <a id="anr-target-pagelink" href="" target="_blank">報告先を確認</a>` +
            `               </div>` +
            `           </div>` +
            `           <div id="anr-section-i-div" style="${marginCSS} display: none;">` +
            `               <label for="anr-section-i-select" style="${labelCSS}">節</label>` +
            `               <select id="anr-section-i-select" style="${siCSS}">` +
            `                   <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" style="${marginCSS} display: none;">` +
            `               <label for="anr-section-s-select" style="${labelCSS}">節</label>` +
            `               <select id="anr-section-s-select" style="${siCSS}">` +
            `                   <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" style="${marginCSS}">` +
                            userHtml +
            `               <div id="anr-btn-div">` +
            `                   <button type="button" id="anr-addBtn" style="${btnCSS}">追加</button>` +
            `               </div>` +
            `           </div>` +
            `           <div id="anr-predefinedreasons-div" style="${marginCSS} display: none;">` +
            `               <label for="anr-predefinedreasons-select" style="${labelCSS}">定型文</label>` +
            `               <select id="anr-predefinedreasons-select">` +
            '                   <optgroup style="display: none;">' + // Adjust font size
            `                       <option selected>定型文を使用する場合は選択してください</option>` +
            '                   </optgroup>' +
            `               </select>` +
            `           </div>` +
            `           <div id="anr-reason-div" style="${marginCSS}">` +
            `               <label for="anr-reason-text" style="${labelCSS}">理由</label>` +
            `               <textarea id="anr-reason-text" rows="6" style="width: 100%"></textarea>` +
            `           </div>` +
            `           <div id="anr-summary-div" style="${marginCSS}">` +
            `               <input id="anr-summary-checkbox" type="checkbox">` +
            `               <label for="anr-summary-checkbox">要約を指定</label>` +
            `               <textarea id="anr-summary-text" rows="3" style="width: 100%; display: none;"></textarea>` +
            `           </div>` +
            `           <div id="anr-checkbox-div" style="${marginCSS}">` +
            `               <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>` +
            `           </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,
                'position': { my: 'center', at: 'top', of: window },
                'open': initializeDialog,
                'buttons': [{
                    'text': 'プレビュー',
                    'click': previewBtn
                }, {
                    'text': '報告',
                    'click': reportBtn
                }]
            });

            // ********** EVENT HANDLERS AND FUNCTIONS **********

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

                // Get sections on WP:AN/S
                getSectionsS();

                // Initialize the design of the dialog
                dialogCSS();

                // Show VIP list
                getVipList();

                // Show the select box for predefined reasons if they're predefined by the user
                getPredefinedReasons();

                // Initialize variables
                checkBlockStatusBeforeEdit = true;
                checkDuplicateReportsBeforeEdit = true;
                /* NOTE: The value of Logids is inherited from before reopening the dialog.
                 * If any bug is reported, consider resetting the variable here as well. */

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

                // Workaround to pick up IP ranges
                if (username === null && mw.config.get('wgCanonicalSpecialPageName') === 'Contributions') {
                    let 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
                 * Note: The code below is partially a duplicate of updateTypeDropdown(), but deliberately coded that way
                 * to prevent bugs and eliminate unnecessary API requests */
                const inputID = '#anr-user1-input';
                const selectID = '#anr-user1-select';
                const 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
            function getSectionsS() {
                const $label = $('#anr-target-options-label'); // Label of '報告先'
                $label.append(toggleLoadingSpinner('add'));

                new mw.Api().get({
                    'action': 'parse',
                    'page': ANS,
                    'prop': 'sections',
                    'formatversion': 2
                }).then(function(res){

                    if (res && res.parse) {

                        // Get VIP's names
                        const sectionInfo = res.parse.sections;
                        const excludeList = [
                            '系列が立てられていないもの',
                            '著作権侵害・犯罪予告',
                            '名誉毀損・なりすまし・個人情報',
                            '妨害編集・いたずら',
                            'その他',
                            'A. 最優先',
                            '暫定A',
                            '休止中A',
                            'B. 優先度高',
                            '暫定B',
                            '休止中B',
                            'C. 優先度中',
                            '暫定C',
                            '休止中C',
                            'D. 優先度低',
                            '暫定D',
                            '休止中D',
                            'N. 未分類',
                            'サブページなし',
                            '休止中N'
                        ];
                        let sectionList = [];
                        for (let i = 0; i < sectionInfo.length; i++) {
                            if (!isInArray(sectionInfo[i].line, excludeList) && 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のセクションリストを取得できませんでした。ダイアログを開き直すと改善する場合があります。');
                    }
                    toggleLoadingSpinner('remove');

                }).catch(function(){
                    alert('WP:AN/Sのセクションリストを取得できませんでした。ダイアログを開き直すと改善する場合があります。');
                    toggleLoadingSpinner('remove');
                });
            }
            
            // Function to change the CSS of the dialog
            function dialogCSS() {
                $('.ui-dialog-content, .ui-dialog-buttonpane, .ui-corner-all, .ui-draggable, .ui-resizable').css('background', '#FFF0E4');
                $('.ui-button').css({
                    'color': 'black',
                    'background-color': 'white'
                });
                $('.ui-dialog-titlebar, .ui-dialog-titlebar-close').attr('style', 'background: #FEC493 !important;');
                $('.ui-dialog').css('font-size', fSize);
            }

            // Function to show the select div for predefined report reasons if they're predefined
            function getPredefinedReasons() {
                if (typeof anrPredefinedReasons !== 'undefined') { // If the user has fixed reasons prepared

                    if (Array.isArray(anrPredefinedReasons)) { // If the fixed reasons are prepared as an array

                        if (anrPredefinedReasons.length === 0) return;

                        const $reasons = $('#anr-predefinedreasons-select');
                        $reasons.css('width', $('#anr-target-options').width()).select2();

                        let reasonsArr = [];
                        for (let i = 0; i < anrPredefinedReasons.length; i++) {
                            reasonsArr.push(`<option>${anrPredefinedReasons[i]}</option>`);
                        }
                        $reasons.children('optgroup').append(reasonsArr.join(''));
                        $('#anr-predefinedreasons-div').css('display', 'block');

                    } else { // If the fixed reasons are NOT prepared as an array
                        alert('エラー: common.js内の定型文の変数定義が不正です。配列 "[理由1, 理由2...]" 形式で指定してください。');
                    }

                }
            }

            // WP:VIP list (for copy to clipboard)
            function getVipList() {
                
                new mw.Api().get({
                    'action': 'parse',
                    'page': VIP,
                    'prop': 'sections',
                    'formatversion': 2
                }).then(function(res){

                    if (res && res.parse) {

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

                            // Show the VIP list on the dialog
                            const vipListHtml =
                            '<div class="anr-viplist-div" style="width: 100%;">' +
                            `   <label for="anr-viplist-select" style="${labelCSS}">VIP</label>` +
                            `   <select id="anr-viplist-select">` +
                            '       <optgroup style="display: none;">' + // Adjust font size
                            '           <option selected disabled hidden>コピーする場合は選択してください</option>' +
                                        vipList.join('') +
                            '       </optgroup>' +
                            '   </select>' +
                            '</div>';
                            $('#anr-predefinedreasons-div').before(vipListHtml);
                            $('#anr-viplist-select').css('width', $('#anr-target-options').width()).select2();

                        }

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

                }).catch(function(){
                    return mw.log.error('VIP list: Failed to retrieve data from the API.');
                });
            }

            // Copy a VIP name if 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();
                const vipLink = '[[WP:VIP#' + vipSelectVal + ']]';
                copyToClipboard(vipLink);
            });

            // Function to copy a string to the clipboard
            function copyToClipboard(str) {
                try { // This should handle browser incompatibility
                    let $temp = $('<input>');
                    $('body').append($temp); // Create a temporarily hidden text field
                    $temp.val(str).select(); // Copy the text string into the field and select the text
                    document.execCommand('copy'); // Copy it to the clipboard
                    $temp.remove(); // Remove the text field
                }
                catch (err) {
                    alert('ご利用のブラウザはこの機能に対応していません');
                    console.log(err);
                }
            }

            // Function to check information typed into the form
            function editPrep() {
                let rqFieldsEmpty = false;

                // Check if at least one username is given and get users to report (and their UserAN types)
                let users = [], types = [], duplicates = [];
                let tempUsername = ''; // An escape hatch for username (for t=logid)

                for (let i = 1; i < Infinity; i++) { // Loop through all inputs

                    if ($(`#anr-user${i}-input`).length === 0) { // if selector is not found

                        break; // exit for

                    } else { // if selector is found

                        const usernameInInput = $(`#anr-user${i}-input`).val().trim2();
                        const selectedType = $(`#anr-user${i}-select`).children('option').filter(':selected').text();

                        if (usernameInInput !== '') { // if input is not empty

                            // If t=logid, necessary to check if Logids has a username corresponding to the logid
                            if (selectedType === 'logid' && Logids[usernameInInput] !== undefined) {

                                // If the object has a username/logid pair, get username from the logid
                                tempUsername = Logids[usernameInInput];

                                // 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 ((isInArray(usernameInInput, users) || isInArray(tempUsername, users)) &&
                                    !isInArray(usernameInInput, duplicates) && !isInArray(tempUsername, duplicates)) {
                                    
                                    // List both the username and the logid as duplicates
                                    duplicates.push(usernameInInput, tempUsername);

                                }
                                
                            } else { // If logids are irrelevant

                                // If the username is already in the array 'users' and hasn't been listed as a duplicate
                                if (isInArray(usernameInInput, users) && !isInArray(usernameInInput, duplicates)) {

                                    // List the username as a duplicate
                                    duplicates.push(usernameInInput);

                                }

                            }

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

                        }
                    }

                    // Reset variables
                    tempUsername = '';

                }

                // Get the name of the section to edit
                let pageToEdit =  $('#anr-target-options').children('option').filter(':selected').text();
                let sectionToEdit = '選択してください';                
                let fixedReason = $('#anr-predefinedreasons-select').find('option').filter(':selected').text();
                let 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}日新規報告$/) !== null) {
                        const sectionIDate = getSectionI(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 ANSOptSelected = $('#anr-section-s-select').find('option').filter(':selected').text();
                    switch(ANSOptSelected) {
                        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 = ANSOptSelected;
                    }

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

                    sectionToEdit = '3RR';

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

                // Check if necessary fields are filled
                if ( 
                    pageToEdit === '選択してください' || // The page dropdown's remained 選択してください 
                    sectionToEdit === '選択してください' || // The section dropdown's remained 選択してください
                    reason === '' || // No reason is given
                    users.length === 0 // No username is given
                ) {
                    rqFieldsEmpty = true;
                }

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

                // Get edit summary
                const editSummarySection = '/*' + sectionToEdit + '*/';

                const editSummary = 
                $('#anr-summary-text').val().trim2() === '' ? 
                editSummarySection + genEditSummary().replace(' - ', '') + scriptAd: 
                editSummarySection + $('#anr-summary-text').val().trim2() + scriptAd;

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

                // Return values
                return {
                    'users': users,
                    'types': types,
                    'duplicates': duplicates,
                    'pageToEdit': pageToEdit,
                    'sectionToEdit': sectionToEdit,
                    'reportToANS': reportToANS,
                    'rqFieldsEmpty': rqFieldsEmpty,
                    'editSummary': editSummary,
                    'textToSubmit': textToSubmit
                }
            }

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

                // Check if the necessary fields are filled and get edit information
                const ep = editPrep();
                if (ep.rqFieldsEmpty) {
                    alert('必須項目が入力・選択されていません'); // Show error and cancel the preview
                    return;
                }

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

                    if (confirm(confirmMsg) === false) 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">' +
                '   <div id="anr-preview-header" style="padding: 0.5em;">' +
                '       <p id="anr-preview-loading">' +
                `           プレビューを読み込み中${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,
                    'position': { my: 'center', at: 'top', of: window },
                    'open': async function(){

                        // Initialize the design of the dialog
                        dialogCSS();

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

                            const previewHtml = parsed.htmltext;
                            const summaryHtml = parsed.htmlsummary.replaceAll2('API', 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');

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

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

            }

            /**
             * Function to convert wikitext to its HTML format
             * @param {String} wikitext The wikitext to convert
             * @param {String} wikisummary The summary to convert
             * @returns {{htmltext: string, htmlsummary: string}}
             */
            async function convertWikitextToHtmlFormat(wikitext, wikisummary) {
                return new Promise(function(resolve) {
                    new mw.Api().post({
                        'action': 'parse',
                        'text': wikitext,
                        'summary': wikisummary,
                        'contentmodel': 'wikitext',
                        'prop': 'text',
                        'disableeditsection': true,
                        'format': 'json'
                    }).then(function(res) {
                            resolve({
                                'htmltext': res.parse.text['*'],
                                'htmlsummary': res.parse.parsedsummary['*']
                            });
                    }).catch(function(err) {
                        resolve(console.log(err));
                    });
                });
            }

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

                // Check if the necessary fields are filled and get edit information
                const ep = editPrep();
                if (ep.rqFieldsEmpty) {
                    alert('必須項目が入力・選択されていません'); // Show error and cancel the edit
                    return;
                }

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

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

                // Variables for edit
                const $dialog = $('#anr-modal-dialog');
                const dWidth = $dialog.width();
                let msgEditing = '<div class="anr-editing" />';
                let msgDone = ''; // Message to show when edit attempt is done
                const wikiPagename = ep.pageToEdit + '#' + ep.sectionToEdit; // The wiki pagename for link

                // Change dialog content
                $dialog.dialog('option', 'width', dWidth); // Set an absolute width
                $dialog.find('form').css('display', 'none'); // Hide dialog content
                $dialog.dialog({'buttons': [] }); // Hide the buttons
                $dialog.append(msgEditing);

                // For debugging
                if (debugMode.editSummary) {
                    ep.editSummary = 'Test edit via mediawiki API' + scriptAd;
                }
                if (debugMode.editTarget) {
                    ep.pageToEdit = '利用者:Dragoniez/test';
                }

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

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

                    // Extract users and IPs from the array
                    let tar = [];
                    for (let i = 0; i < ep.users.length; i++) {
                        switch(ep.types[i]) {
                            case 'UNL':
                            case 'User2':
                            case 'IP2':
                                if (!isInArray(ep.users[i], tar)) {
                                    tar.push(ep.users[i]);
                                }
                                break;
                            case 'logid':
                                if (Logids[ep.users[i]] !== undefined) { // If the logid can be converted to a username
                                    if (!isInArray(Logids[ep.users[i]], tar)) {
                                        tar.push(Logids[ep.users[i]]); // Push username instead of logid
                                    }
                                }
                                break;
                            case 'diff':
                            case 'none':
                                // Do nothing
                        }
                    }

                    // Check if any of the users is blocked
                    const blocked = await getBlocked(tar);

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

                        // Update message on the dialog
                        msgEditing =
                        toggleLoadingSpinner('remove') +
                        `<p style="color: MediumVioletRed">ブロック済みの利用者を検出しました</p>`;
                        $('.anr-editing').append(msgEditing);
                        
                        // Update dialog
                        for (let i = 1; i < Infinity; i++) { // Loop through all inputs

                            if ($(`#anr-user${i}-input`).length === 0) { // if selector is not found

                                break; // exit for

                            } else { // if selector is found

                                const inputVal = $(`#anr-user${i}-input`).val().trim2();
                                const $bsLinkDiv = $(`#anr-user${i}-blockstatus-div`);
                                const $bsLink = $(`#anr-user${i}-blockstatus`);

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

                            }
                        }

                    } else {

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

                    }
                    return blocked;

                }

                /**
                 * Function to check duplicate reports
                 * @returns {{wikitext: string, reportees: Array}} wikitext === null if sections aren't found, wikitext === ''
                 * if there's no duplicate, wikitext === SECTIONTEXT to fetch preview from if there's any. If SECTIONTEXT
                 * is returned, reportees === [reportee1, reportee2...], without logids that can be converted to usernames.
                 */
                async function preeditDuplicateReportQuery() {
                   
                    // Update message on the dialog
                    msgEditing =
                    `<p>重複報告情報を取得しています${toggleLoadingSpinner('add')}</p>`;
                    $('.anr-editing').append(msgEditing);
            
                    // Get sections and the whole wikitext of the page to which to report 
                    async function getWikitextAndSectionInfo(){
                        return new Promise(function(resolve) {
                            new mw.Api().get({
                                'action': 'parse',
                                'page': ep.pageToEdit,
                                'prop': 'wikitext|sections',
                                'formatversion': 2
                            }).then(function(res){
                                resolve({
                                    'sections': res.parse.sections,
                                    'wikitext': res.parse.wikitext
                                });
                            });  
                        });
                    }
                    const parsed = await getWikitextAndSectionInfo();
                    let sections = parsed.sections; // Array of objects
                    let wikitext = parsed.wikitext; // String
                    let sectiontitles = [];
                    let sectionheaders = ['']; // Array of equal-enclosed section headers (e.g. == SECTION ==)
                                               // Note: the top section has no header, thus arr[0] = ''

                    // Get section titles and their corresponding equal-enclosed wikitext
                    for (let i = 0; i < sections.length; i++) {

                        // The transcluded '新規依頼' sections on WP:AN/S are duplicate names, hence shouldn't be included in the array
                        if (sections[i].index.indexOf('T') === -1) { // If the section isn't a transcluded one

                            sectiontitles.push(sections[i].line); // Get section titles

                            switch(sections[i].level) { // Get equal-enclosed section headers
                                case 2:
                                case '2':
                                    sectionheaders.push('== ' + sections[i].line + ' ==');
                                    break;
                                case 3:
                                case '3':
                                    sectionheaders.push('=== ' + sections[i].line + ' ===');
                                    break;
                                case 4:
                                case '4':
                                    sectionheaders.push('==== ' + sections[i].line + ' ====');
                                    break;
                                case 5:
                                case '5':
                                    sectionheaders.push('===== ' + sections[i].line + ' =====');
                                    break;
                            }

                        }
                    }

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

                    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;
                        default: // For debugging, corresponding to the sections on [[User:Dragoniez/test]]
                            tarSections = tarSectionsS;
                    }

                    // Check if the target sections exist
                    if (!arrayIsInArray(tarSections, sectiontitles)) {
                        sectionNotFound();
                        return {'wikitext': null};
                    }

                    // Separate the whole text into an array of sections
                    let sectionsPiped = sectiontitles.join('|').replaceAll2('(', '\\(', ')', '\\)', '.', '\\.');
                    let regex = new RegExp(`={2,5}\\s*(?:${sectionsPiped})\\s*={2,5}`, 'g');
                    let sectionContent = wikitext.split(regex); // Array of the content of each section, without section headers

                    // Re-add to sectionContent the '== TITLE ==' header that's been removed by the split() above
                    for (let i = 0; i < sectionContent.length; i++) {
                        sectionContent[i] = sectionheaders[i] + sectionContent[i];
                    }

                    // Remove irrelevant sections from the array of the content of each section (sectionContent)
                    sectionsPiped = tarSections.join('|').replaceAll2('(', '\\(', ')', '\\)', '.', '\\.');
                    regex = new RegExp(`={2,5}\\s*(?:${sectionsPiped})\\s*={2,5}`, 'g');
                    for (let i = sectionContent.length -1; i >= 0; i--) {
                        if (sectionContent[i].search(regex) === -1) {
                            sectionContent.splice(i, 1);
                        }
                    }
                    //console.log('The contents of each section:');
                    //console.log(sectionContent);

                    // Get all the reportees in the inputs, and if user, try to get logids
                    let reportees = [];
                    let logid;
                    for (let i = 0; i < ep.users.length; i++) { // Loop through all the usernames typed into the inputs

                        let user = ep.users[i];
                        let type = ep.types[i];

                        if (type === 'UNL' || type === 'User2') {

                            if (!isInArray(user, reportees)) { // If not already in the array

                                if (Logids[user] !== undefined) { // If the code already knows the logid for the user

                                    reportees.push(user, Logids[user]); // Push the username and the logid into the array

                                } else { // If the code doesn't know a logid for the user

                                    logid = await getLogid(user); // Try to fetch one from the API
                                    if (logid !== undefined) { // If a valid logid is returned
                                        Logids[user] = logid;
                                        Logids[logid] = user;
                                        reportees.push(user, logid); // Push the username and the logid into the array
                                    }
                                    
                                }

                            }

                        } else if (type === 'logid') {

                            // user = numeral, Logids[user] = username
                            if (Logids[user] !== undefined) { // If the logid can be converted to a username

                                if (!isInArray(Logids[user], reportees)) { // If the corresponding username is not in the array 
                                    reportees.push(Logids[user]); // Push the username into the array
                                }
                                if (!isInArray(user, reportees)) { // If the logid is not in the array
                                    reportees.push(user); // Push the logid into the array
                                }

                            } else { // If the logid cannot be converted to a username

                                if (!isInArray(user, reportees)) { // If the logid is not in the array
                                    reportees.push(user); // Push the logid into the array
                                }

                            }

                        } else { // If other than t=UNL, t=User2, and t=logid

                            if (!isInArray(user, reportees)) { // If not already in the array
                                reportees.push(user); // Push the username into the array
                            }

                        }

                        logid = undefined; // Reset before the next iteration
                    }

                    // Extract UserAN templates and find duplicate reports
                    let templates = [];
                    let dupTemplates = [];
                    let duplicateFound = false;
                    
                    for (let i = sectionContent.length -1; i >= 0; i--) { // Loop through all the relevant section texts

                        /* Extract UserAN templates from the section text
                         * This returns an array of the occurrences of UserAN, e.g. [{{UserAN|t=UNL|ウィキ助}}, ...] */
                        templates = findTemplates(sectionContent[i], 'useran');
                        
                        if (templates.length !== 0) { // If the section text contains at least one UserAN

                            for (let j = templates.length -1; j >= 0; j--) { // Loop through all the occurences of UserAN
                                if (stringContainsElementInArray(templates[j], reportees)) { // If there's a duplciate report
                                    if (!isInArray(templates[j], dupTemplates)) {
                                        dupTemplates.push(templates[j]); // List the occurence as a duplicate
                                    }
                                    duplicateFound = true;
                                }
                            }
                            if (!duplicateFound) { // Remove the section text from the array if it doesn't involve duplicate reports
                                sectionContent.splice(i, 1);
                            }

                        } else { // If the section text has no UserAN

                            sectionContent.splice(i, 1); // Remove the section text from the array

                        }
                        duplicateFound = false; // Reset
                    }
                    //console.log('Templates that involve potential duplicate reports:');
                    //console.log(dupTemplates);
                    //console.log('Section contents that involve the templates:');
                    //console.log(sectionContent);

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

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

                        // Return an empty string
                        return {'wikitext': ''};

                    } else { // If there're duplicate reports

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

                        // Highlight all the duplciate UserAN occurences
                        sectionContent = sectionContent.join(''); // Merge the separate sections
                        for (let i = 0; i < dupTemplates.length; i++) {
                            sectionContent = sectionContent.replaceAll2(dupTemplates[i], '<span style="background-color: #FEC493">' + dupTemplates[i] + '</span>');
                        }
                        
                        // Get rid of logids that can be converted to usernames from the array 'reportees'
                        for (let i = reportees.length -1; i >= 0; i--) {
                            if (String(reportees[i]).match(/^\d+$/) !== null && Logids[reportees[i]] !== undefined) {
                                reportees.splice(i, 1);
                            }
                        }
                        
                        // Return wikitext to fetch preview from
                        return {
                            'wikitext': sectionContent,
                            'reportees': reportees
                        };

                    }

                }

                // Function to get the latest revision of the administrator's noticeboard
                async function getLastestRevision() {
                    return new Promise(function(resolve) {

                        msgEditing =
                        `<p>最新版を取得しています${toggleLoadingSpinner('add')}</p>`;
                        $('.anr-editing').append(msgEditing);

                        new mw.Api().get({
                            'action': 'query',
                            'titles': ep.pageToEdit,
                            'prop': 'revisions',
                            'curtimestamp': true,
                            'formatversion': 2
                        }).then(function(res){

                            if (res && res.query && res.query.pages) { // If the latest revision is successfully retrieved
                                if (res.query.pages[0].missing !== true) { // If the page exists

                                    // Get the timestamps of the latest revision and the API query
                                    const baseTS = res.query.pages[0].revisions[0].timestamp; // The TS of the latest revision
                                    const curTS = res.curtimestamp; // The TS of the API query

                                    // Update message on the dialog
                                    msgEditing =
                                    '   <p style="color: MediumSeaGreen">取得に成功しました</p>' +
                                    `   <p>セクション番号を取得しています${toggleLoadingSpinner('move')}</p>`;
                                    $('.anr-editing').append(msgEditing);

                                    // Return the timestamps as an object
                                    resolve({
                                        'baseTS': baseTS,
                                        'curTS': curTS
                                    });

                                } else { // If the page doesn't exist

                                    msgDone = 
                                    toggleLoadingSpinner('remove') +
                                    '<p style="color: MediumVioletRed">エラー: 報告先のページが存在しません</p>' + 
                                    manualEdit();
                                    $('.anr-editing').append(msgDone);
                                    
                                    editDone($dialog, true, wikiPagename);
                                    resolve();

                                }

                            } else { // If revision retrieval fails
                                queryFailed();
                                resolve();
                            }

                        });

                    });
                }

                // Function to get the section number from the section title 
                async function getSectionNumber() {
                    return new Promise(function(resolve) {

                        new mw.Api().get({
                            'action': 'parse',
                            'page': ep.pageToEdit,
                            'formatversion': 2
                        }).then(function(res){

                            if (res && res.parse && res.parse.sections) { // If the section list is successfully retrieved

                                // Get the titles of all sections and their section numbers
                                let sectionsAPI = {};
                                for (let i = 0; i < Object.keys(res.parse.sections).length; i++) {
                                    sectionsAPI[res.parse.sections[i].line] = res.parse.sections[i].index;
                                }
                                
                                // Return a section number if the section is found, undefined if not
                                const sectionNum = sectionsAPI[ep.sectionToEdit];
                                if (sectionNum === undefined) { // If section title in the dropdown is not found

                                    // Show the details of the error
                                    sectionNotFound();
                                    resolve();

                                } else { // If section title in the dropdown is found

                                    // Update message
                                    msgEditing =
                                    '   <p style="color: MediumSeaGreen">取得に成功しました</p>' +
                                    `   <p>最新版のテキストを取得しています${toggleLoadingSpinner('move')}</p>`;
                                    $('.anr-editing').append(msgEditing);

                                    resolve(sectionNum);

                                }

                            } else { // If the section list retrieval fails
                                queryFailed();
                                resolve();
                            }

                        });

                    });
                }

                // Function to get the text to replace with the current text in the section
                async function getTextToReplace(sectionNum) {
                    return new Promise(function(resolve) {

                        // Get the text of the latest revision
                        new mw.Api().get({
                            'action': 'parse',
                            'page': ep.pageToEdit,
                            'section': sectionNum,
                            'prop': 'wikitext',
                            'formatversion': 2
                        }).then(function(res){

                            if (res && res.parse) {

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

                                // Get the whole text to append
                                const wikitextObtained = res.parse.wikitext;
                                let wholeTextToSubmit;
                                const delimiter = '<!-- ◆';
                                const miscHeader = `{{bgcolor|#eee|{{Visible anchor|他${today()}}}|div}}`;

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

                                    // Add div if the target section is 'その他' but lacks div for the current date
                                    if (ep.sectionToEdit === 'その他' && wikitextObtained.indexOf(miscHeader) === -1) {
                                        ep.textToSubmit = '; ' + miscHeader + '\n\n' + ep.textToSubmit;
                                    }

                                    // Insert text into the right place
                                    let wikitextSplit = wikitextObtained.split(delimiter);
                                    if (wikitextSplit.length === 3) {

                                        wholeTextToSubmit = 
                                        wikitextSplit[0] + delimiter + wikitextSplit[1].trim2() + '\n\n' +
                                        ep.textToSubmit + '\n\n' + delimiter + wikitextSplit[2];
                                        resolve(wholeTextToSubmit);

                                    } else { // If the structure of the section has been changed

                                        // Show error and quit the procedure
                                        msgDone =
                                        toggleLoadingSpinner('remove') +
                                        '<p style="color: MediumVioletRed">報告に失敗しました</p>' +
                                        '<p>セクション構造が改変されているため、' +
                                        `<a href="${mw.util.getUrl('User talk:Dragoniez/AN Reporter')}" target="_blank">開発者</a>` +
                                        'に連絡をお願いします</p>' +
                                        manualEdit();
                                        $('.anr-editing').append(msgDone);

                                        editDone($dialog, true, wikiPagename);
                                        resolve();

                                    }

                                } else { // If the target is WP:AN/I or WP:AN/3RR

                                    wholeTextToSubmit = wikitextObtained.trim2() + '\n\n' + ep.textToSubmit;
                                    resolve(wholeTextToSubmit);

                                }

                            } else { // If wikitext retrieval fails
                                queryFailed();
                                resolve();
                            }

                        });

                    });
                }

                // Function to edit the page
                function edit(sectionNum, text, baseTS, curTS) {
                    
                    $.ajax({
                        'url': mw.util.wikiScript('api'),
                        'data': {
                            'format': 'json',
                            'action': 'edit',
                            'title': ep.pageToEdit,
                            'section': sectionNum,
                            'text': text,
                            'summary': ep.editSummary,
                            'basetimestamp': baseTS,
                            'starttimestamp': curTS,
                            'token': debugMode.causeIntentionalError ? '': mw.user.tokens.get('csrfToken'),
                            'curtimestamp': true
                        },
                        'dataType': 'json',
                        'type': 'POST',
                        success: function(res) {

                            // If the edit was successful 
                            if (res && res.edit && res.edit.result == 'Success') {

                                // Show message
                                toggleLoadingSpinner('remove');
                                $('.anr-editing').append(`<p style="color: MediumSeaGreen">報告が完了しました</p>`);
                                editDone($dialog, false, wikiPagename);

                            // If the edit failed
                            } else if (res && res.error) { 
                                
                                // Show the details of the error
                                msgDone =
                                toggleLoadingSpinner('remove') +
                                '<p style="color: MediumVioletRed">報告に失敗しました</p>' +
                                '<br>' +
                                '<p>詳細:</p>' + 
                                `<p>${res.error.info}</p>` +
                                manualEdit();
                                $('.anr-editing').append(msgDone);

                                editDone($dialog, true, wikiPagename);

                            // If unknown error occurs
                            } else {

                                // Show message
                                msgDone =
                                toggleLoadingSpinner('remove') +
                                '<p style="color: MediumVioletRed">不明なエラーが発生しました</p>' + 
                                manualEdit();
                                $('.anr-editing').append(msgDone);

                                editDone($dialog, true, wikiPagename);

                            }

                        }
                    });

                }

                // Function to execute report
                async function reportUsers() {

                    // Check the block status of the reportees if the checkbox is checked
                    let blocked = [];
                    if (checkBlockStatusBeforeEdit) {
                        blocked = await preeditBlockStatusQuery(); // Query who's blocked
                    }

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

                        // Update dialog buttons
                        $dialog.dialog({
                            'buttons': [{

                                'text': '続行',
                                'click': function(){
                                    $(this).dialog({'buttons': [] });
                                    reportUsersSecondProcedure();
                                }

                            }, {

                                'text': '戻る',
                                'click': function(){
                                    $('#anr-modal-dialog form').css('display', 'block');
                                    $('.anr-editing').remove();
                                    $dialog.dialog({
                                        'width': 'auto',
                                        'buttons': [{
                                            'text': 'プレビュー',
                                            'click': previewBtn
                                        }, {
                                            'text': '報告',
                                            'click': reportBtn
                                        }]
                                    });
                                }

                            }, {

                                'text': '中止',
                                'click': function(){
                                    $(this).dialog('close');
                                }

                            }] 
                        });

                    } else { // If no one is blocked

                        reportUsersSecondProcedure();

                    }
                }

                async function reportUsersSecondProcedure() {

                    // Check duplicate reports if the checkbox is checked
                    let dr;
                    if (checkDuplicateReportsBeforeEdit) {
                        dr = await preeditDuplicateReportQuery();
                    }

                    switch(dr.wikitext) {
                        case null: // Sections aren't found
                            return;
                        case undefined: // checkDuplicateReportsBeforeEdit === false
                        case '': // No duplicate report found
                            reportUsersFinalProcedure();
                            return;
                        default: // Possible duplicate reports present

                            // Update dialog buttons
                            $dialog.dialog({
                                'buttons': [{

                                    'text': '確認',
                                    'click': function() {
                                        previewDuplicateReports(dr.wikitext, dr.reportees);
                                    }

                                }, {

                                    'text': '続行',
                                    'click': function(){
                                        $(this).dialog({'buttons': [] });
                                        reportUsersFinalProcedure();
                                    }

                                }, {

                                    'text': '戻る',
                                    'click': function(){
                                        $('#anr-modal-dialog form').css('display', 'block');
                                        $('.anr-editing').remove();
                                        $dialog.dialog({
                                            'width': 'auto',
                                            'buttons': [{
                                                'text': 'プレビュー',
                                                'click': previewBtn
                                            }, {
                                                'text': '報告',
                                                'click': reportBtn
                                            }]
                                        });
                                    }

                                }, {

                                    'text': '中止',
                                    'click': function(){
                                        $(this).dialog('close');
                                    }

                                }] 
                            });

                    }

                }

                async function reportUsersFinalProcedure() {

                    const rev = await getLastestRevision();
                    if (rev === undefined) {
                        return;
                    } else {
                        var baseTS = rev.baseTS;
                        var curTS = rev.curTS;
                    }

                    const sectionNum = await getSectionNumber();
                    if (sectionNum === undefined) {
                        return;
                    }

                    const text = await getTextToReplace(sectionNum);
                    if (text === undefined) {
                        return;
                    }

                    edit(sectionNum, text, baseTS, curTS);

                }

                /** 
                 * Function to preview duplicate reports on a new dialog
                 * @param {String} wikitext The wikitext to convert to html and preview
                 * @param {Array} reportees The array of reportees, logids should be converted to usernames if possible beforehand
                 */
                function previewDuplicateReports(wikitext, reportees) {

                    // Show logids in parentheses
                    for (let i = 0; i < reportees.length; i++) {
                        if (Logids[reportees[i]]) { // If the code knows the logid for a user
                            reportees[i] += ' (' + Logids[reportees[i]] + ')'; // Show the logid in parentheses
                        }
                    }

                    // 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">' +
                    `           読み込み中${toggleLoadingSpinner('add')}` +
                    '       </p>' +
                    '       <p id="anr-drpreview-userlist" style="display: none; font-size: larger">' +
                    '           <span style="font-weight: bold">入力された利用者名:</span>' +
                    '              <br>' +
                                reportees.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,
                        'position': { my: 'center', at: 'top', of: window },
                        'open': async function(){

                            // Initialize the design of the dialog
                            dialogCSS();

                            // Convert the wikitext to an html form
                            const wikitextInHtml = await convertWikitextToHtmlFormat(wikitext, '');
                            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');

                            } else {
                                $('#anr-drpreview-loading').text('読み込みに失敗しました').css('color', 'MediumVioletRed');
                                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() {
                    msgDone = 
                    toggleLoadingSpinner('remove') +
                    '<p style="color: MediumVioletRed">取得に失敗しました</p>' +
                    manualEdit();
                    $('.anr-editing').append(msgDone);
                    
                    editDone($dialog, true, wikiPagename);
                }

                // Function to generate the html for the manual edit helper tab
                function manualEdit() {
                    let meHtml =
                    '<br>' +
                    '<p>手動編集用:</p>' +
                    `<textarea disabled rows="4" style="width: 100%">${ep.textToSubmit}</textarea>` +
                    '<br>' +
                    '<p>要約:</p>' + 
                    `<textarea disabled rows="2" style="width: 100%">${ep.editSummary.replace(scriptAd, '')}</textarea>`;
                    return meHtml;
                }

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

                    editDone($dialog, true, wikiPagename);
                }

                // Report the user(s)
                reportUsers();

            }

            /** 
             * Function to extract templates from wikitext
             * @param {String} text The text in which to search for templates
             * @param {String} templateName [Optional] Specify the template name (case-insensitive)
             * @returns {Array} An array of the extracted templates
             */
            function findTemplates(text, templateName) {

                // Split the text with '{{', the head delimiter of templates
                const textSplit = text.split('{{');
                let templates = [];
                //console.log(textSplit);
                
                /* Explanation
                 * If the text to parse is "{{UserAN|t=none|{{Logid|123456789|{{User|EXAMPLE}}|s=}}}} - {{UserAN|USER}} is the same.",
                 * textSplit = ['', 'UserAN|t=none|', 'Logid|123456789|', 'User|EXAMPLE}}|s=}}}} - ', 'UserAN|USER}} is the same.'] 
                 */

                if (textSplit.length === 0) { // If the text has no tempalte in it            

                    return templates; // Return an empty array

                } else { // If the text has some templates in it

                    let nest = []; // Array to store element numbers of the split segments involving nested templates 

                    for (let i = 1; i < textSplit.length; i++) { // Loop through all elements (but arr[0]) in the split array

                        let TLCloseCnt = (textSplit[i].match(/\}\}/g) || []).length; // Get the number of '}}' in the split segment
                        let temp = ''; // Temporary escape hatch

                        switch(true) {

                            case TLCloseCnt === 0: // If the split segment doesn't have any '}}' (= it nests another template)

                                nest.push(i); // Push the element number into the array
                                break;

                            case TLCloseCnt === 1: // If the split segment itself is the whole of a template

                                temp = '{{' + textSplit[i].split('}}')[0] + '}}';
                                if (!isInArray(temp, templates)) {
                                    templates.push(temp);
                                }
                                break;

                                /* Explanation
                                 * If textSplit[3] ( = 'User|EXAMPLE}}|s=}}}} - '), .split('}}') returns 
                                 * ['User|EXAMPLE', '|s=', '', ' - ']. Thus '{{' + splitArr[0] +'}}' returns '{{User|EXAMPLE}}'.
                                 */					

                            case TLCloseCnt > 1: // If templates are nested

                                for (let j = 0; j < TLCloseCnt; j++) { // Loop through all the nests

                                    if (j === 0) { // The innermost template

                                        temp = '{{' + textSplit[i].split('}}')[j] + '}}'; // Same as when TLCloseCnt === 1
                                        if (!isInArray(temp, templates)) {
                                            templates.push(temp);
                                        }

                                    } else { // Nesting templates

                                        let elNum = nest[nest.length -1];
                                        nest.pop();
                                        let curSegSplit = textSplit[i].split('}}'); // Array of the current segment split by '}}' 

                                        temp = '{{' + textSplit.slice(elNum, i).join('{{') + '{{' + curSegSplit.slice(0, j +1).join('}}') + '}}';
                                        if (!isInArray(temp, templates)) {
                                            templates.push(temp);
                                        }

                                    }
                   
                                }
                                break;

                            default:
                                // Do nothing
                        }

                    }
                    //console.log('Templates in the section:');
                    //console.log(templates);

                    // Check if the optional parameter is specified
                    if (templateName !== undefined && templates.length !== 0) {
                        let templateRegExp = new RegExp(templateName, 'i');
                        for (let i = templates.length -1; i >= 0; i--) {
                            if (templates[i].split('|')[0].search(templateRegExp) === -1) { // If the array element doesn't contain the template name
                                templates.splice(i, 1); // Remove the element
                            }
                        }
                    }

                    //console.log('UserAN occurrences in the section:');
                    //console.log(templates);

                    return templates;
                }
            }

            /**
             * Function to add/remove/move a loading spinner
             * @param {string} action - 'add', 'remove', or 'move'
             */
            function toggleLoadingSpinner(action) {

                const img = '<img ' +
                                'class="anr-loading-spinner" ' +
                                'src="https://upload.wikimedia.org/wikipedia/commons/4/42/Loading.gif" ' +
                                'style=' +
                                    'vertical-align: middle; ' +
                                    'max-height: 100%; ' +
                                    'border: 0;" ' +
                            '/>';

                switch(action) {
                    case 'add':
                        return img;
                    case 'remove':
                        $('.anr-loading-spinner').remove();
                        return '';
                    case 'move':
                        $('.anr-loading-spinner').remove();
                        return img;
                }

            }

            /**
             * Action for when edit is done (in any way)
             * @param {*} $dialog 
             * @param {Boolean} editFailed 
             * @param {String} wikiPagename 
             */
            function editDone($dialog, editFailed, wikiPagename) {

                // Get the page name without a section specifier
                let tarPage = wikiPagename.split('#')[0];

                // Buttons to show on the dialog when the edit attempt is done
                let btns = [];
                let destBtn, closeBtn;

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

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

                // Show the button(s) on the dialog
                $dialog.dialog({ 
                    'position': { my: 'center', at: 'top', of: window },
                    'buttons': btns
                });

            }

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

                let inputRemains = true;
                let i = 1;
                let arrOfContribs = [];
                let contribs, type, reportee;

                // Check content of all inputs into which usernames are typed
                while (inputRemains) { 

                    if ($(`#anr-user${i}-input`).length === 0) { // if selector doesn't exist
                        inputRemains = false; // No inputs remain to be checked
                    } else {

                        type = $(`#anr-user${i}-select`).children('option').filter(':selected').text(); // UserAN type specified in the dropdown
                        reportee = $(`#anr-user${i}-input`).val().trim2(); // input value

                        if (reportee !== '') { // Skip if the input value is a null string

                            // Get appropriate links depending on the UserAN type
                            switch(type) {
                                case 'UNL':
                                case 'User2':
                                case 'IP2':
                                    contribs = `[[特別:投稿記録/${reportee}|${reportee}]]`;
                                    break;
                                case 'logid':
                                    contribs = `[[特別:転送/logid/${reportee}|Logid/${reportee}]]`;
                                    break;
                                case 'diff':
                                    contribs = `[[特別:差分/${reportee}|差分/${reportee}]]の投稿者`;
                                    break;
                                default:
                                    contribs = reportee;
                            }

                            // Push the link into the array
                            if (!isInArray(contribs, arrOfContribs)) {
                                arrOfContribs.push(contribs);
                            }

                        }
                        i++;

                    }

                }

                // Get edit summary
                let textToShow = '';
                if (arrOfContribs.length === 0) {
                    // Do nothing
                } else if (arrOfContribs.length === 1) {
                    textToShow += '+' + arrOfContribs[0] + ' - ';
                } else if (arrOfContribs.length > 1 && arrOfContribs.length < 4) {
                    textToShow += '+' + arrOfContribs.join(', ') + ' - ';
                } else {
                    textToShow += '+' + arrOfContribs.length + ' - ';
                }

                return textToShow;

            }

            // 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(getSectionI(false));
                        $('#anr-section-i-select').css({'width': $(this).width()});
                        $('#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).width()});
                        //$('#select2-anr-section-s-select-container').attr('style', rlsCSS);
                        $('#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;
                }
            });

            // 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(){
                
                let 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 = $(inputID).val().trim2(); // 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);
                        $(idlinkDivID).css('display', 'block');
                        $(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 = $(inputID).val().trim2();
                const idlinkID = checkboxID.replace('checkbox', 'idlink'); // #anr-userX-idlink
                const idlinkDivID = checkboxID.replace('checkbox', 'idlink-div'); // #anr-userX-idlink-div

                if ($(checkboxID).is(':checked')) { // if the checkbox is checked

                    // Function to update type dropdown
                    let updateDropdown = 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);
                    }

                    if (Logids[inputVal] !== undefined) {

                        $(inputID).val(Logids[inputVal]); // if the object knows the logid for the user, retrieve the data
                        updateDropdown(Logids[inputVal]);

                    } else {

                        // if the object doesn't know the logid for the user, ask the API
                        async function logidApi(){

                            // Get logid from the API
                            const logid = await getLogid(inputVal);

                            setTimeout(function(){ // Deliberately setting lag to prevent bugs

                                // Check the obtained logid 
                                if (logid === undefined) { // If undefined is returned, reject the checking of the checkbox
                                    alert('エラー\n\n取得可能なlogidが存在しません。Logidを手動で入力するか、type=diff または none を使用してください');
                                    $(checkboxID).prop('checked', false);
                                    return;
                                } else { // If a valid logid is returned

                                    // Set the logid to the input
                                    $(inputID).val(logid);

                                    // Push username and logid into object if it doesn't have them
                                    if (Logids[inputVal] === undefined) {
                                        Logids[inputVal] = logid;
                                    }
                                    if (Logids[logid] === undefined) {
                                        Logids[logid] = inputVal;
                                    }

                                    // Update type dropdown
                                    updateDropdown(logid);
                                }

                            }, 100);
                        }
                        logidApi();

                    }

                } else { // if the checkbox is unchecked

                    if (Logids[inputVal] !== undefined) { 

                        $(inputID).val(Logids[inputVal]); // if the object knows the username for the logid, retrieve the data
                        $(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
            let userCnt = 1;
            $('#anr-addBtn').click(function(){

                userCnt++;
                let replaceTar = new RegExp(`${userCnt-1}-`, 'g');
                userHtml = userHtml.replace(replaceTar, `${userCnt}-`); // 1 → 2, 2 → 3 and so forth
                $('#anr-btn-div').before(userHtml);
                $(`#anr-user${userCnt}-input-div`).css('margin-top', '0.2em');

            });

            // 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

                    // Show textarea
                    $textarea.css('display','inline-block').val(genEditSummary()); 

                } else { // Box is unchecked
                    $textarea.css('display','none').val('');
                }
            });

            // When 'check block status before edit' is (un)checked
            $('#anr-blockstatus-checkbox').change(function(){

                if ($(this).is(':checked')) { // Box is checked
                    checkBlockStatusBeforeEdit = true;
                } else { // Box is unchecked
                    checkBlockStatusBeforeEdit = false;
                }

            });

            // When 'check dpulicate reports before edit' is (un)checked
            $('#anr-duplicatereport-checkbox').change(function(){

                if ($(this).is(':checked')) { // Box is checked
                    checkDuplicateReportsBeforeEdit = true;
                } else { // Box is unchecked
                    checkDuplicateReportsBeforeEdit = false;
                }

            });

        }); // addPortletLink

        // Function to get the last day of the month
        let lastDay = function(y,m){
            return new Date(y, m +1, 0).getDate();
        }

        /**
         * Function to get the current date and the section name to which to report
         * @param {Boolean} last - if true, returns the name of the last section
         * @returns {String} - section name
         */
        function getSectionI(last){

            const d = new Date();
            if (last) {
                let subtract = 5;
                if (d.getDate() === 1 || d.getDate() === 2) {
                    subtract = 3;
                } else if (d.getDate() === 31) {
                    subtract = 6;
                }
                d.setDate(d.getDate() - subtract);
            }

            let sectionName;
            switch(true) {
                case (1 <= d.getDate() && d.getDate() <= 5):
                    sectionName = `${d.getFullYear()}年${d.getMonth()+1}月1日 - 5日新規報告`;
                    break;
                case (6 <= d.getDate() && d.getDate() <= 10):
                    sectionName = `${d.getFullYear()}年${d.getMonth()+1}月6日 - 10日新規報告`;
                    break;
                case (11 <= d.getDate() && d.getDate() <= 15):
                    sectionName = `${d.getFullYear()}年${d.getMonth()+1}月11日 - 15日新規報告`;
                    break;
                case (16 <= d.getDate() && d.getDate() <= 20):
                    sectionName = `${d.getFullYear()}年${d.getMonth()+1}月16日 - 20日新規報告`;
                    break;
                case (21 <= d.getDate() && d.getDate() <= 25):
                    sectionName = `${d.getFullYear()}年${d.getMonth()+1}月21日 - 25日新規報告`;
                    break;
                case (26 <= d.getDate() && d.getDate() <= lastDay(d.getFullYear(), d.getMonth())):
                    sectionName = `${d.getFullYear()}年${d.getMonth()+1}月26日 - ${lastDay(d.getFullYear(), d.getMonth())}日新規報告`;
                    break;
                default:
            }
            return sectionName;
        }

        // Function to get today's date
        function today() {
            const d = new Date();
            return d.getMonth()+1 + '月' + d.getDate() + '日';
        }

        // Function to check if a user exists locally
        async function userExists(username) {
            return new Promise(function(resolve) {
                new mw.Api().get({
                    'action': 'query',
                    'list': 'users',
                    'ususers': username,
                    'formatversion': 2
                }).then(function(res){
                    resolve(res.query.users[0].userid !== undefined); // True if the user exists and false if not
                });
            });
        }

        // Function to manipulate dropdown options for UserAN types (also maniputes show/hide of checkbox)
        let updateTypeDropdownTimeout;
        function updateTypeDropdown(inputID) {
            
            const tarVal = $(inputID).val().trim2(); // 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 empty
                    
                    $(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

                    $(selectID).prop('disabled', false); // enable dropdown

                    if (mw.util.isIPAddress(tarVal, 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
                        $(checkboxID).prop('checked', false); // uncheck the checkbox
                        $(idlinkDivID).css('display', 'none');
                        toggleBlockStatusLink(inputID, false, false);

                    } else if (await userExists(tarVal)) { // 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
                        $(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).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
                        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, hide the block status link without API requests
         * @param {Boolean} convertLogid - if true, try to convert a logid to a username
         */
        async function toggleBlockStatusLink(inputID, forceHide, convertLogid) {

            let username = $(inputID).val().trim2(); // 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

            if (forceHide && convertLogid) {
                
                if (Logids[username] !== undefined) { // If the object knows who that logid is for 
                    username = Logids[username]; // Convert the logid to the username and continue to API requests
                } else {  // If the object doesn't know who that logid is for 
                    $bsLinkDiv.css('display', 'none'); // Hide the link div and exit function
                    return;
                }

            } else if (forceHide) { // If forceHide is true, basically just hide the bsLink without API requests

                $bsLinkDiv.css('display', 'none'); // Hide the link div
                return;

            }

            // Check the block status of the user, and if blocked, update the bsLink, or else, hide the link
            const blocked = await getBlocked([username]);

            if (blocked.length !== 0) { // If the user typed into the input 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

            }

        }

        // Function to get an array of blocked users & IPs from an array of random users & IPs
        async function getBlocked(namesArr) {

            let users = [], ips = [], blocked = [];

            // Sort names to users and IPs
            for (i = 0; i < namesArr.length; i++) {
                if (mw.util.isIPAddress(namesArr[i], true)) { // Push IPs into the array
                    ips.push(namesArr[i]);
                } else { // Push users into the array
                    users.push(namesArr[i]);
                }
            }

            // Check who's (b)locked
            if (users.length !== 0 && ips.length !== 0) { // If namesArr contains both users and IPs

                // Check local block status
                blocked = blocked.concat(
                    await getBlockedUsers(users),
                    await getBlockedIps(ips)
                );

                // Remove users/IPs that are already in the array 'blocked' (to make the code faster)
                for (let i = users.length -1; i >= 0; i--) {
                    if (isInArray(users[i], blocked)) {
                        users.splice(i ,1);
                    }
                }
                for (let i = ips.length -1; i >= 0; i--) {
                    if (isInArray(ips[i], blocked)) {
                        ips.splice(i ,1);
                    }
                }

                // Check global (b)lock status
                blocked = blocked.concat(
                    await getGloballyLockedUsers(users),
                    await getGloballyBlockedIps(ips)
                );

            } else if (users.length !== 0) { // If namesArr only contains users

                // Check local block status
                blocked = blocked.concat(await getBlockedUsers(users));

                // Remove users that are already in the array 'blocked' (to make the code faster)
                for (let i = users.length -1; i >= 0; i--) {
                    if (isInArray(users[i], blocked)) {
                        users.splice(i ,1);
                    }
                }

                // Check global lock status
                blocked = blocked.concat(await getGloballyLockedUsers(users));

            } else if (ips.length !== 0) { // If namesArr only contains IPs

                // Check local block status
                blocked = blocked.concat(await getBlockedIps(ips));

                // Remove IPs that are already in the array 'blocked' (to make the code faster)
                for (let i = ips.length -1; i >= 0; i--) {
                    if (isInArray(ips[i], blocked)) {
                        ips.splice(i ,1);
                    }
                }

                // Check global block status
                blocked = blocked.concat(await getGloballyBlockedIps(ips));

            } else {
                // Do nothing
            }

            return blocked;

        }

        // Function to get an array of blocked users from an array of random users
        async function getBlockedUsers(usersArr) {

            if (usersArr.length === 0) return [];
            let blockedArr = [];

            return new Promise(function(resolve) {
                new mw.Api().post({
                    'action': 'query',
                    'list': 'blocks',
                    'bklimit': usersArr.length,
                    'bkusers': usersArr.join('|'),
                    'bkprop': 'user',
                    'formatversion': 2
                }).then(function(res){

                    const blockstatusApi = res.query.blocks;

                    if (blockstatusApi.length === 0) { // If none of the users is blocked

                        resolve(blockedArr); // Return []
                        
                    } else { // If there are one or more blocked users

                        for (let i = 0; i < blockstatusApi.length; i++) {
                            blockedArr.push(blockstatusApi[i].user); // Push blocked users into the array
                        }
                        resolve(blockedArr); // Return e.g. [user1, user2...]
                        
                    }

                });
            });

        }

        /**
         * Function to get an array of locked users from an array of random users
         * @param {Array} usersArr 
         * @returns {Array}
         */
        async function getGloballyLockedUsers(usersArr) {
            if (usersArr.length === 0) return [];
            let lockedArr = [];
            for (let i = 0; i < usersArr.length; i++) {
                const locked = await userIsLocked(usersArr[i]);
                if (locked) {
                    lockedArr.push(usersArr[i]);
                }
                if (i === usersArr.length -1) {
                    return lockedArr;
                }
            }
        }

        /**
         * Function to check if a user is globally locked
         * @param {String} user 
         * @returns {Boolean}
         */
        async function userIsLocked(user) {
            return new Promise(function(resolve) {
                new mw.Api().get({
                    action: 'query',
                    list: 'globalallusers',
                    agulimit: 1,
                    agufrom: user,
                    aguto: user,
                    aguprop: 'lockinfo'
                }).then(function(res) {
                    if (res.query.globalallusers.length === 0) {
                        // If the length is 0, then we couldn't find the global user
                        resolve(false);
                    }
                    // If the 'locked' field is present, then the user is locked
                    resolve(res.query.globalallusers[0].locked !== undefined);
                });
            });
        }

        /**
         * Function to get an array of locally blocked IPs from an array of random IPs
         * @param {Array} ipsArr 
         * @returns {Array}
         */
        async function getBlockedIps(ipsArr) {
            if (ipsArr.length === 0) return [];
            let blockedArr = [];
            for (let i = 0; i < ipsArr.length; i++) {
                const blocked = await ipIsBlocked(ipsArr[i]);
                if (blocked) {
                    blockedArr.push(ipsArr[i]);
                }
                if (i === ipsArr.length -1) {
                    return blockedArr;
                }
            }
        }

        /**
         * Function to check if a given IP is locally blocked
         * @param {String} ip 
         * @returns {Boolean}
         */
        async function ipIsBlocked(ip) {
            return new Promise(function(resolve) {
                new mw.Api().get({
                    'action': 'query',
                    'list': 'blocks',
                    'bklimit': 1,
                    'bkip': ip, // This parameter doesn't take multiple, pipe-separeted arguments, unlike the param 'bkusers'
                    'bkprop': 'user', // Get the blocked IP: Could be a range; Currently this value is not used in the function
                    'formatversion': 2
                }).then(function(res){
                    resolve(res.query.blocks.length !== 0);
                });
            });
        }

        /**
         * Function to get an array of globally blocked IPs from an array of random IPs
         * @param {Array} ipsArr 
         * @returns {Array}
         */
        async function getGloballyBlockedIps(ipsArr) {
            if (ipsArr.length === 0) return [];
            let blockedArr = [];
            for (let i = 0; i < ipsArr.length; i++) {
                const blocked = await ipIsGloballyBlocked(ipsArr[i]);
                if (blocked) {
                    blockedArr.push(ipsArr[i]);
                }
                if (i === ipsArr.length -1) {
                    return blockedArr;
                }
            }
        }

        /**
         * Function to check if a given IP is globally blocked
         * @param {String} ip 
         * @returns {Boolean}
         */
        async function ipIsGloballyBlocked(ip) {
            return new Promise(function(resolve) {
                new mw.Api().get({
                    'action': 'query',
                    'list': 'globalblocks',
                    'bgip': ip,
                    'bglimit': 1,
                    'bgprop': 'address'
                }).then(function(res){
                    resolve(res.query.globalblocks.length !== 0);
                });
            });
        }

        // Function to get account creation logid
        async 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){
                    if (res.query.logevents.length === 0) { // If empty array is returned (=if no logevent exists)
                        resolve();
                    } else {
                        resolve(res.query.logevents[0].logid);
                    }
                });
            });
        }
        
    } // if (userIsInGruoup)

    /**
     * Function to check if an element is in an array
     * @param {String} el 
     * @param {Array} arr 
     * @returns {Boolean}
     */
    function isInArray (el, arr) {
        return arr.indexOf(el) !== -1;
    }
    
    /**
     * Function to check if elements of an array are all contained in another array
     * @param {Array} arr1 
     * @param {Array} arr2
     * @returns {Boolean} 
     */
    function arrayIsInArray(arr1, arr2) {
        for (let i = 0; i < arr1.length; i++) {
            if (!isInArray(arr1[i], arr2)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Function to check what user group the CURRENT USER belongs to (thus it doesn't take a username parameter)
     * @param {String} group - a group name such as 'autoconfirmed' and 'sysop'
     * @returns {Boolean}
     */
    function userIsInGroup(group) {
        return isInArray(group, mw.config.get('wgUserGroups'));
    }

    /**
     * Function to check if a string contains a substring that is an element of an array
     * @param {String} str
     * @param {Array} arr 
     * @returns {Boolean}
     */
    function stringContainsElementInArray(str, arr) {
        for (let i = 0; i < arr.length; i++) {
            if (str.indexOf(arr[i]) !== -1) {
                return true;
            }
        }
        return false;
    }

});
//</nowiki>