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

削除された内容 追加された内容
v4.8: LTA名称の取得中にスピナーが表示される機能を追加ほか(スピナーが消えてから報告先の選択を行えば、WP:AN/Sへの報告時にLTA名称が必ず正常に表示されます)
v5.0: グローバルブロックとグローバルロックの検知機能を追加
2行目:
* AN Reporter (ANR)
* Author: Dragoniez
* Version: 45.80
*************************************/
//<nowiki>
51行目:
replaced = replaced.split(arguments[i]).join(arguments[i + 1]);
}
 
}
return replaced;
132行目:
let Logids = {}; // Object to store usernames and their corresponding logids
let checkBlockStatusBeforeEdit = true;
let checkDuplicateReportsBeforeEdit = true;
 
// Add ANR tab
162行目:
'border-radius: 10%;' ;
/* Experimental design
const lsCSS = // Eliminate gap between inline-blocks
'letter-spacing: -1em;' +
'white-space: nowrap' ;
168行目:
'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:管理者伝言板/投稿ブロック/いせちか';
178 ⟶ 179行目:
const KIYOSHIMA = 'Wikipedia:管理者伝言板/投稿ブロック/清島達郎';
const SHINJU = 'Wikipedia:管理者伝言板/投稿ブロック/真珠王子';
 
// Username input
let userHtml =
//<div classid="anr-user-div">
` <div id="anr-user1-input-div">` +
` <label for="anr-user1-input" style="${labelCSS}">利用者</label>` +
211 ⟶ 212行目:
// The whole html contour
const modalHtml =
`<div classid="anr-modal-dialog" title="AN Reporter" style="max-height: 80vh;">` +
` <div classid="anr-modal-header">` +
` <h2>利用者を報告</h2>` +
` </div>` +
` <div classid="anr-modal-body">` +
` <form>` +
` <div classid="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}">` +
225 ⟶ 226行目:
` <option>${AN3RR}</option>` +
` </select>` +
` <div classid="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 classid="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}">` +
241 ⟶ 242行目:
` </select>` +
` </div>` +
` <div classid="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}">` +
256 ⟶ 257行目:
` </select>` +
` </div>` +
` <div classid="anr-user-div" style="${marginCSS}">` +
userHtml +
` <div classid="anr-btn-div">` +
` <button type="button" classid="anr-addBtn" style="${btnCSS}">追加</button>` +
` </div>` +
` </div>` +
` <div classid="anr-predefinedreasons-div" style="${marginCSS} display: none;">` +
` <label for="anr-predefinedreasons-select" style="${labelCSS}">定型文</label>` +
` <select id="anr-predefinedreasons-select">` +
270 ⟶ 271行目:
` </select>` +
` </div>` +
` <div classid="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 classid="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 classid="anr-checkbox-div" style="${marginCSS}">` +
` <input checked id="anr-blockstatus-checkbox" type="checkbox">` +
` <label for="anr-blockstatus-checkbox">報告前にブロック状態をチェック</label>` +
292 ⟶ 293行目:
// Add the frame div to the page
$('body').append(modalHtml);
 
// Show dialog
$('.#anr-modal-dialog').dialog({
'resizable': false,
'height': 'auto',
320 ⟶ 321行目:
// Initialize the design of the dialog
dialogCSS();
 
// Show VIP list
VIPListgetVipList();
 
// 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
if (!username || username === mw.config.get('wgUserName')) {return;
return;
}
 
/* Initialize the username input and type dropdown
441 ⟶ 440行目:
}
// Function to change the CSS of the dialog and to get predifined reasons
function dialogCSS() {
 
// CSS for the dialog
$('.ui-dialog-content, .ui-dialog-buttonpane, .ui-corner-all, .ui-draggable, .ui-resizable').css('background', '#FFF0E4');
$('.ui-button').css({
452 ⟶ 449行目:
$('.ui-dialog-titlebar, .ui-dialog-titlebar-close').attr('style', 'background: #FEC493 !important;');
$('.ui-dialog').css('font-size', fSize);
 
}
 
471 ⟶ 467行目:
}
$reasons.children('optgroup').append(reasonsArr.join(''));
$('.#anr-predefinedreasons-div').css('display', 'block');
 
} else { // If the fixed reasons are NOT prepared as an array
481 ⟶ 477行目:
 
// WP:VIP list (for copy to clipboard)
async function VIPListgetVipList() {
return new Promise(function(resolve) {
new mw.Api().get({
'action': 'parse',
'page': 'Wikipedia:進行中の荒らし行為'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 resolve(mw.log.error('VIP list: There\'s no VIP to fetch.'));
} else {
 
// Show the VIP list on the dialog
const VIPListHtmlvipListHtml =
'<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(VIPListHtmlvipListHtml);
$('#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(return mw.log.error('VIP list: FailedThe toAPI retrievereturned dataan fromunresolvable the APIobject.'));
});
 
}).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)
let users = [], types = [], duplicates = [];
let types = [];
let duplicates = [];
let usernameInInput = '';
let selectedType = '';
let tempUsername = ''; // An escape hatch for username (for t=logid)
 
587 ⟶ 577行目:
} 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
643 ⟶ 633行目:
// 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) {
sectionToEditconst 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 (reason.substring(reason.length - 4) !== '~~~~') {
reason = reason += '--~~~~';
}
 
704 ⟶ 695行目:
const editSummarySection = '/*' + sectionToEdit + '*/';
 
letconst editSummary =
$('#anr-summary-text').val().trim2() === '' ?
editSummarySection + genEditSummary().replace(' - ', '') + scriptAd:
739 ⟶ 730行目:
 
// Check if the necessary fields are filled and get edit information
letconst ep = editPrep();
if (ep.rqFieldsEmpty) {
alert('必須項目が入力・選択されていません'); // Show error and cancel the preview
747 ⟶ 738行目:
// If the inputs have duplicates in them
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 を押してください';
 
if (confirm(confirmMsg) === false) { // If cancelledreturn;
return;
}
 
}
762 ⟶ 748行目:
const ANSMisc = `<a href="${mw.util.getUrl('WP:AN/S#OTH')}" target="_blank">WP:AN/S#その他</a>`;
const previewDiv =
'<div classid="anr-preview-dialog" title="AN Reporter Preview">' +
' <div id="anr-preview-header" style="padding: 0.5em;">' +
' <p id="anr-preview-loading">' +
785 ⟶ 771行目:
// Show preview dialog
$('body').append(previewDiv);
$('.#anr-preview-dialog').dialog({
'height': 'auto',
'width': $('#content').width() * 0.8,
799 ⟶ 785行目:
if (parsed) {
 
letconst previewHtml = parsed.htmltext;
letconst 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();
812 ⟶ 798行目:
$('#anr-preview-loading').text('プレビューの読み込みに失敗しました').css('color', 'MediumVioletRed');
setTimeout(function(){
$('.#anr-preview-dialog').dialog('close');
}, 5000);
}
828 ⟶ 814行目:
 
/**
* ConvertFunction to convert wikitext to its HTML format
* @param {String} wikitext The wikitext to convert
* @param {String} wikisummary The summary to convert
866 ⟶ 852行目:
// If the inputs have duplicates in them
if (ep.duplicates.length !== 0) {
 
const confirmMsg =
'以下の利用者について、重複入力がある可能性があります。\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
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
let editFailed = false; // Boolean value to pass to function when edit attempt is done
 
// 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 buttonbuttons
$dialog.append(msgEditing);
 
951 ⟶ 931行目:
 
if ($(`#anr-user${i}-input`).length === 0) { // if selector is not found
 
break; // exit for
 
} else { // if selector is found
 
1,010 ⟶ 990行目:
});
});
}
const parsed = await getWikitextAndSectionInfo();
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
if (sections[i].index.indexOf('T') === -1) { // If the section isisn't nota loadedtranscluded from another pageone
 
sectiontitles.push(sections[i].line); // Get section titles
 
switch(sections[i].level) { // Get equal-enclosed section headers
case 2:
1,047 ⟶ 1,027行目:
}
}
 
// The sections in which to search for duplicate reports
let tarSections;
1,099 ⟶ 1,079行目:
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++) {
1,175 ⟶ 1,155行目:
logid = undefined; // Reset before the next iteration
}
 
// Extract UserAN templates and find duplicate reports
let templates = [];
1,182 ⟶ 1,162行目:
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|ウィキ助}}, ...] */
1,188 ⟶ 1,168行目:
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
1,200 ⟶ 1,180行目:
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
1,251 ⟶ 1,231行目:
 
}
 
}
 
// Function to get the latest revision of the administrator's noticeboard
async function getLastestRevision() {
 
return new Promise(function(resolve) {
 
1,262 ⟶ 1,241行目:
`<p>最新版を取得しています${toggleLoadingSpinner('add')}</p>`;
$('.anr-editing').append(msgEditing);
 
new mw.Api().get({
'action': 'query',
1,269 ⟶ 1,248行目:
'curtimestamp': true,
'formatversion': 2
}).donethen(function(res){
 
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
letconst baseTS = res.query.pages[0].revisions[0].timestamp; // The TS of the latest revision
letconst curTS = res.curtimestamp; // The TS of the API query
 
// Update message on the dialog
1,298 ⟶ 1,277行目:
$('.anr-editing').append(msgDone);
editFailed =editDone($dialog, true, wikiPagename);
editDone($dialog, editFailed, wikiPagename);
resolve();
 
1,310 ⟶ 1,288行目:
 
});
 
});
}
1,316 ⟶ 1,295行目:
async function getSectionNumber() {
return new Promise(function(resolve) {
 
new mw.Api().get({
'action': 'parse',
'page': ep.pageToEdit,
'formatversion': 2
}).donethen(function(res){
 
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
letconst sectionNum = sectionsAPI[ep.sectionToEdit];
if (sectionNum === undefined) { // If section title in the dropdown is not found
 
1,364 ⟶ 1,343行目:
async function getTextToReplace(sectionNum) {
return new Promise(function(resolve) {
 
// Get the text of the latest revision
new mw.Api().get({
1,372 ⟶ 1,351行目:
'prop': 'wikitext',
'formatversion': 2
}).donethen(function(res){
 
if (res && res.parse) {
1,383 ⟶ 1,362行目:
 
// Get the whole text to append
letconst wikitextObtained = res.parse.wikitext;
let wholeTextToSubmit;
const delimiter = '<!-- ◆';
1,416 ⟶ 1,395行目:
$('.anr-editing').append(msgDone);
 
editFailed =editDone($dialog, true, wikiPagename);
editDone($dialog, editFailed, wikiPagename);
resolve();
 
1,459 ⟶ 1,437行目:
'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, editFailedfalse, wikiPagename);
return;
 
// If the edit failed
1,482 ⟶ 1,459行目:
$('.anr-editing').append(msgDone);
 
editFailed =editDone($dialog, true, wikiPagename);
editDone($dialog, editFailed, wikiPagename);
return;
 
// If unknown error occurs
1,496 ⟶ 1,471行目:
$('.anr-editing').append(msgDone);
 
editFailed =editDone($dialog, true, wikiPagename);
editDone($dialog, editFailed, wikiPagename);
return;
 
}
1,504 ⟶ 1,477行目:
}
});
 
}
 
1,532 ⟶ 1,505行目:
'text': '戻る',
'click': function(){
$('.#anr-modal-dialog form').css('display', 'block');
$('.anr-editing').remove();
$dialog.dialog({
1,601 ⟶ 1,574行目:
'text': '戻る',
'click': function(){
$('.#anr-modal-dialog form').css('display', 'block');
$('.anr-editing').remove();
$dialog.dialog({
1,630 ⟶ 1,603行目:
 
async function reportUsersFinalProcedure() {
 
const rev = await getLastestRevision();
if (rev === undefined) {
1,659 ⟶ 1,632行目:
*/
function previewDuplicateReports(wikitext, reportees) {
 
// Show logids in parentheses
for (let i = 0; i < reportees.length; i++) {
1,668 ⟶ 1,641行目:
 
// Create dialog
letconst duplicateReportPreviewDiv =
'<div classid="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">' +
1,687 ⟶ 1,660行目:
 
// Show preview dialog
$('.#anr-drpreview-dialog').dialog({
'height': 'auto',
'width': $('#content').width() * 0.8,
1,700 ⟶ 1,673行目:
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();
1,710 ⟶ 1,683行目:
$('#anr-drpreview-loading').text('読み込みに失敗しました').css('color', 'MediumVioletRed');
setTimeout(function(){
$('.#anr-drpreview-dialog').dialog('close');
}, 10000);
}
 
},
'buttons': [{
1,733 ⟶ 1,706行目:
$('.anr-editing').append(msgDone);
editFailed =editDone($dialog, true, wikiPagename);
editDone($dialog, editFailed, wikiPagename);
}
 
1,763 ⟶ 1,735行目:
$('.anr-editing').append(msgDone);
 
editFailed =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.']
*/
 
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 that 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)) {
1,817 ⟶ 1,788行目:
}
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
 
1,839 ⟶ 1,810行目:
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) {
1,870 ⟶ 1,841行目:
//console.log('UserAN occurrences in the section:');
//console.log(templates);
 
return templates;
}
1,903 ⟶ 1,874行目:
}
 
//**
* 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 = [];
1,953 ⟶ 1,929行目:
'buttons': btns
});
 
}
 
// Function to generate edit summary automatically
function genEditSummary() {
 
let inputRemains = true;
let i = 1;
let arrOfContribs = [];
let contribs, =type, ''reportee;
let type = '';
let reportee = '';
 
// 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
 
// Get appropriate links depending on the UserAN type
switch(type) {
2,024 ⟶ 1,998行目:
 
// 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();
});
2,035 ⟶ 2,011行目:
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;
2,060 ⟶ 2,036行目:
 
// 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 = '';
let tarPage = '';
if ($(this).attr('id') === 'anr-section-i-select') {
tarPage = ANI;
2,078 ⟶ 2,055行目:
 
// 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
2,088 ⟶ 2,065行目:
const idlinkDivID = selectID.replace('select', 'idlink-div'); // #anr-userX-idlink-div
const idlinkID = selectID.replace('select', 'idlink'); // #anr-userX-idlink
 
switch(valSelected) {
case 'UNL':
2,120 ⟶ 2,097行目:
 
// 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
2,126 ⟶ 2,103行目:
 
});
 
// 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
2,136 ⟶ 2,113行目:
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) {
2,151 ⟶ 2,128行目:
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
letconst 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
2,176 ⟶ 2,153行目:
// 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) {
2,192 ⟶ 2,169行目:
}
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);
2,208 ⟶ 2,185行目:
$(idlinkDivID).css('display', 'none');
toggleBlockStatusLink(inputID, false, false);
 
} else {
 
alert('エラー\n\nLogidにはアカウント作成記録以外のものも含まれるため、logidからユーザー名への変換機能は実装していません。' +
'テキストボックス下のリンク先からユーザー名を取得するか、手動入力してください。なお、ユーザー名からlogidへの変換が行われた' +
2,216 ⟶ 2,193行目:
$(checkboxID).prop('checked', true);
}
 
}
 
2,223 ⟶ 2,200行目:
// 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');
 
2,234 ⟶ 2,211行目:
 
// When buttons are moused on and off
$(document).off('mouseover mouseleave', '.#anr-modal-dialog form button')
.on({
'mouseover': function(e) {
2,242 ⟶ 2,219行目:
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(){
 
letconst $textarea = $('#anr-summary-text');
 
if ($(this).is(':checked')) { // Box is checked
2,295 ⟶ 2,272行目:
function getSectionI(last){
 
letconst d = new Date();
if (last) {
let subtract = 5;
2,305 ⟶ 2,282行目:
d.setDate(d.getDate() - subtract);
}
let sectionName;
 
let sectionName;
switch(true) {
case (1 <= d.getDate() && d.getDate() <= 5):
2,327 ⟶ 2,304行目:
break;
default:
undefined;
}
return sectionName;
2,334 ⟶ 2,310行目:
// Function to get today's date
function today() {
letconst d = new Date();
return d.getMonth()+1 + '月' + d.getDate() + '日';
}
2,346 ⟶ 2,322行目:
'ususers': username,
'formatversion': 2
}).donethen(function(res){
resolve(res.query.users[0].userid !== undefined); // True if the user exists and false if not
});
2,356 ⟶ 2,332行目:
function updateTypeDropdown(inputID) {
letconst tarVal = $(inputID).val().trim2(); // The value typed into the input
letconst selectID = inputID.replace('input', 'select'); // #anr-userX-select
letconst checkboxDivID = inputID.replace('input', 'checkbox-div'); // #anr-userX-checkbox-div
letconst checkboxID = inputID.replace('input', 'checkbox'); // #anr-userX-checkbox
letconst 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
2,372 ⟶ 2,348行目:
$(idlinkDivID).css('display', 'none'); // Hide logid/diff link
toggleBlockStatusLink(inputID, true, false);
 
} else { // if the field is filled
 
2,415 ⟶ 2,391行目:
 
}
 
}
 
}, 350);
}
2,430 ⟶ 2,406行目:
 
let username = $(inputID).val().trim2(); // The value in the input
letconst $bsLinkDiv = $(inputID.replace('input', 'blockstatus-div')); // #anr-userX-blockstatus-div
letconst $bsLink = $(inputID.replace('input', 'blockstatus')); // #anr-userX-blockstatus
 
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
letconst blocked = await getBlocked([username]);
 
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) {
 
let users = [], ips = [], blocked = [];
let ips = [];
let blocked = [];
 
// Sort names to users and IPs
2,483 ⟶ 2,455行目:
}
 
// Check who's blocked(b)locked
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
 
// 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 {
return blocked; // [], emptyDo arraynothing
}
 
// Sort thereturn blocked in the original order;
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) {
 
if (usersArr.length === 0) return [];
let blockedArr = [];
 
// API query
return new Promise(function(resolve) {
new mw.Api().getpost({
'action': 'query',
'list': 'blocks',
2,519 ⟶ 2,534行目:
'bkprop': 'user',
'formatversion': 2
}).donethen(function(res){
 
letconst blockstatusApi = res.query.blocks;
 
if (blockstatusApi.length === 0) { // If none of the users is blocked
2,538 ⟶ 2,553行目:
});
});
 
}
 
//**
* Function to get an array of blockedlocked IPsusers from an array of random IPsusers
* @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 = [];
let blocked;
for (let i = 0; i < ipsArr.length; i++) {
const blocked = await ipIsBlocked(ipsArr[i]);
if (blocked) {
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) {
return new Promise(function(resolve) {
new mw.Api().get({
'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
'formatversion': 2
}).donethen(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);
});
});
}
2,583 ⟶ 2,687行目:
'lelimit': 1,
'formatversion': 2
}).donethen(function(res){
if (res.query.logevents.length === 0) { // If empty array is returned (=if no logevent exists)
resolve();