「利用者:Dragoniez/scripts/AN Reporter.js」の版間の差分
表示
削除された内容 追加された内容
m v6.1.2: 無用な変数を除去 |
v6.2: 技術的更新(暫定版、コード記録用) |
||
2行目: | 2行目: | ||
* AN Reporter (ANR) * |
* AN Reporter (ANR) * |
||
* Author: Dragoniez * |
* Author: Dragoniez * |
||
* Version: 6 |
* Version: 6.2 * |
||
************************************/ |
************************************/ |
||
//<nowiki> |
//<nowiki> |
||
// ******************** |
// ******************** CONFIGS ******************** |
||
/* Config |
/* Config |
||
31行目: | 31行目: | ||
if (!anrConfig.backgroundColor) anrConfig.backgroundColor = '#FFF0E4'; |
if (!anrConfig.backgroundColor) anrConfig.backgroundColor = '#FFF0E4'; |
||
// 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, |
|||
'drPreviewSections': 'tarSections3RR' // I, S, 3RR, SubpagedLTA |
|||
}; |
|||
const scriptAd = debugMode.scriptAd ? ' ([[User:Dragoniez/AN Reporter|AN Reporter Experimental]])' : ' ([[User:Dragoniez/AN Reporter|AN Reporter]])'; |
|||
const portletLinkText = debugMode.portletLink ? '報告β' : '報告'; |
|||
const developerLink = `<a href="${mw.util.getUrl('User talk:Dragoniez/AN Reporter')}" target="_blank">開発者</a>`; |
|||
// ******************** SCRIPT BODY ******************** |
|||
// 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:管理者伝言板/投稿ブロック/真珠王子'; |
|||
(function(){ // Create a function scope |
|||
/** |
|||
* Object to store logids for usernames {user1: logid, user2: logid...} |
|||
*/ |
|||
const Logids = {}; |
|||
// ******************** VARIABLES ******************** |
|||
// Related to dialog creation |
|||
var userDiv; // What to append when the 'add' button is hit |
|||
var userCnt = 1; // *ID number of the elements in the appended userDiv |
|||
// 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, |
|||
'drPreviewSections': 'tarSectionsI' // I, S, 3RR, SubpagedLTA |
|||
}; |
|||
const scriptAd = debugMode.scriptAd ? ' ([[User:Dragoniez/AN Reporter|AN Reporter Experimental]])' : ' ([[User:Dragoniez/AN Reporter|AN Reporter]])'; |
|||
const portletLinkText = debugMode.portletLink ? '報告β' : '報告'; |
|||
const developerLink = `<a href="${mw.util.getUrl('User talk:Dragoniez/AN Reporter')}" target="_blank">開発者</a>`; |
|||
// Page names |
|||
// ******************** DOM READY FUNCTION ******************** |
|||
const ANI = 'Wikipedia:管理者伝言板/投稿ブロック'; |
|||
const ANS = 'Wikipedia:管理者伝言板/投稿ブロック/ソックパペット'; |
|||
const AN3RR = 'Wikipedia:管理者伝言板/3RR'; |
|||
const VIP = 'Wikipedia:進行中の荒らし行為'; |
|||
const Iccic = 'Wikipedia:進行中の荒らし行為/長期/Iccic/投稿ブロック依頼'; //SockInfo |
|||
const ISECHIKA = 'Wikipedia:管理者伝言板/投稿ブロック/いせちか'; |
|||
const KAGE = 'Wikipedia:管理者伝言板/投稿ブロック/影武者'; |
|||
const KIYOSHIMA = 'Wikipedia:管理者伝言板/投稿ブロック/清島達郎'; |
|||
const SHINJU = 'Wikipedia:管理者伝言板/投稿ブロック/真珠王子'; |
|||
const TEST = '利用者:Dragoniez/test'; |
|||
/** |
|||
// Wait for the required dependencies to be ready |
|||
* Object to store logids for usernames {user1: logid, user2: logid...} |
|||
$.when( |
|||
*/ |
|||
$.getScript('https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js'), |
|||
const Logids = {}; |
|||
mw.loader.using(['jquery.ui', 'mediawiki.util']), |
|||
$.ready |
|||
).then(function(){ |
|||
// |
// Related to dialog creation |
||
var userDiv; // What to append when the 'add' button is hit |
|||
$('head').append('<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css">'); |
|||
var userCnt = 1; // *ID number of the elements in the appended userDiv |
|||
// Run the script only if the user is autoconfirmed and the page is not an edit page |
|||
if (isInArray('autoconfirmed', mw.config.get('wgUserGroups')) && mw.config.get('wgAction') !== 'edit') { |
|||
addAnrPortletLink(); |
|||
} |
|||
// ******************** DOM READY FUNCTION ******************** |
|||
}); |
|||
// Wait for the required dependencies to be ready |
|||
$.when( |
|||
$.getScript('https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js'), |
|||
mw.loader.using(['jquery.ui', 'mediawiki.util']), |
|||
$.ready |
|||
).then(function(){ |
|||
// Load CSS source for Select2 |
|||
// ******************** MAIN FUNCTIONS ******************** |
|||
$('head').append('<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css">'); |
|||
// Run the script only if the user is autoconfirmed and the page is not an edit page |
|||
function addAnrPortletLink() { |
|||
if (isInArray('autoconfirmed', mw.config.get('wgUserGroups')) && mw.config.get('wgAction') !== 'edit') { |
|||
addAnrPortletLink(); |
|||
} |
|||
}); |
|||
// ******************** MAIN FUNCTIONS ******************** |
|||
function addAnrPortletLink() { |
|||
// Define the position of the portletlink (skin-dependent) |
|||
var lkPosition; |
|||
if (anrConfig.portletlinkPosition) { |
|||
lkPosition = anrConfig.portletlinkPosition; |
|||
} else { |
|||
switch(mw.config.get('skin')) { |
|||
case 'vector': |
|||
case 'vector-2022': |
|||
lkPosition = 'p-views'; |
|||
break; |
|||
case 'minerva': |
|||
lkPosition = 'p-personal'; |
|||
break; |
|||
default: // monobook, timeless, or something else |
|||
lkPosition = 'p-cactions'; |
|||
} |
|||
} |
|||
// Add a portletlink for ANR |
|||
$(mw.util.addPortletLink(lkPosition, '#', portletLinkText, 'ca-anr', '管理者伝言板に利用者を報告', null, '#ca-move')).click(openAnrDialog); |
|||
} |
|||
// Set font size |
|||
// Define the position of the portletlink (skin-dependent) |
|||
var |
var fontSize, select2FontSize |
||
if (anrConfig. |
if (anrConfig.fontSize) { |
||
fontSize = anrConfig.fontSize; |
|||
} else { |
} else { |
||
switch(mw.config.get('skin')) { |
switch(mw.config.get('skin')) { |
||
case 'vector': |
case 'vector': |
||
case 'vector-2022': |
case 'vector-2022': |
||
case 'minerva': |
|||
fontSize = '80%'; |
|||
break; |
break; |
||
case 'monobook': |
|||
fontSize = '110%'; |
|||
break; |
|||
case 'timeless': |
|||
fontSize = '90%'; |
|||
break; |
|||
default: |
|||
fontSize = '80%'; |
|||
} |
|||
} |
|||
if (anrConfig.dropdownFontSize) { |
|||
select2FontSize = anrConfig.dropdownFontSize; |
|||
} else { |
|||
switch(mw.config.get('skin')) { |
|||
case 'vector': |
|||
case 'vector-2022': |
|||
case 'minerva': |
case 'minerva': |
||
select2FontSize = '0.9em'; |
|||
break; |
break; |
||
case 'monobook': |
|||
select2FontSize = '1.03em'; |
|||
break; |
|||
case 'timeless': |
|||
select2FontSize = '0.94em'; |
|||
break; |
|||
default: |
|||
select2FontSize = '0.9em'; |
|||
} |
} |
||
} |
} |
||
var styleAppended = false; |
|||
// Add a portletlink for ANR |
|||
function openAnrDialog(e) { |
|||
$(mw.util.addPortletLink(lkPosition, '#', portletLinkText, 'ca-anr', '管理者伝言板に利用者を報告', null, '#ca-move')).click(openAnrDialog); |
|||
e.preventDefault(); |
|||
// Default CSS for Select2 and classes shared by selectors |
|||
} |
|||
if (!styleAppended) { |
|||
$('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: ${select2FontSize}; |
|||
margin: 0; |
|||
} |
|||
.select2-container, .select2-selection--single { |
|||
height: auto !important; |
|||
} |
|||
.anr-dialog-label { |
|||
display: inline-block; |
|||
width: 8ch; |
|||
} |
|||
.anr-dialog-select, .anr-dialog-input { |
|||
border: 1px solid #d3d3d3; |
|||
border-radius: 1%; |
|||
background-color: white; |
|||
padding: 2px 4px; |
|||
} |
|||
.anr-dialog-button { |
|||
color: black; |
|||
font-weight: normal; |
|||
border: 1px solid #d3d3d3; |
|||
background-color: white; |
|||
padding: 0.2em 0.5em; |
|||
border-radius: 10%; |
|||
} |
|||
.anr-dialog-textarea { |
|||
width: 100%; |
|||
box-sizing: border-box; |
|||
} |
|||
.anr-dialog-needmargin { |
|||
margin: 1em 0; |
|||
} |
|||
</style>` |
|||
); |
|||
styleAppended = true; |
|||
} |
|||
// The whole html contour |
|||
// Set font size |
|||
const modalHtml = |
|||
var fontSize, select2FontSize |
|||
`<div id="anr-modal-dialog" title="AN Reporter" style="max-height: 80vh;">` + |
|||
if (anrConfig.fontSize) { |
|||
` <div id="anr-modal-header">` + |
|||
fontSize = anrConfig.fontSize; |
|||
` <h2>利用者を報告</h2>` + |
|||
} else { |
|||
` </div>` + |
|||
switch(mw.config.get('skin')) { |
|||
` <div id="anr-modal-body">` + |
|||
` <form>` + |
|||
` <div id="anr-target-div" class="anr-dialog-needmargin">` + |
|||
case 'minerva': |
|||
` <label for="anr-target-options" id="anr-target-options-label" class="anr-dialog-label">報告先</label>` + |
|||
fontSize = '80%'; |
|||
` <select id="anr-target-options" class="anr-dialog-select">` + |
|||
break; |
|||
` <option selected disabled hidden>選択してください</option>` + |
|||
case 'monobook': |
|||
` <option>${ANI}</option>` + |
|||
` <option>${ANS}</option>` + |
|||
` <option>${AN3RR}</option>` + |
|||
case 'timeless': |
|||
` </select>` + |
|||
` <div id="anr-target-pagelink-div" style="display: none;">` + |
|||
break; |
|||
` <label class="anr-emptylabel anr-dialog-label" for="anr-target-pagelink"></label>` + |
|||
default: |
|||
` <a id="anr-target-pagelink" href="" target="_blank">報告先を確認</a>` + |
|||
fontSize = '80%'; |
|||
` </div>` + |
|||
} |
|||
` </div>` + |
|||
} |
|||
` <div id="anr-section-i-div" class="anr-dialog-needmargin" style="display: none;">` + |
|||
if (anrConfig.dropdownFontSize) { |
|||
` <label for="anr-section-i-select" class="anr-dialog-label">節</label>` + |
|||
select2FontSize = anrConfig.dropdownFontSize; |
|||
` <select id="anr-section-i-select" class="anr-dialog-select">` + |
|||
} else { |
|||
` <option selected disabled hidden class="anr-section-options-initial">選択してください</option>` + |
|||
switch(mw.config.get('skin')) { |
|||
` <option id="anr-section-i-options-date"></option>` + |
|||
case 'vector': |
|||
` <option>不適切な利用者名</option>` + |
|||
case 'vector-2022': |
|||
` <option>公開アカウント</option>` + |
|||
case 'minerva': |
|||
` <option>公開プロキシ・ゾンビマシン・ボット・不特定多数</option>` + |
|||
select2FontSize = '0.9em'; |
|||
` <option>犯罪行為またはその疑いのある投稿</option>` + |
|||
break; |
|||
` </select>` + |
|||
` </div>` + |
|||
` <div id="anr-section-s-div" class="anr-dialog-needmargin" style="display: none;">` + |
|||
break; |
|||
` <label for="anr-section-s-select" class="anr-dialog-label">節</label>` + |
|||
case 'timeless': |
|||
` <select id="anr-section-s-select" class="anr-dialog-select">` + |
|||
select2FontSize = '0.94em'; |
|||
` <option selected disabled hidden class="anr-section-options-initial">選択してください</option>` + |
|||
break; |
|||
` <optgroup label="系列が立てられていないもの">` + |
|||
default: |
|||
` <option>著作権侵害・犯罪予告</option>` + |
|||
select2FontSize = '0.9em'; |
|||
` <option>名誉毀損・なりすまし・個人情報</option>` + |
|||
} |
|||
` <option>妨害編集・いたずら</option>` + |
|||
} |
|||
` <option>その他</option>` + |
|||
` </optgroup>` + |
|||
` <optgroup id="anr-section-s-lta" label="LTA">` + |
|||
// getSectionsS() |
|||
` </optgroup>` + |
|||
` </select>` + |
|||
` </div>` + |
|||
` <div id="anr-user-div" class="anr-dialog-needmargin">` + |
|||
` <div id="anr-user1-div">` + |
|||
` <div id="anr-user1-input-div">` + |
|||
` <label for="anr-user1-input" class="anr-dialog-label">利用者</label>` + |
|||
` <input id="anr-user1-input" class="anr-dialog-input" style="width: 34ch;">` + |
|||
` <select disabled id="anr-user1-select" class="anr-dialog-select">` + |
|||
` <option class="anr-opt-UNL">UNL</option>` + |
|||
` <option class="anr-opt-User2">User2</option>` + |
|||
` <option class="anr-opt-IP2">IP2</option>` + |
|||
` <option class="anr-opt-logid">logid</option>` + |
|||
` <option class="anr-opt-diff">diff</option>` + |
|||
` <option selected class="anr-opt-none">none</option>` + |
|||
` </select>` + |
|||
` </div>` + |
|||
` <div id="anr-user1-checkbox-div" style="display: none;">` + |
|||
` <label class="anr-emptylabel anr-dialog-label"></label>` + |
|||
` <input type="checkbox" id="anr-user1-checkbox">` + |
|||
` <label for="anr-user1-checkbox">利用者名を隠す</label>` + |
|||
` </div>` + |
|||
` <div id="anr-user1-idlink-div" style="display: none;">` + |
|||
` <label for="anr-user1-idlink" class="anr-dialog-label"></label>` + |
|||
` <a id="anr-user1-idlink" href="" target="_blank"></a>` + |
|||
` </div>` + |
|||
` <div id="anr-user1-blockstatus-div" style="display: none;">` + |
|||
` <label for="anr-user1-blockstatus" class="anr-dialog-label"></label>` + |
|||
` <a id="anr-user1-blockstatus" href="" target="_blank" style="color: MediumVioletRed;">ブロックあり</a>` + |
|||
` </div>` + |
|||
` </div>` + |
|||
` <div id="anr-btn-div">` + |
|||
` <button type="button" id="anr-addBtn" class="anr-dialog-button">追加</button>` + |
|||
` </div>` + |
|||
` </div>` + |
|||
' <div id="anr-viplist-div" style="width: 100%; display: none;">' + |
|||
` <label for="anr-viplist-select" class="anr-dialog-label">VIP</label>` + |
|||
` <select id="anr-viplist-select">` + |
|||
' <optgroup style="display: none;">' + // Adjust font size |
|||
' <option selected disabled hidden>コピーする場合は選択してください</option>' + |
|||
// getVipList() |
|||
' </optgroup>' + |
|||
' </select>' + |
|||
' </div>' + |
|||
` <div id="anr-predefinedreasons-div" class="anr-dialog-needmargin" style="display: none;">` + |
|||
` <label for="anr-predefinedreasons-select" class="anr-dialog-label">定型文</label>` + |
|||
` <select id="anr-predefinedreasons-select">` + |
|||
' <optgroup style="display: none;">' + // Adjust font size |
|||
` <option selected>定型文を使用する場合は選択してください</option>` + |
|||
' </optgroup>' + |
|||
` </select>` + |
|||
` </div>` + |
|||
` <div id="anr-reason-div" class="anr-dialog-needmargin">` + |
|||
` <label for="anr-reason-text" class="anr-dialog-label">理由</label>` + |
|||
` <textarea id="anr-reason-text" class="anr-dialog-textarea" rows="6"></textarea>` + |
|||
` </div>` + |
|||
` <div id="anr-summary-div" class="anr-dialog-needmargin">` + |
|||
` <input id="anr-summary-checkbox" type="checkbox">` + |
|||
` <label for="anr-summary-checkbox">要約を指定</label>` + |
|||
` <textarea id="anr-summary-text" class="anr-dialog-textarea" rows="3" style="display: none;"></textarea>` + |
|||
` </div>` + |
|||
` <div id="anr-checkbox-div" class="anr-dialog-needmargin">` + |
|||
` <input checked id="anr-blockstatus-checkbox" type="checkbox">` + |
|||
` <label for="anr-blockstatus-checkbox">報告前にブロック状態をチェック</label>` + |
|||
` <br>` + |
|||
` <input checked id="anr-duplicatereport-checkbox" type="checkbox">` + |
|||
` <label for="anr-duplicatereport-checkbox">報告前に重複報告をチェック</label>` + |
|||
` <br>` + |
|||
` <input checked id="anr-watchlist-checkbox" type="checkbox">` + |
|||
` <label for="anr-watchlist-checkbox">報告対象者をウォッチリストに追加</label>` + |
|||
` </div>` + |
|||
` </form>` + |
|||
` </div>` + |
|||
`</div>`; |
|||
// Add the frame div to the page |
|||
var styleAppended = false; |
|||
$('body').append(modalHtml); |
|||
function openAnrDialog(e) { |
|||
e.preventDefault(); |
|||
// Show dialog |
|||
$('#anr-modal-dialog').dialog({ |
|||
'resizable': false, |
|||
'height': 'auto', |
|||
'width': 'auto', |
|||
'modal': true, |
|||
'open': initializeAnrDialog, |
|||
'buttons': [{ |
|||
'text': 'プレビュー', |
|||
'click': preview |
|||
}, { |
|||
'text': '報告', |
|||
'click': report |
|||
}] |
|||
}); |
|||
// Default CSS for Select2 and classes shared by selectors |
|||
if (!styleAppended) { |
|||
$('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: ${select2FontSize}; |
|||
margin: 0; |
|||
} |
|||
.select2-container, .select2-selection--single { |
|||
height: auto !important; |
|||
} |
|||
.anr-dialog-label { |
|||
display: inline-block; |
|||
width: 8ch; |
|||
} |
|||
.anr-dialog-select, .anr-dialog-input { |
|||
border: 1px solid #d3d3d3; |
|||
border-radius: 1%; |
|||
background-color: white; |
|||
padding: 2px 4px; |
|||
} |
|||
.anr-dialog-button { |
|||
color: black; |
|||
font-weight: normal; |
|||
border: 1px solid #d3d3d3; |
|||
background-color: white; |
|||
padding: 0.2em 0.5em; |
|||
border-radius: 10%; |
|||
} |
|||
.anr-dialog-textarea { |
|||
width: 100%; |
|||
box-sizing: border-box; |
|||
} |
|||
.anr-dialog-needmargin { |
|||
margin: 1em 0; |
|||
} |
|||
</style>` |
|||
); |
|||
styleAppended = true; |
|||
} |
} |
||
// |
// Function to initialze the modal dialog |
||
function initializeAnrDialog(){ |
|||
const modalHtml = |
|||
`<div id="anr-modal-dialog" title="AN Reporter" style="max-height: 80vh;">` + |
|||
` <div id="anr-modal-header">` + |
|||
` <h2>利用者を報告</h2>` + |
|||
` </div>` + |
|||
` <div id="anr-modal-body">` + |
|||
` <form>` + |
|||
` <div id="anr-target-div" class="anr-dialog-needmargin">` + |
|||
` <label for="anr-target-options" id="anr-target-options-label" class="anr-dialog-label">報告先</label>` + |
|||
` <select id="anr-target-options" class="anr-dialog-select">` + |
|||
` <option selected disabled hidden>選択してください</option>` + |
|||
` <option>${ANI}</option>` + |
|||
` <option>${ANS}</option>` + |
|||
` <option>${AN3RR}</option>` + |
|||
` </select>` + |
|||
` <div id="anr-target-pagelink-div" style="display: none;">` + |
|||
` <label class="anr-emptylabel anr-dialog-label" for="anr-target-pagelink"></label>` + |
|||
` <a id="anr-target-pagelink" href="" target="_blank">報告先を確認</a>` + |
|||
` </div>` + |
|||
` </div>` + |
|||
` <div id="anr-section-i-div" class="anr-dialog-needmargin" style="display: none;">` + |
|||
` <label for="anr-section-i-select" class="anr-dialog-label">節</label>` + |
|||
` <select id="anr-section-i-select" class="anr-dialog-select">` + |
|||
` <option selected disabled hidden class="anr-section-options-initial">選択してください</option>` + |
|||
` <option id="anr-section-i-options-date"></option>` + |
|||
` <option>不適切な利用者名</option>` + |
|||
` <option>公開アカウント</option>` + |
|||
` <option>公開プロキシ・ゾンビマシン・ボット・不特定多数</option>` + |
|||
` <option>犯罪行為またはその疑いのある投稿</option>` + |
|||
` </select>` + |
|||
` </div>` + |
|||
` <div id="anr-section-s-div" class="anr-dialog-needmargin" style="display: none;">` + |
|||
` <label for="anr-section-s-select" class="anr-dialog-label">節</label>` + |
|||
` <select id="anr-section-s-select" class="anr-dialog-select">` + |
|||
` <option selected disabled hidden class="anr-section-options-initial">選択してください</option>` + |
|||
` <optgroup label="系列が立てられていないもの">` + |
|||
` <option>著作権侵害・犯罪予告</option>` + |
|||
` <option>名誉毀損・なりすまし・個人情報</option>` + |
|||
` <option>妨害編集・いたずら</option>` + |
|||
` <option>その他</option>` + |
|||
` </optgroup>` + |
|||
` <optgroup id="anr-section-s-lta" label="LTA">` + |
|||
// getSectionsS() |
|||
` </optgroup>` + |
|||
` </select>` + |
|||
` </div>` + |
|||
` <div id="anr-user-div" class="anr-dialog-needmargin">` + |
|||
` <div id="anr-user1-div">` + |
|||
` <div id="anr-user1-input-div">` + |
|||
` <label for="anr-user1-input" class="anr-dialog-label">利用者</label>` + |
|||
` <input id="anr-user1-input" class="anr-dialog-input" style="width: 34ch;">` + |
|||
` <select disabled id="anr-user1-select" class="anr-dialog-select">` + |
|||
` <option class="anr-opt-UNL">UNL</option>` + |
|||
` <option class="anr-opt-User2">User2</option>` + |
|||
` <option class="anr-opt-IP2">IP2</option>` + |
|||
` <option class="anr-opt-logid">logid</option>` + |
|||
` <option class="anr-opt-diff">diff</option>` + |
|||
` <option selected class="anr-opt-none">none</option>` + |
|||
` </select>` + |
|||
` </div>` + |
|||
` <div id="anr-user1-checkbox-div" style="display: none;">` + |
|||
` <label class="anr-emptylabel anr-dialog-label"></label>` + |
|||
` <input type="checkbox" id="anr-user1-checkbox">` + |
|||
` <label for="anr-user1-checkbox">利用者名を隠す</label>` + |
|||
` </div>` + |
|||
` <div id="anr-user1-idlink-div" style="display: none;">` + |
|||
` <label for="anr-user1-idlink" class="anr-dialog-label"></label>` + |
|||
` <a id="anr-user1-idlink" href="" target="_blank"></a>` + |
|||
` </div>` + |
|||
` <div id="anr-user1-blockstatus-div" style="display: none;">` + |
|||
` <label for="anr-user1-blockstatus" class="anr-dialog-label"></label>` + |
|||
` <a id="anr-user1-blockstatus" href="" target="_blank" style="color: MediumVioletRed;">ブロックあり</a>` + |
|||
` </div>` + |
|||
` </div>` + |
|||
` <div id="anr-btn-div">` + |
|||
` <button type="button" id="anr-addBtn" class="anr-dialog-button">追加</button>` + |
|||
` </div>` + |
|||
` </div>` + |
|||
' <div id="anr-viplist-div" style="width: 100%; display: none;">' + |
|||
` <label for="anr-viplist-select" class="anr-dialog-label">VIP</label>` + |
|||
` <select id="anr-viplist-select">` + |
|||
' <optgroup style="display: none;">' + // Adjust font size |
|||
' <option selected disabled hidden>コピーする場合は選択してください</option>' + |
|||
// getVipList() |
|||
' </optgroup>' + |
|||
' </select>' + |
|||
' </div>' + |
|||
` <div id="anr-predefinedreasons-div" class="anr-dialog-needmargin" style="display: none;">` + |
|||
` <label for="anr-predefinedreasons-select" class="anr-dialog-label">定型文</label>` + |
|||
` <select id="anr-predefinedreasons-select">` + |
|||
' <optgroup style="display: none;">' + // Adjust font size |
|||
` <option selected>定型文を使用する場合は選択してください</option>` + |
|||
' </optgroup>' + |
|||
` </select>` + |
|||
` </div>` + |
|||
` <div id="anr-reason-div" class="anr-dialog-needmargin">` + |
|||
` <label for="anr-reason-text" class="anr-dialog-label">理由</label>` + |
|||
` <textarea id="anr-reason-text" class="anr-dialog-textarea" rows="6"></textarea>` + |
|||
` </div>` + |
|||
` <div id="anr-summary-div" class="anr-dialog-needmargin">` + |
|||
` <input id="anr-summary-checkbox" type="checkbox">` + |
|||
` <label for="anr-summary-checkbox">要約を指定</label>` + |
|||
` <textarea id="anr-summary-text" class="anr-dialog-textarea" rows="3" style="display: none;"></textarea>` + |
|||
` </div>` + |
|||
` <div id="anr-checkbox-div" class="anr-dialog-needmargin">` + |
|||
` <input checked id="anr-blockstatus-checkbox" type="checkbox">` + |
|||
` <label for="anr-blockstatus-checkbox">報告前にブロック状態をチェック</label>` + |
|||
` <br>` + |
|||
` <input checked id="anr-duplicatereport-checkbox" type="checkbox">` + |
|||
` <label for="anr-duplicatereport-checkbox">報告前に重複報告をチェック</label>` + |
|||
` <br>` + |
|||
` <input checked id="anr-watchlist-checkbox" type="checkbox">` + |
|||
` <label for="anr-watchlist-checkbox">報告対象者をウォッチリストに追加</label>` + |
|||
` </div>` + |
|||
` </form>` + |
|||
` </div>` + |
|||
`</div>`; |
|||
userDiv = $('#anr-user1-div').prop('innerHTML'); // A div of the same structure is appended when the 'add' button is hit |
|||
// Add the frame div to the page |
|||
$('body').append(modalHtml); |
|||
getSectionsS(); // Get sections on WP:AN/S |
|||
// Show dialog |
|||
dialogCSS(); // Initialize the design of the dialog |
|||
$('#anr-modal-dialog').dialog({ |
|||
getVipList(); // Show VIP list |
|||
getPredefinedReasons(); // Show the select box for predefined reasons |
|||
'height': 'auto', |
|||
'width': 'auto', |
|||
'modal': true, |
|||
'open': initializeAnrDialog, |
|||
'buttons': [{ |
|||
'text': 'プレビュー', |
|||
'click': preview |
|||
}, { |
|||
'text': '報告', |
|||
'click': report |
|||
}] |
|||
}); |
|||
// Add to wathchlist? |
|||
} |
|||
if (anrConfig.addToWatchlist === false) { |
|||
$('#anr-watchlist-checkbox').prop('checked', false); |
|||
}; |
|||
// Get the name of the user to report if it can be retrieved from the page |
|||
// Function to initialze the modal dialog |
|||
var username = mw.config.get('wgRelevantUserName'); // Note: This does not pick up IP ranges |
|||
function initializeAnrDialog(){ |
|||
// Workaround to pick up IP ranges |
|||
userDiv = $('#anr-user1-div').prop('innerHTML'); // A div of the same structure is appended when the 'add' button is hit |
|||
if (!username && mw.config.get('wgCanonicalSpecialPageName') === 'Contributions') { |
|||
const relUsername = $('#firstHeading').text().replace('の投稿記録', ''); |
|||
if (mw.util.isIPAddress(relUsername, true)) username = relUsername; |
|||
} |
|||
// Exit function if the current user is on his/her own page or username has remained undefined or null |
|||
getSectionsS(); // Get sections on WP:AN/S |
|||
if (!username || username === mw.config.get('wgUserName')) return; |
|||
dialogCSS(); // Initialize the design of the dialog |
|||
getVipList(); // Show VIP list |
|||
getPredefinedReasons(); // Show the select box for predefined reasons |
|||
// Initialize the username input and type dropdown |
|||
// Add to wathchlist? |
|||
const inputID = '#anr-user1-input', selectID = '#anr-user1-select', checkboxDivID = '#anr-user1-checkbox-div'; |
|||
if (anrConfig.addToWatchlist == false) { |
|||
$('#anr-watchlist-checkbox').prop('checked', false); |
|||
}; |
|||
$(inputID).val(username); // Fill the input with the username |
|||
// Get the name of the user to report if it can be retrieved from the page |
|||
$(selectID).prop('disabled', false); // enable dropdown |
|||
var username = mw.config.get('wgRelevantUserName'); // Note: This does not pick up IP ranges |
|||
if (mw.util.isIPAddress(username, true)) { // if IP |
|||
// Workaround to pick up IP ranges |
|||
if (!username && mw.config.get('wgCanonicalSpecialPageName') === 'Contributions') { |
|||
const relUsername = $('#firstHeading').text().replace('の投稿記録', ''); |
|||
if (mw.util.isIPAddress(relUsername, true)) username = relUsername; |
|||
} |
|||
$(selectID).children('.anr-opt-UNL').prop('hidden', true); |
|||
// Exit function if the current user is on his/her own page or username has remained undefined or null |
|||
$(selectID).children('.anr-opt-User2').prop('hidden', true); |
|||
if (!username || username === mw.config.get('wgUserName')) return; |
|||
$(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 |
|||
// Initialize the username input and type dropdown |
|||
const inputID = '#anr-user1-input', selectID = '#anr-user1-select', checkboxDivID = '#anr-user1-checkbox-div'; |
|||
$(selectID).children('.anr-opt-UNL').prop({'hidden': false, 'selected': true}); |
|||
$(inputID).val(username); // Fill the input with the username |
|||
$(selectID).prop(' |
$(selectID).children('.anr-opt-User2').prop('hidden', false); |
||
$(selectID).children('.anr-opt-IP2').prop('hidden', true); |
|||
$(selectID).children('.anr-opt-logid').prop('hidden', true); |
|||
if (mw.util.isIPAddress(username, true)) { // if IP |
|||
$(selectID).children('.anr-opt-diff').prop('hidden', true); |
|||
$(selectID).children('.anr-opt- |
$(selectID).children('.anr-opt-none').prop('hidden', false); |
||
$(checkboxDivID).css('display', 'block'); // show 'hide username' checkbox |
|||
$(selectID).children('.anr-opt-User2').prop('hidden', true); |
|||
toggleBlockStatusLink(inputID, false, false); |
|||
$(selectID).children('.anr-opt-IP2').prop({'hidden': false, 'selected': true}); |
|||
} |
|||
$(selectID).children('.anr-opt-logid').prop('hidden', true); |
|||
$(selectID).children('.anr-opt-diff').prop('hidden', true); |
|||
$(selectID).children('.anr-opt-none').prop('hidden', false); |
|||
$(checkboxDivID).css('display', 'none'); // hide 'hide username' checkbox |
|||
toggleBlockStatusLink(inputID, false, false); |
|||
} else { // if user |
|||
$(selectID).children('.anr-opt-UNL').prop({'hidden': false, 'selected': true}); |
|||
$(selectID).children('.anr-opt-User2').prop('hidden', false); |
|||
$(selectID).children('.anr-opt-IP2').prop('hidden', true); |
|||
$(selectID).children('.anr-opt-logid').prop('hidden', true); |
|||
$(selectID).children('.anr-opt-diff').prop('hidden', true); |
|||
$(selectID).children('.anr-opt-none').prop('hidden', false); |
|||
$(checkboxDivID).css('display', 'block'); // show 'hide username' checkbox |
|||
toggleBlockStatusLink(inputID, false, false); |
|||
} |
} |
||
// Function to get sections on WP:AN/S from the API |
|||
} |
|||
async function getSectionsS() { |
|||
const $label = $('#anr-target-options-label'); // Label of '報告先' |
|||
$label.append(toggleLoadingSpinner('add')); // Show a loading spinner while trying to get sections on WP:AN/S |
|||
const parse = await parsePage(ANS, 'sections'); |
|||
// Function to get sections on WP:AN/S from the API |
|||
if (parse) { |
|||
async function getSectionsS() { |
|||
const $label = $('#anr-target-options-label'); // Label of '報告先' |
|||
$label.append(toggleLoadingSpinner('add')); // Show a loading spinner while trying to get sections on WP:AN/S |
|||
// Get VIP's names |
|||
const parse = await parsePage(ANS, 'sections'); |
|||
const sectionInfo = parse.sections; |
|||
if (parse) { |
|||
const excludeList = [ |
|||
'系列が立てられていないもの', |
|||
// Get VIP's names |
|||
'著作権侵害・犯罪予告', |
|||
const sectionInfo = parse.sections; |
|||
'名誉毀損・なりすまし・個人情報', |
|||
const excludeList = [ |
|||
' |
'妨害編集・いたずら', |
||
' |
'その他', |
||
' |
'A. 最優先', |
||
' |
'暫定A', |
||
' |
'休止中A', |
||
' |
'B. 優先度高', |
||
'暫定 |
'暫定B', |
||
'休止中 |
'休止中B', |
||
' |
'C. 優先度中', |
||
'暫定 |
'暫定C', |
||
'休止中 |
'休止中C', |
||
' |
'D. 優先度低', |
||
'暫定 |
'暫定D', |
||
'休止中 |
'休止中D', |
||
' |
'N. 未分類', |
||
' |
'サブページなし', |
||
'休止中 |
'休止中N' |
||
]; |
|||
const sectionList = []; |
|||
for (let i = 0; i < sectionInfo.length; i++) { |
|||
if (!isInArray(sectionInfo[i].line, excludeList) && sectionInfo[i].index.indexOf('T') === -1) { |
|||
]; |
|||
sectionList.push(`<option>${sectionInfo[i].line}</option>`); |
|||
const sectionList = []; |
|||
} |
|||
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'); |
|||
} |
|||
$('#anr-section-s-lta').append(sectionList.join('')); |
|||
/** |
|||
* Function to add/remove/move a loading spinner |
|||
alert('WP:AN/Sのセクションリストを取得できませんでした。ダイアログを開き直すと改善する場合があります。'); |
|||
* @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; |
|||
} |
|||
} |
} |
||
toggleLoadingSpinner('remove'); |
|||
/** |
|||
} |
|||
* Function to get a pages's information |
|||
* @param {string} pagename |
|||
/** |
|||
* @param {string} prop wikitext, sections, wikitext|sections |
|||
* Function to add/remove/move a loading spinner |
|||
* @param {number} sectionNum optional |
|||
* @returns {Promise} res.parse ({{sections: []}, wikitext: ''}) (undefined if query failed) |
|||
*/ |
|||
*/ |
|||
function toggleLoadingSpinner(action) { |
|||
function parsePage(pagename, prop, sectionNum) { |
|||
const img = '<img class="anr-loading-spinner" src="https://upload.wikimedia.org/wikipedia/commons/4/42/Loading.gif" ' + |
|||
return new Promise(function(resolve) { |
|||
'style="vertical-align: middle; max-height: 100%; border: 0;">'; |
|||
var params = { |
|||
'action': 'parse', |
|||
'page': pagename, |
|||
'prop': prop, |
|||
'formatversion': 2 |
|||
} |
|||
if (sectionNum) params = Object.assign(params, {'section': sectionNum}); // Concatenate params |
|||
case 'move': |
|||
new mw.Api().get(params).then(function(res){ |
|||
resolve(res && res.parse ? res.parse : undefined); |
|||
return img; |
|||
}); |
|||
}); |
|||
} |
} |
||
} |
|||
// Function to change the CSS of the dialog |
|||
/** |
|||
function dialogCSS() { |
|||
* Function to get a pages's information |
|||
$('.ui-dialog-content, .ui-dialog-buttonpane, .ui-corner-all, .ui-draggable, .ui-resizable').css('background', anrConfig.backgroundColor); |
|||
* @param {string} pagename |
|||
$('.ui-button').css({ |
|||
* @param {string} prop wikitext, sections, wikitext|sections |
|||
'color': 'black', |
|||
* @param {number} sectionNum optional |
|||
'background-color': 'white' |
|||
* @returns {Promise} res.parse ({{sections: []}, wikitext: ''}) (undefined if query failed) |
|||
*/ |
|||
function parsePage(pagename, prop, sectionNum) { |
|||
return new Promise(function(resolve) { |
|||
var params = { |
|||
'action': 'parse', |
|||
'page': pagename, |
|||
'prop': prop, |
|||
'formatversion': 2 |
|||
} |
|||
if (sectionNum) params = Object.assign(params, {'section': sectionNum}); // Concatenate params |
|||
new mw.Api().get(params).then(function(res){ |
|||
resolve(res && res.parse ? res.parse : undefined); |
|||
}); |
}); |
||
$('.ui-dialog-titlebar, .ui-dialog-titlebar-close').attr('style', `background: ${anrConfig.headerColor} !important;`); |
|||
}); |
|||
$('.ui-dialog').css('font-size', fontSize); |
|||
} |
|||
} |
|||
// WP:VIP list (for copy to clipboard) |
|||
// Function to change the CSS of the dialog |
|||
function |
async function getVipList() { |
||
$('.ui-dialog-content, .ui-dialog-buttonpane, .ui-corner-all, .ui-draggable, .ui-resizable').css('background', anrConfig.backgroundColor); |
|||
const parse = await parsePage(VIP, 'sections'); |
|||
$('.ui-button').css({ |
|||
if (parse) { |
|||
'background-color': 'white' |
|||
}); |
|||
$('.ui-dialog-titlebar, .ui-dialog-titlebar-close').attr('style', `background: ${anrConfig.headerColor} !important;`); |
|||
$('.ui-dialog').css('font-size', fontSize); |
|||
} |
|||
// Get VIP's names |
|||
// WP:VIP list (for copy to clipboard) |
|||
const sectionInfo = parse.sections; |
|||
async function getVipList() { |
|||
const excludeList = [ |
|||
'記述について', |
|||
const parse = await parsePage(VIP, 'sections'); |
|||
'急を要する二段階', |
|||
if (parse) { |
|||
'配列', |
|||
'ブロック等の手段', |
|||
'このページに利用者名を加える', |
|||
'注意と選択', |
|||
'警告の方法', |
|||
'未登録(匿名・IP)ユーザーの場合', |
|||
'登録済み(ログイン)ユーザーの場合', |
|||
'警告中', |
|||
'関連項目' |
|||
]; |
|||
const 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.'); |
|||
const sectionInfo = parse.sections; |
|||
} else { |
|||
' |
$('#anr-viplist-select') |
||
.css('width', $('#anr-target-options').innerWidth()) |
|||
'急を要する二段階', |
|||
.select2() |
|||
.children('optgroup').append(vipList.join('')); |
|||
'ブロック等の手段', |
|||
' |
$('#anr-viplist-div').css('display', 'block'); |
||
centerDialog(); |
|||
'警告の方法', |
|||
'未登録(匿名・IP)ユーザーの場合', |
|||
'登録済み(ログイン)ユーザーの場合', |
|||
'警告中', |
|||
'関連項目' |
|||
]; |
|||
const 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 { |
} else { |
||
return mw.log.error('VIP list: The API returned an unresolvable object.'); |
|||
$('#anr-viplist-select') |
|||
.css('width', $('#anr-target-options').innerWidth()) |
|||
.select2() |
|||
.children('optgroup').append(vipList.join('')); |
|||
$('#anr-viplist-div').css('display', 'block'); |
|||
centerDialog(); |
|||
} |
} |
||
} else { |
|||
return mw.log.error('VIP list: The API returned an unresolvable object.'); |
|||
} |
} |
||
// Function to show the select div for predefined report reasons if they're predefined |
|||
} |
|||
function getPredefinedReasons() { |
|||
const pdReasons = anrConfig.predefinedReasons; |
|||
if (typeof pdReasons !== 'undefined' && !$.isEmptyObject(pdReasons)) { // If the user has fixed reasons prepared |
|||
const $reasons = $('#anr-predefinedreasons-select'); |
|||
// Function to show the select div for predefined report reasons if they're predefined |
|||
$reasons.css('width', $('#anr-target-options').innerWidth()).select2(); |
|||
function getPredefinedReasons() { |
|||
const pdReasons = anrConfig.predefinedReasons; |
|||
for (let key in pdReasons) { |
|||
if (typeof pdReasons !== 'undefined' && !$.isEmptyObject(pdReasons)) { // If the user has fixed reasons prepared |
|||
$reasons.children('optgroup').append(`<option>${pdReasons[key]}</option>`); |
|||
} |
|||
$('#anr-predefinedreasons-div').css('display', 'block'); |
|||
centerDialog(); |
|||
const $reasons = $('#anr-predefinedreasons-select'); |
|||
$reasons.css('width', $('#anr-target-options').innerWidth()).select2(); |
|||
for (let key in pdReasons) { |
|||
$reasons.children('optgroup').append(`<option>${pdReasons[key]}</option>`); |
|||
} |
} |
||
$('#anr-predefinedreasons-div').css('display', 'block'); |
|||
centerDialog(); |
|||
} |
} |
||
} |
|||
function centerDialog() { |
function centerDialog() { |
||
var $dialog; |
var $dialog; |
||
if ($('#anr-preview-dialog').length !== 0) { |
if ($('#anr-preview-dialog').length !== 0) { |
||
$dialog = $('#anr-preview-dialog'); |
$dialog = $('#anr-preview-dialog'); |
||
} else if ($('#anr-drpreview-dialog').length !== 0) { |
} else if ($('#anr-drpreview-dialog').length !== 0) { |
||
$dialog = $('#anr-drpreview-dialog'); |
$dialog = $('#anr-drpreview-dialog'); |
||
} else { |
} else { |
||
$dialog = $('#anr-modal-dialog'); |
$dialog = $('#anr-modal-dialog'); |
||
} |
|||
$dialog.dialog({'position': {my: 'center', at: 'center', of: window}}); |
|||
} |
} |
||
$dialog.dialog({'position': {my: 'center', at: 'center', of: window}}); |
|||
} |
|||
// Function to check information typed into the form |
// Function to check information typed into the form |
||
function editPrep() { |
function editPrep() { |
||
// Get all input values and UserAN types, and check for duplicates |
// Get all input values and UserAN types, and check for duplicates |
||
const users = [], types = [], duplicates = []; |
const users = [], types = [], duplicates = []; |
||
$('#anr-user-div :text').each(function(){ // Loop through all inputs |
$('#anr-user-div :text').each(function(){ // Loop through all inputs |
||
const inputID = '#' + $(this).attr('id'); |
const inputID = '#' + $(this).attr('id'); |
||
const type = $(inputID.replace('input', 'select')).children('option').filter(':selected').text(); // UserAN type |
const type = $(inputID.replace('input', 'select')).children('option').filter(':selected').text(); // UserAN type |
||
const inputVal = $(this).val().trimANR(); // Username |
const inputVal = $(this).val().trimANR(); // Username |
||
if (!inputVal) return; |
if (!inputVal) return; |
||
var username, logid; |
var username, logid; |
||
if (type === 'logid' && (username = getKeyByValue(Logids, logid = inputVal))) { // if t=logid and the logid can be converted to a username |
if (type === 'logid' && (username = getKeyByValue(Logids, logid = inputVal))) { // if t=logid and the logid can be converted to a username |
||
// If either of the username or the logid is already in the array 'users' and if they have yet to be listed as duplicates |
// If 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(username, users) || isInArray(logid, users)) && !isInArray(username, duplicates) && !isInArray(logid, duplicates)) { |
if ((isInArray(username, users) || isInArray(logid, users)) && !isInArray(username, duplicates) && !isInArray(logid, duplicates)) { |
||
duplicates.push(username, logid); // List both the username and the logid as duplicates |
duplicates.push(username, logid); // List both the username and the logid as duplicates |
||
} |
} |
||
} else { // If t!=logid or t=logid but a username can't be obtained |
} else { // If t!=logid or t=logid but a username can't be obtained |
||
// If the username is already in the array 'users' (and if it hasn't been listed as a duplicate) |
|||
if (isInArray(username = inputVal, users) && !isInArray(username, duplicates)) { |
|||
duplicates.push(username); // List the username as a duplicate |
|||
} |
|||
// If the username is already in the array 'users' (and if it hasn't been listed as a duplicate) |
|||
if (isInArray(username = inputVal, users) && !isInArray(username, duplicates)) { |
|||
duplicates.push(username); // List the username as a duplicate |
|||
} |
} |
||
users.push(inputVal); // Push the username into the array |
|||
} |
|||
types.push(type); // Push the UserAN type into the array |
|||
}); |
|||
users.push(inputVal); // Push the username into the array |
|||
types.push(type); // Push the UserAN type into the array |
|||
// Get the name of the section to edit |
|||
}); |
|||
var pageToEdit = $('#anr-target-options').children('option').filter(':selected').text(); |
|||
var sectionToEdit = '選択してください', reportToANS = false; |
|||
// |
if (pageToEdit === ANI) { // If WP:AN/I is selected as the target page to edit |
||
var pageToEdit = $('#anr-target-options').children('option').filter(':selected').text(); |
|||
var sectionToEdit = '選択してください', reportToANS = false; |
|||
sectionToEdit = $('#anr-section-i-select').children('option').filter(':selected').text(); |
|||
if (pageToEdit === ANI) { // If WP:AN/I is selected as the target page to edit |
|||
// Update the target section for cases in which the date has changed since the date-dependent section was chosen |
|||
sectionToEdit = $('#anr-section-i-select').children('option').filter(':selected').text(); |
|||
if (sectionToEdit.match(/^\d{4}年\d{1,2}月\d{1,2}日 - \d{1,2}日新規報告$/)) { |
|||
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 |
|||
if (sectionToEdit.match(/^\d{4}年\d{1,2}月\d{1,2}日 - \d{1,2}日新規報告$/)) { |
|||
const sectionIDate = getSectionI(false); |
|||
sectionToEdit = sectionIDate; |
|||
$('#anr-section-i-options-date').text(sectionIDate); |
|||
} |
|||
reportToANS = true; |
|||
} else if (pageToEdit === ANS) { // If WP:AN/S is selected as the target page to edit |
|||
const sectionANS = $('#anr-section-s-select').find('option').filter(':selected').text(); |
|||
switch(sectionANS) { |
|||
case 'Iccic系 (Iccic)': |
|||
pageToEdit = Iccic; |
|||
sectionToEdit = '新規依頼'; |
|||
break; |
|||
case 'いせちか系 (ISECHIKA)': |
|||
pageToEdit = ISECHIKA; |
|||
sectionToEdit = '新規依頼'; |
|||
break; |
|||
case '影武者系(KAGE)': |
|||
pageToEdit = KAGE; |
|||
sectionToEdit = '新規依頼'; |
|||
break; |
|||
case '清島達郎系 (清島、KIYOSHIMA)': |
|||
pageToEdit = KIYOSHIMA; |
|||
sectionToEdit = '新規依頼'; |
|||
break; |
|||
case '真珠王子系(SHINJU)': |
|||
pageToEdit = SHINJU; |
|||
sectionToEdit = '新規依頼'; |
|||
break; |
|||
default: |
|||
sectionToEdit = sectionANS; |
|||
} |
|||
} else if (pageToEdit === AN3RR) { // If WP:AN/3RR is selected as the target page to edit |
|||
reportToANS = true; |
|||
const sectionANS = $('#anr-section-s-select').find('option').filter(':selected').text(); |
|||
switch(sectionANS) { |
|||
case 'Iccic系 (Iccic)': |
|||
pageToEdit = Iccic; |
|||
sectionToEdit = '新規依頼'; |
|||
break; |
|||
case 'いせちか系 (ISECHIKA)': |
|||
pageToEdit = ISECHIKA; |
|||
sectionToEdit = '新規依頼'; |
|||
break; |
|||
case '影武者系(KAGE)': |
|||
pageToEdit = KAGE; |
|||
sectionToEdit = '新規依頼'; |
|||
break; |
|||
case '清島達郎系 (清島、KIYOSHIMA)': |
|||
pageToEdit = KIYOSHIMA; |
|||
sectionToEdit = '新規依頼'; |
|||
break; |
|||
case '真珠王子系(SHINJU)': |
|||
pageToEdit = SHINJU; |
|||
sectionToEdit = '新規依頼'; |
|||
break; |
|||
default: |
|||
sectionToEdit = sectionANS; |
|||
} |
|||
sectionToEdit = '3RR'; |
|||
} else if (pageToEdit === AN3RR) { // If WP:AN/3RR is selected as the target page to edit |
|||
} |
|||
// The reason of the report |
|||
var fixedReason = $('#anr-predefinedreasons-select').find('option').filter(':selected').text(); |
|||
fixedReason = fixedReason === '定型文を使用する場合は選択してください' ? '': fixedReason; |
|||
var reason = fixedReason + $('#anr-reason-text').val().trimANR(); |
|||
// Check if necessary fields are filled |
|||
} |
|||
if (pageToEdit === '選択してください' || sectionToEdit === '選択してください' || reason === '' || users.length === 0) { |
|||
alert('必須項目が入力・選択されていません'); |
|||
// The reason of the report |
|||
return; |
|||
var fixedReason = $('#anr-predefinedreasons-select').find('option').filter(':selected').text(); |
|||
} |
|||
fixedReason = fixedReason === '定型文を使用する場合は選択してください' ? '': fixedReason; |
|||
var reason = fixedReason + $('#anr-reason-text').val().trimANR(); |
|||
// Duplicate warning |
|||
// Check if necessary fields are filled |
|||
if (duplicates.length !== 0) { // If the inputs have duplicates in them |
|||
if (pageToEdit === '選択してください' || sectionToEdit === '選択してください' || reason === '' || users.length === 0) { |
|||
const confirmMsg = |
|||
alert('必須項目が入力・選択されていません'); |
|||
'以下の利用者について、重複入力がある可能性があります。\n\n' + duplicates.join(', ') + '\n\n' + |
|||
return; |
|||
'続行する場合は OK を、フォームに戻る場合は Cancel を押してください'; |
|||
} |
|||
if (confirm(confirmMsg) === false) return; |
|||
// Duplicate warning |
|||
} |
|||
if (duplicates.length !== 0) { // If the inputs have duplicates in them |
|||
const confirmMsg = |
|||
'以下の利用者について、重複入力がある可能性があります。\n\n' + duplicates.join(', ') + '\n\n' + |
|||
'続行する場合は OK を、フォームに戻る場合は Cancel を押してください'; |
|||
// If the reason doesn't contain a signature, add one |
|||
if (confirm(confirmMsg) === false) return; |
|||
if (reason.substring(reason.length - 4) !== '~~~~') { |
|||
} |
|||
reason += '--~~~~'; |
|||
} |
|||
// Get edit summary |
|||
// If the reason doesn't contain a signature, add one |
|||
const summaryText = $('#anr-summary-text').val().trimANR(), editSummarySection = '/*' + sectionToEdit + '*/'; |
|||
if (reason.substring(reason.length - 4) !== '~~~~') { |
|||
var editSummary, summaryCustomized; |
|||
if (summaryText) { |
|||
} |
|||
editSummary = editSummarySection + summaryText + scriptAd; |
|||
summaryCustomized = true; |
|||
// Get edit summary |
|||
} else { |
|||
const summaryText = $('#anr-summary-text').val().trimANR(), editSummarySection = '/*' + sectionToEdit + '*/'; |
|||
editSummary = editSummarySection + genEditSummary().replace(' - ', '') + scriptAd; |
|||
var editSummary, summaryCustomized; |
|||
if (summaryText) { |
|||
editSummary = editSummarySection + summaryText + scriptAd; |
|||
summaryCustomized = true; |
|||
} else { |
|||
editSummary = editSummarySection + genEditSummary().replace(' - ', '') + scriptAd; |
|||
} |
|||
// Warn if a username is hidden but shown in the summary |
|||
if (summaryCustomized) { |
|||
const hiddenUsernames = []; |
|||
for (let i = 0; i < types.length; i++) { |
|||
let type = types[i], inputVal = users[i], username; |
|||
if (type === 'logid' && (username = getKeyByValue(Logids, inputVal)) && editSummary.indexOf(username) !== -1) hiddenUsernames.push(username); |
|||
} |
} |
||
if (hiddenUsernames.length !== 0) { |
|||
// Warn if a username is hidden but shown in the summary |
|||
const warnText = '警告\n以下の利用者名は、フォーム上では隠されていますが、編集要約内では隠されていません。\n・' + hiddenUsernames.join('\n・') + |
|||
if (summaryCustomized) { |
|||
'\nこのまま続行する場合は OK を、中止する場合は Cancel を押してください。'; |
|||
const hiddenUsernames = []; |
|||
for (let i = 0; i < types.length; i++) { |
|||
let type = types[i], inputVal = users[i], username; |
|||
if (type === 'logid' && (username = getKeyByValue(Logids, inputVal)) && editSummary.indexOf(username) !== -1) hiddenUsernames.push(username); |
|||
} |
|||
if (hiddenUsernames.length !== 0) { |
|||
const warnText = '警告\n以下の利用者名は、フォーム上では隠されていますが、編集要約内では隠されていません。\n・' + hiddenUsernames.join('\n・') + |
|||
'\nこのまま続行する場合は OK を、中止する場合は Cancel を押してください。'; |
|||
if (confirm(warnText) === false) return; |
|||
} |
|||
} |
} |
||
} |
|||
// Get text to add to the page |
// Get text to add to the page |
||
var reportText = ''; |
var reportText = ''; |
||
const UserAN = '{{UserAN|t=TYPE|USER}}'; |
const UserAN = '{{UserAN|t=TYPE|USER}}'; |
||
if (users.length < 2) { // If user to report is just one |
if (users.length < 2) { // If user to report is just one |
||
reportText = '\* ' + UserAN.replaceAllANR('TYPE', types[0], 'USER', users[0]) + ' - ' + reason; |
reportText = '\* ' + UserAN.replaceAllANR('TYPE', types[0], 'USER', users[0]) + ' - ' + reason; |
||
} else { // If two or more |
} else { // If two or more |
||
for (let i = 0; i < users.length; i++) { |
for (let i = 0; i < users.length; i++) { |
||
reportText += '\* ' + UserAN.replaceAllANR('TYPE', types[i], 'USER', users[i]) + '\n'; |
reportText += '\* ' + UserAN.replaceAllANR('TYPE', types[i], 'USER', users[i]) + '\n'; |
||
} |
|||
reportText += ': ' + reason; |
|||
} |
} |
||
reportText += ': ' + reason; |
|||
} |
|||
// Return values |
// Return values |
||
return { |
return { |
||
'users': users, |
'users': users, |
||
'types': types, |
'types': types, |
||
'pageToEdit': debugMode.editTarget ? |
'pageToEdit': debugMode.editTarget ? TEST : pageToEdit, |
||
'sectionToEdit': sectionToEdit, |
'sectionToEdit': sectionToEdit, |
||
'wikiPagename': debugMode.editTarget ? ' |
'wikiPagename': debugMode.editTarget ? TEST + '#' + sectionToEdit : pageToEdit + '#' + sectionToEdit, |
||
'reportToANS': reportToANS, |
'reportToANS': reportToANS, |
||
'editSummary': debugMode.editSummary ? 'Test edit via mediawiki API' + scriptAd : editSummary, |
'editSummary': debugMode.editSummary ? 'Test edit via mediawiki API' + scriptAd : editSummary, |
||
'reportText': reportText |
'reportText': reportText |
||
} |
|||
} |
} |
||
} |
|||
// Function for the 'preview' button of the dialog |
// Function for the 'preview' button of the dialog |
||
function preview() { |
function preview() { |
||
// Check if the necessary fields are filled and get edit information |
// Check if the necessary fields are filled and get edit information |
||
const ep = editPrep(); |
const ep = editPrep(); |
||
if (!ep) return; |
if (!ep) return; |
||
// Preview dialog contour |
// Preview dialog contour |
||
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 id="anr-preview-dialog" title="AN Reporter Preview" style="max-height: 80vh;">' + |
'<div id="anr-preview-dialog" title="AN Reporter Preview" style="max-height: 80vh;">' + |
||
' <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">' + |
||
` プレビューを読み込み中${toggleLoadingSpinner('add')}` + |
` プレビューを読み込み中${toggleLoadingSpinner('add')}` + |
||
' </p>' + |
' </p>' + |
||
' <p id="anr-preview-warning" style="display: none;">' + |
' <p id="anr-preview-warning" style="display: none;">' + |
||
' 注意1: このプレビュー上のリンクは全て新しいタブで開かれます' + |
' 注意1: このプレビュー上のリンクは全て新しいタブで開かれます' + |
||
' <br>' + |
' <br>' + |
||
` 注意2: 報告先が ${ANSMisc} の場合、このプレビューには表示されませんが「他X月X日」のヘッダーは必要に応じて自動挿入されます` + |
` 注意2: 報告先が ${ANSMisc} の場合、このプレビューには表示されませんが「他X月X日」のヘッダーは必要に応じて自動挿入されます` + |
||
' </p>' + |
' </p>' + |
||
' </div>' + |
' </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-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;">' + |
' <div id="anr-preview-text" style="border: 1px solid silver; padding: 0.2em 0.5em; background: white;">' + |
||
// previewHtml |
// previewHtml |
||
' </div>' + |
' </div>' + |
||
' <div id="anr-preview-summary" style="margin-top: 0.8em; border: 1px solid silver; padding: 0.2em 0.5em; background: white;">' + |
' <div id="anr-preview-summary" style="margin-top: 0.8em; border: 1px solid silver; padding: 0.2em 0.5em; background: white;">' + |
||
// summaryHtml |
// summaryHtml |
||
' </div>' + |
' </div>' + |
||
' </div>' + |
' </div>' + |
||
'</div>'; |
'</div>'; |
||
// 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, |
||
'modal': true, |
'modal': true, |
||
'open': async function(){ |
'open': async function(){ |
||
// Initialize the design of the dialog |
// Initialize the design of the dialog |
||
dialogCSS(); |
dialogCSS(); |
||
// Convert text on the dialog to html |
// Convert text on the dialog to html |
||
const parsed = await convertWikitextToHtmlFormat(ep.reportText, ep.editSummary); |
const parsed = await convertWikitextToHtmlFormat(ep.reportText, ep.editSummary); |
||
if (parsed) { |
if (parsed) { |
||
const previewHtml = parsed.htmltext; |
const previewHtml = parsed.htmltext; |
||
const summaryHtml = parsed.htmlsummary.replaceAllANR('API', ep.pageToEdit); |
const summaryHtml = parsed.htmlsummary.replaceAllANR('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(); |
||
$('#anr-preview-warning').css('display', 'inline'); |
$('#anr-preview-warning').css('display', 'inline'); |
||
centerDialog(); |
centerDialog(); |
||
} else { |
} else { |
||
$('#anr-preview-loading').text('プレビューの読み込みに失敗しました').css('color', 'MediumVioletRed'); |
$('#anr-preview-loading').text('プレビューの読み込みに失敗しました').css('color', 'MediumVioletRed'); |
||
centerDialog(); |
centerDialog(); |
||
setTimeout(function(){ |
setTimeout(function(){ |
||
$('#anr-preview-dialog').dialog('close'); |
$('#anr-preview-dialog').dialog('close'); |
||
}, 5000); |
}, 5000); |
||
} |
} |
||
}, |
}, |
||
'buttons': [{ |
'buttons': [{ |
||
'text': '閉じる', |
'text': '閉じる', |
||
'click': function(){ |
'click': function(){ |
||
$(this).dialog('close'); |
$(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 {Promise<{htmltext: string, htmlsummary: string}>} |
|||
*/ |
|||
function convertWikitextToHtmlFormat(wikitext, wikisummary) { |
function convertWikitextToHtmlFormat(wikitext, wikisummary) { |
||
return new Promise(function(resolve) { |
return new Promise(function(resolve) { |
||
new mw.Api().post({ |
new mw.Api().post({ |
||
'action': 'parse', |
'action': 'parse', |
||
'text': wikitext, |
'text': wikitext, |
||
'summary': wikisummary, |
'summary': wikisummary, |
||
'contentmodel': 'wikitext', |
'contentmodel': 'wikitext', |
||
'prop': 'text', |
'prop': 'text', |
||
'disableeditsection': true, |
'disableeditsection': true, |
||
'formatversion': 2 |
'formatversion': 2 |
||
}).then(function(res) { |
}).then(function(res) { |
||
resolve({ |
resolve({ |
||
'htmltext': res.parse.text, |
'htmltext': res.parse.text, |
||
'htmlsummary': res.parse.parsedsummary |
'htmlsummary': res.parse.parsedsummary |
||
}); |
|||
}).catch(function(code, err) { |
|||
console.log(err.error.info); |
|||
resolve(); |
|||
}); |
}); |
||
}).catch(function(code, err) { |
|||
console.log(err.error.info); |
|||
resolve(); |
|||
}); |
}); |
||
} |
} |
||
} |
|||
// Function for the 'report' button of the dialog |
// Function for the 'report' button of the dialog |
||
function report() { |
function report() { |
||
// Check if the necessary fields are filled and get edit information |
// Check if the necessary fields are filled and get edit information |
||
const ep = editPrep(); |
const ep = editPrep(); |
||
if (!ep) return; |
if (!ep) return; |
||
// Change dialog content |
// Change dialog content |
||
$('#anr-modal-dialog') |
$('#anr-modal-dialog') |
||
.dialog({ |
.dialog({ |
||
'width': $(this).innerWidth(), // Absolute width |
'width': $(this).innerWidth(), // Absolute width |
||
'buttons': [] // Hide buttons |
'buttons': [] // Hide buttons |
||
}) |
}) |
||
.append('<div class="anr-editing">') // Append div to show edit status |
.append('<div class="anr-editing">') // Append div to show edit status |
||
.find('form').css('display', 'none'); // Hide dialog content |
.find('form').css('display', 'none'); // Hide dialog content |
||
reportUsers(ep); |
|||
addUsersToWatchlist(ep); |
|||
} |
|||
reportUsers(ep); |
|||
// Function to execute report |
|||
addUsersToWatchlist(ep); |
|||
async function reportUsers(ep) { |
|||
// Check the block status of the reportees if the checkbox is checked |
|||
var blocked = []; |
|||
if ($('#anr-blockstatus-checkbox').is(':checked')) { |
|||
blocked = await preeditBlockStatusQuery(ep); // Query who's blocked |
|||
} |
} |
||
// Function to execute report |
|||
if (blocked.length !== 0) { // If any of the reportees is blocked |
|||
async function reportUsers(ep) { |
|||
// Check the block status of the reportees if the checkbox is checked |
|||
// Update dialog buttons |
|||
var blocked = []; |
|||
if ($('#anr-blockstatus-checkbox').is(':checked')) { |
|||
' |
blocked = await preeditBlockStatusQuery(ep); // Query who's blocked |
||
} |
|||
$(this).dialog({'buttons': [] }); |
|||
reportUsers2(ep); |
|||
} |
|||
}, { |
|||
'text': '戻る', |
|||
'click': function(){ |
|||
$(this).find('form').css('display', 'block'); |
|||
$('.anr-editing').remove(); |
|||
$(this).dialog({ |
|||
'width': 'auto', |
|||
'buttons': [{ |
|||
'text': 'プレビュー', |
|||
'click': preview |
|||
}, { |
|||
'text': '報告', |
|||
'click': report |
|||
}] |
|||
}); |
|||
} |
|||
}, { |
|||
'text': '中止', |
|||
'click': function(){ |
|||
$(this).dialog('close'); |
|||
} |
|||
}] |
|||
}); |
|||
if (blocked.length !== 0) { // If any of the reportees is blocked |
|||
reportUsers2(ep); |
|||
} |
|||
} |
|||
async function reportUsers2(ep) { |
|||
// Check duplicate reports if the checkbox is checked |
|||
if ($('#anr-duplicatereport-checkbox').is(':checked')) { |
|||
var dr = await preeditDuplicateReportQuery(ep); |
|||
} |
|||
if (typeof dr === 'undefined') var dr = {}; |
|||
switch(dr.wikitext) { |
|||
case null: // Error occurred |
|||
return; |
|||
case undefined: // The checkbox is unchecked or no duplicate report found |
|||
reportUsers3(ep); |
|||
return; |
|||
default: // Possible duplicate reports present |
|||
// Update dialog buttons |
// Update dialog buttons |
||
$('#anr-modal-dialog').dialog({ |
$('#anr-modal-dialog').dialog({ |
||
'buttons': [{ |
'buttons': [{ |
||
'text': '確認', |
|||
'click': function() { |
|||
previewDuplicateReports(dr.wikitext, dr.dupUsernames); |
|||
} |
|||
}, { |
|||
'text': '続行', |
'text': '続行', |
||
'click': function(){ |
'click': function(){ |
||
$(this).dialog({'buttons': [] }); |
$(this).dialog({'buttons': [] }); |
||
reportUsers2(ep); |
|||
} |
} |
||
}, { |
}, { |
||
960行目: | 910行目: | ||
}); |
}); |
||
} else { // If no one is blocked |
|||
reportUsers2(ep); |
|||
} |
|||
} |
} |
||
async function reportUsers2(ep) { |
|||
} |
|||
// Check duplicate reports if the checkbox is checked |
|||
/** |
|||
if ($('#anr-duplicatereport-checkbox').is(':checked')) { |
|||
* Function to check block status before edit |
|||
var dr = await preeditDuplicateReportQuery(ep); |
|||
* @returns {Array} [] if no one is blocked, [user1, user2...] if someone is blocked |
|||
} |
|||
*/ |
|||
if (typeof dr === 'undefined') var dr = {}; |
|||
async function preeditBlockStatusQuery(ep) { |
|||
switch(dr.wikitext) { |
|||
case null: // Error occurred |
|||
return; |
|||
case undefined: // The checkbox is unchecked or no duplicate report found |
|||
reportUsers3(ep); |
|||
return; |
|||
default: // Possible duplicate reports present |
|||
// Update dialog buttons |
|||
$('#anr-modal-dialog').dialog({ |
|||
'buttons': [{ |
|||
'text': '確認', |
|||
'click': function() { |
|||
previewDuplicateReports(dr.wikitext, dr.dupUsernames); |
|||
} |
|||
}, { |
|||
'text': '続行', |
|||
'click': function(){ |
|||
$(this).dialog({'buttons': [] }); |
|||
reportUsers3(ep); |
|||
} |
|||
}, { |
|||
'text': '戻る', |
|||
'click': function(){ |
|||
$(this).find('form').css('display', 'block'); |
|||
$('.anr-editing').remove(); |
|||
$(this).dialog({ |
|||
'width': 'auto', |
|||
'buttons': [{ |
|||
'text': 'プレビュー', |
|||
'click': preview |
|||
}, { |
|||
'text': '報告', |
|||
'click': report |
|||
}] |
|||
}); |
|||
} |
|||
}, { |
|||
'text': '中止', |
|||
'click': function(){ |
|||
$(this).dialog('close'); |
|||
} |
|||
}] |
|||
}); |
|||
// Can't check block status if the input values are only of t=diff or t=none |
|||
var proceed; |
|||
for (let i = 0; i < ep.types.length; i++) { |
|||
if (ep.types[i] !== 'diff' && ep.types[i] !== 'none') { |
|||
proceed = true; |
|||
break; |
|||
} |
} |
||
} |
|||
if (!proceed) { |
|||
$('.anr-editing').append('<p>ブロックチェックはスキップされました</p>'); |
|||
return []; |
|||
} |
} |
||
/** |
|||
// Update message on the dialog |
|||
* Function to check block status before edit |
|||
var msg = `<p>報告対象者のブロック情報を取得しています${toggleLoadingSpinner('add')}</p>`; |
|||
* @returns {Array} [] if no one is blocked, [user1, user2...] if someone is blocked |
|||
$('.anr-editing').append(msg); |
|||
*/ |
|||
async function preeditBlockStatusQuery(ep) { |
|||
// Can't check block status if the input values are only of t=diff or t=none |
|||
// Extract users and IPs from the array |
|||
var proceed; |
|||
const usersForBlockCheck = []; |
|||
for (let i = 0; i < ep. |
for (let i = 0; i < ep.types.length; i++) { |
||
if (ep.types[i] !== 'diff' && ep.types[i] !== 'none') { |
|||
proceed = true; |
|||
switch(ep.types[i]) { |
|||
case 'UNL': |
|||
case 'User2': |
|||
case 'IP2': |
|||
if (!isInArray(inputVal, usersForBlockCheck)) usersForBlockCheck.push(inputVal); |
|||
break; |
break; |
||
} |
|||
} |
|||
if (!proceed) { |
|||
if ((username = getKeyByValue(Logids, inputVal)) !== undefined) { // If the logid can be converted to a username |
|||
$('.anr-editing').append('<p>ブロックチェックはスキップされました</p>'); |
|||
if (!isInArray(username, usersForBlockCheck)) usersForBlockCheck.push(username); |
|||
return []; |
|||
break; |
|||
default: // Do nothing if t=diff or t=none (impossible to check block status) |
|||
} |
} |
||
} |
|||
// Check if any of the users is blocked |
|||
const blocked = await getBlocked(usersForBlockCheck); |
|||
// If any of the users is blocked |
|||
if (blocked.length !== 0) { |
|||
// Update message on the dialog |
// Update message on the dialog |
||
var msg = `<p>報告対象者のブロック情報を取得しています${toggleLoadingSpinner('add')}</p>`; |
|||
msg = |
|||
toggleLoadingSpinner('remove') + |
|||
`<p style="color: MediumVioletRed">ブロック済みの利用者を検出しました</p>`; |
|||
$('.anr-editing').append(msg); |
$('.anr-editing').append(msg); |
||
// Update block status links on the dialog |
|||
$('#anr-user-div :text').each(function() { // Loop through all inputs |
|||
const inputID = '#' + $(this).attr('id'); |
|||
const inputVal = $(inputID).val().trimANR(); |
|||
const $bsLinkDiv = $(inputID.replace('input', 'blockstatus-div')); |
|||
const $bsLink = $(inputID.replace('input', 'blockstatus')); |
|||
// Extract users and IPs from the array |
|||
$bsLinkDiv.css('display', 'none'); // Temporarily hide the div |
|||
const usersForBlockCheck = []; |
|||
if (isInArray(inputVal, blocked)) { |
|||
for (let i = 0; i < ep.users.length; i++) { |
|||
$bsLink.attr('href', mw.util.getUrl('特別:投稿記録/' + inputVal)); |
|||
const inputVal = ep.users[i]; |
|||
switch(ep.types[i]) { |
|||
case 'UNL': |
|||
case 'User2': |
|||
case 'IP2': |
|||
if (!isInArray(inputVal, usersForBlockCheck)) usersForBlockCheck.push(inputVal); |
|||
break; |
|||
case 'logid': |
|||
let username; |
|||
if ((username = getKeyByValue(Logids, inputVal)) !== undefined) { // If the logid can be converted to a username |
|||
if (!isInArray(username, usersForBlockCheck)) usersForBlockCheck.push(username); |
|||
} |
|||
break; |
|||
default: // Do nothing if t=diff or t=none (impossible to check block status) |
|||
} |
} |
||
} |
} |
||
// Check if any of the users is blocked |
|||
} else { |
|||
const blocked = await getBlocked(usersForBlockCheck); |
|||
// |
// If any of the users is blocked |
||
if (blocked.length !== 0) { |
|||
toggleLoadingSpinner('remove') + |
|||
`<p style="color: MediumSeaGreen">ブロック済みの利用者は検出されませんでした</p>`; |
|||
$('.anr-editing').append(msg); |
|||
// Update message on the dialog |
|||
} |
|||
msg = |
|||
toggleLoadingSpinner('remove') + |
|||
`<p style="color: MediumVioletRed">ブロック済みの利用者を検出しました</p>`; |
|||
$('.anr-editing').append(msg); |
|||
// Update block status links on the dialog |
|||
$('#anr-user-div :text').each(function() { // Loop through all inputs |
|||
const inputID = '#' + $(this).attr('id'); |
|||
const inputVal = $(inputID).val().trimANR(); |
|||
const $bsLinkDiv = $(inputID.replace('input', 'blockstatus-div')); |
|||
const $bsLink = $(inputID.replace('input', '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 { |
|||
/** |
|||
* Function to check duplicate reports |
|||
* @returns {Promise<{wikitext: string, dupUsernames: Array}>} wikitext === null if error occurs, undefined if no duplicate report is found, |
|||
* SECTIONTEXT to fetch preview from if there're potential duplicate reports. If SECTIONTEXT is returned, dupUsernames === |
|||
* [username1, username2...], without logids that can be converted to usernames. |
|||
*/ |
|||
async function preeditDuplicateReportQuery(ep) { |
|||
// Update message on the dialog |
// Update message on the dialog |
||
msg = |
|||
var msg = `<p>重複報告情報を取得しています${toggleLoadingSpinner('add')}</p>`; |
|||
toggleLoadingSpinner('remove') + |
|||
$('.anr-editing').append(msg); |
|||
`<p style="color: MediumSeaGreen">ブロック済みの利用者は検出されませんでした</p>`; |
|||
$('.anr-editing').append(msg); |
|||
} |
|||
// Get sections and the whole wikitext of the page to which to report |
|||
return blocked; |
|||
const parsed = await parsePage(ep.pageToEdit, 'wikitext|sections'); |
|||
const sections = parsed.sections, wikitext = parsed.wikitext; |
|||
const sectiontitles = []; |
|||
const 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++) { |
|||
/** |
|||
const section = sections[i]; |
|||
* Function to check duplicate reports |
|||
if (section.index.indexOf('T') === -1) { // If the section isn't a transcluded one |
|||
* @returns {Promise<{wikitext: string, dupUsernames: Array}>} wikitext === null if error occurs, undefined if no duplicate report is found, |
|||
sectiontitles.push(section.line); // Get the title of the section |
|||
* SECTIONTEXT to fetch preview from if there're potential duplicate reports. If SECTIONTEXT is returned, dupUsernames === |
|||
if (section.level == 2) { // Get equal-enclosed section headers |
|||
* [username1, username2...], without logids that can be converted to usernames. |
|||
sectionheaders.push('== ' + section.line + ' =='); |
|||
*/ |
|||
} else if (section.level == 3) { |
|||
async function preeditDuplicateReportQuery(ep) { |
|||
sectionheaders.push('=== ' + section.line + ' ==='); |
|||
} else if (section.level == 4) { |
|||
// Update message on the dialog |
|||
sectionheaders.push('==== ' + section.line + ' ===='); |
|||
var msg = `<p>重複報告情報を取得しています${toggleLoadingSpinner('add')}</p>`; |
|||
} else if (section.level == 5) { |
|||
$('.anr-editing').append(msg); |
|||
sectionheaders.push('===== ' + section.line + ' ====='); |
|||
// Get sections and the whole wikitext of the page to which to report |
|||
const parsed = await parsePage(ep.pageToEdit, 'wikitext|sections'); |
|||
const sections = parsed.sections, wikitext = parsed.wikitext; |
|||
const sectiontitles = []; |
|||
const 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++) { |
|||
const section = sections[i]; |
|||
if (section.index.indexOf('T') === -1) { // If the section isn't a transcluded one |
|||
sectiontitles.push(section.line); // Get the title of the section |
|||
if (section.level == 2) { // Get equal-enclosed section headers |
|||
sectionheaders.push('== ' + section.line + ' =='); |
|||
} else if (section.level == 3) { |
|||
sectionheaders.push('=== ' + section.line + ' ==='); |
|||
} else if (section.level == 4) { |
|||
sectionheaders.push('==== ' + section.line + ' ===='); |
|||
} else if (section.level == 5) { |
|||
sectionheaders.push('===== ' + section.line + ' ====='); |
|||
} |
|||
} |
} |
||
} |
} |
||
} |
|||
// The sections in which to search for duplicate reports |
// The sections in which to search for duplicate reports |
||
const tarSectionsI = [ |
const tarSectionsI = [ |
||
getSectionI(true), |
getSectionI(true), |
||
getSectionI(false), |
getSectionI(false), |
||
'不適切な利用者名', |
'不適切な利用者名', |
||
'公開アカウント', |
'公開アカウント', |
||
'公開プロキシ・ゾンビマシン・ボット・不特定多数', |
'公開プロキシ・ゾンビマシン・ボット・不特定多数', |
||
'犯罪行為またはその疑いのある投稿' |
'犯罪行為またはその疑いのある投稿' |
||
]; |
]; |
||
const tarSectionsS = [ |
const tarSectionsS = [ |
||
'著作権侵害・犯罪予告', |
'著作権侵害・犯罪予告', |
||
'名誉毀損・なりすまし・個人情報', |
'名誉毀損・なりすまし・個人情報', |
||
'妨害編集・いたずら', |
'妨害編集・いたずら', |
||
'その他', |
'その他', |
||
ep.sectionToEdit |
ep.sectionToEdit |
||
]; |
]; |
||
const tarSections3RR = ['3RR']; |
const tarSections3RR = ['3RR']; |
||
const tarSectionsSubpagedLTA = ['新規依頼']; |
const tarSectionsSubpagedLTA = ['新規依頼']; |
||
var tarSections; |
var tarSections; |
||
switch(ep.pageToEdit) { |
switch(ep.pageToEdit) { |
||
case ANI: |
case ANI: |
||
tarSections = tarSectionsI; |
tarSections = tarSectionsI; |
||
break; |
break; |
||
case ANS: |
case ANS: |
||
tarSections = tarSectionsS; |
tarSections = tarSectionsS; |
||
break; |
break; |
||
case AN3RR: |
case AN3RR: |
||
tarSections = tarSections3RR; |
tarSections = tarSections3RR; |
||
break; |
break; |
||
case Iccic: |
case Iccic: |
||
case ISECHIKA: |
case ISECHIKA: |
||
case KAGE: |
case KAGE: |
||
case KIYOSHIMA: |
case KIYOSHIMA: |
||
case SHINJU: |
case SHINJU: |
||
tarSections = tarSectionsSubpagedLTA; |
tarSections = tarSectionsSubpagedLTA; |
||
break; |
break; |
||
case |
case TEST: // For debugging |
||
eval(`tarSections = ${debugMode.drPreviewSections}`); |
eval(`tarSections = ${debugMode.drPreviewSections}`); |
||
break; |
break; |
||
default: // Error: Target pagename not defined |
default: // Error: Target pagename not defined |
||
msg = |
msg = |
||
toggleLoadingSpinner('remove') + |
toggleLoadingSpinner('remove') + |
||
'<p style="color: MediumVioletRed">致命的なエラーが発生しました</p><br>' + |
'<p style="color: MediumVioletRed">致命的なエラーが発生しました</p><br>' + |
||
`<p>${developerLink}に、<u>${ep.wikiPagename}</u>への報告においてこのエラーが発生したことの報告をお願いします。</p>` + |
`<p>${developerLink}に、<u>${ep.wikiPagename}</u>への報告においてこのエラーが発生したことの報告をお願いします。</p>` + |
||
manualEdit(ep); |
manualEdit(ep); |
||
$('.anr-editing').append(msg); |
$('.anr-editing').append(msg); |
||
editDone(ep, true); |
editDone(ep, true); |
||
return {'wikitext': null}; |
|||
} |
|||
// Error handler for when pageToEdit doesn't have sections that it's supposed to have |
|||
if (!arrayIsInArray(tarSections, sectiontitles)) { |
|||
sectionNotFound(ep); |
|||
return {'wikitext': null}; |
return {'wikitext': null}; |
||
} |
} |
||
// Separate the content of the parsed page into the content of each section |
|||
// Error handler for when pageToEdit doesn't have sections that it's supposed to have |
|||
var sectionsPiped = sectiontitles.join('|').escapeRegExpANR(); |
|||
var regExp = new RegExp(`={2,5}\\s*(?:${sectionsPiped})\\s*={2,5}`, 'g'); |
|||
sectionNotFound(ep); |
|||
var sectionContent = wikitext.split(regExp); // Array of the content of each section, without section headers |
|||
return {'wikitext': null}; |
|||
for (let i = 0; i < sectionContent.length; i++) { // Re-add to sectionContent the '== TITLE ==' header that's been removed by the split() above |
|||
} |
|||
sectionContent[i] = sectionheaders[i] + sectionContent[i]; |
|||
} |
|||
// |
// Remove the contents of irrelevant sections from the array 'sectionContent' |
||
sectionsPiped = tarSections.join('|').escapeRegExpANR(); |
|||
regExp = new RegExp(`={2,5}\\s*(?:${sectionsPiped})\\s*={2,5}`, 'g'); |
|||
for (let i = sectionContent.length -1; i >= 0; i--) { |
|||
var sectionContent = wikitext.split(regExp); // Array of the content of each section, without section headers |
|||
if (sectionContent[i].search(regExp) === -1) sectionContent.splice(i, 1); |
|||
for (let i = 0; i < sectionContent.length; i++) { // Re-add to sectionContent the '== TITLE ==' header that's been removed by the split() above |
|||
} |
|||
sectionContent[i] = sectionheaders[i] + sectionContent[i]; |
|||
} |
|||
// Get usernames for duplicate report check |
|||
// Remove the contents of irrelevant sections from the array 'sectionContent' |
|||
const usersDR = ep.users; // Input values without duplicates: usersDR will be used for duplicate report check |
|||
sectionsPiped = tarSections.join('|').escapeRegExpANR(); |
|||
for (let i = 0; i < ep.types.length; i++) { // Loop through all the input values |
|||
regExp = new RegExp(`={2,5}\\s*(?:${sectionsPiped})\\s*={2,5}`, 'g'); |
|||
let type = ep.types[i], username, logid, ip; |
|||
for (let i = sectionContent.length -1; i >= 0; i--) { |
|||
switch(type) { |
|||
if (sectionContent[i].search(regExp) === -1) sectionContent.splice(i, 1); |
|||
case 'UNL': // Registered users need duplicate report check also for their logids |
|||
} |
|||
case 'User2': |
|||
if (logid = Logids[username = usersDR[i]]) { // If the object knows the required logid, just push it into the array |
|||
// Get usernames for duplicate report check |
|||
const usersDR = ep.users; // Input values without duplicates: usersDR will be used for duplicate report check |
|||
for (let i = 0; i < ep.types.length; i++) { // Loop through all the input values |
|||
let type = ep.types[i], username, logid, ip; |
|||
switch(type) { |
|||
case 'UNL': // Registered users need duplicate report check also for their logids |
|||
case 'User2': |
|||
if (logid = Logids[username = usersDR[i]]) { // If the object knows the required logid, just push it into the array |
|||
usersDR.push(logid); |
|||
} else { // If not, get the logid from the API and push it into the array (if the response isn't undefined) |
|||
if (logid = await getLogid(username)) { |
|||
Logids[username] = logid; // Save the logid into the object |
|||
usersDR.push(logid); |
usersDR.push(logid); |
||
} else { // If not, get the logid from the API and push it into the array (if the response isn't undefined) |
|||
if (logid = await getLogid(username)) { |
|||
Logids[username] = logid; // Save the logid into the object |
|||
usersDR.push(logid); |
|||
} |
|||
} |
} |
||
break; |
|||
case 'IP2': // IPv6s need to be case-insensitive |
|||
if (mw.util.isIPv6Address(ip = usersDR[i], true) && ip.match(/[A-Z]/i)) { // If the IP is an IPv6 and if it contains alphabets |
|||
case 'IP2': // IPv6s need to be case-insensitive |
|||
if (ip.match(/[A-Z]/)) { // If the IPv6 is in uppercase, push its lowercase ver, if in lowercase, push the uppercase ver |
|||
usersDR.push(ip.toLowerCase()); |
|||
} else { |
|||
usersDR.push(ip.toUpperCase()); |
|||
} |
|||
} |
} |
||
break; |
|||
case 'logid': // The corresponding username needs to be checked |
|||
if (username = getKeyByValue(Logids, logid = usersDR[i])) usersDR.push(username); |
|||
break; |
|||
default: // t=diff or t=none: no need to do anything because the relevant input value is already in the array |
|||
break; |
|||
} |
|||
default: // t=diff or t=none: no need to do anything because the relevant input value is already in the array |
|||
} |
} |
||
} |
|||
// Extract UserAN templates and find duplicate reports |
// Extract UserAN templates and find duplicate reports |
||
const dupTemplates = [], dupUsernames = []; |
const dupTemplates = [], dupUsernames = []; |
||
for (let i = sectionContent.length -1; i >= 0; i--) { // Loop through all section contents |
for (let i = sectionContent.length -1; i >= 0; i--) { // Loop through all section contents |
||
const templates = findTemplates(sectionContent[i], 'useran'); // Extract UserAN templates as an array |
const templates = findTemplates(sectionContent[i], 'useran'); // Extract UserAN templates as an array |
||
let dupUsername, duplicateFound; |
let dupUsername, duplicateFound; |
||
if (templates.length !== 0) { // If the section content contains at least one UserAN |
if (templates.length !== 0) { // If the section content contains at least one UserAN |
||
for (let j = templates.length -1; j >= 0; j--) { // Loop through all the occurences of UserAN |
for (let j = templates.length -1; j >= 0; j--) { // Loop through all the occurences of UserAN |
||
if (dupUsername = stringContainsElementInArray(templates[j], usersDR)) { // If there's a duplciate report |
if (dupUsername = stringContainsElementInArray(templates[j], usersDR)) { // If there's a duplciate report |
||
if (!isInArray(templates[j], dupTemplates)) dupTemplates.push(templates[j]); // List the UserAN as a duplicate |
if (!isInArray(templates[j], dupTemplates)) dupTemplates.push(templates[j]); // List the UserAN as a duplicate |
||
if (!isInArray(dupUsername, dupUsernames)) dupUsernames.push(dupUsername); // List the duplicate username |
if (!isInArray(dupUsername, dupUsernames)) dupUsernames.push(dupUsername); // List the duplicate username |
||
duplicateFound = true; |
duplicateFound = true; |
||
} |
|||
} |
} |
||
} |
} |
||
if (!duplicateFound) sectionContent.splice(i, 1); // Remove the section text from the array if it doesn't involve duplicate reports |
|||
} |
} |
||
if (!duplicateFound) sectionContent.splice(i, 1); // Remove the section text from the array if it doesn't involve duplicate reports |
|||
// Return text and update dialog |
|||
} |
|||
if (sectionContent.length === 0) { // If there's no duplicate report |
|||
msg = `<p style="color: MediumSeaGreen">重複報告は検出されませんでした${toggleLoadingSpinner('remove')}</p>`; |
|||
// Return text and update dialog |
|||
$('.anr-editing').append(msg); // Update message on the dialog |
|||
if (sectionContent.length === 0) { // If there's no duplicate report |
|||
return; // Return undefined |
|||
} else { // If there're duplicate reports |
|||
msg = `<p style="color: MediumSeaGreen">重複報告は検出されませんでした${toggleLoadingSpinner('remove')}</p>`; |
|||
$('.anr-editing').append(msg); // Update message on the dialog |
|||
return; // Return undefined |
|||
msg = `<p style="color: MediumVioletRed">重複報告の可能性があります${toggleLoadingSpinner('remove')}</p>`; |
|||
} else { // If there're duplicate reports |
|||
$('.anr-editing').append(msg); |
|||
// Highlight all the duplciate UserAN occurences |
|||
msg = `<p style="color: MediumVioletRed">重複報告の可能性があります${toggleLoadingSpinner('remove')}</p>`; |
|||
sectionContent = sectionContent.join(''); // Merge the separate sections |
|||
$('.anr-editing').append(msg); |
|||
for (let i = 0; i < dupTemplates.length; i++) { |
|||
sectionContent = sectionContent.replaceAllANR(dupTemplates[i], `<span style="background-color: ${anrConfig.headerColor}">${dupTemplates[i]}</span>`); |
|||
} |
|||
// Return wikitext to fetch preview from |
|||
return { |
|||
'wikitext': sectionContent, |
|||
'dupUsernames': dupUsernames |
|||
}; |
|||
// Highlight all the duplciate UserAN occurences |
|||
sectionContent = sectionContent.join(''); // Merge the separate sections |
|||
for (let i = 0; i < dupTemplates.length; i++) { |
|||
sectionContent = sectionContent.replaceAllANR(dupTemplates[i], `<span style="background-color: ${anrConfig.headerColor}">${dupTemplates[i]}</span>`); |
|||
} |
} |
||
} |
|||
// Return wikitext to fetch preview from |
|||
return { |
|||
'wikitext': sectionContent, |
|||
'dupUsernames': dupUsernames |
|||
}; |
|||
// Function to show error message if sections that must be there are not found |
|||
function sectionNotFound(ep) { |
|||
const msg = |
|||
toggleLoadingSpinner('remove') + |
|||
'<p style="color: MediumVioletRed">取得に失敗しました</p>' + |
|||
'<p>指定されたセクションが見つかりませんでした</p>' + |
|||
'<br>' + |
|||
'<p>考えられる原因:</p>' + |
|||
`<p>1. 編集先のページの節構成が変更された</p>` + |
|||
`<p>2. 通信に失敗した</p>` + |
|||
`<p>3. スクリプトのバグ</p>` + |
|||
manualEdit(ep); |
|||
$('.anr-editing').append(msg); |
|||
editDone(ep, true); |
|||
} |
} |
||
// Function to generate the html for the manual edit helper tab |
|||
} |
|||
function manualEdit(ep) { |
|||
const meHtml = |
|||
'<br>' + |
|||
'<p>手動編集用:</p>' + |
|||
`<textarea disabled class="anr-dialog-textarea" rows="4">${ep.reportText}</textarea>` + |
|||
'<br>' + |
|||
'<p>要約:</p>' + |
|||
`<textarea disabled class="anr-dialog-textarea" rows="2">${ep.editSummary.replace(scriptAd, '')}</textarea>`; |
|||
return meHtml; |
|||
} |
|||
/** |
|||
// Function to show error message if sections that must be there are not found |
|||
* Action for when edit is done (in any way) |
|||
function sectionNotFound(ep) { |
|||
* @param {Object} ep |
|||
const msg = |
|||
* @param {boolean} editFailed |
|||
toggleLoadingSpinner('remove') + |
|||
*/ |
|||
'<p style="color: MediumVioletRed">取得に失敗しました</p>' + |
|||
function editDone(ep, editFailed) { |
|||
'<p>指定されたセクションが見つかりませんでした</p>' + |
|||
'<br>' + |
|||
'<p>考えられる原因:</p>' + |
|||
`<p>1. 編集先のページの節構成が変更された</p>` + |
|||
`<p>2. 通信に失敗した</p>` + |
|||
`<p>3. スクリプトのバグ</p>` + |
|||
manualEdit(ep); |
|||
$('.anr-editing').append(msg); |
|||
editDone(ep, true); |
|||
} |
|||
const btns = [], $dialog = $('#anr-modal-dialog'); |
|||
// Function to generate the html for the manual edit helper tab |
|||
function manualEdit(ep) { |
|||
const meHtml = |
|||
'<br>' + |
|||
'<p>手動編集用:</p>' + |
|||
`<textarea disabled class="anr-dialog-textarea" rows="4">${ep.reportText}</textarea>` + |
|||
'<br>' + |
|||
'<p>要約:</p>' + |
|||
`<textarea disabled class="anr-dialog-textarea" rows="2">${ep.editSummary.replace(scriptAd, '')}</textarea>`; |
|||
return meHtml; |
|||
} |
|||
// Button to jump to the report page |
|||
/** |
|||
if (editFailed || mw.config.get('wgPageName') !== ep.pageToEdit) { // Show the button if the edit failed or if the user is NOT on the page |
|||
* Action for when edit is done (in any way) |
|||
const destBtn = { |
|||
* @param {Object} ep |
|||
'text': '報告先', |
|||
* @param {boolean} editFailed |
|||
'click': function(){ |
|||
*/ |
|||
window.open(mw.util.getUrl(ep.wikiPagename), '_blank'); |
|||
function editDone(ep, editFailed) { |
|||
} |
|||
}; |
|||
btns.push(destBtn); |
|||
} |
|||
// Button to |
// Button to close the dialog (always shown) |
||
const closeBtn = { |
|||
if (editFailed || mw.config.get('wgPageName') !== ep.pageToEdit) { // Show the button if the edit failed or if the user is NOT on the page |
|||
'text': '閉じる', |
|||
'text': '報告先', |
|||
'click': function(){ |
'click': function(){ |
||
$dialog.dialog('close'); |
|||
var curPage = mw.config.get('wgPageName'); |
|||
if (curPage === ANI || |
|||
curPage === ANS || |
|||
curPage === AN3RR || |
|||
curPage === Iccic || |
|||
curPage === ISECHIKA || |
|||
curPage === KAGE || |
|||
curPage === KIYOSHIMA || |
|||
curPage === SHINJU || |
|||
curPage === TEST) |
|||
{ |
|||
location.reload(true); |
|||
} |
|||
} |
} |
||
}; |
}; |
||
btns.push( |
btns.push(closeBtn); |
||
// Show the button(s) on the dialog |
|||
$dialog.dialog({'buttons': btns}); |
|||
if (editFailed) centerDialog(); |
|||
} |
} |
||
/** |
|||
// Button to close the dialog (always shown) |
|||
* Function to preview duplicate reports on a new dialog |
|||
const closeBtn = { |
|||
* @param {string} wikitext wikitext for preview (inherited from preeditDuplicateReportQuery) |
|||
'text': '閉じる', |
|||
* @param {Array} dupUsernames usernames found to be duplicate reports (inherited from preeditDuplicateReportQuery) |
|||
'click': function(){ |
|||
*/ |
|||
$dialog.dialog('close'); |
|||
function previewDuplicateReports(wikitext, dupUsernames) { |
|||
var curPage = mw.config.get('wgPageName'); |
|||
if (curPage === ANI || |
|||
// Duplicate usernames to show on the dialog (logids are to be shown in parentheses) |
|||
const usernames = []; |
|||
for (let i = 0; i < dupUsernames.length; i++) { |
|||
let username, logid; |
|||
if (username = getKeyByValue(Logids, logid = dupUsernames[i])) { // if the dupUsername is a logid and that can be converted to a username |
|||
curPage === KAGE || |
|||
usernames.push(`${username} (${logid})`); |
|||
} else if (logid = Logids[username = dupUsernames[i]]) { // if the dupUsername is a username and that can be converted to a logid |
|||
curPage === SHINJU || |
|||
usernames.push(`${username} (${logid})`); |
|||
{ |
} else { |
||
usernames.push(username); // if else, just push the username into the array |
|||
} |
} |
||
} |
} |
||
}; |
|||
btns.push(closeBtn); |
|||
// Create dialog |
|||
const duplicateReportPreviewDiv = |
|||
$dialog.dialog({'buttons': btns}); |
|||
'<div id="anr-drpreview-dialog" title="AN Reporter Duplicate Report Preview" style="max-height: 80vh;">' + |
|||
if (editFailed) centerDialog(); |
|||
' <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>' + |
|||
usernames.join(', ') + |
|||
' </p>' + |
|||
' </div>' + |
|||
' <div id="anr-drpreview-body" style="display: none; font-size: 1.1em; padding: 0.5em; border: 1px solid silver; background-color: white;">' + |
|||
// Added when the dialog is opened |
|||
' </div>' + |
|||
'</div>'; |
|||
$('body').append(duplicateReportPreviewDiv); |
|||
// Show preview dialog |
|||
} |
|||
$('#anr-drpreview-dialog').dialog({ |
|||
'height': 'auto', |
|||
'width': $('#content').width() * 0.8, |
|||
'modal': true, |
|||
'open': async function(){ |
|||
// Initialize the design of the dialog |
|||
/** |
|||
dialogCSS(); |
|||
* Function to preview duplicate reports on a new dialog |
|||
* @param {string} wikitext wikitext for preview (inherited from preeditDuplicateReportQuery) |
|||
* @param {Array} dupUsernames usernames found to be duplicate reports (inherited from preeditDuplicateReportQuery) |
|||
*/ |
|||
function previewDuplicateReports(wikitext, dupUsernames) { |
|||
// Convert the wikitext to an html form |
|||
// Duplicate usernames to show on the dialog (logids are to be shown in parentheses) |
|||
const wikitextInHtml = await convertWikitextToHtmlFormat(wikitext.trim(), ''); |
|||
const usernames = []; |
|||
if (wikitextInHtml) { |
|||
$('#anr-drpreview-body').append(wikitextInHtml.htmltext); |
|||
let username, logid; |
|||
$('#anr-drpreview-dialog a').attr('target', '_blank'); // Open all links on a new tab |
|||
if (username = getKeyByValue(Logids, logid = dupUsernames[i])) { // if the dupUsername is a logid and that can be converted to a username |
|||
$('#anr-drpreview-body').css('display', 'block'); |
|||
usernames.push(`${username} (${logid})`); |
|||
$('#anr-drpreview-loading').remove(); |
|||
} else if (logid = Logids[username = dupUsernames[i]]) { // if the dupUsername is a username and that can be converted to a logid |
|||
$('#anr-drpreview-userlist').css('display', 'inline'); |
|||
usernames.push(`${username} (${logid})`); |
|||
centerDialog(); |
|||
} else { |
|||
$('#anr-drpreview-loading').text('読み込みに失敗しました').css('color', 'MediumVioletRed'); |
|||
} |
|||
centerDialog(); |
|||
} |
|||
setTimeout(function(){ |
|||
$('#anr-drpreview-dialog').dialog('close'); |
|||
}, 10000); |
|||
} |
|||
}, |
|||
'buttons': [{ |
|||
const duplicateReportPreviewDiv = |
|||
'text': '閉じる', |
|||
'<div id="anr-drpreview-dialog" title="AN Reporter Duplicate Report Preview" style="max-height: 80vh;">' + |
|||
'click': function(){ |
|||
' <div id="anr-drpreview-header" style="padding: 0.5em;">' + |
|||
$(this).dialog('close'); |
|||
} |
|||
}] |
|||
}); |
|||
' <p id="anr-drpreview-userlist" style="display: none; font-size: larger">' + |
|||
' <span style="font-weight: bold">重複報告の可能性のある値:</span>' + |
|||
' <br>' + |
|||
usernames.join(', ') + |
|||
' </p>' + |
|||
' </div>' + |
|||
' <div id="anr-drpreview-body" style="display: none; font-size: 1.1em; padding: 0.5em; border: 1px solid silver; background-color: white;">' + |
|||
// Added when the dialog is opened |
|||
' </div>' + |
|||
'</div>'; |
|||
$('body').append(duplicateReportPreviewDiv); |
|||
} |
|||
// Show preview dialog |
|||
$('#anr-drpreview-dialog').dialog({ |
|||
'height': 'auto', |
|||
'width': $('#content').width() * 0.8, |
|||
'modal': true, |
|||
'open': async function(){ |
|||
async function reportUsers3(ep) { |
|||
// Initialize the design of the dialog |
|||
dialogCSS(); |
|||
const ts = await getTimestamps(ep); |
|||
// Convert the wikitext to an html form |
|||
if (!ts) return; |
|||
const wikitextInHtml = await convertWikitextToHtmlFormat(wikitext.trim(), ''); |
|||
const baseTS = ts.baseTS, curTS = ts.curTS; |
|||
$('#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'); |
|||
centerDialog(); |
|||
} else { |
|||
$('#anr-drpreview-loading').text('読み込みに失敗しました').css('color', 'MediumVioletRed'); |
|||
centerDialog(); |
|||
setTimeout(function(){ |
|||
$('#anr-drpreview-dialog').dialog('close'); |
|||
}, 10000); |
|||
} |
|||
const sectionNum = await getSectionNumber(ep); |
|||
}, |
|||
if (!sectionNum) return; |
|||
'text': '閉じる', |
|||
'click': function(){ |
|||
$(this).dialog('close'); |
|||
} |
|||
}] |
|||
}); |
|||
const reportText = await getReportText(ep, sectionNum); |
|||
} |
|||
if (!reportText) return; |
|||
edit(ep, sectionNum, reportText, baseTS, curTS); |
|||
async function reportUsers3(ep) { |
|||
} |
|||
const ts = await getTimestamps(ep); |
|||
if (!ts) return; |
|||
const baseTS = ts.baseTS, curTS = ts.curTS; |
|||
// Function to get the latest revision of the administrator's noticeboard |
|||
const sectionNum = await getSectionNumber(ep); |
|||
function getTimestamps(ep) { |
|||
if (!sectionNum) return; |
|||
return new Promise(function(resolve) { |
|||
var msg = `<p>最新版を取得しています${toggleLoadingSpinner('add')}</p>`; |
|||
const reportText = await getReportText(ep, sectionNum); |
|||
$('.anr-editing').append(msg); |
|||
if (!reportText) return; |
|||
new mw.Api().get({ |
|||
edit(ep, sectionNum, reportText, baseTS, curTS); |
|||
'action': 'query', |
|||
'titles': ep.pageToEdit, |
|||
'prop': 'revisions', |
|||
'curtimestamp': true, |
|||
'formatversion': 2 |
|||
}).then(function(res){ |
|||
var resPages; |
|||
} |
|||
if (res && res.query && (resPages = res.query.pages)) { // If the latest revision is successfully retrieved |
|||
if (!resPages[0].missing) { // .missing is true if the page doesn't exist, otherwise undefined |
|||
// |
// Get the timestamps of the latest revision and the API query |
||
const baseTS = resPages[0].revisions[0].timestamp; // The TS of the latest revision |
|||
function getTimestamps(ep) { |
|||
const curTS = res.curtimestamp; // The TS of the API query |
|||
return new Promise(function(resolve) { |
|||
// Update message on the dialog |
|||
var msg = `<p>最新版を取得しています${toggleLoadingSpinner('add')}</p>`; |
|||
msg = |
|||
'<p style="color: MediumSeaGreen">取得に成功しました</p>' + |
|||
`<p>セクション番号を取得しています${toggleLoadingSpinner('move')}</p>`; |
|||
$('.anr-editing').append(msg); |
|||
// Return the timestamps as an object |
|||
new mw.Api().get({ |
|||
resolve({ |
|||
' |
'baseTS': baseTS, |
||
' |
'curTS': curTS |
||
}); |
|||
'formatversion': 2 |
|||
}).then(function(res){ |
|||
} else { // If the page doesn't exist |
|||
if (res && res.query && (resPages = res.query.pages)) { // If the latest revision is successfully retrieved |
|||
if (!resPages[0].missing) { // .missing is true if the page doesn't exist, otherwise undefined |
|||
msg = |
|||
toggleLoadingSpinner('remove') + |
|||
'<p style="color: MediumVioletRed">エラー: 報告先のページが存在しません</p>' + |
|||
const curTS = res.curtimestamp; // The TS of the API query |
|||
manualEdit(ep); |
|||
$('.anr-editing').append(msg); |
|||
editDone(ep, true); |
|||
resolve(); |
|||
} |
|||
msg = |
|||
'<p style="color: MediumSeaGreen">取得に成功しました</p>' + |
|||
`<p>セクション番号を取得しています${toggleLoadingSpinner('move')}</p>`; |
|||
$('.anr-editing').append(msg); |
|||
} else { // If revision retrieval fails |
|||
queryFailed(ep); |
|||
'baseTS': baseTS, |
|||
'curTS': curTS |
|||
}); |
|||
} else { // If the page doesn't exist |
|||
msg = |
|||
toggleLoadingSpinner('remove') + |
|||
'<p style="color: MediumVioletRed">エラー: 報告先のページが存在しません</p>' + |
|||
manualEdit(ep); |
|||
$('.anr-editing').append(msg); |
|||
editDone(ep, true); |
|||
resolve(); |
resolve(); |
||
} |
} |
||
} |
}); |
||
queryFailed(ep); |
|||
resolve(); |
|||
} |
|||
}); |
}); |
||
} |
|||
function addUsersToWatchlist(ep) { |
|||
}); |
|||
if (!$('#anr-watchlist-checkbox').is(':checked')) return console.log('ウォッチリストへの追加設定はオフになっています。'); |
|||
} |
|||
return new Promise(function(resolve) { |
|||
// Get pagenames to watch |
|||
function addUsersToWatchlist(ep) { |
|||
const pagenames = []; |
|||
if (!$('#anr-watchlist-checkbox').is(':checked')) return console.log('ウォッチリストへの追加設定はオフになっています。'); |
|||
for (let i = 0; i < ep.types.length; i++) { |
|||
return new Promise(function(resolve) { |
|||
const type = ep.types[i], user = ep.users[i]; |
|||
if (type === 'User2' || type === 'UNL' || type === 'IP2') { |
|||
// Get pagenames to watch |
|||
if (!isInArray('利用者:' + user, pagenames)) pagenames.push('利用者:' + user); |
|||
} else if (type === 'logid') { |
|||
let username; |
|||
if (username = getKeyByValue(Logids, user) && !isInArray('利用者:' + username, pagenames)) pagenames.push('利用者:' + username); |
|||
if (type === 'User2' || type === 'UNL' || type === 'IP2') { |
|||
} |
|||
if (!isInArray('利用者:' + user, pagenames)) pagenames.push('利用者:' + user); |
|||
} else if (type === 'logid') { |
|||
let username; |
|||
if (username = getKeyByValue(Logids, user) && !isInArray('利用者:' + username, pagenames)) pagenames.push('利用者:' + username); |
|||
} |
} |
||
} |
|||
// Add the pages to watchlist |
// Add the pages to watchlist |
||
new mw.Api().get({ |
new mw.Api().get({ |
||
'action': 'query', |
'action': 'query', |
||
'meta': 'tokens', |
'meta': 'tokens', |
||
'type': 'watch' |
'type': 'watch' |
||
}).then(function(res){ |
}).then(function(res){ |
||
const token = res.query.tokens.watchtoken; |
const token = res.query.tokens.watchtoken; |
||
if (!token) resolve(mw.log.error('ウォッチトークンの取得に失敗しました')); |
if (!token) resolve(mw.log.error('ウォッチトークンの取得に失敗しました')); |
||
new mw.Api().post({ |
|||
'action': 'watch', |
|||
'titles': pagenames.join('|'), |
|||
'token': token, |
|||
'formatversion': 2 |
|||
}).then(function(res) { |
|||
resolve(console.log('以下のページをウォッチリストに追加しました:\n' + pagenames.join(', '))); |
|||
}).catch(function(code, err) { |
|||
resolve(mw.log.error('ウォッチリストへの追加に失敗しました:\n' + err.error.info)); |
|||
}); |
|||
new mw.Api().post({ |
|||
'action': 'watch', |
|||
'titles': pagenames.join('|'), |
|||
'token': token, |
|||
'formatversion': 2 |
|||
}).then(function(res) { |
|||
resolve(console.log('以下のページをウォッチリストに追加しました:\n' + pagenames.join(', '))); |
|||
}).catch(function(code, err) { |
|||
resolve(mw.log.error('ウォッチリストへの追加に失敗しました:\n' + err.error.info)); |
|||
}); |
}); |
||
}); |
}); |
||
} |
|||
// Function to show message when edit attempt is done |
|||
}); |
|||
function queryFailed(ep) { |
|||
} |
|||
const msg = |
|||
toggleLoadingSpinner('remove') + |
|||
'<p style="color: MediumVioletRed">取得に失敗しました</p>' + |
|||
manualEdit(ep); |
|||
$('.anr-editing').append(msg); |
|||
editDone(ep, true); |
|||
} |
|||
// Function to |
// Function to get the section number from the section title |
||
function |
async function getSectionNumber(ep) { |
||
const msg = |
|||
toggleLoadingSpinner('remove') + |
|||
'<p style="color: MediumVioletRed">取得に失敗しました</p>' + |
|||
manualEdit(ep); |
|||
$('.anr-editing').append(msg); |
|||
editDone(ep, true); |
|||
} |
|||
const parse = await parsePage(ep.pageToEdit, 'sections'); |
|||
// Function to get the section number from the section title |
|||
var resSect, sectionNum; |
|||
async function getSectionNumber(ep) { |
|||
if (parse && (resSect = parse.sections)) { // If the section list is successfully retrieved |
|||
// Get the titles of all sections and their section numbers |
|||
const parse = await parsePage(ep.pageToEdit, 'sections'); |
|||
for (let i = 0; i < resSect.length; i++) { |
|||
var resSect, sectionNum; |
|||
if (resSect[i].line === ep.sectionToEdit) { |
|||
if (parse && (resSect = parse.sections)) { // If the section list is successfully retrieved |
|||
sectionNum = resSect[i].index; |
|||
break; |
|||
} |
|||
} |
|||
// |
// Return a section number if the section is found, undefined if not |
||
if (!sectionNum) { |
|||
sectionNotFound(ep); |
|||
return; |
|||
} else { |
|||
const msg = |
|||
'<p style="color: MediumSeaGreen">取得に成功しました</p>' + |
|||
`<p>最新版のテキストを取得しています${toggleLoadingSpinner('move')}</p>`; |
|||
$('.anr-editing').append(msg); |
|||
return sectionNum; |
|||
} |
} |
||
} |
|||
} else { // If the section list retrieval fails |
|||
queryFailed(ep); |
|||
sectionNotFound(ep); |
|||
return; |
return; |
||
} else { |
|||
const msg = |
|||
'<p style="color: MediumSeaGreen">取得に成功しました</p>' + |
|||
`<p>最新版のテキストを取得しています${toggleLoadingSpinner('move')}</p>`; |
|||
$('.anr-editing').append(msg); |
|||
return sectionNum; |
|||
} |
} |
||
} else { // If the section list retrieval fails |
|||
queryFailed(ep); |
|||
return; |
|||
} |
} |
||
// Function to get the text to replace with the current text in the section |
|||
} |
|||
async function getReportText(ep, sectionNum) { |
|||
const parse = await parsePage(ep.pageToEdit, 'wikitext', sectionNum); |
|||
// Function to get the text to replace with the current text in the section |
|||
if (parse) { |
|||
async function getReportText(ep, sectionNum) { |
|||
// Update message |
|||
const parse = await parsePage(ep.pageToEdit, 'wikitext', sectionNum); |
|||
var msg = |
|||
if (parse) { |
|||
'<p style="color: MediumSeaGreen">取得に成功しました</p>' + |
|||
`<p>報告を試みています${toggleLoadingSpinner('move')}</p>`; |
|||
$('.anr-editing').append(msg); |
|||
// |
// Get the whole text to append |
||
const wikitext = parse.wikitext; |
|||
var reportText; |
|||
'<p style="color: MediumSeaGreen">取得に成功しました</p>' + |
|||
if (ep.reportToANS) { // If the target is WP:AN/S |
|||
`<p>報告を試みています${toggleLoadingSpinner('move')}</p>`; |
|||
$('.anr-editing').append(msg); |
|||
// Add div if the target section is 'その他' but lacks div for the current date |
|||
// Get the whole text to append |
|||
const miscHeader = `{{bgcolor|#eee|{{Visible anchor|他${today()}}}|div}}`; |
|||
const wikitext = parse.wikitext; |
|||
if (ep.sectionToEdit === 'その他' && wikitext.indexOf(miscHeader) === -1) ep.reportText = '; ' + miscHeader + '\n\n' + ep.reportText; |
|||
var reportText; |
|||
if (ep.reportToANS) { // If the target is WP:AN/S |
|||
// Define comment-outs |
|||
// Add div if the target section is 'その他' but lacks div for the current date |
|||
const tail = '\n\n<!-- ◆以下は消さないで下さい。新規依頼はこの上へ -->}}'; |
|||
const miscHeader = `{{bgcolor|#eee|{{Visible anchor|他${today()}}}|div}}`; |
|||
let head; |
|||
if (ep.sectionToEdit === 'その他' && wikitext.indexOf(miscHeader) === -1) ep.reportText = '; ' + miscHeader + '\n\n' + ep.reportText; |
|||
if (ep.pageToEdit === ISECHIKA) { |
|||
head = '<!-- ◆ここから下に新しい報告を記入して下さい -->\n<!-- 不適切な利用者名を載せないでください。何時何分に作成された等の依頼方法でお願いします。 -->\n\n'; |
|||
} else { |
|||
head = '<!-- ◆ここから下に新しい報告を記入して下さい -->\n\n'; |
|||
} |
|||
// Extract the '報告' parameter of SockInfo and get a new wikitext for the section including the report to be submitted |
|||
// Insert text into the right place |
|||
let sockInfo = findTemplates(wikitext, 'sockinfo'); // Array |
|||
let sockInfoRep, matched; |
|||
if (sockInfo.length === 1) { // One section on WP:AN/S should have one SockInfo |
|||
sockInfo = sockInfo[0]; |
|||
if (matched = sockInfo.match(/\|\s*報告[^\S\r\n]*=\s*/g)) { // If the template has a '報告' parameter |
|||
return reportText; |
|||
if (matched.length === 1) { |
|||
// |
sockInfoRep = sockInfo.split(matched[0])[1]; // The content of the '報告' parameter |
||
if (matched = sockInfoRep.match(/(\(UTC\))+([^\S\r\n]*<\/*\w+[^\S\r\n]*>)*/g)) { // If there're other reports |
|||
msg = |
|||
toggleLoadingSpinner('remove') + |
|||
'<p style="color: MediumVioletRed">報告に失敗しました</p>' + |
|||
`<p>セクション構造が改変されているため、${developerLink}に連絡をお願いします</p>` + |
|||
manualEdit(ep); |
|||
$('.anr-editing').append(msg); |
|||
editDone(ep, true); |
|||
return; |
|||
const lastUtc = matched[matched.length - 1]; // The last occurrence of 'UTC' (the string itself) |
|||
const splitIndex = wikitext.lastIndexOf(lastUtc) + lastUtc.length; // The repot should be inserted at the nth character |
|||
reportText = wikitext.substring(0, splitIndex).trimANR() + '\n\n' + ep.reportText + '\n\n' + wikitext.substring(splitIndex).trimANR() + '\n\n'; |
|||
} else { // If there're no other reports |
|||
reportText = wikitext.replace(sockInfoRep, head + ep.reportText + tail); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
if (reportText) { |
|||
return reportText; |
|||
} else { |
|||
// Show error and quit the procedure |
|||
msg = |
|||
toggleLoadingSpinner('remove') + |
|||
'<p style="color: MediumVioletRed">報告に失敗しました</p><br>' + |
|||
`<p>{{SockInfo}}がない、または複数個あるか、テンプレートの「報告」引数の値が不正です</p>` + |
|||
manualEdit(ep); |
|||
$('.anr-editing').append(msg); |
|||
editDone(ep, true); |
|||
return; |
|||
} |
|||
} else { // If the target is WP:AN/I or WP:AN/3RR |
|||
reportText = wikitext.trimANR() + '\n\n' + ep.reportText; |
|||
return reportText; |
|||
} |
} |
||
} else { // If |
} else { // If wikitext retrieval fails |
||
queryFailed(ep); |
|||
return |
return; |
||
} |
} |
||
} else { // If wikitext retrieval fails |
|||
queryFailed(ep); |
|||
return; |
|||
} |
} |
||
// Function to edit the page |
|||
} |
|||
function edit(ep, sectionNum, reportText, baseTS, curTS) { |
|||
new mw.Api().post({ |
|||
// Function to edit the page |
|||
'action': 'edit', |
|||
function edit(ep, sectionNum, reportText, baseTS, curTS) { |
|||
'title': ep.pageToEdit, |
|||
'section': sectionNum, |
|||
'text': reportText, |
|||
'summary': ep.editSummary, |
|||
'basetimestamp': baseTS, |
|||
'starttimestamp': curTS, |
|||
'token': debugMode.causeIntentionalError ? '': mw.user.tokens.get('csrfToken'), |
|||
'format': 'json' |
|||
}).then(function(res) { |
|||
toggleLoadingSpinner('remove'); |
|||
new mw.Api().post({ |
|||
$('.anr-editing').append(`<p style="color: MediumSeaGreen">報告が完了しました</p>`); |
|||
'action': 'edit', |
|||
editDone(ep, false); |
|||
'section': sectionNum, |
|||
'text': reportText, |
|||
'summary': ep.editSummary, |
|||
'basetimestamp': baseTS, |
|||
'starttimestamp': curTS, |
|||
'token': debugMode.causeIntentionalError ? '': mw.user.tokens.get('csrfToken'), |
|||
'format': 'json' |
|||
}).then(function(res) { |
|||
}).catch(function(code, err) { |
|||
var msg; |
|||
$('.anr-editing').append(`<p style="color: MediumSeaGreen">報告が完了しました</p>`); |
|||
if (err && err.error) { |
|||
// Show the details of the error |
|||
}).catch(function(code, err) { |
|||
msg = |
|||
toggleLoadingSpinner('remove') + |
|||
'<p style="color: MediumVioletRed">報告に失敗しました</p><br>' + |
|||
'<p>詳細:</p>' + |
|||
`<p>${err.error.info}</p>` + |
|||
manualEdit(ep); |
|||
$('.anr-editing').append(msg); |
|||
editDone(ep, true); |
|||
} else { // If unknown error occurred |
|||
msg = |
|||
toggleLoadingSpinner('remove') + |
|||
'<p style="color: MediumVioletRed">報告に失敗しました</p><br>' + |
|||
'<p>詳細:</p>' + |
|||
`<p>${err.error.info}</p>` + |
|||
manualEdit(ep); |
|||
$('.anr-editing').append(msg); |
|||
editDone(ep, true); |
|||
msg = |
|||
toggleLoadingSpinner('remove') + |
|||
'<p style="color: MediumVioletRed">不明なエラーが発生しました</p>' + |
|||
manualEdit(ep); |
|||
$('.anr-editing').append(msg); |
|||
editDone(ep, true); |
|||
} |
|||
}); |
|||
toggleLoadingSpinner('remove') + |
|||
'<p style="color: MediumVioletRed">不明なエラーが発生しました</p>' + |
|||
manualEdit(ep); |
|||
$('.anr-editing').append(msg); |
|||
editDone(ep, true); |
|||
} |
|||
}); |
|||
/** |
|||
} |
|||
* 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 tempInnerContent = text.split('{{'); // Note: tempInnerContent[0] is always an empty string or a string that has nothing to do with templates |
|||
* Function to extract templates from wikitext |
|||
const templates = []; // The array of extracted templates to return |
|||
* @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) { |
|||
// Extract templates from the text |
|||
// Split the text with '{{', the head delimiter of templates |
|||
if (tempInnerContent.length === 0) { // If the text has no tempalte in it |
|||
const templates = []; // The array of extracted templates to return |
|||
return templates; // Return an empty array |
|||
if (tempInnerContent.length === 0) { // If the text has no tempalte in it |
|||
} else { // If the text has some templates in it |
|||
const nest = []; // Stores the element number of tempInnerContent if the element involves nested templates |
|||
} else { // If the text has some templates in it |
|||
for (let i = 1; i < tempInnerContent.length; i++) { // Loop through all elements in tempInnerContent (except tempInnerContent[0]) |
|||
let tempTailCnt = (tempInnerContent[i].match(/\}\}/g) || []).length; // The number of '}}' in the split segment |
|||
let temp = ''; // Temporary escape hatch for templates |
|||
for (let i = 1; i < tempInnerContent.length; i++) { // Loop through all elements in tempInnerContent (except tempInnerContent[0]) |
|||
if (tempTailCnt === 0) { // The split segment not having any '}}' means that it nests another template |
|||
let temp = ''; // Temporary escape hatch for templates |
|||
nest.push(i); // Push the element number into the array |
|||
} else if (tempTailCnt === 1) { // The split segment itself is the whole inner content of one template |
|||
temp = '{{' + tempInnerContent[i].split('}}')[0] + '}}'; |
|||
} else if (tempTailCnt === 1) { // The split segment itself is the whole inner content of one template |
|||
if (!isInArray(temp, templates)) templates.push(temp); |
|||
} else if (tempTailCnt > 1) { // The split segment is part of more than one template (e.g. TL2|...}}...}} ) |
|||
temp = '{{' + tempInnerContent[i].split('}}')[0] + '}}'; |
|||
if (!isInArray(temp, templates)) templates.push(temp); |
|||
for (let j = 0; j < tempTailCnt; j++) { // Loop through all the nests |
|||
if (j === 0) { // The innermost template |
|||
temp = '{{' + tempInnerContent[i].split('}}')[j] + '}}'; // Same as when tempTailCnt === 1 |
|||
if (!isInArray(temp, templates)) templates.push(temp); |
|||
} else { // Nesting templates |
|||
if (!isInArray(temp, templates)) templates.push(temp); |
|||
const elNum = nest[nest.length -1]; // The start of the nesting template |
|||
nest.pop(); |
|||
const nestedTempInnerContent = tempInnerContent[i].split('}}'); |
|||
temp = '{{' + tempInnerContent.slice(elNum, i).join('{{') + '{{' + nestedTempInnerContent.slice(0, j + 1).join('}}') + '}}'; |
|||
if (!isInArray(temp, templates)) templates.push(temp); |
|||
const nestedTempInnerContent = tempInnerContent[i].split('}}'); |
|||
} |
|||
temp = '{{' + tempInnerContent.slice(elNum, i).join('{{') + '{{' + nestedTempInnerContent.slice(0, j + 1).join('}}') + '}}'; |
|||
if (!isInArray(temp, templates)) templates.push(temp); |
|||
} |
} |
||
1,705行目: | 1,741行目: | ||
} |
} |
||
// Check if the optional parameter is specified |
|||
} |
|||
if (templateName && templates.length !== 0) { |
|||
const templateRegExp = new RegExp(templateName, 'i'); |
|||
// Check if the optional parameter is specified |
|||
for (let i = templates.length -1; i >= 0; i--) { |
|||
// Remove the template from the array if it's not an instance of the specified template |
|||
const templateRegExp = new RegExp(templateName, 'i'); |
|||
if (templates[i].split('|')[0].search(templateRegExp) === -1) templates.splice(i, 1); |
|||
for (let i = templates.length -1; i >= 0; i--) { |
|||
} |
|||
// Remove the template from the array if it's not an instance of the specified template |
|||
if (templates[i].split('|')[0].search(templateRegExp) === -1) templates.splice(i, 1); |
|||
} |
} |
||
} |
|||
return templates; |
return templates; |
||
} |
|||
} |
} |
||
} |
|||
// Function to generate edit summary automatically |
// Function to generate edit summary automatically |
||
function genEditSummary() { |
function genEditSummary() { |
||
const links = []; |
const links = []; |
||
$('#anr-user-div :text').each(function() { // Loop through all inputs |
$('#anr-user-div :text').each(function() { // Loop through all inputs |
||
const inputID = '#' + $(this).attr('id'); |
const inputID = '#' + $(this).attr('id'); |
||
const type = $(inputID.replace('input', 'select')).children('option').filter(':selected').text(); // UserAN type specified in the dropdown |
const type = $(inputID.replace('input', 'select')).children('option').filter(':selected').text(); // UserAN type specified in the dropdown |
||
const reportee = $(this).val().trimANR(); // Username |
const reportee = $(this).val().trimANR(); // Username |
||
let link; |
let link; |
||
if (reportee !== '') { // Skip if the input value is a null string |
if (reportee !== '') { // Skip if the input value is a null string |
||
switch(type) { // Get appropriate links depending on the UserAN type |
switch(type) { // Get appropriate links depending on the UserAN type |
||
case 'UNL': |
case 'UNL': |
||
case 'User2': |
case 'User2': |
||
case 'IP2': |
case 'IP2': |
||
link = `[[特別:投稿記録/${reportee}|${reportee}]]`; |
link = `[[特別:投稿記録/${reportee}|${reportee}]]`; |
||
break; |
break; |
||
case 'logid': |
case 'logid': |
||
link = `[[特別:転送/logid/${reportee}|Logid/${reportee}]]`; |
link = `[[特別:転送/logid/${reportee}|Logid/${reportee}]]`; |
||
break; |
break; |
||
case 'diff': |
case 'diff': |
||
link = `[[特別:差分/${reportee}|差分/${reportee}]]の投稿者`; |
link = `[[特別:差分/${reportee}|差分/${reportee}]]の投稿者`; |
||
break; |
break; |
||
default: |
default: |
||
link = reportee; |
link = reportee; |
||
} |
|||
if (!isInArray(link, links)) links.push(link); // Push the link into the array |
|||
} |
} |
||
}); |
|||
if (!isInArray(link, links)) links.push(link); // Push the link into the array |
|||
// Get edit summary |
|||
var summary = ''; |
|||
switch(true) { |
|||
case links.length === 0: |
|||
break; |
|||
case links.length === 1: |
|||
summary = '+' + links[0] + ' - '; |
|||
break; |
|||
case links.length > 1 && 5 > links.length: |
|||
summary = '+' + links.join(', ') + ' - '; |
|||
break; |
|||
default: |
|||
summary = '+' + links.length + ' - '; |
|||
} |
} |
||
return summary; |
|||
// Get edit summary |
|||
var summary = ''; |
|||
switch(true) { |
|||
case links.length === 0: |
|||
break; |
|||
case links.length === 1: |
|||
summary = '+' + links[0] + ' - '; |
|||
break; |
|||
case links.length > 1 && 5 > links.length: |
|||
summary = '+' + links.join(', ') + ' - '; |
|||
break; |
|||
default: |
|||
summary = '+' + links.length + ' - '; |
|||
} |
} |
||
return summary; |
|||
/** |
|||
} |
|||
* Function to get the current date and the section name on WP:AN/I to which to report users |
|||
* @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) { |
|||
* Function to get the current date and the section name on WP:AN/I to which to report users |
|||
let subtract = 5; |
|||
* @param {boolean} last if true, returns the name of the last section |
|||
if (d.getDate() === 1 || d.getDate() === 2) { |
|||
* @returns {string} section name |
|||
subtract = 3; |
|||
*/ |
|||
} else if (d.getDate() === 31) { |
|||
function getSectionI(last){ |
|||
subtract = 6; |
|||
} |
|||
d.setDate(d.getDate() - subtract); |
|||
} |
|||
var sectionName; |
|||
switch(true) { |
|||
case (1 <= d.getDate() && d.getDate() <= 5): |
|||
let subtract = 5; |
|||
sectionName = `${d.getFullYear()}年${d.getMonth() + 1}月1日 - 5日新規報告`; |
|||
if (d.getDate() === 1 || d.getDate() === 2) { |
|||
break; |
|||
case (6 <= d.getDate() && d.getDate() <= 10): |
|||
sectionName = `${d.getFullYear()}年${d.getMonth() + 1}月6日 - 10日新規報告`; |
|||
subtract = 6; |
|||
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; |
|||
} |
|||
var 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 check if a user exists locally |
// Function to check if a user exists locally |
||
function userExists(username) { |
function userExists(username) { |
||
return new Promise(function(resolve) { |
return new Promise(function(resolve) { |
||
new mw.Api().get({ |
new mw.Api().get({ |
||
'action': 'query', |
'action': 'query', |
||
'list': 'users', |
'list': 'users', |
||
'ususers': username, |
'ususers': username, |
||
'formatversion': 2 |
'formatversion': 2 |
||
}).then(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 |
||
}); |
|||
}); |
}); |
||
} |
} |
||
} |
|||
// Function to manipulate dropdown options for UserAN types (also maniputes show/hide of checkbox) |
// Function to manipulate dropdown options for UserAN types (also maniputes show/hide of checkbox) |
||
var updateTypeDropdownTimeout; |
var updateTypeDropdownTimeout; |
||
function updateTypeDropdown(inputID) { |
function updateTypeDropdown(inputID) { |
||
const tarVal = $(inputID).val().trimANR(); // The value typed into the input |
const tarVal = $(inputID).val().trimANR(); // The value typed into the input |
||
const selectID = inputID.replace('input', 'select'); // #anr-userX-select |
const selectID = inputID.replace('input', 'select'); // #anr-userX-select |
||
const checkboxDivID = inputID.replace('input', 'checkbox-div'); // #anr-userX-checkbox-div |
const checkboxDivID = inputID.replace('input', 'checkbox-div'); // #anr-userX-checkbox-div |
||
const checkboxID = inputID.replace('input', 'checkbox'); // #anr-userX-checkbox |
const checkboxID = inputID.replace('input', 'checkbox'); // #anr-userX-checkbox |
||
const 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 |
||
updateTypeDropdownTimeout = setTimeout(async function(){ |
updateTypeDropdownTimeout = setTimeout(async function(){ |
||
if (tarVal === '') { // if the field is blanked |
if (tarVal === '') { // if the field is blanked |
||
$(selectID).prop('disabled', true).children('.anr-opt-none').prop('selected', true); // Disable dropdown and select 'none' |
$(selectID).prop('disabled', true).children('.anr-opt-none').prop('selected', true); // Disable dropdown and select 'none' |
||
$(checkboxDivID).css('display', 'none'); // Hide 'hide username' checkbox |
$(checkboxDivID).css('display', 'none'); // Hide 'hide username' checkbox |
||
$(checkboxID).prop('checked', false); // Uncheck the checkbox |
$(checkboxID).prop('checked', false); // Uncheck the checkbox |
||
$(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 |
||
if (mw.util.isIPAddress(tarVal, true)) { // if IP |
if (mw.util.isIPAddress(tarVal, true)) { // if IP |
||
$(selectID).prop('disabled', false); // enable dropdown (repeated to prevent a strange lag) |
$(selectID).prop('disabled', false); // enable dropdown (repeated to prevent a strange lag) |
||
$(selectID).children('.anr-opt-UNL').prop('hidden', true); |
$(selectID).children('.anr-opt-UNL').prop('hidden', true); |
||
$(selectID).children('.anr-opt-User2').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-IP2').prop({'hidden': false, 'selected': true}); |
||
$(selectID).children('.anr-opt-logid').prop('hidden', true); |
$(selectID).children('.anr-opt-logid').prop('hidden', true); |
||
$(selectID).children('.anr-opt-diff').prop('hidden', true); |
$(selectID).children('.anr-opt-diff').prop('hidden', true); |
||
$(selectID).children('.anr-opt-none').prop('hidden', false); |
$(selectID).children('.anr-opt-none').prop('hidden', false); |
||
$(checkboxDivID).css('display', 'none'); // hide 'hide username' checkbox |
$(checkboxDivID).css('display', 'none'); // hide 'hide username' checkbox |
||
$(checkboxID).prop('checked', false); // uncheck the checkbox |
$(checkboxID).prop('checked', false); // uncheck the checkbox |
||
$(idlinkDivID).css('display', 'none'); |
$(idlinkDivID).css('display', 'none'); |
||
toggleBlockStatusLink(inputID, false, false); |
toggleBlockStatusLink(inputID, false, false); |
||
} else if (await userExists(tarVal)) { // if user |
} else if (await userExists(tarVal)) { // if user |
||
$(selectID).prop('disabled', false); // enable dropdown (repeated to prevent a strange lag) |
$(selectID).prop('disabled', false); // enable dropdown (repeated to prevent a strange lag) |
||
$(selectID).children('.anr-opt-UNL').prop({'hidden': false, 'selected': true}); |
$(selectID).children('.anr-opt-UNL').prop({'hidden': false, 'selected': true}); |
||
$(selectID).children('.anr-opt-User2').prop('hidden', false); |
$(selectID).children('.anr-opt-User2').prop('hidden', false); |
||
$(selectID).children('.anr-opt-IP2').prop('hidden', true); |
$(selectID).children('.anr-opt-IP2').prop('hidden', true); |
||
$(selectID).children('.anr-opt-logid').prop('hidden', true); |
$(selectID).children('.anr-opt-logid').prop('hidden', true); |
||
$(selectID).children('.anr-opt-diff').prop('hidden', true); |
$(selectID).children('.anr-opt-diff').prop('hidden', true); |
||
$(selectID).children('.anr-opt-none').prop('hidden', false); |
$(selectID).children('.anr-opt-none').prop('hidden', false); |
||
$(checkboxDivID).css('display', 'block'); // show 'hide username' checkbox |
$(checkboxDivID).css('display', 'block'); // show 'hide username' checkbox |
||
$(checkboxID).prop('checked', false); // uncheck the checkbox |
$(checkboxID).prop('checked', false); // uncheck the checkbox |
||
$(idlinkDivID).css('display', 'none'); |
$(idlinkDivID).css('display', 'none'); |
||
toggleBlockStatusLink(inputID, false, false); |
toggleBlockStatusLink(inputID, false, false); |
||
} else { // if something else (like random numbers or strings) |
} else { // if something else (like random numbers or strings) |
||
$(selectID).prop('disabled', false); // enable dropdown (repeated to prevent a strange lag) |
$(selectID).prop('disabled', false); // enable dropdown (repeated to prevent a strange lag) |
||
$(selectID).children('.anr-opt-UNL').prop('hidden', true); |
$(selectID).children('.anr-opt-UNL').prop('hidden', true); |
||
$(selectID).children('.anr-opt-User2').prop('hidden', true); |
$(selectID).children('.anr-opt-User2').prop('hidden', true); |
||
$(selectID).children('.anr-opt-IP2').prop('hidden', true); |
$(selectID).children('.anr-opt-IP2').prop('hidden', true); |
||
$(selectID).children('.anr-opt-logid').prop('hidden', false); |
$(selectID).children('.anr-opt-logid').prop('hidden', false); |
||
$(selectID).children('.anr-opt-diff').prop('hidden', false); |
$(selectID).children('.anr-opt-diff').prop('hidden', false); |
||
$(selectID).children('.anr-opt-none').prop({'hidden': false, 'selected': true}); |
$(selectID).children('.anr-opt-none').prop({'hidden': false, 'selected': true}); |
||
$(checkboxDivID).css('display', 'none'); // hide 'hide username' checkbox |
$(checkboxDivID).css('display', 'none'); // hide 'hide username' checkbox |
||
$(idlinkDivID).css('display', 'none'); |
$(idlinkDivID).css('display', 'none'); |
||
toggleBlockStatusLink(inputID, true, false); |
toggleBlockStatusLink(inputID, true, false); |
||
} |
|||
} |
} |
||
} |
}, 350); |
||
} |
|||
/** |
|||
* Function to show/hide 'This user has blocks' links |
|||
} |
|||
* @param {string} inputID the ID of the input |
|||
* @param {boolean} forceHide if true, just hide the block status link (for when t=diff and t=none; block check impossible) |
|||
* @param {boolean} convertLogid if true, try to convert a logid to a username (for when t=logid; username is needed for block check) |
|||
*/ |
|||
function toggleBlockStatusLink(inputID, forceHide, convertLogid) { |
|||
centerDialog(); |
|||
/** |
|||
* Function to show/hide 'This user has blocks' links |
|||
* @param {string} inputID the ID of the input |
|||
* @param {boolean} forceHide if true, just hide the block status link (for when t=diff and t=none; block check impossible) |
|||
* @param {boolean} convertLogid if true, try to convert a logid to a username (for when t=logid; username is needed for block check) |
|||
*/ |
|||
function toggleBlockStatusLink(inputID, forceHide, convertLogid) { |
|||
const inputVal = $(inputID).val().trimANR(); // The value in the input |
|||
centerDialog(); |
|||
const $bsLinkDiv = $(inputID.replace('input', 'blockstatus-div')); // #anr-userX-blockstatus-div |
|||
const $bsLink = $(inputID.replace('input', 'blockstatus')); // #anr-userX-blockstatus |
|||
var username, logid; |
|||
const inputVal = $(inputID).val().trimANR(); // The value in the input |
|||
if (forceHide && convertLogid) { // t=logid |
|||
const $bsLinkDiv = $(inputID.replace('input', 'blockstatus-div')); // #anr-userX-blockstatus-div |
|||
// Check if the logid can be converted to a username and if it can, proceed to block check, and if it can't, just hide the block status link |
|||
const $bsLink = $(inputID.replace('input', 'blockstatus')); // #anr-userX-blockstatus |
|||
if (!(username = getKeyByValue(Logids, logid = inputVal))) { |
|||
$bsLinkDiv.css('display', 'none'); |
|||
var username, logid; |
|||
centerDialog(); |
|||
if (forceHide && convertLogid) { // t=logid |
|||
return; |
|||
// Check if the logid can be converted to a username and if it can, proceed to block check, and if it can't, just hide the block status link |
|||
} |
|||
if (!(username = getKeyByValue(Logids, logid = inputVal))) { |
|||
} else if (forceHide) { // t=diff or t=none |
|||
$bsLinkDiv.css('display', 'none'); // Hide the link div |
|||
centerDialog(); |
centerDialog(); |
||
return; |
return; |
||
} else { // t=UNL, t=User2, or t=IP2 |
|||
username = inputVal; |
|||
} |
} |
||
} else if (forceHide) { // t=diff or t=none |
|||
$bsLinkDiv.css('display', 'none'); // Hide the link div |
|||
centerDialog(); |
|||
return; |
|||
} else { // t=UNL, t=User2, or t=IP2 |
|||
username = inputVal; |
|||
} |
|||
// Check the block status of the user, and if blocked, update the bsLink, or else, hide the link |
// Check the block status of the user, and if blocked, update the bsLink, or else, hide the link |
||
getBlocked([username]).then(function(blocked) { |
getBlocked([username]).then(function(blocked) { |
||
if (blocked.length !== 0) { // If the user is blocked |
if (blocked.length !== 0) { // If the user is blocked |
||
$bsLink.attr('href', mw.util.getUrl('特別:投稿記録/' + username)); // Update the link |
$bsLink.attr('href', mw.util.getUrl('特別:投稿記録/' + username)); // Update the link |
||
$bsLinkDiv.css('display', 'block'); // Show the link div |
$bsLinkDiv.css('display', 'block'); // Show the link div |
||
} else { |
} else { |
||
$bsLinkDiv.css('display', 'none'); // Hide the link div |
$bsLinkDiv.css('display', 'none'); // Hide the link div |
||
} |
} |
||
centerDialog(); |
centerDialog(); |
||
}); |
}); |
||
} |
|||
} |
|||
// Function to get an array of blocked users & IPs from an array of random users & IPs |
// Function to get an array of blocked users & IPs from an array of random users & IPs |
||
async function getBlocked(namesArr) { |
async function getBlocked(namesArr) { |
||
const users = [], ips = []; |
const users = [], ips = []; |
||
var blocked = []; |
var blocked = []; |
||
// Sort names to users and IPs |
// Sort names to users and IPs |
||
for (let i = 0; i < namesArr.length; i++) { |
for (let i = 0; i < namesArr.length; i++) { |
||
if (mw.util.isIPAddress(namesArr[i], true)) { // Push IPs into the array |
if (mw.util.isIPAddress(namesArr[i], true)) { // Push IPs into the array |
||
ips.push(namesArr[i]); |
ips.push(namesArr[i]); |
||
} else { // Push users into the array |
} else { // Push users into the array |
||
users.push(namesArr[i]); |
users.push(namesArr[i]); |
||
} |
|||
} |
} |
||
} |
|||
// Check who's (b)locked |
// 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 |
||
// Check local block status |
// Check local block status |
||
blocked = blocked.concat( |
blocked = blocked.concat( |
||
await getBlockedUsers(users), |
await getBlockedUsers(users), |
||
await getBlockedIps(ips) |
await getBlockedIps(ips) |
||
); |
); |
||
// Remove users/IPs that are already in the array 'blocked' (to make the code faster) |
// Remove users/IPs that are already in the array 'blocked' (to make the code faster) |
||
for (let i = users.length -1; i >= 0; i--) { |
for (let i = users.length -1; i >= 0; i--) { |
||
if (isInArray(users[i], blocked)) { |
if (isInArray(users[i], blocked)) { |
||
users.splice(i ,1); |
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 |
// Check global (b)lock status |
||
blocked = blocked.concat( |
blocked = blocked.concat( |
||
await getGloballyLockedUsers(users), |
await getGloballyLockedUsers(users), |
||
await getGloballyBlockedIps(ips) |
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 |
// 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) |
// Remove users that are already in the array 'blocked' (to make the code faster) |
||
for (let i = users.length -1; i >= 0; i--) { |
for (let i = users.length -1; i >= 0; i--) { |
||
if (isInArray(users[i], blocked)) { |
if (isInArray(users[i], blocked)) { |
||
users.splice(i ,1); |
users.splice(i ,1); |
||
} |
|||
} |
} |
||
} |
|||
// Check global lock status |
// Check global lock status |
||
blocked = blocked.concat(await getGloballyLockedUsers(users)); |
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 |
// 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) |
// Remove IPs that are already in the array 'blocked' (to make the code faster) |
||
for (let i = ips.length -1; i >= 0; i--) { |
for (let i = ips.length -1; i >= 0; i--) { |
||
if (isInArray(ips[i], blocked)) { |
if (isInArray(ips[i], blocked)) { |
||
ips.splice(i ,1); |
ips.splice(i ,1); |
||
} |
|||
} |
} |
||
// Check global block status |
|||
blocked = blocked.concat(await getGloballyBlockedIps(ips)); |
|||
} else { |
|||
// Do nothing |
|||
} |
} |
||
return blocked; |
|||
blocked = blocked.concat(await getGloballyBlockedIps(ips)); |
|||
} else { |
|||
// Do nothing |
|||
} |
} |
||
/** |
|||
* Function to get an array of blocked users from an array of random users |
|||
* @param {Array} usersArr |
|||
* @returns {Promise<Array>} |
|||
*/ |
|||
function getBlockedUsers(usersArr) { // Note: this function needs to be modified if there're cases in which the reportees are more than 50 |
|||
return new Promise(function(resolve) { |
|||
if (usersArr.length === 0) resolve([]); |
|||
new mw.Api().post({ |
|||
'action': 'query', |
|||
'list': 'blocks', |
|||
'bklimit': usersArr.length, |
|||
'bkusers': usersArr.join('|'), |
|||
'bkprop': 'user', |
|||
'formatversion': 2 |
|||
}).then(function(res){ |
|||
const resBlk = res.query.blocks, blockedUsers = []; |
|||
if (resBlk.length === 0) resolve(blockedUsers); // None of the users is blocked; return an empty array |
|||
for (let i = 0; i < resBlk.length; i++) { |
|||
blockedUsers.push(resBlk[i].user); // Push blocked users into the array |
|||
} |
|||
resolve(blockedUsers); // Return e.g. [user1, user2...] |
|||
}); |
|||
}); |
|||
} |
|||
/** |
|||
} |
|||
* Function to get an array of locked users from an array of random users |
|||
* @param {Array} usersArr |
|||
* @returns {Promise<Array>} |
|||
*/ |
|||
async function getGloballyLockedUsers(usersArr) { |
|||
if (usersArr.length === 0) return []; |
|||
const lockedUsers = []; |
|||
for (let i = 0; i < usersArr.length; i++) { |
|||
const locked = await userIsLocked(usersArr[i]); |
|||
if (locked) lockedUsers.push(usersArr[i]); |
|||
} |
|||
return lockedUsers; |
|||
} |
|||
/** |
/** |
||
* Function to check if a user is globally locked |
|||
* Function to get an array of blocked users from an array of random users |
|||
* @param {string} user |
|||
* @returns {Promise<boolean>} |
|||
*/ |
|||
function userIsLocked(user) { |
|||
function getBlockedUsers(usersArr) { // Note: this function needs to be modified if there're cases in which the reportees are more than 50 |
|||
return new Promise(function(resolve) { |
return new Promise(function(resolve) { |
||
new mw.Api().get({ |
|||
action: 'query', |
|||
list: 'globalallusers', |
|||
agulimit: 1, |
|||
agufrom: user, |
|||
aguto: user, |
|||
aguprop: 'lockinfo' |
|||
}).then(function(res) { |
|||
const resLck = res.query.globalallusers; |
|||
}).then(function(res){ |
|||
if (resLck.length === 0) resolve(false); // The global user doesn't exist |
|||
const resBlk = res.query.blocks, blockedUsers = []; |
|||
resolve(resLck[0].locked !== undefined); // resLck[0].locked === '' if locked, otherwise undefined |
|||
}); |
|||
blockedUsers.push(resBlk[i].user); // Push blocked users into the array |
|||
} |
|||
resolve(blockedUsers); // Return e.g. [user1, user2...] |
|||
}); |
}); |
||
} |
} |
||
} |
|||
/** |
/** |
||
* Function to get an array of locally blocked IPs from an array of random IPs |
|||
* @param {Array} ipsArr |
|||
* @returns {Promise<Array>} |
|||
*/ |
|||
async function |
async function getBlockedIps(ipsArr) { |
||
if ( |
if (ipsArr.length === 0) return []; |
||
const |
const blockedIps = []; |
||
for (let i = 0; i < |
for (let i = 0; i < ipsArr.length; i++) { |
||
const |
const blocked = await ipIsBlocked(ipsArr[i]); |
||
if ( |
if (blocked) blockedIps.push(ipsArr[i]); |
||
} |
|||
return blockedIps; |
|||
} |
} |
||
return lockedUsers; |
|||
} |
|||
/** |
/** |
||
* Function to check if a given IP is locally blocked |
|||
* @param {String} ip |
|||
* @returns {Promise<boolean>} |
|||
*/ |
|||
function |
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', |
||
list: ' |
'list': 'blocks', |
||
'bklimit': 1, |
|||
'bkip': ip, |
|||
'bkprop': 'user', |
|||
'formatversion': 2 |
|||
}).then(function(res) |
}).then(function(res){ |
||
resolve(res.query.blocks.length !== 0); |
|||
}); |
|||
if (resLck.length === 0) resolve(false); // The global user doesn't exist |
|||
resolve(resLck[0].locked !== undefined); // resLck[0].locked === '' if locked, otherwise undefined |
|||
}); |
}); |
||
} |
} |
||
} |
|||
/** |
/** |
||
* Function to get an array of globally blocked IPs from an array of random IPs |
|||
* @param {Array} ipsArr |
|||
* @returns {Promise<Array>} |
|||
*/ |
|||
async function |
async function getGloballyBlockedIps(ipsArr) { |
||
if (ipsArr.length === 0) return []; |
if (ipsArr.length === 0) return []; |
||
const blockedIps = []; |
const blockedIps = []; |
||
for (let i = 0; i < ipsArr.length; i++) { |
for (let i = 0; i < ipsArr.length; i++) { |
||
const blocked = await |
const blocked = await ipIsGloballyBlocked(ipsArr[i]); |
||
if (blocked) blockedIps.push(ipsArr[i]); |
if (blocked) blockedIps.push(ipsArr[i]); |
||
} |
|||
return blockedIps; |
|||
} |
} |
||
return blockedIps; |
|||
} |
|||
/** |
/** |
||
* Function to check if a given IP is globally blocked |
|||
* @param {String} ip |
|||
* @returns {Promise<boolean>} |
|||
*/ |
|||
function |
function ipIsGloballyBlocked(ip) { |
||
return new Promise(function(resolve) { |
return new Promise(function(resolve) { |
||
new mw.Api().get({ |
new mw.Api().get({ |
||
'action': 'query', |
'action': 'query', |
||
'list': ' |
'list': 'globalblocks', |
||
' |
'bgip': ip, |
||
' |
'bglimit': 1, |
||
' |
'bgprop': 'address' |
||
}).then(function(res){ |
|||
resolve(res.query.globalblocks.length !== 0); |
|||
}); |
|||
}); |
}); |
||
}); |
|||
} |
|||
/** |
|||
* Function to get an array of globally blocked IPs from an array of random IPs |
|||
* @param {Array} ipsArr |
|||
* @returns {Promise<Array>} |
|||
*/ |
|||
async function getGloballyBlockedIps(ipsArr) { |
|||
if (ipsArr.length === 0) return []; |
|||
const blockedIps = []; |
|||
for (let i = 0; i < ipsArr.length; i++) { |
|||
const blocked = await ipIsGloballyBlocked(ipsArr[i]); |
|||
if (blocked) blockedIps.push(ipsArr[i]); |
|||
} |
} |
||
return blockedIps; |
|||
} |
|||
// Function to get account creation logid |
|||
/** |
|||
function getLogid(username) { |
|||
* Function to check if a given IP is globally blocked |
|||
return new Promise(function(resolve) { |
|||
* @param {String} ip |
|||
new mw.Api().get({ |
|||
* @returns {Promise<boolean>} |
|||
'action': 'query', |
|||
*/ |
|||
'list': 'logevents', |
|||
function ipIsGloballyBlocked(ip) { |
|||
'leuser': username, |
|||
return new Promise(function(resolve) { |
|||
'ledir': 'newer', |
|||
' |
'lelimit': 1, |
||
' |
'formatversion': 2 |
||
}).then(function(res){ |
|||
if (res.query.logevents.length === 0) resolve(); // No logevent exists if the API returns an empty array |
|||
'bglimit': 1, |
|||
resolve(res.query.logevents[0].logid); |
|||
}) |
}); |
||
resolve(res.query.globalblocks.length !== 0); |
|||
}); |
}); |
||
} |
|||
// ******************** EVENT HANDLERS ******************** |
|||
// Copy a VIP name when the selection is changed |
|||
$(document).off('change', '#anr-viplist-select').on('change', '#anr-viplist-select', function() { |
|||
const vipSelectVal = $('#anr-viplist-select').find('option').filter(':selected').text().trim(); |
|||
copyToClipboard('[[WP:VIP#' + vipSelectVal + ']]'); |
|||
}); |
}); |
||
} |
|||
// Reset dialog when closed |
|||
// Function to get account creation logid |
|||
$(document) |
|||
function getLogid(username) { |
|||
.off('dialogclose', '#anr-modal-dialog, #anr-preview-dialog, #anr-drpreview-dialog') |
|||
return new Promise(function(resolve) { |
|||
.on('dialogclose', '#anr-modal-dialog, #anr-preview-dialog, #anr-drpreview-dialog', |
|||
new mw.Api().get({ |
|||
function() { |
|||
'action': 'query', |
|||
$(this).remove(); |
|||
userCnt = 1; |
|||
'ledir': 'newer', |
|||
'lelimit': 1, |
|||
'formatversion': 2 |
|||
}).then(function(res){ |
|||
if (res.query.logevents.length === 0) resolve(); // No logevent exists if the API returns an empty array |
|||
resolve(res.query.logevents[0].logid); |
|||
}); |
|||
}); |
}); |
||
} |
|||
// 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).innerWidth()}); |
|||
$('#anr-target-pagelink-div').css('display', 'block'); |
|||
$('#anr-target-pagelink').attr('href', mw.util.getUrl(ANI)); |
|||
break; |
|||
case ANS: |
|||
$('#anr-section-i-div').css('display', 'none'); |
|||
$('#anr-section-s-div').css('display', 'block'); |
|||
$('#anr-section-s-select').select2({'width': $(this).innerWidth()}); |
|||
$('#anr-target-pagelink-div').css('display', 'block'); |
|||
$('#anr-target-pagelink').attr('href', mw.util.getUrl(ANS)); |
|||
break; |
|||
case AN3RR: |
|||
$('#anr-section-i-div').css('display', 'none'); |
|||
$('#anr-section-s-div').css('display', 'none'); |
|||
$('#anr-target-pagelink-div').css('display', 'block'); |
|||
$('#anr-target-pagelink').attr('href', mw.util.getUrl(AN3RR)); |
|||
break; |
|||
} |
|||
centerDialog(); |
|||
}); |
|||
// Add section name to the '報告先' link when section is specified |
|||
// ******************** EVENT HANDLERS ******************** |
|||
$(document) |
|||
.off('change', '#anr-section-i-select, #anr-section-s-select') |
|||
.on('change', '#anr-section-i-select, #anr-section-s-select', |
|||
function(){ |
|||
var tarSection = '', tarPage = ''; |
|||
if ($(this).attr('id') === 'anr-section-i-select') { |
|||
tarPage = ANI; |
|||
} else if ($(this).attr('id') === 'anr-section-s-select') { |
|||
tarPage = ANS; |
|||
} |
|||
if ($(this).find('option').filter(':selected').text() !== '選択してください') { |
|||
tarSection = '#' + $(this).find('option').filter(':selected').text(); |
|||
$('#anr-target-pagelink').attr('href', mw.util.getUrl(tarPage + tarSection)); |
|||
} |
|||
}); |
|||
// When the selection is changed in the type dropdown |
|||
$(document).off('change', |
$(document).off('change','#anr-user-div select').on('change','#anr-user-div select', function(e){ |
||
const vipSelectVal = $('#anr-viplist-select').find('option').filter(':selected').text().trim(); |
|||
copyToClipboard('[[WP:VIP#' + vipSelectVal + ']]'); |
|||
}); |
|||
const selectID = '#' + e.target.id; // #anr-userX-select |
|||
// Reset dialog when closed |
|||
const valSelected = $(selectID).children('option').filter(':selected').text(); // Selected type |
|||
$(document) |
|||
const inputID = selectID.replace('select', 'input'); // #anr-userX-input |
|||
.off('dialogclose', '#anr-modal-dialog, #anr-preview-dialog, #anr-drpreview-dialog') |
|||
const valInput = $(inputID).val().trimANR(); // The input value |
|||
.on('dialogclose', '#anr-modal-dialog, #anr-preview-dialog, #anr-drpreview-dialog', |
|||
const checkboxDivID = selectID.replace('select', 'checkbox-div'); // #anr-userX-checkbox-div |
|||
function() { |
|||
const checkboxID = selectID.replace('select', 'checkbox'); // #anr-userX-checkbox |
|||
$(this).remove(); |
|||
const idlinkDivID = selectID.replace('select', 'idlink-div'); // #anr-userX-idlink-div |
|||
userCnt = 1; |
|||
const idlinkID = selectID.replace('select', 'idlink'); // #anr-userX-idlink |
|||
}); |
|||
switch(valSelected) { |
|||
// Dynamically change the content of the section dropdown depending on the value selected in '報告先' |
|||
case 'UNL': |
|||
$(document).off('change', '#anr-target-options').on('change', '#anr-target-options', function(){ |
|||
case 'User2': |
|||
const selectedTar = $(this).children('option').filter(':selected').text(); |
|||
$(checkboxDivID).css('display', 'block'); |
|||
$('.anr-section-options-initial').prop('selected', true); // Reset the dropdown value |
|||
toggleBlockStatusLink(inputID, false, false); |
|||
switch(selectedTar) { |
|||
break; |
|||
case 'IP2': |
|||
toggleBlockStatusLink(inputID, false, false); |
|||
break; |
|||
$('#anr-section-i-options-date').text(getSectionI(false)); |
|||
case 'logid': |
|||
$('#anr-section-i-select').css({'width': $(this).innerWidth()}); |
|||
$( |
$(checkboxDivID).css('display', 'block'); |
||
$( |
$(checkboxID).prop('checked', true); |
||
$(idlinkID).attr('href', mw.util.getUrl('Special:redirect/logid/' + valInput)).text('特別:転送/logid/' + valInput); |
|||
break; |
|||
toggleBlockStatusLink(inputID, true, true); |
|||
case ANS: |
|||
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); |
|||
$('#anr-target-pagelink').attr('href', mw.util.getUrl(AN3RR)); |
|||
} |
|||
} |
|||
centerDialog(); |
|||
}); |
|||
}); |
|||
// Add section name to the '報告先' link when section is specified |
|||
$(document) |
|||
.off('change', '#anr-section-i-select, #anr-section-s-select') |
|||
.on('change', '#anr-section-i-select, #anr-section-s-select', |
|||
function(){ |
|||
var tarSection = '', tarPage = ''; |
|||
if ($(this).attr('id') === 'anr-section-i-select') { |
|||
tarPage = ANI; |
|||
} else if ($(this).attr('id') === 'anr-section-s-select') { |
|||
tarPage = ANS; |
|||
} |
|||
if ($(this).find('option').filter(':selected').text() !== '選択してください') { |
|||
tarSection = '#' + $(this).find('option').filter(':selected').text(); |
|||
$('#anr-target-pagelink').attr('href', mw.util.getUrl(tarPage + tarSection)); |
|||
} |
|||
}); |
|||
// When |
// When username is typed in, change dropdown options for UserAN types |
||
$(document).off(' |
$(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 |
|||
const selectID = '#' + e.target.id; // #anr-userX-select |
|||
$(document).off('change', '#anr-user-div :checkbox').on('change', '#anr-user-div :checkbox', function(e){ |
|||
const valSelected = $(selectID).children('option').filter(':selected').text(); // Selected type |
|||
const inputID = selectID.replace('select', 'input'); // #anr-userX-input |
|||
const valInput = $(inputID).val().trimANR(); // 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 |
|||
const checkboxID = '#' + e.target.id; // #anr-userX-checkbox |
|||
switch(valSelected) { |
|||
const selectID = checkboxID.replace('checkbox', 'select'); // #anr-userX-select |
|||
case 'UNL': |
|||
const inputID = checkboxID.replace('checkbox', 'input'); // #anr-userX-input |
|||
case 'User2': |
|||
const inputVal = $(inputID).val().trimANR(); |
|||
const idlinkID = checkboxID.replace('checkbox', 'idlink'); // #anr-userX-idlink |
|||
toggleBlockStatusLink(inputID, false, false); |
|||
const idlinkDivID = checkboxID.replace('checkbox', 'idlink-div'); // #anr-userX-idlink-div |
|||
break; |
|||
case 'IP2': |
|||
// Function to update type dropdown for logid |
|||
toggleBlockStatusLink(inputID, false, false); |
|||
const updateTypeDropdownLogid = function(logid) { |
|||
break; |
|||
$(selectID).children('.anr-opt-UNL').prop('hidden', true); |
|||
case 'logid': |
|||
$( |
$(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); |
|||
toggleBlockStatusLink(inputID, true, true); |
|||
$(selectID).children('.anr-opt-none').prop('hidden', false); |
|||
break; |
|||
case 'diff': |
|||
$(checkboxDivID).css('display', 'none'); |
|||
$(idlinkDivID).css('display', 'block'); |
$(idlinkDivID).css('display', 'block'); |
||
$(idlinkID).attr('href', mw.util.getUrl('Special: |
$(idlinkID).attr('href', mw.util.getUrl('Special:redirect/logid/' + logid)).text('特別:転送/logid/' + logid); |
||
toggleBlockStatusLink(inputID, true, |
toggleBlockStatusLink(inputID, true, true); |
||
} |
|||
default: |
|||
$(checkboxDivID).css('display', 'none'); |
|||
$(idlinkDivID).css('display', 'none'); |
|||
toggleBlockStatusLink(inputID, true, false); |
|||
} |
|||
var logid, username; |
|||
}); |
|||
if ($(checkboxID).is(':checked')) { // If the checkbox is checked (the input value is a username and this needs to be converted to a logid) |
|||
if (logid = Logids[username = inputVal]) { // If the object knows the logid for the user |
|||
// When username is typed in, change dropdown options for UserAN types |
|||
$(inputID).val(logid); // Replace the username with the logid in the object |
|||
$(document).off('input', '#anr-user-div :text').on('input', '#anr-user-div :text', function(e){ |
|||
updateTypeDropdownLogid(logid); |
|||
const inputID = '#' + e.target.id; // #anr-userX-input |
|||
} else { |
|||
updateTypeDropdown(inputID); |
|||
(async function() { |
|||
}); |
|||
logid = await getLogid(username = inputVal); // Get logid from the API |
|||
if (!logid) { // If undefined is returned, reject the checking of the checkbox |
|||
alert('エラー\n\n取得可能なlogidが存在しません。Logidを手動で入力するか、type=diff または none を使用してください'); |
|||
$(checkboxID).prop('checked', false); |
|||
} else { // If a valid logid is returned |
|||
$(inputID).val(logid); // Set the logid to the input |
|||
Logids[username] = logid; // Save the logid in the object |
|||
updateTypeDropdownLogid(logid); |
|||
} |
|||
})(); |
|||
} |
|||
} else { // if the checkbox is unchecked (the input value is a logid and this needs to be converted to a username) |
|||
// 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){ |
|||
if (username = getKeyByValue(Logids, logid = inputVal)) { // Username converted from logid |
|||
const checkboxID = '#' + e.target.id; // #anr-userX-checkbox |
|||
$(inputID).val(username); // Replace the logid with the username in the object |
|||
const selectID = checkboxID.replace('checkbox', 'select'); // #anr-userX-select |
|||
$(selectID).children('.anr-opt-UNL').prop('hidden', false).prop('selected', true); |
|||
const inputID = checkboxID.replace('checkbox', 'input'); // #anr-userX-input |
|||
$(selectID).children('.anr-opt-User2').prop('hidden', false); |
|||
const inputVal = $(inputID).val().trimANR(); |
|||
$(selectID).children('.anr-opt-IP2').prop('hidden', true); |
|||
const idlinkID = checkboxID.replace('checkbox', 'idlink'); // #anr-userX-idlink |
|||
$(selectID).children('.anr-opt-logid').prop('hidden', true); |
|||
const idlinkDivID = checkboxID.replace('checkbox', 'idlink-div'); // #anr-userX-idlink-div |
|||
$(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); |
|||
} |
|||
} |
|||
// Function to update type dropdown for logid |
|||
const updateTypeDropdownLogid = function(logid) { |
|||
$(selectID).children('.anr-opt-UNL').prop('hidden', true); |
|||
$(selectID).children('.anr-opt-User2').prop('hidden', true); |
|||
$(selectID).children('.anr-opt-IP2').prop('hidden', true); |
|||
$(selectID).children('.anr-opt-logid').prop('hidden', false).prop('selected', true); |
|||
$(selectID).children('.anr-opt-diff').prop('hidden', false); |
|||
$(selectID).children('.anr-opt-none').prop('hidden', false); |
|||
$(idlinkDivID).css('display', 'block'); |
|||
$(idlinkID).attr('href', mw.util.getUrl('Special:redirect/logid/' + logid)).text('特別:転送/logid/' + logid); |
|||
toggleBlockStatusLink(inputID, true, true); |
|||
} |
|||
}); |
|||
if ($(checkboxID).is(':checked')) { // If the checkbox is checked (the input value is a username and this needs to be converted to a logid) |
|||
// When the 'add' button is hit, add another input layer |
|||
if (logid = Logids[username = inputVal]) { // If the object knows the logid for the user |
|||
$(document).off('click', '#anr-addBtn').on('click', '#anr-addBtn', function(){ |
|||
$(inputID).val(logid); // Replace the username with the logid in the object |
|||
userCnt++; |
|||
$('#anr-btn-div').before(userDiv.replaceAllANR('1-', userCnt + '-')); |
|||
} else { |
|||
$(`#anr-user${userCnt}-div`).css('margin-top', '0.2em'); |
|||
(async function() { |
|||
centerDialog(); |
|||
logid = await getLogid(username = inputVal); // Get logid from the API |
|||
}); |
|||
if (!logid) { // If undefined is returned, reject the checking of the checkbox |
|||
alert('エラー\n\n取得可能なlogidが存在しません。Logidを手動で入力するか、type=diff または none を使用してください'); |
|||
$(checkboxID).prop('checked', false); |
|||
} else { // If a valid logid is returned |
|||
$(inputID).val(logid); // Set the logid to the input |
|||
Logids[username] = logid; // Save the logid in the object |
|||
updateTypeDropdownLogid(logid); |
|||
} |
|||
})(); |
|||
} |
|||
// When buttons are moused on and off |
|||
} else { // if the checkbox is unchecked (the input value is a logid and this needs to be converted to a username) |
|||
$(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 |
|||
if (username = getKeyByValue(Logids, logid = inputVal)) { // Username converted from logid |
|||
$(document).off('change', '#anr-summary-checkbox').on('change', '#anr-summary-checkbox', function(){ |
|||
$(inputID).val(username); // Replace the logid with the username in the object |
|||
const $textarea = $('#anr-summary-text'); |
|||
$(selectID).children('.anr-opt-UNL').prop('hidden', false).prop('selected', true); |
|||
if ($(this).is(':checked')) { // Box is checked |
|||
$textarea.css('display','inline-block').val(genEditSummary()); // Show textarea with an edit summary |
|||
$(selectID).children('.anr-opt-IP2').prop('hidden', true); |
|||
} else { // Box is unchecked |
|||
$(selectID).children('.anr-opt-logid').prop('hidden', true); |
|||
$ |
$textarea.css('display','none').val(''); |
||
$(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); |
|||
} |
} |
||
centerDialog(); |
|||
}); |
|||
} |
|||
// ******************** AUXILIARY FUNCTIONS ******************** |
|||
}); |
|||
/** |
|||
// When the 'add' button is hit, add another input layer |
|||
* Function to check if an element is in an array |
|||
$(document).off('click', '#anr-addBtn').on('click', '#anr-addBtn', function(){ |
|||
* @param {string} el |
|||
userCnt++; |
|||
* @param {Array} arr |
|||
$('#anr-btn-div').before(userDiv.replaceAllANR('1-', userCnt + '-')); |
|||
* @returns {boolean} |
|||
$(`#anr-user${userCnt}-div`).css('margin-top', '0.2em'); |
|||
*/ |
|||
function isInArray (el, arr) { |
|||
}); |
|||
return arr.indexOf(el) !== -1; |
|||
} |
|||
/** |
|||
// When buttons are moused on and off |
|||
* Function to check if elements of an array are all contained in another array |
|||
$(document).off('mouseover mouseleave', '#anr-modal-dialog form button').on({ |
|||
* @param {Array} arr1 |
|||
'mouseover': function(e) {e.target.style.borderColor = '#999999';}, |
|||
* @param {Array} arr2 |
|||
'mouseleave': function(e) {e.target.style.borderColor = '#d3d3d3';} |
|||
* @returns {boolean} |
|||
}, '#anr-modal-dialog form button'); |
|||
*/ |
|||
function arrayIsInArray(arr1, arr2) { |
|||
// When the summary checkbox is (un)checked |
|||
for (let i = 0; i < arr1.length; i++) { |
|||
$(document).off('change', '#anr-summary-checkbox').on('change', '#anr-summary-checkbox', function(){ |
|||
if (!isInArray(arr1[i], arr2)) { |
|||
const $textarea = $('#anr-summary-text'); |
|||
return false; |
|||
if ($(this).is(':checked')) { // Box is checked |
|||
} |
|||
$textarea.css('display','inline-block').val(genEditSummary()); // Show textarea with an edit summary |
|||
} |
|||
return true; |
|||
$textarea.css('display','none').val(''); |
|||
} |
} |
||
centerDialog(); |
|||
}); |
|||
/** |
|||
* Function to check if a string contains a substring in an element of an array |
|||
// ******************** AUXILIARY FUNCTIONS ******************** |
|||
* @param {string} str |
|||
* @param {Array} arr |
|||
/** |
|||
* @returns {*} the first element matched in the array (if there's no match, returns undefined) |
|||
*/ |
|||
* @param {string} el |
|||
function stringContainsElementInArray(str, arr) { |
|||
* @param {Array} arr |
|||
for (let i = 0; i < arr.length; i++) { |
|||
* @returns {boolean} |
|||
if (str.indexOf(arr[i]) !== -1) { |
|||
*/ |
|||
return arr[i]; |
|||
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 get the key of a value in an object |
|||
* @param {Object} object |
|||
* @param {*} value |
|||
* @returns {*} key |
|||
* @returns {*} the first element matched in the array (if there's no match, returns undefined) |
|||
*/ |
|||
function |
function getKeyByValue(object, value) { |
||
for (let key in object) { |
|||
if ( |
if (object[key] == value) return key; |
||
return arr[i]; |
|||
} |
} |
||
} |
} |
||
} |
|||
// Function to copy a string to the clipboard |
|||
/** |
|||
function copyToClipboard(str) { |
|||
* Function to get the key of a value in an object |
|||
const $temp = $('<input>'); |
|||
* @param {Object} object |
|||
$('body').append($temp); // Create a temporarily hidden text field |
|||
* @param {*} value |
|||
$temp.val(str).select(); // Copy the text string into the field and select the text |
|||
* @returns {*} key |
|||
document.execCommand('copy'); // Copy it to the clipboard |
|||
*/ |
|||
$temp.remove(); // Remove the text field |
|||
function getKeyByValue(object, value) { |
|||
} |
|||
for (let key in object) { |
|||
if (object[key] == value) return key; |
|||
} |
|||
} |
|||
// Function to get today's date |
|||
// Function to copy a string to the clipboard |
|||
function |
function today() { |
||
const |
const d = new Date(); |
||
return d.getMonth() + 1 + '月' + d.getDate() + '日'; |
|||
$('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 |
|||
} |
|||
// Function to get |
// Function to get the last day of the month |
||
function |
function lastDay(y, m){ |
||
return new Date(y, m + 1, 0).getDate(); |
|||
} |
|||
return d.getMonth() + 1 + '月' + d.getDate() + '日'; |
|||
} |
|||
/** |
|||
// Function to get the last day of the month |
|||
* String method to get rid of the U+200E space, in addition to the function of $.trim() |
|||
function lastDay(y, m){ |
|||
* @returns {string} |
|||
return new Date(y, m + 1, 0).getDate(); |
|||
*/ |
|||
} |
|||
String.prototype.trimANR = function() { |
|||
return this.replace(/\u200e/g, '').trim(); |
|||
}; |
|||
/** |
/** |
||
* String method (alternative) to replace all occurences of a string with another |
|||
* String method to get rid of the U+200E space, in addition to the function of $.trim() |
|||
* (takes a replacer and a replacee as arguments) |
|||
* @returns {string} |
|||
* @returns {string} |
|||
*/ |
|||
*/ |
|||
String.prototype.trimANR = function() { |
|||
String.prototype.replaceAllANR = function() { |
|||
return this.replace(/\u200e/g, '').trim(); |
|||
if (arguments.length %2 !== 0) { |
|||
}; |
|||
return new Error('SyntaxError: replaceAllANR takes an even number of arguments.'); |
|||
} else { |
|||
/** |
|||
let replaced = ''; |
|||
* String method (alternative) to replace all occurences of a string with another |
|||
for (let i = 0; i < arguments.length; i = i + 2) { |
|||
* (takes a replacer and a replacee as arguments) |
|||
if (i === 0) { |
|||
* @returns {string} |
|||
replaced = this.split(arguments[i]).join(arguments[i + 1]); |
|||
*/ |
|||
} else { |
|||
String.prototype.replaceAllANR = function() { |
|||
replaced = replaced.split(arguments[i]).join(arguments[i + 1]); |
|||
if (arguments.length %2 !== 0) { |
|||
} |
|||
return new Error('SyntaxError: replaceAllANR takes an even number of arguments.'); |
|||
} else { |
|||
let replaced = ''; |
|||
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; |
|||
} |
} |
||
}; |
|||
String.prototype.escapeRegExpANR = function() { // Just a note: ^$.*+?()[]{}| |
|||
return this.replaceAllANR('(', '\\(', ')', '\\)', '.', '\\.'); |
|||
} |
} |
||
}; |
|||
})(); |
|||
String.prototype.escapeRegExpANR = function() { // Just a note: ^$.*+?()[]{}| |
|||
return this.replaceAllANR('(', '\\(', ')', '\\)', '.', '\\.'); |
|||
} |
|||
//</nowiki> |
//</nowiki> |
2022年5月5日 (木) 16:42時点における版
/************************************
* AN Reporter (ANR) *
* Author: Dragoniez *
* Version: 6.2 *
************************************/
//<nowiki>
// ******************** CONFIGS ********************
/* Config
anrConfig: {
predefinedReasons: {},
addToWatchlist: true,
headerColor: '#FEC493',
backgroundColor: '#FFF0E4',
portletlinkPosition: 'skin-dependent',
fontSize: 'skin-dependent',
dropdownFontSize: 'skin-dependent'
} */
if (typeof anrConfig === 'undefined') var anrConfig = {};
if (typeof anrPredefinedReasons !== 'undefined' && Array.isArray(anrPredefinedReasons)) { // Predefined reasons were previously defined as an array
if (anrPredefinedReasons.length !== 0) {
anrConfig.predefinedReasons = {};
for (let i = 0; i < anrPredefinedReasons.length; i++) {
anrConfig.predefinedReasons[i] = anrPredefinedReasons[i];
}
}
}
if (!anrConfig.headerColor) anrConfig.headerColor = '#FEC493';
if (!anrConfig.backgroundColor) anrConfig.backgroundColor = '#FFF0E4';
// ******************** SCRIPT BODY ********************
(function(){ // Create a function scope
// ******************** VARIABLES ********************
// Debugging Mode
const debugMode = {
'scriptAd': false, // 'AN Reporter Experimental' if true
'editSummary': false, // 'Test edit via mediawiki API' + scriptAd if true
'editTarget': false, // 'User:Dragoniez/test' if true
'portletLink': false,
'causeIntentionalError': false,
'drPreviewSections': 'tarSectionsI' // I, S, 3RR, SubpagedLTA
};
const scriptAd = debugMode.scriptAd ? ' ([[User:Dragoniez/AN Reporter|AN Reporter Experimental]])' : ' ([[User:Dragoniez/AN Reporter|AN Reporter]])';
const portletLinkText = debugMode.portletLink ? '報告β' : '報告';
const developerLink = `<a href="${mw.util.getUrl('User talk:Dragoniez/AN Reporter')}" target="_blank">開発者</a>`;
// Page names
const ANI = 'Wikipedia:管理者伝言板/投稿ブロック';
const ANS = 'Wikipedia:管理者伝言板/投稿ブロック/ソックパペット';
const AN3RR = 'Wikipedia:管理者伝言板/3RR';
const VIP = 'Wikipedia:進行中の荒らし行為';
const Iccic = 'Wikipedia:進行中の荒らし行為/長期/Iccic/投稿ブロック依頼'; //SockInfo
const ISECHIKA = 'Wikipedia:管理者伝言板/投稿ブロック/いせちか';
const KAGE = 'Wikipedia:管理者伝言板/投稿ブロック/影武者';
const KIYOSHIMA = 'Wikipedia:管理者伝言板/投稿ブロック/清島達郎';
const SHINJU = 'Wikipedia:管理者伝言板/投稿ブロック/真珠王子';
const TEST = '利用者:Dragoniez/test';
/**
* Object to store logids for usernames {user1: logid, user2: logid...}
*/
const Logids = {};
// Related to dialog creation
var userDiv; // What to append when the 'add' button is hit
var userCnt = 1; // *ID number of the elements in the appended userDiv
// ******************** DOM READY FUNCTION ********************
// Wait for the required dependencies to be ready
$.when(
$.getScript('https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js'),
mw.loader.using(['jquery.ui', 'mediawiki.util']),
$.ready
).then(function(){
// Load CSS source for Select2
$('head').append('<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css">');
// Run the script only if the user is autoconfirmed and the page is not an edit page
if (isInArray('autoconfirmed', mw.config.get('wgUserGroups')) && mw.config.get('wgAction') !== 'edit') {
addAnrPortletLink();
}
});
// ******************** MAIN FUNCTIONS ********************
function addAnrPortletLink() {
// Define the position of the portletlink (skin-dependent)
var lkPosition;
if (anrConfig.portletlinkPosition) {
lkPosition = anrConfig.portletlinkPosition;
} else {
switch(mw.config.get('skin')) {
case 'vector':
case 'vector-2022':
lkPosition = 'p-views';
break;
case 'minerva':
lkPosition = 'p-personal';
break;
default: // monobook, timeless, or something else
lkPosition = 'p-cactions';
}
}
// Add a portletlink for ANR
$(mw.util.addPortletLink(lkPosition, '#', portletLinkText, 'ca-anr', '管理者伝言板に利用者を報告', null, '#ca-move')).click(openAnrDialog);
}
// Set font size
var fontSize, select2FontSize
if (anrConfig.fontSize) {
fontSize = anrConfig.fontSize;
} else {
switch(mw.config.get('skin')) {
case 'vector':
case 'vector-2022':
case 'minerva':
fontSize = '80%';
break;
case 'monobook':
fontSize = '110%';
break;
case 'timeless':
fontSize = '90%';
break;
default:
fontSize = '80%';
}
}
if (anrConfig.dropdownFontSize) {
select2FontSize = anrConfig.dropdownFontSize;
} else {
switch(mw.config.get('skin')) {
case 'vector':
case 'vector-2022':
case 'minerva':
select2FontSize = '0.9em';
break;
case 'monobook':
select2FontSize = '1.03em';
break;
case 'timeless':
select2FontSize = '0.94em';
break;
default:
select2FontSize = '0.9em';
}
}
var styleAppended = false;
function openAnrDialog(e) {
e.preventDefault();
// Default CSS for Select2 and classes shared by selectors
if (!styleAppended) {
$('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: ${select2FontSize};
margin: 0;
}
.select2-container, .select2-selection--single {
height: auto !important;
}
.anr-dialog-label {
display: inline-block;
width: 8ch;
}
.anr-dialog-select, .anr-dialog-input {
border: 1px solid #d3d3d3;
border-radius: 1%;
background-color: white;
padding: 2px 4px;
}
.anr-dialog-button {
color: black;
font-weight: normal;
border: 1px solid #d3d3d3;
background-color: white;
padding: 0.2em 0.5em;
border-radius: 10%;
}
.anr-dialog-textarea {
width: 100%;
box-sizing: border-box;
}
.anr-dialog-needmargin {
margin: 1em 0;
}
</style>`
);
styleAppended = true;
}
// The whole html contour
const modalHtml =
`<div id="anr-modal-dialog" title="AN Reporter" style="max-height: 80vh;">` +
` <div id="anr-modal-header">` +
` <h2>利用者を報告</h2>` +
` </div>` +
` <div id="anr-modal-body">` +
` <form>` +
` <div id="anr-target-div" class="anr-dialog-needmargin">` +
` <label for="anr-target-options" id="anr-target-options-label" class="anr-dialog-label">報告先</label>` +
` <select id="anr-target-options" class="anr-dialog-select">` +
` <option selected disabled hidden>選択してください</option>` +
` <option>${ANI}</option>` +
` <option>${ANS}</option>` +
` <option>${AN3RR}</option>` +
` </select>` +
` <div id="anr-target-pagelink-div" style="display: none;">` +
` <label class="anr-emptylabel anr-dialog-label" for="anr-target-pagelink"></label>` +
` <a id="anr-target-pagelink" href="" target="_blank">報告先を確認</a>` +
` </div>` +
` </div>` +
` <div id="anr-section-i-div" class="anr-dialog-needmargin" style="display: none;">` +
` <label for="anr-section-i-select" class="anr-dialog-label">節</label>` +
` <select id="anr-section-i-select" class="anr-dialog-select">` +
` <option selected disabled hidden class="anr-section-options-initial">選択してください</option>` +
` <option id="anr-section-i-options-date"></option>` +
` <option>不適切な利用者名</option>` +
` <option>公開アカウント</option>` +
` <option>公開プロキシ・ゾンビマシン・ボット・不特定多数</option>` +
` <option>犯罪行為またはその疑いのある投稿</option>` +
` </select>` +
` </div>` +
` <div id="anr-section-s-div" class="anr-dialog-needmargin" style="display: none;">` +
` <label for="anr-section-s-select" class="anr-dialog-label">節</label>` +
` <select id="anr-section-s-select" class="anr-dialog-select">` +
` <option selected disabled hidden class="anr-section-options-initial">選択してください</option>` +
` <optgroup label="系列が立てられていないもの">` +
` <option>著作権侵害・犯罪予告</option>` +
` <option>名誉毀損・なりすまし・個人情報</option>` +
` <option>妨害編集・いたずら</option>` +
` <option>その他</option>` +
` </optgroup>` +
` <optgroup id="anr-section-s-lta" label="LTA">` +
// getSectionsS()
` </optgroup>` +
` </select>` +
` </div>` +
` <div id="anr-user-div" class="anr-dialog-needmargin">` +
` <div id="anr-user1-div">` +
` <div id="anr-user1-input-div">` +
` <label for="anr-user1-input" class="anr-dialog-label">利用者</label>` +
` <input id="anr-user1-input" class="anr-dialog-input" style="width: 34ch;">` +
` <select disabled id="anr-user1-select" class="anr-dialog-select">` +
` <option class="anr-opt-UNL">UNL</option>` +
` <option class="anr-opt-User2">User2</option>` +
` <option class="anr-opt-IP2">IP2</option>` +
` <option class="anr-opt-logid">logid</option>` +
` <option class="anr-opt-diff">diff</option>` +
` <option selected class="anr-opt-none">none</option>` +
` </select>` +
` </div>` +
` <div id="anr-user1-checkbox-div" style="display: none;">` +
` <label class="anr-emptylabel anr-dialog-label"></label>` +
` <input type="checkbox" id="anr-user1-checkbox">` +
` <label for="anr-user1-checkbox">利用者名を隠す</label>` +
` </div>` +
` <div id="anr-user1-idlink-div" style="display: none;">` +
` <label for="anr-user1-idlink" class="anr-dialog-label"></label>` +
` <a id="anr-user1-idlink" href="" target="_blank"></a>` +
` </div>` +
` <div id="anr-user1-blockstatus-div" style="display: none;">` +
` <label for="anr-user1-blockstatus" class="anr-dialog-label"></label>` +
` <a id="anr-user1-blockstatus" href="" target="_blank" style="color: MediumVioletRed;">ブロックあり</a>` +
` </div>` +
` </div>` +
` <div id="anr-btn-div">` +
` <button type="button" id="anr-addBtn" class="anr-dialog-button">追加</button>` +
` </div>` +
` </div>` +
' <div id="anr-viplist-div" style="width: 100%; display: none;">' +
` <label for="anr-viplist-select" class="anr-dialog-label">VIP</label>` +
` <select id="anr-viplist-select">` +
' <optgroup style="display: none;">' + // Adjust font size
' <option selected disabled hidden>コピーする場合は選択してください</option>' +
// getVipList()
' </optgroup>' +
' </select>' +
' </div>' +
` <div id="anr-predefinedreasons-div" class="anr-dialog-needmargin" style="display: none;">` +
` <label for="anr-predefinedreasons-select" class="anr-dialog-label">定型文</label>` +
` <select id="anr-predefinedreasons-select">` +
' <optgroup style="display: none;">' + // Adjust font size
` <option selected>定型文を使用する場合は選択してください</option>` +
' </optgroup>' +
` </select>` +
` </div>` +
` <div id="anr-reason-div" class="anr-dialog-needmargin">` +
` <label for="anr-reason-text" class="anr-dialog-label">理由</label>` +
` <textarea id="anr-reason-text" class="anr-dialog-textarea" rows="6"></textarea>` +
` </div>` +
` <div id="anr-summary-div" class="anr-dialog-needmargin">` +
` <input id="anr-summary-checkbox" type="checkbox">` +
` <label for="anr-summary-checkbox">要約を指定</label>` +
` <textarea id="anr-summary-text" class="anr-dialog-textarea" rows="3" style="display: none;"></textarea>` +
` </div>` +
` <div id="anr-checkbox-div" class="anr-dialog-needmargin">` +
` <input checked id="anr-blockstatus-checkbox" type="checkbox">` +
` <label for="anr-blockstatus-checkbox">報告前にブロック状態をチェック</label>` +
` <br>` +
` <input checked id="anr-duplicatereport-checkbox" type="checkbox">` +
` <label for="anr-duplicatereport-checkbox">報告前に重複報告をチェック</label>` +
` <br>` +
` <input checked id="anr-watchlist-checkbox" type="checkbox">` +
` <label for="anr-watchlist-checkbox">報告対象者をウォッチリストに追加</label>` +
` </div>` +
` </form>` +
` </div>` +
`</div>`;
// Add the frame div to the page
$('body').append(modalHtml);
// Show dialog
$('#anr-modal-dialog').dialog({
'resizable': false,
'height': 'auto',
'width': 'auto',
'modal': true,
'open': initializeAnrDialog,
'buttons': [{
'text': 'プレビュー',
'click': preview
}, {
'text': '報告',
'click': report
}]
});
}
// Function to initialze the modal dialog
function initializeAnrDialog(){
userDiv = $('#anr-user1-div').prop('innerHTML'); // A div of the same structure is appended when the 'add' button is hit
getSectionsS(); // Get sections on WP:AN/S
dialogCSS(); // Initialize the design of the dialog
getVipList(); // Show VIP list
getPredefinedReasons(); // Show the select box for predefined reasons
// Add to wathchlist?
if (anrConfig.addToWatchlist === false) {
$('#anr-watchlist-checkbox').prop('checked', false);
};
// Get the name of the user to report if it can be retrieved from the page
var username = mw.config.get('wgRelevantUserName'); // Note: This does not pick up IP ranges
// Workaround to pick up IP ranges
if (!username && mw.config.get('wgCanonicalSpecialPageName') === 'Contributions') {
const relUsername = $('#firstHeading').text().replace('の投稿記録', '');
if (mw.util.isIPAddress(relUsername, true)) username = relUsername;
}
// Exit function if the current user is on his/her own page or username has remained undefined or null
if (!username || username === mw.config.get('wgUserName')) return;
// Initialize the username input and type dropdown
const inputID = '#anr-user1-input', selectID = '#anr-user1-select', checkboxDivID = '#anr-user1-checkbox-div';
$(inputID).val(username); // Fill the input with the username
$(selectID).prop('disabled', false); // enable dropdown
if (mw.util.isIPAddress(username, true)) { // if IP
$(selectID).children('.anr-opt-UNL').prop('hidden', true);
$(selectID).children('.anr-opt-User2').prop('hidden', true);
$(selectID).children('.anr-opt-IP2').prop({'hidden': false, 'selected': true});
$(selectID).children('.anr-opt-logid').prop('hidden', true);
$(selectID).children('.anr-opt-diff').prop('hidden', true);
$(selectID).children('.anr-opt-none').prop('hidden', false);
$(checkboxDivID).css('display', 'none'); // hide 'hide username' checkbox
toggleBlockStatusLink(inputID, false, false);
} else { // if user
$(selectID).children('.anr-opt-UNL').prop({'hidden': false, 'selected': true});
$(selectID).children('.anr-opt-User2').prop('hidden', false);
$(selectID).children('.anr-opt-IP2').prop('hidden', true);
$(selectID).children('.anr-opt-logid').prop('hidden', true);
$(selectID).children('.anr-opt-diff').prop('hidden', true);
$(selectID).children('.anr-opt-none').prop('hidden', false);
$(checkboxDivID).css('display', 'block'); // show 'hide username' checkbox
toggleBlockStatusLink(inputID, false, false);
}
}
// Function to get sections on WP:AN/S from the API
async function getSectionsS() {
const $label = $('#anr-target-options-label'); // Label of '報告先'
$label.append(toggleLoadingSpinner('add')); // Show a loading spinner while trying to get sections on WP:AN/S
const parse = await parsePage(ANS, 'sections');
if (parse) {
// Get VIP's names
const sectionInfo = parse.sections;
const excludeList = [
'系列が立てられていないもの',
'著作権侵害・犯罪予告',
'名誉毀損・なりすまし・個人情報',
'妨害編集・いたずら',
'その他',
'A. 最優先',
'暫定A',
'休止中A',
'B. 優先度高',
'暫定B',
'休止中B',
'C. 優先度中',
'暫定C',
'休止中C',
'D. 優先度低',
'暫定D',
'休止中D',
'N. 未分類',
'サブページなし',
'休止中N'
];
const sectionList = [];
for (let i = 0; i < sectionInfo.length; i++) {
if (!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');
}
/**
* 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;
}
}
/**
* Function to get a pages's information
* @param {string} pagename
* @param {string} prop wikitext, sections, wikitext|sections
* @param {number} sectionNum optional
* @returns {Promise} res.parse ({{sections: []}, wikitext: ''}) (undefined if query failed)
*/
function parsePage(pagename, prop, sectionNum) {
return new Promise(function(resolve) {
var params = {
'action': 'parse',
'page': pagename,
'prop': prop,
'formatversion': 2
}
if (sectionNum) params = Object.assign(params, {'section': sectionNum}); // Concatenate params
new mw.Api().get(params).then(function(res){
resolve(res && res.parse ? res.parse : undefined);
});
});
}
// 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', anrConfig.backgroundColor);
$('.ui-button').css({
'color': 'black',
'background-color': 'white'
});
$('.ui-dialog-titlebar, .ui-dialog-titlebar-close').attr('style', `background: ${anrConfig.headerColor} !important;`);
$('.ui-dialog').css('font-size', fontSize);
}
// WP:VIP list (for copy to clipboard)
async function getVipList() {
const parse = await parsePage(VIP, 'sections');
if (parse) {
// Get VIP's names
const sectionInfo = parse.sections;
const excludeList = [
'記述について',
'急を要する二段階',
'配列',
'ブロック等の手段',
'このページに利用者名を加える',
'注意と選択',
'警告の方法',
'未登録(匿名・IP)ユーザーの場合',
'登録済み(ログイン)ユーザーの場合',
'警告中',
'関連項目'
];
const 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 {
$('#anr-viplist-select')
.css('width', $('#anr-target-options').innerWidth())
.select2()
.children('optgroup').append(vipList.join(''));
$('#anr-viplist-div').css('display', 'block');
centerDialog();
}
} else {
return mw.log.error('VIP list: The API returned an unresolvable object.');
}
}
// Function to show the select div for predefined report reasons if they're predefined
function getPredefinedReasons() {
const pdReasons = anrConfig.predefinedReasons;
if (typeof pdReasons !== 'undefined' && !$.isEmptyObject(pdReasons)) { // If the user has fixed reasons prepared
const $reasons = $('#anr-predefinedreasons-select');
$reasons.css('width', $('#anr-target-options').innerWidth()).select2();
for (let key in pdReasons) {
$reasons.children('optgroup').append(`<option>${pdReasons[key]}</option>`);
}
$('#anr-predefinedreasons-div').css('display', 'block');
centerDialog();
}
}
function centerDialog() {
var $dialog;
if ($('#anr-preview-dialog').length !== 0) {
$dialog = $('#anr-preview-dialog');
} else if ($('#anr-drpreview-dialog').length !== 0) {
$dialog = $('#anr-drpreview-dialog');
} else {
$dialog = $('#anr-modal-dialog');
}
$dialog.dialog({'position': {my: 'center', at: 'center', of: window}});
}
// Function to check information typed into the form
function editPrep() {
// Get all input values and UserAN types, and check for duplicates
const users = [], types = [], duplicates = [];
$('#anr-user-div :text').each(function(){ // Loop through all inputs
const inputID = '#' + $(this).attr('id');
const type = $(inputID.replace('input', 'select')).children('option').filter(':selected').text(); // UserAN type
const inputVal = $(this).val().trimANR(); // Username
if (!inputVal) return;
var username, logid;
if (type === 'logid' && (username = getKeyByValue(Logids, logid = inputVal))) { // if t=logid and the logid can be converted to a username
// If either of the username or the logid is already in the array 'users' and if they have yet to be listed as duplicates
if ((isInArray(username, users) || isInArray(logid, users)) && !isInArray(username, duplicates) && !isInArray(logid, duplicates)) {
duplicates.push(username, logid); // List both the username and the logid as duplicates
}
} else { // If t!=logid or t=logid but a username can't be obtained
// If the username is already in the array 'users' (and if it hasn't been listed as a duplicate)
if (isInArray(username = inputVal, users) && !isInArray(username, duplicates)) {
duplicates.push(username); // List the username as a duplicate
}
}
users.push(inputVal); // Push the username into the array
types.push(type); // Push the UserAN type into the array
});
// Get the name of the section to edit
var pageToEdit = $('#anr-target-options').children('option').filter(':selected').text();
var sectionToEdit = '選択してください', reportToANS = false;
if (pageToEdit === ANI) { // If WP:AN/I is selected as the target page to edit
sectionToEdit = $('#anr-section-i-select').children('option').filter(':selected').text();
// Update the target section for cases in which the date has changed since the date-dependent section was chosen
if (sectionToEdit.match(/^\d{4}年\d{1,2}月\d{1,2}日 - \d{1,2}日新規報告$/)) {
const sectionIDate = 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 sectionANS = $('#anr-section-s-select').find('option').filter(':selected').text();
switch(sectionANS) {
case 'Iccic系 (Iccic)':
pageToEdit = Iccic;
sectionToEdit = '新規依頼';
break;
case 'いせちか系 (ISECHIKA)':
pageToEdit = ISECHIKA;
sectionToEdit = '新規依頼';
break;
case '影武者系(KAGE)':
pageToEdit = KAGE;
sectionToEdit = '新規依頼';
break;
case '清島達郎系 (清島、KIYOSHIMA)':
pageToEdit = KIYOSHIMA;
sectionToEdit = '新規依頼';
break;
case '真珠王子系(SHINJU)':
pageToEdit = SHINJU;
sectionToEdit = '新規依頼';
break;
default:
sectionToEdit = sectionANS;
}
} else if (pageToEdit === AN3RR) { // If WP:AN/3RR is selected as the target page to edit
sectionToEdit = '3RR';
}
// The reason of the report
var fixedReason = $('#anr-predefinedreasons-select').find('option').filter(':selected').text();
fixedReason = fixedReason === '定型文を使用する場合は選択してください' ? '': fixedReason;
var reason = fixedReason + $('#anr-reason-text').val().trimANR();
// Check if necessary fields are filled
if (pageToEdit === '選択してください' || sectionToEdit === '選択してください' || reason === '' || users.length === 0) {
alert('必須項目が入力・選択されていません');
return;
}
// Duplicate warning
if (duplicates.length !== 0) { // If the inputs have duplicates in them
const confirmMsg =
'以下の利用者について、重複入力がある可能性があります。\n\n' + duplicates.join(', ') + '\n\n' +
'続行する場合は OK を、フォームに戻る場合は Cancel を押してください';
if (confirm(confirmMsg) === false) return;
}
// If the reason doesn't contain a signature, add one
if (reason.substring(reason.length - 4) !== '~~~~') {
reason += '--~~~~';
}
// Get edit summary
const summaryText = $('#anr-summary-text').val().trimANR(), editSummarySection = '/*' + sectionToEdit + '*/';
var editSummary, summaryCustomized;
if (summaryText) {
editSummary = editSummarySection + summaryText + scriptAd;
summaryCustomized = true;
} else {
editSummary = editSummarySection + genEditSummary().replace(' - ', '') + scriptAd;
}
// Warn if a username is hidden but shown in the summary
if (summaryCustomized) {
const hiddenUsernames = [];
for (let i = 0; i < types.length; i++) {
let type = types[i], inputVal = users[i], username;
if (type === 'logid' && (username = getKeyByValue(Logids, inputVal)) && editSummary.indexOf(username) !== -1) hiddenUsernames.push(username);
}
if (hiddenUsernames.length !== 0) {
const warnText = '警告\n以下の利用者名は、フォーム上では隠されていますが、編集要約内では隠されていません。\n・' + hiddenUsernames.join('\n・') +
'\nこのまま続行する場合は OK を、中止する場合は Cancel を押してください。';
if (confirm(warnText) === false) return;
}
}
// Get text to add to the page
var reportText = '';
const UserAN = '{{UserAN|t=TYPE|USER}}';
if (users.length < 2) { // If user to report is just one
reportText = '\* ' + UserAN.replaceAllANR('TYPE', types[0], 'USER', users[0]) + ' - ' + reason;
} else { // If two or more
for (let i = 0; i < users.length; i++) {
reportText += '\* ' + UserAN.replaceAllANR('TYPE', types[i], 'USER', users[i]) + '\n';
}
reportText += ': ' + reason;
}
// Return values
return {
'users': users,
'types': types,
'pageToEdit': debugMode.editTarget ? TEST : pageToEdit,
'sectionToEdit': sectionToEdit,
'wikiPagename': debugMode.editTarget ? TEST + '#' + sectionToEdit : pageToEdit + '#' + sectionToEdit,
'reportToANS': reportToANS,
'editSummary': debugMode.editSummary ? 'Test edit via mediawiki API' + scriptAd : editSummary,
'reportText': reportText
}
}
// Function for the 'preview' button of the dialog
function preview() {
// Check if the necessary fields are filled and get edit information
const ep = editPrep();
if (!ep) return;
// Preview dialog contour
const ANSMisc = `<a href="${mw.util.getUrl('WP:AN/S#OTH')}" target="_blank">WP:AN/S#その他</a>`;
const previewDiv =
'<div id="anr-preview-dialog" title="AN Reporter Preview" style="max-height: 80vh;">' +
' <div id="anr-preview-header" style="padding: 0.5em;">' +
' <p id="anr-preview-loading">' +
` プレビューを読み込み中${toggleLoadingSpinner('add')}` +
' </p>' +
' <p id="anr-preview-warning" style="display: none;">' +
' 注意1: このプレビュー上のリンクは全て新しいタブで開かれます' +
' <br>' +
` 注意2: 報告先が ${ANSMisc} の場合、このプレビューには表示されませんが「他X月X日」のヘッダーは必要に応じて自動挿入されます` +
' </p>' +
' </div>' +
' <div id="anr-preview-body" style="display: none; font-size: 1.1em; padding-top: 1em; border-top: 1px solid silver;">' +
' <div id="anr-preview-text" style="border: 1px solid silver; padding: 0.2em 0.5em; background: white;">' +
// previewHtml
' </div>' +
' <div id="anr-preview-summary" style="margin-top: 0.8em; border: 1px solid silver; padding: 0.2em 0.5em; background: white;">' +
// summaryHtml
' </div>' +
' </div>' +
'</div>';
// Show preview dialog
$('body').append(previewDiv);
$('#anr-preview-dialog').dialog({
'height': 'auto',
'width': $('#content').width() * 0.8,
'modal': true,
'open': async function(){
// Initialize the design of the dialog
dialogCSS();
// Convert text on the dialog to html
const parsed = await convertWikitextToHtmlFormat(ep.reportText, ep.editSummary);
if (parsed) {
const previewHtml = parsed.htmltext;
const summaryHtml = parsed.htmlsummary.replaceAllANR('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');
centerDialog();
} else {
$('#anr-preview-loading').text('プレビューの読み込みに失敗しました').css('color', 'MediumVioletRed');
centerDialog();
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 {Promise<{htmltext: string, htmlsummary: string}>}
*/
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,
'formatversion': 2
}).then(function(res) {
resolve({
'htmltext': res.parse.text,
'htmlsummary': res.parse.parsedsummary
});
}).catch(function(code, err) {
console.log(err.error.info);
resolve();
});
});
}
// Function for the 'report' button of the dialog
function report() {
// Check if the necessary fields are filled and get edit information
const ep = editPrep();
if (!ep) return;
// Change dialog content
$('#anr-modal-dialog')
.dialog({
'width': $(this).innerWidth(), // Absolute width
'buttons': [] // Hide buttons
})
.append('<div class="anr-editing">') // Append div to show edit status
.find('form').css('display', 'none'); // Hide dialog content
reportUsers(ep);
addUsersToWatchlist(ep);
}
// Function to execute report
async function reportUsers(ep) {
// Check the block status of the reportees if the checkbox is checked
var blocked = [];
if ($('#anr-blockstatus-checkbox').is(':checked')) {
blocked = await preeditBlockStatusQuery(ep); // Query who's blocked
}
if (blocked.length !== 0) { // If any of the reportees is blocked
// Update dialog buttons
$('#anr-modal-dialog').dialog({
'buttons': [{
'text': '続行',
'click': function(){
$(this).dialog({'buttons': [] });
reportUsers2(ep);
}
}, {
'text': '戻る',
'click': function(){
$(this).find('form').css('display', 'block');
$('.anr-editing').remove();
$(this).dialog({
'width': 'auto',
'buttons': [{
'text': 'プレビュー',
'click': preview
}, {
'text': '報告',
'click': report
}]
});
}
}, {
'text': '中止',
'click': function(){
$(this).dialog('close');
}
}]
});
} else { // If no one is blocked
reportUsers2(ep);
}
}
async function reportUsers2(ep) {
// Check duplicate reports if the checkbox is checked
if ($('#anr-duplicatereport-checkbox').is(':checked')) {
var dr = await preeditDuplicateReportQuery(ep);
}
if (typeof dr === 'undefined') var dr = {};
switch(dr.wikitext) {
case null: // Error occurred
return;
case undefined: // The checkbox is unchecked or no duplicate report found
reportUsers3(ep);
return;
default: // Possible duplicate reports present
// Update dialog buttons
$('#anr-modal-dialog').dialog({
'buttons': [{
'text': '確認',
'click': function() {
previewDuplicateReports(dr.wikitext, dr.dupUsernames);
}
}, {
'text': '続行',
'click': function(){
$(this).dialog({'buttons': [] });
reportUsers3(ep);
}
}, {
'text': '戻る',
'click': function(){
$(this).find('form').css('display', 'block');
$('.anr-editing').remove();
$(this).dialog({
'width': 'auto',
'buttons': [{
'text': 'プレビュー',
'click': preview
}, {
'text': '報告',
'click': report
}]
});
}
}, {
'text': '中止',
'click': function(){
$(this).dialog('close');
}
}]
});
}
}
/**
* Function to check block status before edit
* @returns {Array} [] if no one is blocked, [user1, user2...] if someone is blocked
*/
async function preeditBlockStatusQuery(ep) {
// Can't check block status if the input values are only of t=diff or t=none
var proceed;
for (let i = 0; i < ep.types.length; i++) {
if (ep.types[i] !== 'diff' && ep.types[i] !== 'none') {
proceed = true;
break;
}
}
if (!proceed) {
$('.anr-editing').append('<p>ブロックチェックはスキップされました</p>');
return [];
}
// Update message on the dialog
var msg = `<p>報告対象者のブロック情報を取得しています${toggleLoadingSpinner('add')}</p>`;
$('.anr-editing').append(msg);
// Extract users and IPs from the array
const usersForBlockCheck = [];
for (let i = 0; i < ep.users.length; i++) {
const inputVal = ep.users[i];
switch(ep.types[i]) {
case 'UNL':
case 'User2':
case 'IP2':
if (!isInArray(inputVal, usersForBlockCheck)) usersForBlockCheck.push(inputVal);
break;
case 'logid':
let username;
if ((username = getKeyByValue(Logids, inputVal)) !== undefined) { // If the logid can be converted to a username
if (!isInArray(username, usersForBlockCheck)) usersForBlockCheck.push(username);
}
break;
default: // Do nothing if t=diff or t=none (impossible to check block status)
}
}
// Check if any of the users is blocked
const blocked = await getBlocked(usersForBlockCheck);
// If any of the users is blocked
if (blocked.length !== 0) {
// Update message on the dialog
msg =
toggleLoadingSpinner('remove') +
`<p style="color: MediumVioletRed">ブロック済みの利用者を検出しました</p>`;
$('.anr-editing').append(msg);
// Update block status links on the dialog
$('#anr-user-div :text').each(function() { // Loop through all inputs
const inputID = '#' + $(this).attr('id');
const inputVal = $(inputID).val().trimANR();
const $bsLinkDiv = $(inputID.replace('input', 'blockstatus-div'));
const $bsLink = $(inputID.replace('input', '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
msg =
toggleLoadingSpinner('remove') +
`<p style="color: MediumSeaGreen">ブロック済みの利用者は検出されませんでした</p>`;
$('.anr-editing').append(msg);
}
return blocked;
}
/**
* Function to check duplicate reports
* @returns {Promise<{wikitext: string, dupUsernames: Array}>} wikitext === null if error occurs, undefined if no duplicate report is found,
* SECTIONTEXT to fetch preview from if there're potential duplicate reports. If SECTIONTEXT is returned, dupUsernames ===
* [username1, username2...], without logids that can be converted to usernames.
*/
async function preeditDuplicateReportQuery(ep) {
// Update message on the dialog
var msg = `<p>重複報告情報を取得しています${toggleLoadingSpinner('add')}</p>`;
$('.anr-editing').append(msg);
// Get sections and the whole wikitext of the page to which to report
const parsed = await parsePage(ep.pageToEdit, 'wikitext|sections');
const sections = parsed.sections, wikitext = parsed.wikitext;
const sectiontitles = [];
const 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++) {
const section = sections[i];
if (section.index.indexOf('T') === -1) { // If the section isn't a transcluded one
sectiontitles.push(section.line); // Get the title of the section
if (section.level == 2) { // Get equal-enclosed section headers
sectionheaders.push('== ' + section.line + ' ==');
} else if (section.level == 3) {
sectionheaders.push('=== ' + section.line + ' ===');
} else if (section.level == 4) {
sectionheaders.push('==== ' + section.line + ' ====');
} else if (section.level == 5) {
sectionheaders.push('===== ' + section.line + ' =====');
}
}
}
// The sections in which to search for duplicate reports
const tarSectionsI = [
getSectionI(true),
getSectionI(false),
'不適切な利用者名',
'公開アカウント',
'公開プロキシ・ゾンビマシン・ボット・不特定多数',
'犯罪行為またはその疑いのある投稿'
];
const tarSectionsS = [
'著作権侵害・犯罪予告',
'名誉毀損・なりすまし・個人情報',
'妨害編集・いたずら',
'その他',
ep.sectionToEdit
];
const tarSections3RR = ['3RR'];
const tarSectionsSubpagedLTA = ['新規依頼'];
var tarSections;
switch(ep.pageToEdit) {
case ANI:
tarSections = tarSectionsI;
break;
case ANS:
tarSections = tarSectionsS;
break;
case AN3RR:
tarSections = tarSections3RR;
break;
case Iccic:
case ISECHIKA:
case KAGE:
case KIYOSHIMA:
case SHINJU:
tarSections = tarSectionsSubpagedLTA;
break;
case TEST: // For debugging
eval(`tarSections = ${debugMode.drPreviewSections}`);
break;
default: // Error: Target pagename not defined
msg =
toggleLoadingSpinner('remove') +
'<p style="color: MediumVioletRed">致命的なエラーが発生しました</p><br>' +
`<p>${developerLink}に、<u>${ep.wikiPagename}</u>への報告においてこのエラーが発生したことの報告をお願いします。</p>` +
manualEdit(ep);
$('.anr-editing').append(msg);
editDone(ep, true);
return {'wikitext': null};
}
// Error handler for when pageToEdit doesn't have sections that it's supposed to have
if (!arrayIsInArray(tarSections, sectiontitles)) {
sectionNotFound(ep);
return {'wikitext': null};
}
// Separate the content of the parsed page into the content of each section
var sectionsPiped = sectiontitles.join('|').escapeRegExpANR();
var regExp = new RegExp(`={2,5}\\s*(?:${sectionsPiped})\\s*={2,5}`, 'g');
var sectionContent = wikitext.split(regExp); // Array of the content of each section, without section headers
for (let i = 0; i < sectionContent.length; i++) { // Re-add to sectionContent the '== TITLE ==' header that's been removed by the split() above
sectionContent[i] = sectionheaders[i] + sectionContent[i];
}
// Remove the contents of irrelevant sections from the array 'sectionContent'
sectionsPiped = tarSections.join('|').escapeRegExpANR();
regExp = new RegExp(`={2,5}\\s*(?:${sectionsPiped})\\s*={2,5}`, 'g');
for (let i = sectionContent.length -1; i >= 0; i--) {
if (sectionContent[i].search(regExp) === -1) sectionContent.splice(i, 1);
}
// Get usernames for duplicate report check
const usersDR = ep.users; // Input values without duplicates: usersDR will be used for duplicate report check
for (let i = 0; i < ep.types.length; i++) { // Loop through all the input values
let type = ep.types[i], username, logid, ip;
switch(type) {
case 'UNL': // Registered users need duplicate report check also for their logids
case 'User2':
if (logid = Logids[username = usersDR[i]]) { // If the object knows the required logid, just push it into the array
usersDR.push(logid);
} else { // If not, get the logid from the API and push it into the array (if the response isn't undefined)
if (logid = await getLogid(username)) {
Logids[username] = logid; // Save the logid into the object
usersDR.push(logid);
}
}
break;
case 'IP2': // IPv6s need to be case-insensitive
if (mw.util.isIPv6Address(ip = usersDR[i], true) && ip.match(/[A-Z]/i)) { // If the IP is an IPv6 and if it contains alphabets
if (ip.match(/[A-Z]/)) { // If the IPv6 is in uppercase, push its lowercase ver, if in lowercase, push the uppercase ver
usersDR.push(ip.toLowerCase());
} else {
usersDR.push(ip.toUpperCase());
}
}
break;
case 'logid': // The corresponding username needs to be checked
if (username = getKeyByValue(Logids, logid = usersDR[i])) usersDR.push(username);
break;
default: // t=diff or t=none: no need to do anything because the relevant input value is already in the array
}
}
// Extract UserAN templates and find duplicate reports
const dupTemplates = [], dupUsernames = [];
for (let i = sectionContent.length -1; i >= 0; i--) { // Loop through all section contents
const templates = findTemplates(sectionContent[i], 'useran'); // Extract UserAN templates as an array
let dupUsername, duplicateFound;
if (templates.length !== 0) { // If the section content contains at least one UserAN
for (let j = templates.length -1; j >= 0; j--) { // Loop through all the occurences of UserAN
if (dupUsername = stringContainsElementInArray(templates[j], usersDR)) { // If there's a duplciate report
if (!isInArray(templates[j], dupTemplates)) dupTemplates.push(templates[j]); // List the UserAN as a duplicate
if (!isInArray(dupUsername, dupUsernames)) dupUsernames.push(dupUsername); // List the duplicate username
duplicateFound = true;
}
}
}
if (!duplicateFound) sectionContent.splice(i, 1); // Remove the section text from the array if it doesn't involve duplicate reports
}
// Return text and update dialog
if (sectionContent.length === 0) { // If there's no duplicate report
msg = `<p style="color: MediumSeaGreen">重複報告は検出されませんでした${toggleLoadingSpinner('remove')}</p>`;
$('.anr-editing').append(msg); // Update message on the dialog
return; // Return undefined
} else { // If there're duplicate reports
msg = `<p style="color: MediumVioletRed">重複報告の可能性があります${toggleLoadingSpinner('remove')}</p>`;
$('.anr-editing').append(msg);
// Highlight all the duplciate UserAN occurences
sectionContent = sectionContent.join(''); // Merge the separate sections
for (let i = 0; i < dupTemplates.length; i++) {
sectionContent = sectionContent.replaceAllANR(dupTemplates[i], `<span style="background-color: ${anrConfig.headerColor}">${dupTemplates[i]}</span>`);
}
// Return wikitext to fetch preview from
return {
'wikitext': sectionContent,
'dupUsernames': dupUsernames
};
}
}
// Function to show error message if sections that must be there are not found
function sectionNotFound(ep) {
const msg =
toggleLoadingSpinner('remove') +
'<p style="color: MediumVioletRed">取得に失敗しました</p>' +
'<p>指定されたセクションが見つかりませんでした</p>' +
'<br>' +
'<p>考えられる原因:</p>' +
`<p>1. 編集先のページの節構成が変更された</p>` +
`<p>2. 通信に失敗した</p>` +
`<p>3. スクリプトのバグ</p>` +
manualEdit(ep);
$('.anr-editing').append(msg);
editDone(ep, true);
}
// Function to generate the html for the manual edit helper tab
function manualEdit(ep) {
const meHtml =
'<br>' +
'<p>手動編集用:</p>' +
`<textarea disabled class="anr-dialog-textarea" rows="4">${ep.reportText}</textarea>` +
'<br>' +
'<p>要約:</p>' +
`<textarea disabled class="anr-dialog-textarea" rows="2">${ep.editSummary.replace(scriptAd, '')}</textarea>`;
return meHtml;
}
/**
* Action for when edit is done (in any way)
* @param {Object} ep
* @param {boolean} editFailed
*/
function editDone(ep, editFailed) {
const btns = [], $dialog = $('#anr-modal-dialog');
// Button to jump to the report page
if (editFailed || mw.config.get('wgPageName') !== ep.pageToEdit) { // Show the button if the edit failed or if the user is NOT on the page
const destBtn = {
'text': '報告先',
'click': function(){
window.open(mw.util.getUrl(ep.wikiPagename), '_blank');
}
};
btns.push(destBtn);
}
// Button to close the dialog (always shown)
const closeBtn = {
'text': '閉じる',
'click': function(){
$dialog.dialog('close');
var curPage = mw.config.get('wgPageName');
if (curPage === ANI ||
curPage === ANS ||
curPage === AN3RR ||
curPage === Iccic ||
curPage === ISECHIKA ||
curPage === KAGE ||
curPage === KIYOSHIMA ||
curPage === SHINJU ||
curPage === TEST)
{
location.reload(true);
}
}
};
btns.push(closeBtn);
// Show the button(s) on the dialog
$dialog.dialog({'buttons': btns});
if (editFailed) centerDialog();
}
/**
* Function to preview duplicate reports on a new dialog
* @param {string} wikitext wikitext for preview (inherited from preeditDuplicateReportQuery)
* @param {Array} dupUsernames usernames found to be duplicate reports (inherited from preeditDuplicateReportQuery)
*/
function previewDuplicateReports(wikitext, dupUsernames) {
// Duplicate usernames to show on the dialog (logids are to be shown in parentheses)
const usernames = [];
for (let i = 0; i < dupUsernames.length; i++) {
let username, logid;
if (username = getKeyByValue(Logids, logid = dupUsernames[i])) { // if the dupUsername is a logid and that can be converted to a username
usernames.push(`${username} (${logid})`);
} else if (logid = Logids[username = dupUsernames[i]]) { // if the dupUsername is a username and that can be converted to a logid
usernames.push(`${username} (${logid})`);
} else {
usernames.push(username); // if else, just push the username into the array
}
}
// Create dialog
const duplicateReportPreviewDiv =
'<div id="anr-drpreview-dialog" title="AN Reporter Duplicate Report Preview" style="max-height: 80vh;">' +
' <div id="anr-drpreview-header" style="padding: 0.5em;">' +
' <p id="anr-drpreview-loading">' +
` 読み込み中${toggleLoadingSpinner('add')}` +
' </p>' +
' <p id="anr-drpreview-userlist" style="display: none; font-size: larger">' +
' <span style="font-weight: bold">重複報告の可能性のある値:</span>' +
' <br>' +
usernames.join(', ') +
' </p>' +
' </div>' +
' <div id="anr-drpreview-body" style="display: none; font-size: 1.1em; padding: 0.5em; border: 1px solid silver; background-color: white;">' +
// Added when the dialog is opened
' </div>' +
'</div>';
$('body').append(duplicateReportPreviewDiv);
// Show preview dialog
$('#anr-drpreview-dialog').dialog({
'height': 'auto',
'width': $('#content').width() * 0.8,
'modal': true,
'open': async function(){
// Initialize the design of the dialog
dialogCSS();
// Convert the wikitext to an html form
const wikitextInHtml = await convertWikitextToHtmlFormat(wikitext.trim(), '');
if (wikitextInHtml) {
$('#anr-drpreview-body').append(wikitextInHtml.htmltext);
$('#anr-drpreview-dialog a').attr('target', '_blank'); // Open all links on a new tab
$('#anr-drpreview-body').css('display', 'block');
$('#anr-drpreview-loading').remove();
$('#anr-drpreview-userlist').css('display', 'inline');
centerDialog();
} else {
$('#anr-drpreview-loading').text('読み込みに失敗しました').css('color', 'MediumVioletRed');
centerDialog();
setTimeout(function(){
$('#anr-drpreview-dialog').dialog('close');
}, 10000);
}
},
'buttons': [{
'text': '閉じる',
'click': function(){
$(this).dialog('close');
}
}]
});
}
async function reportUsers3(ep) {
const ts = await getTimestamps(ep);
if (!ts) return;
const baseTS = ts.baseTS, curTS = ts.curTS;
const sectionNum = await getSectionNumber(ep);
if (!sectionNum) return;
const reportText = await getReportText(ep, sectionNum);
if (!reportText) return;
edit(ep, sectionNum, reportText, baseTS, curTS);
}
// Function to get the latest revision of the administrator's noticeboard
function getTimestamps(ep) {
return new Promise(function(resolve) {
var msg = `<p>最新版を取得しています${toggleLoadingSpinner('add')}</p>`;
$('.anr-editing').append(msg);
new mw.Api().get({
'action': 'query',
'titles': ep.pageToEdit,
'prop': 'revisions',
'curtimestamp': true,
'formatversion': 2
}).then(function(res){
var resPages;
if (res && res.query && (resPages = res.query.pages)) { // If the latest revision is successfully retrieved
if (!resPages[0].missing) { // .missing is true if the page doesn't exist, otherwise undefined
// Get the timestamps of the latest revision and the API query
const baseTS = resPages[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
msg =
'<p style="color: MediumSeaGreen">取得に成功しました</p>' +
`<p>セクション番号を取得しています${toggleLoadingSpinner('move')}</p>`;
$('.anr-editing').append(msg);
// Return the timestamps as an object
resolve({
'baseTS': baseTS,
'curTS': curTS
});
} else { // If the page doesn't exist
msg =
toggleLoadingSpinner('remove') +
'<p style="color: MediumVioletRed">エラー: 報告先のページが存在しません</p>' +
manualEdit(ep);
$('.anr-editing').append(msg);
editDone(ep, true);
resolve();
}
} else { // If revision retrieval fails
queryFailed(ep);
resolve();
}
});
});
}
function addUsersToWatchlist(ep) {
if (!$('#anr-watchlist-checkbox').is(':checked')) return console.log('ウォッチリストへの追加設定はオフになっています。');
return new Promise(function(resolve) {
// Get pagenames to watch
const pagenames = [];
for (let i = 0; i < ep.types.length; i++) {
const type = ep.types[i], user = ep.users[i];
if (type === 'User2' || type === 'UNL' || type === 'IP2') {
if (!isInArray('利用者:' + user, pagenames)) pagenames.push('利用者:' + user);
} else if (type === 'logid') {
let username;
if (username = getKeyByValue(Logids, user) && !isInArray('利用者:' + username, pagenames)) pagenames.push('利用者:' + username);
}
}
// Add the pages to watchlist
new mw.Api().get({
'action': 'query',
'meta': 'tokens',
'type': 'watch'
}).then(function(res){
const token = res.query.tokens.watchtoken;
if (!token) resolve(mw.log.error('ウォッチトークンの取得に失敗しました'));
new mw.Api().post({
'action': 'watch',
'titles': pagenames.join('|'),
'token': token,
'formatversion': 2
}).then(function(res) {
resolve(console.log('以下のページをウォッチリストに追加しました:\n' + pagenames.join(', ')));
}).catch(function(code, err) {
resolve(mw.log.error('ウォッチリストへの追加に失敗しました:\n' + err.error.info));
});
});
});
}
// Function to show message when edit attempt is done
function queryFailed(ep) {
const msg =
toggleLoadingSpinner('remove') +
'<p style="color: MediumVioletRed">取得に失敗しました</p>' +
manualEdit(ep);
$('.anr-editing').append(msg);
editDone(ep, true);
}
// Function to get the section number from the section title
async function getSectionNumber(ep) {
const parse = await parsePage(ep.pageToEdit, 'sections');
var resSect, sectionNum;
if (parse && (resSect = parse.sections)) { // If the section list is successfully retrieved
// Get the titles of all sections and their section numbers
for (let i = 0; i < resSect.length; i++) {
if (resSect[i].line === ep.sectionToEdit) {
sectionNum = resSect[i].index;
break;
}
}
// Return a section number if the section is found, undefined if not
if (!sectionNum) {
sectionNotFound(ep);
return;
} else {
const msg =
'<p style="color: MediumSeaGreen">取得に成功しました</p>' +
`<p>最新版のテキストを取得しています${toggleLoadingSpinner('move')}</p>`;
$('.anr-editing').append(msg);
return sectionNum;
}
} else { // If the section list retrieval fails
queryFailed(ep);
return;
}
}
// Function to get the text to replace with the current text in the section
async function getReportText(ep, sectionNum) {
const parse = await parsePage(ep.pageToEdit, 'wikitext', sectionNum);
if (parse) {
// Update message
var msg =
'<p style="color: MediumSeaGreen">取得に成功しました</p>' +
`<p>報告を試みています${toggleLoadingSpinner('move')}</p>`;
$('.anr-editing').append(msg);
// Get the whole text to append
const wikitext = parse.wikitext;
var reportText;
if (ep.reportToANS) { // If the target is WP:AN/S
// Add div if the target section is 'その他' but lacks div for the current date
const miscHeader = `{{bgcolor|#eee|{{Visible anchor|他${today()}}}|div}}`;
if (ep.sectionToEdit === 'その他' && wikitext.indexOf(miscHeader) === -1) ep.reportText = '; ' + miscHeader + '\n\n' + ep.reportText;
// Define comment-outs
const tail = '\n\n<!-- ◆以下は消さないで下さい。新規依頼はこの上へ -->}}';
let head;
if (ep.pageToEdit === ISECHIKA) {
head = '<!-- ◆ここから下に新しい報告を記入して下さい -->\n<!-- 不適切な利用者名を載せないでください。何時何分に作成された等の依頼方法でお願いします。 -->\n\n';
} else {
head = '<!-- ◆ここから下に新しい報告を記入して下さい -->\n\n';
}
// Extract the '報告' parameter of SockInfo and get a new wikitext for the section including the report to be submitted
let sockInfo = findTemplates(wikitext, 'sockinfo'); // Array
let sockInfoRep, matched;
if (sockInfo.length === 1) { // One section on WP:AN/S should have one SockInfo
sockInfo = sockInfo[0];
if (matched = sockInfo.match(/\|\s*報告[^\S\r\n]*=\s*/g)) { // If the template has a '報告' parameter
if (matched.length === 1) {
sockInfoRep = sockInfo.split(matched[0])[1]; // The content of the '報告' parameter
if (matched = sockInfoRep.match(/(\(UTC\))+([^\S\r\n]*<\/*\w+[^\S\r\n]*>)*/g)) { // If there're other reports
const lastUtc = matched[matched.length - 1]; // The last occurrence of 'UTC' (the string itself)
const splitIndex = wikitext.lastIndexOf(lastUtc) + lastUtc.length; // The repot should be inserted at the nth character
reportText = wikitext.substring(0, splitIndex).trimANR() + '\n\n' + ep.reportText + '\n\n' + wikitext.substring(splitIndex).trimANR() + '\n\n';
} else { // If there're no other reports
reportText = wikitext.replace(sockInfoRep, head + ep.reportText + tail);
}
}
}
}
if (reportText) {
return reportText;
} else {
// Show error and quit the procedure
msg =
toggleLoadingSpinner('remove') +
'<p style="color: MediumVioletRed">報告に失敗しました</p><br>' +
`<p>{{SockInfo}}がない、または複数個あるか、テンプレートの「報告」引数の値が不正です</p>` +
manualEdit(ep);
$('.anr-editing').append(msg);
editDone(ep, true);
return;
}
} else { // If the target is WP:AN/I or WP:AN/3RR
reportText = wikitext.trimANR() + '\n\n' + ep.reportText;
return reportText;
}
} else { // If wikitext retrieval fails
queryFailed(ep);
return;
}
}
// Function to edit the page
function edit(ep, sectionNum, reportText, baseTS, curTS) {
new mw.Api().post({
'action': 'edit',
'title': ep.pageToEdit,
'section': sectionNum,
'text': reportText,
'summary': ep.editSummary,
'basetimestamp': baseTS,
'starttimestamp': curTS,
'token': debugMode.causeIntentionalError ? '': mw.user.tokens.get('csrfToken'),
'format': 'json'
}).then(function(res) {
toggleLoadingSpinner('remove');
$('.anr-editing').append(`<p style="color: MediumSeaGreen">報告が完了しました</p>`);
editDone(ep, false);
}).catch(function(code, err) {
var msg;
if (err && err.error) {
// Show the details of the error
msg =
toggleLoadingSpinner('remove') +
'<p style="color: MediumVioletRed">報告に失敗しました</p><br>' +
'<p>詳細:</p>' +
`<p>${err.error.info}</p>` +
manualEdit(ep);
$('.anr-editing').append(msg);
editDone(ep, true);
} else { // If unknown error occurred
msg =
toggleLoadingSpinner('remove') +
'<p style="color: MediumVioletRed">不明なエラーが発生しました</p>' +
manualEdit(ep);
$('.anr-editing').append(msg);
editDone(ep, true);
}
});
}
/**
* 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 tempInnerContent = text.split('{{'); // Note: tempInnerContent[0] is always an empty string or a string that has nothing to do with templates
const templates = []; // The array of extracted templates to return
// Extract templates from the text
if (tempInnerContent.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
const nest = []; // Stores the element number of tempInnerContent if the element involves nested templates
for (let i = 1; i < tempInnerContent.length; i++) { // Loop through all elements in tempInnerContent (except tempInnerContent[0])
let tempTailCnt = (tempInnerContent[i].match(/\}\}/g) || []).length; // The number of '}}' in the split segment
let temp = ''; // Temporary escape hatch for templates
if (tempTailCnt === 0) { // The split segment not having any '}}' means that it nests another template
nest.push(i); // Push the element number into the array
} else if (tempTailCnt === 1) { // The split segment itself is the whole inner content of one template
temp = '{{' + tempInnerContent[i].split('}}')[0] + '}}';
if (!isInArray(temp, templates)) templates.push(temp);
} else if (tempTailCnt > 1) { // The split segment is part of more than one template (e.g. TL2|...}}...}} )
for (let j = 0; j < tempTailCnt; j++) { // Loop through all the nests
if (j === 0) { // The innermost template
temp = '{{' + tempInnerContent[i].split('}}')[j] + '}}'; // Same as when tempTailCnt === 1
if (!isInArray(temp, templates)) templates.push(temp);
} else { // Nesting templates
const elNum = nest[nest.length -1]; // The start of the nesting template
nest.pop();
const nestedTempInnerContent = tempInnerContent[i].split('}}');
temp = '{{' + tempInnerContent.slice(elNum, i).join('{{') + '{{' + nestedTempInnerContent.slice(0, j + 1).join('}}') + '}}';
if (!isInArray(temp, templates)) templates.push(temp);
}
}
}
}
// Check if the optional parameter is specified
if (templateName && templates.length !== 0) {
const templateRegExp = new RegExp(templateName, 'i');
for (let i = templates.length -1; i >= 0; i--) {
// Remove the template from the array if it's not an instance of the specified template
if (templates[i].split('|')[0].search(templateRegExp) === -1) templates.splice(i, 1);
}
}
return templates;
}
}
// Function to generate edit summary automatically
function genEditSummary() {
const links = [];
$('#anr-user-div :text').each(function() { // Loop through all inputs
const inputID = '#' + $(this).attr('id');
const type = $(inputID.replace('input', 'select')).children('option').filter(':selected').text(); // UserAN type specified in the dropdown
const reportee = $(this).val().trimANR(); // Username
let link;
if (reportee !== '') { // Skip if the input value is a null string
switch(type) { // Get appropriate links depending on the UserAN type
case 'UNL':
case 'User2':
case 'IP2':
link = `[[特別:投稿記録/${reportee}|${reportee}]]`;
break;
case 'logid':
link = `[[特別:転送/logid/${reportee}|Logid/${reportee}]]`;
break;
case 'diff':
link = `[[特別:差分/${reportee}|差分/${reportee}]]の投稿者`;
break;
default:
link = reportee;
}
if (!isInArray(link, links)) links.push(link); // Push the link into the array
}
});
// Get edit summary
var summary = '';
switch(true) {
case links.length === 0:
break;
case links.length === 1:
summary = '+' + links[0] + ' - ';
break;
case links.length > 1 && 5 > links.length:
summary = '+' + links.join(', ') + ' - ';
break;
default:
summary = '+' + links.length + ' - ';
}
return summary;
}
/**
* Function to get the current date and the section name on WP:AN/I to which to report users
* @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);
}
var 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 check if a user exists locally
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)
var updateTypeDropdownTimeout;
function updateTypeDropdown(inputID) {
const tarVal = $(inputID).val().trimANR(); // The value typed into the input
const selectID = inputID.replace('input', 'select'); // #anr-userX-select
const checkboxDivID = inputID.replace('input', 'checkbox-div'); // #anr-userX-checkbox-div
const checkboxID = inputID.replace('input', 'checkbox'); // #anr-userX-checkbox
const idlinkDivID = inputID.replace('input', 'idlink-div'); // #anr-userX-idlink-div
clearTimeout(updateTypeDropdownTimeout); // Run the async function only once when there's been no input change for 0.35 seconds
updateTypeDropdownTimeout = setTimeout(async function(){
if (tarVal === '') { // if the field is blanked
$(selectID).prop('disabled', true).children('.anr-opt-none').prop('selected', true); // Disable dropdown and select 'none'
$(checkboxDivID).css('display', 'none'); // Hide 'hide username' checkbox
$(checkboxID).prop('checked', false); // Uncheck the checkbox
$(idlinkDivID).css('display', 'none'); // Hide logid/diff link
toggleBlockStatusLink(inputID, true, false);
} else { // if the field is filled
if (mw.util.isIPAddress(tarVal, true)) { // if IP
$(selectID).prop('disabled', false); // enable dropdown (repeated to prevent a strange lag)
$(selectID).children('.anr-opt-UNL').prop('hidden', true);
$(selectID).children('.anr-opt-User2').prop('hidden', true);
$(selectID).children('.anr-opt-IP2').prop({'hidden': false, 'selected': true});
$(selectID).children('.anr-opt-logid').prop('hidden', true);
$(selectID).children('.anr-opt-diff').prop('hidden', true);
$(selectID).children('.anr-opt-none').prop('hidden', false);
$(checkboxDivID).css('display', 'none'); // hide 'hide username' checkbox
$(checkboxID).prop('checked', false); // uncheck the checkbox
$(idlinkDivID).css('display', 'none');
toggleBlockStatusLink(inputID, false, false);
} else if (await userExists(tarVal)) { // if user
$(selectID).prop('disabled', false); // enable dropdown (repeated to prevent a strange lag)
$(selectID).children('.anr-opt-UNL').prop({'hidden': false, 'selected': true});
$(selectID).children('.anr-opt-User2').prop('hidden', false);
$(selectID).children('.anr-opt-IP2').prop('hidden', true);
$(selectID).children('.anr-opt-logid').prop('hidden', true);
$(selectID).children('.anr-opt-diff').prop('hidden', true);
$(selectID).children('.anr-opt-none').prop('hidden', false);
$(checkboxDivID).css('display', 'block'); // show 'hide username' checkbox
$(checkboxID).prop('checked', false); // uncheck the checkbox
$(idlinkDivID).css('display', 'none');
toggleBlockStatusLink(inputID, false, false);
} else { // if something else (like random numbers or strings)
$(selectID).prop('disabled', false); // enable dropdown (repeated to prevent a strange lag)
$(selectID).children('.anr-opt-UNL').prop('hidden', true);
$(selectID).children('.anr-opt-User2').prop('hidden', true);
$(selectID).children('.anr-opt-IP2').prop('hidden', true);
$(selectID).children('.anr-opt-logid').prop('hidden', false);
$(selectID).children('.anr-opt-diff').prop('hidden', false);
$(selectID).children('.anr-opt-none').prop({'hidden': false, 'selected': true});
$(checkboxDivID).css('display', 'none'); // hide 'hide username' checkbox
$(idlinkDivID).css('display', 'none');
toggleBlockStatusLink(inputID, true, false);
}
}
}, 350);
}
/**
* Function to show/hide 'This user has blocks' links
* @param {string} inputID the ID of the input
* @param {boolean} forceHide if true, just hide the block status link (for when t=diff and t=none; block check impossible)
* @param {boolean} convertLogid if true, try to convert a logid to a username (for when t=logid; username is needed for block check)
*/
function toggleBlockStatusLink(inputID, forceHide, convertLogid) {
centerDialog();
const inputVal = $(inputID).val().trimANR(); // The value in the input
const $bsLinkDiv = $(inputID.replace('input', 'blockstatus-div')); // #anr-userX-blockstatus-div
const $bsLink = $(inputID.replace('input', 'blockstatus')); // #anr-userX-blockstatus
var username, logid;
if (forceHide && convertLogid) { // t=logid
// Check if the logid can be converted to a username and if it can, proceed to block check, and if it can't, just hide the block status link
if (!(username = getKeyByValue(Logids, logid = inputVal))) {
$bsLinkDiv.css('display', 'none');
centerDialog();
return;
}
} else if (forceHide) { // t=diff or t=none
$bsLinkDiv.css('display', 'none'); // Hide the link div
centerDialog();
return;
} else { // t=UNL, t=User2, or t=IP2
username = inputVal;
}
// Check the block status of the user, and if blocked, update the bsLink, or else, hide the link
getBlocked([username]).then(function(blocked) {
if (blocked.length !== 0) { // If the user is blocked
$bsLink.attr('href', mw.util.getUrl('特別:投稿記録/' + username)); // Update the link
$bsLinkDiv.css('display', 'block'); // Show the link div
} else {
$bsLinkDiv.css('display', 'none'); // Hide the link div
}
centerDialog();
});
}
// Function to get an array of blocked users & IPs from an array of random users & IPs
async function getBlocked(namesArr) {
const users = [], ips = [];
var blocked = [];
// Sort names to users and IPs
for (let 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
* @param {Array} usersArr
* @returns {Promise<Array>}
*/
function getBlockedUsers(usersArr) { // Note: this function needs to be modified if there're cases in which the reportees are more than 50
return new Promise(function(resolve) {
if (usersArr.length === 0) resolve([]);
new mw.Api().post({
'action': 'query',
'list': 'blocks',
'bklimit': usersArr.length,
'bkusers': usersArr.join('|'),
'bkprop': 'user',
'formatversion': 2
}).then(function(res){
const resBlk = res.query.blocks, blockedUsers = [];
if (resBlk.length === 0) resolve(blockedUsers); // None of the users is blocked; return an empty array
for (let i = 0; i < resBlk.length; i++) {
blockedUsers.push(resBlk[i].user); // Push blocked users into the array
}
resolve(blockedUsers); // Return e.g. [user1, user2...]
});
});
}
/**
* Function to get an array of locked users from an array of random users
* @param {Array} usersArr
* @returns {Promise<Array>}
*/
async function getGloballyLockedUsers(usersArr) {
if (usersArr.length === 0) return [];
const lockedUsers = [];
for (let i = 0; i < usersArr.length; i++) {
const locked = await userIsLocked(usersArr[i]);
if (locked) lockedUsers.push(usersArr[i]);
}
return lockedUsers;
}
/**
* Function to check if a user is globally locked
* @param {string} user
* @returns {Promise<boolean>}
*/
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) {
const resLck = res.query.globalallusers;
if (resLck.length === 0) resolve(false); // The global user doesn't exist
resolve(resLck[0].locked !== undefined); // resLck[0].locked === '' if locked, otherwise undefined
});
});
}
/**
* Function to get an array of locally blocked IPs from an array of random IPs
* @param {Array} ipsArr
* @returns {Promise<Array>}
*/
async function getBlockedIps(ipsArr) {
if (ipsArr.length === 0) return [];
const blockedIps = [];
for (let i = 0; i < ipsArr.length; i++) {
const blocked = await ipIsBlocked(ipsArr[i]);
if (blocked) blockedIps.push(ipsArr[i]);
}
return blockedIps;
}
/**
* Function to check if a given IP is locally blocked
* @param {String} ip
* @returns {Promise<boolean>}
*/
function ipIsBlocked(ip) {
return new Promise(function(resolve) {
new mw.Api().get({
'action': 'query',
'list': 'blocks',
'bklimit': 1,
'bkip': ip,
'bkprop': 'user',
'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 {Promise<Array>}
*/
async function getGloballyBlockedIps(ipsArr) {
if (ipsArr.length === 0) return [];
const blockedIps = [];
for (let i = 0; i < ipsArr.length; i++) {
const blocked = await ipIsGloballyBlocked(ipsArr[i]);
if (blocked) blockedIps.push(ipsArr[i]);
}
return blockedIps;
}
/**
* Function to check if a given IP is globally blocked
* @param {String} ip
* @returns {Promise<boolean>}
*/
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
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) resolve(); // No logevent exists if the API returns an empty array
resolve(res.query.logevents[0].logid);
});
});
}
// ******************** EVENT HANDLERS ********************
// Copy a VIP name when the selection is changed
$(document).off('change', '#anr-viplist-select').on('change', '#anr-viplist-select', function() {
const vipSelectVal = $('#anr-viplist-select').find('option').filter(':selected').text().trim();
copyToClipboard('[[WP:VIP#' + vipSelectVal + ']]');
});
// Reset dialog when closed
$(document)
.off('dialogclose', '#anr-modal-dialog, #anr-preview-dialog, #anr-drpreview-dialog')
.on('dialogclose', '#anr-modal-dialog, #anr-preview-dialog, #anr-drpreview-dialog',
function() {
$(this).remove();
userCnt = 1;
});
// 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).innerWidth()});
$('#anr-target-pagelink-div').css('display', 'block');
$('#anr-target-pagelink').attr('href', mw.util.getUrl(ANI));
break;
case ANS:
$('#anr-section-i-div').css('display', 'none');
$('#anr-section-s-div').css('display', 'block');
$('#anr-section-s-select').select2({'width': $(this).innerWidth()});
$('#anr-target-pagelink-div').css('display', 'block');
$('#anr-target-pagelink').attr('href', mw.util.getUrl(ANS));
break;
case AN3RR:
$('#anr-section-i-div').css('display', 'none');
$('#anr-section-s-div').css('display', 'none');
$('#anr-target-pagelink-div').css('display', 'block');
$('#anr-target-pagelink').attr('href', mw.util.getUrl(AN3RR));
break;
}
centerDialog();
});
// Add section name to the '報告先' link when section is specified
$(document)
.off('change', '#anr-section-i-select, #anr-section-s-select')
.on('change', '#anr-section-i-select, #anr-section-s-select',
function(){
var tarSection = '', tarPage = '';
if ($(this).attr('id') === 'anr-section-i-select') {
tarPage = ANI;
} else if ($(this).attr('id') === 'anr-section-s-select') {
tarPage = ANS;
}
if ($(this).find('option').filter(':selected').text() !== '選択してください') {
tarSection = '#' + $(this).find('option').filter(':selected').text();
$('#anr-target-pagelink').attr('href', mw.util.getUrl(tarPage + tarSection));
}
});
// When the selection is changed in the type dropdown
$(document).off('change','#anr-user-div select').on('change','#anr-user-div select', function(e){
const selectID = '#' + e.target.id; // #anr-userX-select
const valSelected = $(selectID).children('option').filter(':selected').text(); // Selected type
const inputID = selectID.replace('select', 'input'); // #anr-userX-input
const valInput = $(inputID).val().trimANR(); // The input value
const checkboxDivID = selectID.replace('select', 'checkbox-div'); // #anr-userX-checkbox-div
const checkboxID = selectID.replace('select', 'checkbox'); // #anr-userX-checkbox
const idlinkDivID = selectID.replace('select', 'idlink-div'); // #anr-userX-idlink-div
const idlinkID = selectID.replace('select', 'idlink'); // #anr-userX-idlink
switch(valSelected) {
case 'UNL':
case 'User2':
$(checkboxDivID).css('display', 'block');
toggleBlockStatusLink(inputID, false, false);
break;
case 'IP2':
toggleBlockStatusLink(inputID, false, false);
break;
case 'logid':
$(checkboxDivID).css('display', 'block');
$(checkboxID).prop('checked', true);
$(idlinkID).attr('href', mw.util.getUrl('Special:redirect/logid/' + valInput)).text('特別:転送/logid/' + valInput);
toggleBlockStatusLink(inputID, true, true);
break;
case 'diff':
$(checkboxDivID).css('display', 'none');
$(idlinkDivID).css('display', 'block');
$(idlinkID).attr('href', mw.util.getUrl('Special:diff/' + valInput)).text('特別:差分/' + valInput);
toggleBlockStatusLink(inputID, true, false);
break;
default:
$(checkboxDivID).css('display', 'none');
$(idlinkDivID).css('display', 'none');
toggleBlockStatusLink(inputID, true, false);
}
});
// When username is typed in, change dropdown options for UserAN types
$(document).off('input', '#anr-user-div :text').on('input', '#anr-user-div :text', function(e){
const inputID = '#' + e.target.id; // #anr-userX-input
updateTypeDropdown(inputID);
});
// When 'hide username' is clicked, get logid, change dropdown options, show href and so on
$(document).off('change', '#anr-user-div :checkbox').on('change', '#anr-user-div :checkbox', function(e){
const checkboxID = '#' + e.target.id; // #anr-userX-checkbox
const selectID = checkboxID.replace('checkbox', 'select'); // #anr-userX-select
const inputID = checkboxID.replace('checkbox', 'input'); // #anr-userX-input
const inputVal = $(inputID).val().trimANR();
const idlinkID = checkboxID.replace('checkbox', 'idlink'); // #anr-userX-idlink
const idlinkDivID = checkboxID.replace('checkbox', 'idlink-div'); // #anr-userX-idlink-div
// Function to update type dropdown for logid
const updateTypeDropdownLogid = function(logid) {
$(selectID).children('.anr-opt-UNL').prop('hidden', true);
$(selectID).children('.anr-opt-User2').prop('hidden', true);
$(selectID).children('.anr-opt-IP2').prop('hidden', true);
$(selectID).children('.anr-opt-logid').prop('hidden', false).prop('selected', true);
$(selectID).children('.anr-opt-diff').prop('hidden', false);
$(selectID).children('.anr-opt-none').prop('hidden', false);
$(idlinkDivID).css('display', 'block');
$(idlinkID).attr('href', mw.util.getUrl('Special:redirect/logid/' + logid)).text('特別:転送/logid/' + logid);
toggleBlockStatusLink(inputID, true, true);
}
var logid, username;
if ($(checkboxID).is(':checked')) { // If the checkbox is checked (the input value is a username and this needs to be converted to a logid)
if (logid = Logids[username = inputVal]) { // If the object knows the logid for the user
$(inputID).val(logid); // Replace the username with the logid in the object
updateTypeDropdownLogid(logid);
} else {
(async function() {
logid = await getLogid(username = inputVal); // Get logid from the API
if (!logid) { // If undefined is returned, reject the checking of the checkbox
alert('エラー\n\n取得可能なlogidが存在しません。Logidを手動で入力するか、type=diff または none を使用してください');
$(checkboxID).prop('checked', false);
} else { // If a valid logid is returned
$(inputID).val(logid); // Set the logid to the input
Logids[username] = logid; // Save the logid in the object
updateTypeDropdownLogid(logid);
}
})();
}
} else { // if the checkbox is unchecked (the input value is a logid and this needs to be converted to a username)
if (username = getKeyByValue(Logids, logid = inputVal)) { // Username converted from logid
$(inputID).val(username); // Replace the logid with the username in the object
$(selectID).children('.anr-opt-UNL').prop('hidden', false).prop('selected', true);
$(selectID).children('.anr-opt-User2').prop('hidden', false);
$(selectID).children('.anr-opt-IP2').prop('hidden', true);
$(selectID).children('.anr-opt-logid').prop('hidden', true);
$(selectID).children('.anr-opt-diff').prop('hidden', true);
$(selectID).children('.anr-opt-none').prop('hidden', false);
$(idlinkDivID).css('display', 'none');
toggleBlockStatusLink(inputID, false, false);
} else {
alert('エラー\n\nLogidにはアカウント作成記録以外のものも含まれるため、logidからユーザー名への変換機能は実装していません。' +
'テキストボックス下のリンク先からユーザー名を取得するか、手動入力してください。なお、ユーザー名からlogidへの変換が行われた' +
'場合のみ、その逆の変換が可能です');
$(checkboxID).prop('checked', true);
}
}
});
// When the 'add' button is hit, add another input layer
$(document).off('click', '#anr-addBtn').on('click', '#anr-addBtn', function(){
userCnt++;
$('#anr-btn-div').before(userDiv.replaceAllANR('1-', userCnt + '-'));
$(`#anr-user${userCnt}-div`).css('margin-top', '0.2em');
centerDialog();
});
// When buttons are moused on and off
$(document).off('mouseover mouseleave', '#anr-modal-dialog form button').on({
'mouseover': function(e) {e.target.style.borderColor = '#999999';},
'mouseleave': function(e) {e.target.style.borderColor = '#d3d3d3';}
}, '#anr-modal-dialog form button');
// When the summary checkbox is (un)checked
$(document).off('change', '#anr-summary-checkbox').on('change', '#anr-summary-checkbox', function(){
const $textarea = $('#anr-summary-text');
if ($(this).is(':checked')) { // Box is checked
$textarea.css('display','inline-block').val(genEditSummary()); // Show textarea with an edit summary
} else { // Box is unchecked
$textarea.css('display','none').val('');
}
centerDialog();
});
// ******************** AUXILIARY FUNCTIONS ********************
/**
* 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 if a string contains a substring in an element of an array
* @param {string} str
* @param {Array} arr
* @returns {*} the first element matched in the array (if there's no match, returns undefined)
*/
function stringContainsElementInArray(str, arr) {
for (let i = 0; i < arr.length; i++) {
if (str.indexOf(arr[i]) !== -1) {
return arr[i];
}
}
}
/**
* Function to get the key of a value in an object
* @param {Object} object
* @param {*} value
* @returns {*} key
*/
function getKeyByValue(object, value) {
for (let key in object) {
if (object[key] == value) return key;
}
}
// Function to copy a string to the clipboard
function copyToClipboard(str) {
const $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
}
// Function to get today's date
function today() {
const d = new Date();
return d.getMonth() + 1 + '月' + d.getDate() + '日';
}
// Function to get the last day of the month
function lastDay(y, m){
return new Date(y, m + 1, 0).getDate();
}
/**
* String method to get rid of the U+200E space, in addition to the function of $.trim()
* @returns {string}
*/
String.prototype.trimANR = 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.replaceAllANR = function() {
if (arguments.length %2 !== 0) {
return new Error('SyntaxError: replaceAllANR takes an even number of arguments.');
} else {
let replaced = '';
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;
}
};
String.prototype.escapeRegExpANR = function() { // Just a note: ^$.*+?()[]{}|
return this.replaceAllANR('(', '\\(', ')', '\\)', '.', '\\.');
}
})();
//</nowiki>