「利用者:Dragoniez/scripts/RFD Helper.js」の版間の差分
表示
削除された内容 追加された内容
m v1.3.2: cl |
m typo |
||
(同じ利用者による、間の1版が非表示) | |||
2行目: | 2行目: | ||
* Name: RFD Helper * |
* Name: RFD Helper * |
||
* Author: Dragoniez * |
* Author: Dragoniez * |
||
* Version: 1. |
* Version: 1.4.0 * |
||
****************************/ |
****************************/ |
||
//<nowiki> |
//<nowiki> |
||
// ******************** CONFIGS ******************** |
// **************************************** CONFIGS **************************************** |
||
// var {dragoLib} = require('../dragoLib/dragoLib.js'); |
// var {dragoLib} = require('../dragoLib/dragoLib.js'); |
||
46行目: | 46行目: | ||
} |
} |
||
// ******************** SCRIPT BODY ******************** |
// **************************************** SCRIPT BODY **************************************** |
||
(function() { // Create a function scope |
(function() { // Create a function scope |
||
// Run the script only if the user is autoconfirmed and the page is not an edit page |
|||
// ************************* VARIABLES ************************* |
|||
if (mw.config.get('wgUserGroups').indexOf('autoconfirmed') === -1 || ['edit', 'submit'].indexOf(mw.config.get('wgAction')) !== -1) return; |
|||
// ********************************************* VARIABLES ********************************************* |
|||
// DebugMode |
// DebugMode |
||
var debuggingMode = { |
|||
library: false, |
library: false, |
||
portletlinkText: false, |
portletlinkText: false, |
||
targetPage: false, |
targetPage: false, |
||
scriptAd: false, |
scriptAd: false, |
||
causeIntentionalError: false |
causeIntentionalError: false, |
||
fillFields: false // Fill the redirect source and reason fields |
|||
}; |
}; |
||
var library = debuggingMode.library ? |
|||
'http://127.0.0.1:5500/dragoLib/dragoLib.js' : |
'http://127.0.0.1:5500/dragoLib/dragoLib.js' : |
||
'//ja-two.iwiki.icu/w/index.php?title=User:Dragoniez/scripts/dragoLib.js&action=raw&ctype=text/javascript'; |
'//ja-two.iwiki.icu/w/index.php?title=User:Dragoniez/scripts/dragoLib.js&action=raw&ctype=text/javascript'; |
||
var portletlinkText = debuggingMode.portletlinkText ? 'リダイレクトの削除依頼β' : 'リダイレクトの削除依頼'; |
|||
var RFDR = debuggingMode.targetPage ? '利用者:Dragoniez/test3' : 'Wikipedia:リダイレクトの削除依頼/受付'; |
|||
var scriptAd = ' ([[User:Dragoniez/scripts/RFD Helper|' + (debuggingMode.scriptAd ? 'RFD Helper Dev]])' : 'RFD Helper]])'); |
|||
// Buttons of the dialog |
|||
// Others |
|||
var btns = [{ |
|||
const REDIRS = {}; // {source: goal, source2: goal2}... |
|||
const btns = [{ // Buttons of the dialog |
|||
text: '依頼', |
text: '依頼', |
||
click: submitRequest |
click: submitRequest |
||
82行目: | 85行目: | ||
}]; |
}]; |
||
// ************************* DOM READY FUNCTION ************************* |
// ********************************************* DOM READY FUNCTION ********************************************* |
||
$.when( |
$.when( |
||
$.getScript(library), |
$.getScript(library), |
||
mw.loader.using('jquery.ui'), |
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.ui']), |
||
$.ready |
$.ready |
||
).then(function() { |
).then(function() { |
||
$('head').append( // <style> for the dialog |
|||
// Add <style> |
|||
$('head').append( |
|||
'<style>' + |
'<style>' + |
||
'.rfdh-needmargin {' + |
|||
'margin: 0.5em 0;' + |
|||
'}' + |
|||
'.rfdh-dialog-main textarea {' + |
|||
'box-sizing: border-box;' + |
|||
'width : 100%;' + |
|||
'}' + |
|||
'.rfdh-dialog-main input[type="checkbox"],' + |
|||
'.rfdh-dialog-preview input[type="checkbox"] {' + |
|||
` background: ${rfdhConfig.headerColor};` + |
|||
'margin-right: 0.5em;' + |
|||
'}' + |
|||
'.rfdh-preview-notarget {' + |
|||
'background: ' + rfdhConfig.headerColor + ';' + |
|||
'}' + |
|||
'</style>' |
'</style>' |
||
); |
); |
||
if (dragoLib.inGroup('autoconfirmed') && mw.config.get('wgAction') !== 'edit') { |
|||
// Create a portletlink for the main dialog |
|||
$(mw.util.addPortletLink(rfdhConfig.portletlinkPosition, '#', portletlinkText, 'ca-rfdh', 'リダイレクトの削除依頼を提出', null, '#ca-move')).click(openDialog); |
|||
$(mw.util.addPortletLink(rfdhConfig.portletlinkPosition, '#', portletlinkText, 'ca-rfdh', 'リダイレクトの削除依頼を提出', null, '#ca-move')).click(openDialog); |
|||
} |
|||
}); |
}); |
||
// ************************* MAIN FUNCTIONS ************************* |
// ********************************************* MAIN FUNCTIONS ********************************************* |
||
/** Create and open the main dialog when the portletlink is hit */ |
|||
function openDialog(e) { |
function openDialog(e) { |
||
e.preventDefault(); |
e.preventDefault(); |
||
var votes = |
|||
'<option value="">なし</option>' + |
'<option value="">なし</option>' + |
||
'<option value="{{AFD|削除}}">削除</option>' + |
'<option value="{{AFD|削除}}">削除</option>' + |
||
126行目: | 137行目: | ||
'<option value="{{AFD|履歴統合}}">履歴統合</option>'; |
'<option value="{{AFD|履歴統合}}">履歴統合</option>'; |
||
var dialogHtml = |
|||
'<div id="rfdh-dialog" title="RFD Helper" style="max-height: 80vh; min-width: 515px;">' + |
'<div id="rfdh-dialog" title="RFD Helper" style="max-height: 80vh; min-width: 515px;">' + |
||
'<div id="rfdh-dialog-header">' + |
|||
'<h2>リダイレクトの削除依頼</h2>' + |
|||
'</div>' + |
|||
'<div id="rfdh-dialog-body">' + |
|||
'<form>' + |
|||
'<div id="rfdh-redirectlist-div">' + |
|||
'<label for="rfdh-redirectlist-input">リダイレクト元 (ページごとに改行)</label>' + |
|||
'<textarea id="rfdh-redirectlist-input" rows="8"></textarea>' + |
|||
'<input id="rfdh-redirectlist-cleanup" class="rfdh-needmargin" type="button" value="整形" style="margin-right: 0.5em;">' + |
|||
'<span>(余分な改行および重複項目を除去)</span>' + |
|||
'</div>' + |
|||
'<div id="rfdh-vote-div" class="rfdh-needmargin">' + |
|||
'<label for="rfdh-vote-select">依頼者票 <span style="font-size: smaller;">(複数選択する場合のみ中間表現必須)</span></label><br>' + |
|||
'<select id="rfdh-vote-select" style="margin-right: 0.3em;">' + |
|||
votes + |
votes + |
||
'</select>' + |
|||
'<select id="rfdh-votedelimiter-select" style="margin-right: 0.3em;">' + |
|||
'<option></option>' + |
|||
'<option value="および">および</option>' + |
|||
'<option value="または">または</option>' + |
|||
'</select>' + |
|||
'<select id="rfdh-vote2-select">' + |
|||
votes + |
votes + |
||
'</select>' + |
|||
'</div>' + |
|||
'<div id="rfdh-reason-div" class="rfdh-needmargin">' + |
|||
'<label for="rfdh-reason-input">依頼文 <span style="font-size: smaller;">(署名不要)</span></label>' + |
|||
'<textarea id="rfdh-reason-input" rows="3"></textarea>' + |
|||
'</div>' + |
|||
'<div id="rfdh-summary-div" class="rfdh-needmargin">' + |
|||
'<input id="rfdh-summary-checkbox" type="checkbox">' + |
|||
'<label for="rfdh-summary-checkbox">要約を指定</label>' + |
|||
'<textarea id="rfdh-summary-input" rows="3" style="display: none;"></textarea>' + |
|||
'</div>' + |
|||
'</form>' + |
|||
'</div>' + |
|||
'</div>'; |
'</div>'; |
||
179行目: | 190行目: | ||
dragoLib.dialogCSS($('.rfdh-dialog-main'), rfdhConfig.headerColor, rfdhConfig.backgroundColor, rfdhConfig.fontSize); |
dragoLib.dialogCSS($('.rfdh-dialog-main'), rfdhConfig.headerColor, rfdhConfig.backgroundColor, rfdhConfig.fontSize); |
||
$('#rfdh-vote-select').children('option').eq(1).prop('selected', true); |
$('#rfdh-vote-select').children('option').eq(1).prop('selected', true); |
||
if (debuggingMode.fillFields) { |
|||
$('#rfdh-redirectlist-input').val(['利用者:Dragoniez', 'User:Dragoniez/AN Reporter'].join('\n')); |
|||
$('#rfdh-reason-input').val('Test.'); |
|||
} |
|||
}, |
}, |
||
buttons: btns |
buttons: btns |
||
185行目: | 200行目: | ||
} |
} |
||
/** |
|||
// Check whether the necessary fileds are filleld |
|||
* Check whether the necessary fileds are filled, and get neccesary information to submit an RFD |
|||
function editPrep1() { |
|||
* @returns {{redirects: Array, reqText: string, summary: string}|undefined} |
|||
* [IMPORTANT] reqText doesn't contain RFD templates. The object needs to be updated by updateEp(). |
|||
*/ |
|||
function editPrep() { |
|||
// Error storage |
// Error storage |
||
var problems = { |
|||
redirects: '', |
redirects: '', |
||
conjunction: '', |
conjunction: '', |
||
196行目: | 215行目: | ||
// Redirect sources |
// Redirect sources |
||
var redirects = getRedirectSources(); |
|||
if (redirects.length === 0) problems.redirects = '・リダイレクト元'; |
if (redirects.length === 0) problems.redirects = '・リダイレクト元'; |
||
// Votes |
// Votes |
||
var $vote = $('#rfdh-vote-select'), |
|||
$delimiter = $('#rfdh-votedelimiter-select'), |
|||
$vote2 = $('#rfdh-vote2-select'), |
|||
vote = $vote.children('option').filter(':selected').val(), |
|||
delimiter = $delimiter.children('option').filter(':selected').val(), |
|||
vote2 = $vote2.children('option').filter(':selected').val(); |
|||
if (vote && vote2) { |
if (vote && vote2) { |
||
if (!delimiter) problems.conjunction = '・依頼者票の中間表現'; |
if (!delimiter) problems.conjunction = '・依頼者票の中間表現'; |
||
212行目: | 233行目: | ||
} |
} |
||
} else if (!vote && vote2) { |
} else if (!vote && vote2) { |
||
$vote.children( |
$vote.children('option[value=' + vote2 + ']').prop('selected', true); // Move vote2 to vote |
||
$delimiter.children('option').eq(0).prop('selected', true); // Blank delimiter |
$delimiter.children('option').eq(0).prop('selected', true); // Blank delimiter |
||
$vote2.children('option').eq(0).prop('selected', true); // Blank vote2 |
$vote2.children('option').eq(0).prop('selected', true); // Blank vote2 |
||
219行目: | 240行目: | ||
vote2 = ''; |
vote2 = ''; |
||
} |
} |
||
var votetext = vote + delimiter + vote2; |
|||
// Get text for the request (at this point RFD templates are not included) |
// Get text for the request (at this point RFD templates are not included) |
||
228行目: | 249行目: | ||
// Check the necessary fields |
// Check the necessary fields |
||
var msg = '以下の項目に入力の不備があります:', proceed = true; |
var msg = '以下の項目に入力の不備があります:', |
||
proceed = true; |
|||
for ( |
for (var key in problems) { |
||
if (problems[key]) { |
if (problems[key]) { |
||
proceed = false; |
proceed = false; |
||
235行目: | 257行目: | ||
} |
} |
||
} |
} |
||
if (!proceed) |
if (!proceed) return alert(msg); |
||
alert(msg); |
|||
return; |
|||
} |
|||
// Return values once (Go on to API query) |
// Return values once (Go on to API query) |
||
249行目: | 268行目: | ||
} |
} |
||
/** |
|||
// Add RFD templates to reqText |
|||
* Check and update the redirect list textarea, and return its content as an array without duplicates |
|||
async function editPred2(ep) { |
|||
* @returns {Array} [redirsrc1, redirsrc2...] |
|||
*/ |
|||
function getRedirectSources() { |
|||
var $input = $('#rfdh-redirectlist-input'); |
|||
var inputVal = dragoLib.trim($input.val()); |
|||
if (!inputVal) return []; |
|||
inputVal = inputVal.split('\n'); |
|||
inputVal = inputVal.filter(function(item, index) { |
|||
return item !== '' && inputVal.indexOf(item) === index; |
|||
}); |
|||
$input.val(inputVal.join('\n')); |
|||
return inputVal; |
|||
} |
|||
/** |
|||
// Anonymous function to get redirect targets and update REDIRS |
|||
* Generate a summary for the RFD |
|||
const getRredirectTargets = async function(redirects) { |
|||
* @param {string} sectiontitle |
|||
* @param {number|string} redirectsCnt |
|||
* @returns |
|||
*/ |
|||
function generateSummary(sectiontitle, redirectsCnt) { |
|||
var summary = dragoLib.trim($('#rfdh-summary-input').val()); |
|||
summary = summary ? summary : '+' + redirectsCnt; |
|||
summary = '/*' + sectiontitle + '*/' + summary + scriptAd; |
|||
return summary; |
|||
} |
|||
/** |
|||
const queryRedirects = function(redirectsArr) { |
|||
* Get redirect targets and add an array of objects to ep |
|||
return new Promise(function(resolve) { |
|||
* @param {{}} ep |
|||
new mw.Api().get({ |
|||
* @returns {jQuery.Promise<{reqRFD: Array<{template: string, source: string, goal: string|undefined}>}>} Added to ep as a property: |
|||
action: 'query', |
|||
* The passed ep object doesn't need to be substituted |
|||
titles: redirectsArr.join('|'), // Max 50 |
|||
*/ |
|||
redirects: 1, |
|||
function updateEp(ep) { |
|||
formatversion: 2 |
|||
var def = new $.Deferred(); |
|||
var resRdr, redirect; |
|||
var query = function(redirSrcArr) { |
|||
if (res && (resRdr = res.query)) { |
|||
var deferred = new $.Deferred(); |
|||
if (resRdr = resRdr.redirects) { |
|||
new mw.Api().get({ |
|||
for (let i = 0; i < resRdr.length; i++) { |
|||
action: 'query', |
|||
titles: redirSrcArr.join('|'), // Max 50 |
|||
REDIRS[redirect.from] = redirect.to; |
|||
redirects: 1, |
|||
formatversion: 2 |
|||
} else { // If the API doesn't return res.query.redirects, the queried titles are all non-redirects |
|||
}).then(function(res) { |
|||
for (let i = 0; i < redirectsArr.length; i++) { |
|||
var resRdr, resNom; |
|||
if (!res || !res.query || !(resRdr = res.query.redirects)) return deferred.resolve(); |
|||
REDIRS[redirect] = undefined; |
|||
if (resNom = res.query.normalized) { // Namespace prefixes are translated in the response (e.g. User: => 利用者:) |
|||
resNom.forEach(function(objN) { // Loop through all the translations |
|||
resRdr.some(function(objR) { |
|||
if (objN.to === objR.from) { |
|||
objR.from = objN.from; // Translate back to the inputted page name |
|||
return true; |
|||
} |
} |
||
} |
}); |
||
resolve(); |
|||
}).catch(function(code, err) { |
|||
console.log(err.error.info); |
|||
resolve(); |
|||
}); |
}); |
||
} |
} |
||
deferred.resolve(resRdr); |
|||
}).catch(function(code, err) { |
|||
deferred.resolve(console.log(err.error.info)); |
|||
const redirectsArr = JSON.parse(JSON.stringify(redirects)); // Prevent pass-by-reference (the variable will be spliced) |
|||
}); |
|||
return deferred.promise(); |
|||
while(redirectsArr.length !== 0) { |
|||
queries.push(queryRedirects(redirectsArr.slice(0, 50))); |
|||
redirectsArr.splice(0, 50); |
|||
} |
|||
return await Promise.all(queries); |
|||
}; |
}; |
||
var apilimit = dragoLib.apiHighLimit() ? 500 : 50; |
|||
// Return reqText and summary as an object |
|||
var deferreds = []; |
|||
const tlRfd = '* {{RFD|SOURCE|GOAL}}\n', tlRfdForRequest = []; |
|||
var |
var redirSrcArr = JSON.parse(JSON.stringify(ep.redirects)); |
||
while (redirSrcArr.length) { |
|||
deferreds.push(query(redirSrcArr.splice(0, apilimit))); |
|||
if (!REDIRS[redirects[i]]) { // If the object doesn't know the target of any redirect, it can be retrieved from the API |
|||
needQuery = true; |
|||
break; |
|||
} |
|||
} |
} |
||
$.when.apply($, deferreds).then(function() { |
|||
if (needQuery) { |
|||
needQuery = false; |
|||
await getRredirectTargets(redirects); |
|||
} |
|||
for (let i = 0; i < redirects.length; i++) { |
|||
if (!REDIRS[redirects[i]]) needQuery = true; |
|||
tlRfdForRequest.push(tlRfd.replace('SOURCE', redirects[i]).replace('GOAL', REDIRS[redirects[i]])); |
|||
} |
|||
reqText = tlRfdForRequest.join('') + reqText; |
|||
return { |
|||
reqText: reqText, |
|||
redirects: redirects, |
|||
summary: ep.summary, |
|||
needQuery: needQuery |
|||
}; |
|||
// Convert the response object to an array of objects |
|||
} |
|||
var args = arguments; |
|||
var res = Object.keys(args).filter(function(key) { return args[key]; }).map(function(key) { return args[key]; }); |
|||
res = res.concat.apply([], res); |
|||
// Create an array of objects that stores RFD templates to be added to reqText |
|||
function generateSummary(section, redirectsCnt) { |
|||
var RFD = '* {{RFD|SOURCE|GOAL}}\n'; |
|||
var summary = dragoLib.trim($('#rfdh-summary-input').val()); |
|||
var reqRFD = ep.redirects.reduce(function(acc, src) { |
|||
summary = summary ? summary : '+' + redirectsCnt; |
|||
var goal = res.filter(function(obj) { return obj.from === src; }); |
|||
summary = `/*${section}*/ ${summary}${scriptAd}`; |
|||
goal = goal[0] && goal[0].to ? goal[0].to : undefined; |
|||
return summary; |
|||
acc.push({ |
|||
template: dragoLib.replaceAll(RFD, 'SOURCE', src, 'GOAL', goal ? goal : 'undefined'), |
|||
source: src, |
|||
goal: goal |
|||
}); |
|||
return acc; |
|||
}, []); |
|||
// Update ep |
|||
ep.reqRFD = reqRFD; |
|||
def.resolve(ep); |
|||
}); |
|||
return def.promise(); |
|||
} |
} |
||
/** Preview the RFD to be sumitted */ |
|||
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 |
||
var ep = |
var ep = editPrep(); |
||
if (!ep) return; |
if (!ep) return; |
||
// Preview dialog contour |
// Preview dialog contour |
||
var previewDiv = |
|||
'<div id="rfdh-preview-dialog" title="RFD Helper Preview" style="max-height: 80vh;">' + |
'<div id="rfdh-preview-dialog" title="RFD Helper Preview" style="max-height: 80vh;">' + |
||
'<div id="rfdh-preview-header" style="padding: 0.5em;">' + |
|||
'<p id="rfdh-preview-loading">' + |
|||
'プレビューを読み込み中' + dragoLib.toggleLoadingSpinner('add') + |
|||
'</p>' + |
|||
'<div id="rfdh-preview-warning" style="display: none;">' + |
|||
'<p>' + |
|||
'転送先不明の項目は以下のいずれかに起因します:' + |
|||
'</p>' + |
|||
'<ol>' + |
|||
'<li>リダイレクト元のページ名が間違っている</li>' + |
|||
'<li>リダイレクト元として入力されたページがリダイレクトではない</li>' + |
|||
'<li>リダイレクト先の取得時に通信に失敗した</li>' + |
|||
'</ol>' + |
|||
'</div>' + |
|||
'</div>' + |
|||
'<div id="rfdh-preview-body" style="display: none; font-size: 1.1em; padding-top: 1em; border-top: 1px solid silver;">' + |
|||
'<div id="rfdh-preview-text" style="border: 1px solid silver; padding: 0.2em 0.5em; background: white;">' + |
|||
// previewHtml |
|||
'</div>' + |
|||
'<div id="rfdh-preview-summary" style="margin-top: 0.8em; border: 1px solid silver; padding: 0.2em 0.5em; background: white;">' + |
|||
// summaryHtml |
|||
'</div>' + |
|||
'<div id="rfdh-preview-checkbox-div" style="margin: 0.5em 0; display: none;">' + |
|||
'<input id="rfdh-preview-checkbox" type="checkbox">' + |
|||
'<label for="rfdh-preview-checkbox">プレビューを閉じる際に転送先不明の項目を自動的にリダイレクト元リストから除去</label>' + |
|||
'</div>' + |
|||
'</div>' + |
|||
'</div>'; |
'</div>'; |
||
374行目: | 414行目: | ||
width: $('#content').width() * 0.8, |
width: $('#content').width() * 0.8, |
||
modal: true, |
modal: true, |
||
open: |
open: function() { |
||
// Initialize the design of the dialog |
// Initialize the design of the dialog |
||
dragoLib.dialogCSS($('.rfdh-dialog-preview'), rfdhConfig.headerColor, rfdhConfig.backgroundColor, rfdhConfig.fontSize); |
dragoLib.dialogCSS($('.rfdh-dialog-preview'), rfdhConfig.headerColor, rfdhConfig.backgroundColor, rfdhConfig.fontSize); |
||
// |
// Preview |
||
ep |
updateEp(ep).then(function() { // Add 'reqRFD' to ep |
||
dragoLib.getParsedHtml(mergeReqText(ep), ep.summary).then(function(parsed) { // Convert reqText to HTML |
|||
// Convert text on the dialog to html |
|||
const parsed = await dragoLib.getParsedHtml(ep.reqText, ep.summary); |
|||
if (parsed) { |
if (parsed) { |
||
if (ep.reqRFD.some(function(obj) { return !obj.goal; })) { |
|||
$('#rfdh-preview-checkbox-div').css('display', 'block').children('input').prop('checked', true); |
|||
} |
|||
var previewHtml = parsed.htmltext; |
|||
var summaryHtml = parsed.htmlsummary.replace(/API/g, RFDR); |
|||
$('#rfdh-preview-text').append(previewHtml); |
|||
$('#rfdh-preview-text a').each(function() { // Loop through all <a> tags in preview |
|||
if ($(this).text() === 'undefined') $(this).addClass('rfdh-preview-notarget').text('転送先不明'); // Highlight if undefined |
|||
}); |
|||
$('#rfdh-preview-summary').append(summaryHtml); |
|||
$('.autocomment a').css('color', 'gray'); // Change color of section spec in summary |
|||
$('#rfdh-preview-dialog a').attr('target', '_blank'); // Open all links on a new tab |
|||
$('#rfdh-preview-body').css('display', 'block'); |
|||
$('#rfdh-preview-loading').remove(); |
|||
$('#rfdh-preview-warning').css('display', 'block'); |
|||
dragoLib.centerDialog('#rfdh-preview-dialog'); |
|||
} else { |
|||
$('#rfdh-preview-loading').text('プレビューの読み込みに失敗しました').css('color', 'MediumVioletRed'); |
|||
dragoLib.centerDialog('#rfdh-preview-dialog'); |
|||
setTimeout(function() { |
|||
$('#rfdh-preview-dialog').dialog('close'); |
|||
}, 5000); |
|||
} |
|||
if (ep.needQuery) $('#rfdh-preview-checkbox-div').css('display', 'block').children('input').prop('checked', true); |
|||
const previewHtml = parsed.htmltext; |
|||
const summaryHtml = parsed.htmlsummary.replace(/API/g, RFDR); |
|||
$('#rfdh-preview-text').append(previewHtml); |
|||
$('#rfdh-preview-text a').each(function() { // Loop through all <a> tags in preview |
|||
if ($(this).text() === 'undefined') $(this).addClass('rfdh-preview-notarget').text('転送先不明'); // Highlight if undefined |
|||
}); |
}); |
||
}); |
|||
$('.autocomment a').css('color', 'gray'); // Change color of section spec in summary |
|||
$('#rfdh-preview-dialog a').attr('target', '_blank'); // Open all links on a new tab |
|||
$('#rfdh-preview-body').css('display', 'block'); |
|||
$('#rfdh-preview-loading').remove(); |
|||
$('#rfdh-preview-warning').css('display', 'block'); |
|||
dragoLib.centerDialog('#rfdh-preview-dialog'); |
|||
} else { |
|||
$('#rfdh-preview-loading').text('プレビューの読み込みに失敗しました').css('color', 'MediumVioletRed'); |
|||
dragoLib.centerDialog('#rfdh-preview-dialog'); |
|||
setTimeout(function(){ |
|||
$('#rfdh-preview-dialog').dialog('close'); |
|||
}, 5000); |
|||
} |
|||
}, |
}, |
||
buttons: [{ |
buttons: [{ |
||
text: '閉じる', |
text: '閉じる', |
||
click: function(){ |
click: function() { |
||
if ($('#rfdh-preview-checkbox').is(':checked')) { |
if ($('#rfdh-preview-checkbox').is(':checked')) { |
||
var $input = $('#rfdh-redirectlist-input'); |
|||
var inputVal = dragoLib.trim($input.val()); |
var inputVal = dragoLib.trim($input.val()); |
||
inputVal = inputVal.split('\n'); |
inputVal = inputVal.split('\n'); |
||
inputVal = inputVal.filter(function(item) { |
inputVal = inputVal.filter(function(item) { |
||
return |
return ep.reqRFD.some(function(obj) { return item === obj.source && obj.goal; }); // Remove the undefined |
||
}); |
}); |
||
$input.val(inputVal.join('\n')); |
$input.val(inputVal.join('\n')); |
||
424行目: | 467行目: | ||
$(this).dialog('close'); |
$(this).dialog('close'); |
||
$('#rfdh-dialog') |
$('#rfdh-dialog') |
||
.dialog({ |
.dialog({buttons: btns}) |
||
.find('form').css('display', 'block'); |
.find('form').css('display', 'block'); |
||
$('#rfdh-editing').remove(); |
|||
} |
} |
||
}] |
}] |
||
432行目: | 476行目: | ||
} |
} |
||
/** |
|||
async function submitRequest() { |
|||
* Merge ep.reqRFD and ep.reqText into a single text |
|||
* @param {{}} ep The object must contain a 'reqRFD' and 'reqText' property |
|||
if (!dragoLib.inGroup('autoconfirmed')) { // Just in case |
|||
* @returns {string} |
|||
$('#rfdh-dialog, #ca-rfdh').remove(); |
|||
*/ |
|||
return; |
|||
function mergeReqText(ep) { |
|||
} |
|||
return ep.reqRFD.map(function(obj) { return obj.template; }).join('') + ep.reqText; |
|||
} |
|||
/** Submit RFD: 1st procedure (get redirect goals) */ |
|||
const $dialog = $('#rfdh-dialog'); |
|||
function submitRequest() { |
|||
// Check if the necessary fields are filled |
// Check if the necessary fields are filled |
||
var ep = |
var ep = editPrep(); |
||
if (!ep) return; |
if (!ep) return; |
||
// Hide the form and buttons and show a message |
// Hide the form and buttons and show a message |
||
var $dialog = $('#rfdh-dialog'); |
|||
$dialog |
$dialog |
||
.append( |
.append('<p id="rfdh-editing">準備中' + dragoLib.toggleLoadingSpinner('add') + '</p>') |
||
.dialog({buttons: []}) |
.dialog({buttons: []}) |
||
.find('form').css('display', 'none'); |
.find('form').css('display', 'none'); |
||
updateEp(ep).then(function() { |
|||
ep = await editPred2(ep); |
|||
if (ep.needQuery) { // If any redirect target failed to be fetched |
|||
if (ep.reqRFD.some(function(obj) { return !obj.goal; })) { // If any redirect target failed to be fetched |
|||
$('#rfdh-editting').prop('innerHTML', '<span style="color: MediumVioletRed;">転送先不明のリダイレクトを検出しました</span>'); |
|||
$('#rfdh-editing').prop('innerHTML', '<span style="color: MediumVioletRed;">転送先不明のリダイレクトを検出しました</span>'); |
|||
$dialog.dialog({ |
|||
$dialog.dialog({ |
|||
buttons: [{ |
|||
text: 'プレビュー', |
|||
click: preview |
|||
}, { |
|||
text: '続行', |
|||
click: function() { |
|||
$(this).dialog({buttons: []}); |
|||
submitRequest2(ep); |
|||
} |
|||
}, { |
|||
text: '戻る', |
|||
click: function() { |
|||
$(this). |
$(this).find('form').css('display', 'block'); |
||
$( |
$(this).dialog({buttons: btns}); |
||
$('#rfdh-editing').remove(); |
|||
} |
|||
}, { |
|||
text: '中止', |
|||
click: function() { |
|||
$(this).remove(); |
|||
} |
} |
||
} |
}] |
||
}); |
|||
} else { |
|||
submitRequest2(ep); |
|||
} |
|||
} |
|||
}); |
|||
} |
} |
||
/** |
|||
async function submitRequest2(ep) { |
|||
* Submit RFD: 2nd procedure (edit) |
|||
* @param {{}} ep |
|||
* @returns |
|||
*/ |
|||
function submitRequest2(ep) { |
|||
$('#rfdh- |
$('#rfdh-editing').prop('innerHTML', '依頼を提出しています' + dragoLib.toggleLoadingSpinner('add')); |
||
// Get timestamps |
// Get timestamps |
||
dragoLib.getLatestRevision(RFDR).then(function(lr) { |
|||
if (!lr) return editDone(ep, 'ts'); |
|||
lr = lr[0]; |
|||
if (!lr) return editDone(ep, 'lr'); |
|||
// Get section title |
|||
lr = lr[0]; |
|||
var sectiontitle = dragoLib.getSection5('依頼'); |
|||
ep.section = sectiontitle; |
|||
// |
// Re-get the title of the section to get just in case |
||
var |
var sectiontitle = dragoLib.getSection5('依頼'); |
||
ep.section = sectiontitle; |
|||
sections = sections.filter(function(obj) { |
|||
return obj.title === sectiontitle; |
|||
}); |
|||
if (sections.length === 0) return editDone(ep, 'sect'); |
|||
var sectNum = sections[0].index; |
|||
// Get the content of the section to which the request will be submitted |
|||
// Check for duplicate requests |
|||
var sections = dragoLib.parseContentBySection(lr.content); |
|||
const duplicateRequests = []; |
|||
sections = sections.filter(function(obj) { |
|||
const redirectsEscaped = dragoLib.escapeRegExp(ep.redirects.join('|')); |
|||
return obj.title === sectiontitle; |
|||
const redirectRegExp = new RegExp(`(?:${redirectsEscaped})`); |
|||
}); |
|||
const templates = dragoLib.findTemplates(lr.content, 'rfd'); // Extract RFD templates |
|||
if ( |
if (sections.length === 0) return editDone(ep, 'sect'); |
||
var sectNum = sections[0].index; |
|||
for (const tl of templates) { // Loop through all the extracted RFD templates and check if the 1st parameter contains the redirect(s) to be RFD-ed |
|||
let mtch; |
|||
// Check for duplicate requests |
|||
if (mtch = tl.split('|')[1].match(redirectRegExp)) { |
|||
var templates = dragoLib.findTemplates(lr.content, 'RFD'); // Extract RFD templates |
|||
var duplicates = []; |
|||
if ($.inArray(mtch, duplicateRequests) === -1) duplicateRequests.push(mtch); |
|||
if (templates.length !== 0) { |
|||
// duplicates = templates.filter(function(tpml) { |
|||
// var params = dragoLib.getTemplateParams(tpml); |
|||
// var param1 = params.filter(function(el) { return el.match(/^1\s*=/); }); |
|||
// param1 = param1.length !== 0 ? param1[0].replace(/^1\s*=/, '').trim() : params[0]; |
|||
// var isDuplicate = ep.redirects.some(function(title) { return dragoLib.isSameTitle(title, param1); }); |
|||
// return isDuplicate; |
|||
// }); |
|||
duplicates = templates.reduce(function(acc, tpml) { // Get the redirect sources of duplicate RFD templates |
|||
var params = dragoLib.getTemplateParams(tpml); |
|||
var param1 = params.filter(function(el) { return el.match(/^1\s*=/); }); |
|||
var paramUnnamed = params.filter(function(el) { return !el.match(/^[^\s]+\s*=/); }); |
|||
param1 = param1.length !== 0 ? param1[0].replace(/^1\s*=/, '').trim() : paramUnnamed[0] ? paramUnnamed[0] : null; |
|||
if (!param1) return acc; |
|||
var isDuplicate = ep.redirects.some(function(title) { return dragoLib.isSameTitle(title, param1); }); |
|||
if (isDuplicate && acc.indexOf(param1) === -1) acc.push(param1); |
|||
return acc; |
|||
}, []); |
|||
} |
} |
||
ep.duplicates = duplicates; |
|||
} |
|||
var reqRFD = ep.reqRFD.filter(function(obj) { |
|||
return !duplicates.some(function(title) { return dragoLib.isSameTitle(title, obj.source); }); |
|||
}); |
|||
if (reqRFD.length === 0) return editDone(ep, 'dr'); // All the redirects have already been reported as RFD candidates |
|||
// |
// Edit page |
||
var params = { |
|||
const drEscaped = dragoLib.escapeRegExp(duplicateRequests.join('|')); |
|||
action: 'edit', |
|||
const rqTxtRegExp = new RegExp('\\* \\{{2}RFD\\|(?:' + drEscaped + ')\\|.+\\}{2}\\n', 'g'); |
|||
title: RFDR, |
|||
if (ep.reqText.match(rqTxtRegExp)) { |
|||
section: sectNum, |
|||
ep.reqText = ep.reqText.replace(rqTxtRegExp, ''); // Remove all duplicates from the report text |
|||
appendtext: '\n\n' + mergeReqText(ep), |
|||
ep.updated = true; // Create a new property in ep which signals that reqText has been updated |
|||
basetimestamp: lr.basetimestamp, |
|||
} |
|||
starttimestamp: lr.curtimestamp, |
|||
var mtch; |
|||
summary: ep.summary |
|||
if (!(mtch = ep.reqText.match(/\* {{RFD\|/g))) return editDone(ep, 'dr'); // If reqText has no RFD template in it, all the redirects have already been reported |
|||
}; |
|||
ep.summary = generateSummary(sectiontitle, mtch.length); // Re-generate edit summary |
|||
dragoLib.editPage(params, debuggingMode.causeIntentionalError).then(function(result) { |
|||
switch (result) { |
|||
case true: // Edit succeeded |
|||
return editDone(ep, true); |
|||
case false: // Edit failed with an unknown error |
|||
return editDone(ep, false); |
|||
default: // Edit failed with a known error ('result' stores error info) |
|||
return editDone(ep, result); |
|||
} |
|||
}); |
|||
// Edit page |
|||
const result = await new mw.Api().post({ |
|||
action: 'edit', |
|||
title: RFDR, |
|||
section: sectNum, |
|||
appendtext: '\n\n' + ep.reqText, |
|||
basetimestamp: lr.basetimestamp, |
|||
starttimestamp: lr.curtimestamp, |
|||
summary: ep.summary, |
|||
token: debuggingMode.causeIntentionalError ? '' : mw.user.tokens.get('csrfToken'), |
|||
format: 'json' |
|||
}).then(function(res) { |
|||
if (res && res.edit) { |
|||
if (res.edit.result === 'Success') return true; |
|||
} |
|||
return false; |
|||
}).catch(function(code, err) { |
|||
return err.error.info; |
|||
}); |
}); |
||
switch (result) { |
|||
case true: // Edit succeeded |
|||
editDone(ep, true); |
|||
return; |
|||
case false: // Edit failed with an unknown error |
|||
editDone(ep, false); |
|||
return; |
|||
default: // Edit failed with a known error ('result' stores error info) |
|||
editDone(ep, result); |
|||
} |
|||
} |
} |
||
/** |
/** |
||
* @param { |
* @param {{}} ep |
||
* @param { |
* @param {string|boolean} type 'dr' when request is cancelled because of duplicates, 'lr' when the latest revision failed to be fetched, 'sect' when section number |
||
* failed to be fetcehd, true when edit succeeded, false when unexpected error occurred on edit, errcode when edit failed |
* failed to be fetcehd, true when edit succeeded, false when unexpected error occurred on edit, errcode when edit failed |
||
*/ |
*/ |
||
function editDone(ep, type) { |
|||
var drText = ep.duplicates.length !== 0 ? ' (' + ep.duplicates.length + '件の重複依頼分は除去されました)' : '', |
|||
$msg = $('#rfdh-editing'), |
|||
editFailed = true; |
|||
switch(type) { |
switch (type) { |
||
case 'dr': |
case 'dr': |
||
$msg.prop('innerHTML', '<span style="color: MediumVioletRed;">中止: 指定されたリダイレクトは既に全て依頼されています</span>'); |
$msg.prop('innerHTML', '<span style="color: MediumVioletRed;">中止: 指定されたリダイレクトは既に全て依頼されています</span>'); |
||
editFailed = false; |
|||
break; |
break; |
||
case 'lr': |
case 'lr': |
||
587行目: | 638行目: | ||
break; |
break; |
||
case true: |
case true: |
||
$msg.prop('innerHTML', |
$msg.prop('innerHTML', '<span style="color: MediumSeaGreen;">成功: 依頼が完了しました' + drText + '</span>'); |
||
editFailed = false; |
editFailed = false; |
||
showCopyButton = false; |
|||
break; |
break; |
||
case false: |
case false: |
||
595行目: | 645行目: | ||
break; |
break; |
||
default: |
default: |
||
$msg.prop('innerHTML', |
$msg.prop('innerHTML', '<span style="color: MediumVioletRed;">失敗: ' + type + '</span>'); |
||
break; |
|||
} |
} |
||
var $dialog = $('#rfdh-dialog'); |
|||
var btnsAfterEdit = []; |
|||
$dialog.dialog({ |
|||
var onThePage = mw.config.get('wgPageName') === RFDR; |
|||
buttons: [{ |
|||
if (!onThePage) { |
|||
btnsAfterEdit.push({ |
|||
text: '報告先', |
text: '報告先', |
||
click: function() { |
click: function() { |
||
window.open(mw.util.getUrl(RFDR + '#' + ep.section), '_blank'); |
window.open(mw.util.getUrl(RFDR + '#' + ep.section), '_blank'); |
||
} |
} |
||
} |
}); |
||
} |
|||
text: '閉じる', |
|||
btnsAfterEdit.push({ |
|||
click: function() { |
|||
text: '閉じる', |
|||
click: function() { |
|||
if (mw.config.get('wgPageName') === RFDR) location.reload(true); |
|||
$dialog.dialog('close'); |
|||
if (onThePage) location.reload(true); |
|||
}] |
|||
} |
|||
}); |
}); |
||
$dialog.dialog({ buttons: btnsAfterEdit }); |
|||
if (editFailed && showCopyButton) { |
|||
if (editFailed) { |
|||
$dialog.append( |
$dialog.append( |
||
'<label for="rfdh-editfailed">手動編集用 |
'<label for="rfdh-editfailed">手動編集用</label>' + |
||
'< |
'<textarea id="rfdh-editfailed" rows="5" disabled></textarea>' |
||
); |
); |
||
$('#rfdh-editfailed').val(mergeReqText(ep)); |
|||
} |
} |
||
$('#rfdh-editfailed').click(function() { |
|||
dragoLib.copyToClipboard(ep.reqText); |
|||
alert('コピーしました'); |
|||
}); |
|||
} |
} |
||
// ********************************************* EVENT HANDLERS ********************************************* |
|||
/** |
|||
* Check and update the redirect list textarea, and return its content as an array without duplicates |
|||
* @returns {Array} |
|||
*/ |
|||
function getRedirectSources() { |
|||
const $input = $('#rfdh-redirectlist-input'); |
|||
var inputVal = dragoLib.trim($input.val()); |
|||
if (!inputVal) return []; |
|||
inputVal = inputVal.split('\n'); |
|||
inputVal = inputVal.filter(function(item, index) { |
|||
return item !== '' && inputVal.indexOf(item) === index; |
|||
}); |
|||
$input.val(inputVal.join('\n')); |
|||
return inputVal; |
|||
} |
|||
// ************************* EVENT HANDLERS ************************* |
|||
// Clean up the redirect list textarea when the button is hit |
// Clean up the redirect list textarea when the button is hit |
||
$(document).off('click', '#rfdh-redirectlist-cleanup').on('click', '#rfdh-redirectlist-cleanup', |
$(document).off('click', '#rfdh-redirectlist-cleanup').on('click', '#rfdh-redirectlist-cleanup', getRedirectSources); |
||
getRedirectSources(); |
|||
}); |
|||
// Expand/shrink the summary textarea when the checkbox is (un)checked |
// Expand/shrink the summary textarea when the checkbox is (un)checked |
||
$(document).off('click', '#rfdh-summary-checkbox').on('click', '#rfdh-summary-checkbox', function() { |
$(document).off('click', '#rfdh-summary-checkbox').on('click', '#rfdh-summary-checkbox', function() { |
||
var $textarea = $('#rfdh-summary-input'); |
|||
var redirectsCnt, summary; |
|||
if ($(this).is(':checked')) { |
if ($(this).is(':checked')) { |
||
let redirectsCnt, summary; |
|||
summary = (redirectsCnt = getRedirectSources().length) === 0 ? '' : '+' + redirectsCnt; |
summary = (redirectsCnt = getRedirectSources().length) === 0 ? '' : '+' + redirectsCnt; |
||
$textarea.css('display', 'inline-block').val(summary); |
$textarea.css('display', 'inline-block').val(summary); |
||
668行目: | 699行目: | ||
$(this).remove(); |
$(this).remove(); |
||
}); |
}); |
||
// ****************************************************************************************** |
|||
})(); |
})(); |
2022年11月5日 (土) 16:34時点における版
/****************************
* Name: RFD Helper *
* Author: Dragoniez *
* Version: 1.4.0 *
****************************/
//<nowiki>
// **************************************** CONFIGS ****************************************
// var {dragoLib} = require('../dragoLib/dragoLib.js');
/* Config
rfdhConfig: {
headerColor: '#FEC493',
backgroundColor: '#FFF0E4',
portletlinkPosition: 'skin-dependent',
fontSize: 'skin-dependent'
} */
if (typeof rfdhConfig === 'undefined') var rfdhConfig = {};
if (!rfdhConfig.headerColor) rfdhConfig.headerColor = '#FEC493';
if (!rfdhConfig.backgroundColor) rfdhConfig.backgroundColor = '#FFF0E4';
if (!rfdhConfig.portletlinkPosition) {
if (mw.config.get('skin') === 'minerva') {
rfdhConfig.portletlinkPosition = 'p-personal';
} else {
rfdhConfig.portletlinkPosition = 'p-cactions';
}
}
if (!rfdhConfig.fontSize) {
switch(mw.config.get('skin')) {
case 'vector':
case 'vector-2022':
case 'minerva':
rfdhConfig.fontSize = '80%';
break;
case 'monobook':
rfdhConfig.fontSize = '110%';
break;
case 'timeless':
rfdhConfig.fontSize = '90%';
break;
default:
rfdhConfig.fontSize = '80%';
}
}
// **************************************** SCRIPT BODY ****************************************
(function() { // Create a function scope
// Run the script only if the user is autoconfirmed and the page is not an edit page
if (mw.config.get('wgUserGroups').indexOf('autoconfirmed') === -1 || ['edit', 'submit'].indexOf(mw.config.get('wgAction')) !== -1) return;
// ********************************************* VARIABLES *********************************************
// DebugMode
var debuggingMode = {
library: false,
portletlinkText: false,
targetPage: false,
scriptAd: false,
causeIntentionalError: false,
fillFields: false // Fill the redirect source and reason fields
};
var library = debuggingMode.library ?
'http://127.0.0.1:5500/dragoLib/dragoLib.js' :
'//ja-two.iwiki.icu/w/index.php?title=User:Dragoniez/scripts/dragoLib.js&action=raw&ctype=text/javascript';
var portletlinkText = debuggingMode.portletlinkText ? 'リダイレクトの削除依頼β' : 'リダイレクトの削除依頼';
var RFDR = debuggingMode.targetPage ? '利用者:Dragoniez/test3' : 'Wikipedia:リダイレクトの削除依頼/受付';
var scriptAd = ' ([[User:Dragoniez/scripts/RFD Helper|' + (debuggingMode.scriptAd ? 'RFD Helper Dev]])' : 'RFD Helper]])');
// Buttons of the dialog
var btns = [{
text: '依頼',
click: submitRequest
}, {
text: 'プレビュー',
click: preview
}, {
text: '閉じる',
click: function() {
$(this).dialog('close');
}
}];
// ********************************************* DOM READY FUNCTION *********************************************
$.when(
$.getScript(library),
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.ui']),
$.ready
).then(function() {
// Add <style>
$('head').append(
'<style>' +
'.rfdh-needmargin {' +
'margin: 0.5em 0;' +
'}' +
'.rfdh-dialog-main textarea {' +
'box-sizing: border-box;' +
'width : 100%;' +
'}' +
'.rfdh-dialog-main input[type="checkbox"],' +
'.rfdh-dialog-preview input[type="checkbox"] {' +
'margin-right: 0.5em;' +
'}' +
'.rfdh-preview-notarget {' +
'background: ' + rfdhConfig.headerColor + ';' +
'}' +
'</style>'
);
// Create a portletlink for the main dialog
$(mw.util.addPortletLink(rfdhConfig.portletlinkPosition, '#', portletlinkText, 'ca-rfdh', 'リダイレクトの削除依頼を提出', null, '#ca-move')).click(openDialog);
});
// ********************************************* MAIN FUNCTIONS *********************************************
/** Create and open the main dialog when the portletlink is hit */
function openDialog(e) {
e.preventDefault();
var votes =
'<option value="">なし</option>' +
'<option value="{{AFD|削除}}">削除</option>' +
'<option value="{{AFD|全削除}}">全削除</option>' +
'<option value="{{AFD|即時削除}}">即時削除</option>' +
'<option value="{{AFD|全即時削除}}">全即時削除</option>' +
'<option value="{{AFD|緊急削除}}">緊急削除</option>' +
'<option value="{{AFD|緊急即時削除}}">緊急即時削除</option>' +
'<option value="{{AFD|一部}}">一部削除/存続</option>' +
'<option value="{{AFD|存続}}">存続</option>' +
'<option value="{{AFD|全存続}}">全存続</option>' +
'<option value="{{AFD|履歴統合}}">履歴統合</option>';
var dialogHtml =
'<div id="rfdh-dialog" title="RFD Helper" style="max-height: 80vh; min-width: 515px;">' +
'<div id="rfdh-dialog-header">' +
'<h2>リダイレクトの削除依頼</h2>' +
'</div>' +
'<div id="rfdh-dialog-body">' +
'<form>' +
'<div id="rfdh-redirectlist-div">' +
'<label for="rfdh-redirectlist-input">リダイレクト元 (ページごとに改行)</label>' +
'<textarea id="rfdh-redirectlist-input" rows="8"></textarea>' +
'<input id="rfdh-redirectlist-cleanup" class="rfdh-needmargin" type="button" value="整形" style="margin-right: 0.5em;">' +
'<span>(余分な改行および重複項目を除去)</span>' +
'</div>' +
'<div id="rfdh-vote-div" class="rfdh-needmargin">' +
'<label for="rfdh-vote-select">依頼者票 <span style="font-size: smaller;">(複数選択する場合のみ中間表現必須)</span></label><br>' +
'<select id="rfdh-vote-select" style="margin-right: 0.3em;">' +
votes +
'</select>' +
'<select id="rfdh-votedelimiter-select" style="margin-right: 0.3em;">' +
'<option></option>' +
'<option value="および">および</option>' +
'<option value="または">または</option>' +
'</select>' +
'<select id="rfdh-vote2-select">' +
votes +
'</select>' +
'</div>' +
'<div id="rfdh-reason-div" class="rfdh-needmargin">' +
'<label for="rfdh-reason-input">依頼文 <span style="font-size: smaller;">(署名不要)</span></label>' +
'<textarea id="rfdh-reason-input" rows="3"></textarea>' +
'</div>' +
'<div id="rfdh-summary-div" class="rfdh-needmargin">' +
'<input id="rfdh-summary-checkbox" type="checkbox">' +
'<label for="rfdh-summary-checkbox">要約を指定</label>' +
'<textarea id="rfdh-summary-input" rows="3" style="display: none;"></textarea>' +
'</div>' +
'</form>' +
'</div>' +
'</div>';
// Add the frame div to the page
$('body').append(dialogHtml);
// Show dialog
$('#rfdh-dialog').dialog({
dialogClass: 'rfdh-dialog-main',
resizable: false,
height: 'auto',
width: 'auto',
modal: true,
open: function() {
dragoLib.dialogCSS($('.rfdh-dialog-main'), rfdhConfig.headerColor, rfdhConfig.backgroundColor, rfdhConfig.fontSize);
$('#rfdh-vote-select').children('option').eq(1).prop('selected', true);
if (debuggingMode.fillFields) {
$('#rfdh-redirectlist-input').val(['利用者:Dragoniez', 'User:Dragoniez/AN Reporter'].join('\n'));
$('#rfdh-reason-input').val('Test.');
}
},
buttons: btns
});
}
/**
* Check whether the necessary fileds are filled, and get neccesary information to submit an RFD
* @returns {{redirects: Array, reqText: string, summary: string}|undefined}
* [IMPORTANT] reqText doesn't contain RFD templates. The object needs to be updated by updateEp().
*/
function editPrep() {
// Error storage
var problems = {
redirects: '',
conjunction: '',
text: ''
};
// Redirect sources
var redirects = getRedirectSources();
if (redirects.length === 0) problems.redirects = '・リダイレクト元';
// Votes
var $vote = $('#rfdh-vote-select'),
$delimiter = $('#rfdh-votedelimiter-select'),
$vote2 = $('#rfdh-vote2-select'),
vote = $vote.children('option').filter(':selected').val(),
delimiter = $delimiter.children('option').filter(':selected').val(),
vote2 = $vote2.children('option').filter(':selected').val();
if (vote && vote2) {
if (!delimiter) problems.conjunction = '・依頼者票の中間表現';
} else if ((vote && !vote2) || (!vote && !vote2)) {
if (delimiter) {
$delimiter.children('option').eq(0).prop('selected', true); // Blank delimiter
delimiter = '';
}
} else if (!vote && vote2) {
$vote.children('option[value=' + vote2 + ']').prop('selected', true); // Move vote2 to vote
$delimiter.children('option').eq(0).prop('selected', true); // Blank delimiter
$vote2.children('option').eq(0).prop('selected', true); // Blank vote2
vote = vote2;
delimiter = '';
vote2 = '';
}
var votetext = vote + delimiter + vote2;
// Get text for the request (at this point RFD templates are not included)
var reqText = dragoLib.trim($('#rfdh-reason-input').val());
if (!reqText) problems.text = '・依頼文';
$('#rfdh-reason-input').val(reqText);
reqText = '** ' + votetext + ' ' + reqText + (reqText.substring(reqText.length - 4) === '~~~~' ? '' : '--~~~~');
// Check the necessary fields
var msg = '以下の項目に入力の不備があります:',
proceed = true;
for (var key in problems) {
if (problems[key]) {
proceed = false;
msg += '\n' + problems[key];
}
}
if (!proceed) return alert(msg);
// Return values once (Go on to API query)
return {
redirects: redirects,
reqText: reqText,
summary: generateSummary(dragoLib.getSection5('依頼'), redirects.length)
};
}
/**
* Check and update the redirect list textarea, and return its content as an array without duplicates
* @returns {Array} [redirsrc1, redirsrc2...]
*/
function getRedirectSources() {
var $input = $('#rfdh-redirectlist-input');
var inputVal = dragoLib.trim($input.val());
if (!inputVal) return [];
inputVal = inputVal.split('\n');
inputVal = inputVal.filter(function(item, index) {
return item !== '' && inputVal.indexOf(item) === index;
});
$input.val(inputVal.join('\n'));
return inputVal;
}
/**
* Generate a summary for the RFD
* @param {string} sectiontitle
* @param {number|string} redirectsCnt
* @returns
*/
function generateSummary(sectiontitle, redirectsCnt) {
var summary = dragoLib.trim($('#rfdh-summary-input').val());
summary = summary ? summary : '+' + redirectsCnt;
summary = '/*' + sectiontitle + '*/' + summary + scriptAd;
return summary;
}
/**
* Get redirect targets and add an array of objects to ep
* @param {{}} ep
* @returns {jQuery.Promise<{reqRFD: Array<{template: string, source: string, goal: string|undefined}>}>} Added to ep as a property:
* The passed ep object doesn't need to be substituted
*/
function updateEp(ep) {
var def = new $.Deferred();
var query = function(redirSrcArr) {
var deferred = new $.Deferred();
new mw.Api().get({
action: 'query',
titles: redirSrcArr.join('|'), // Max 50
redirects: 1,
formatversion: 2
}).then(function(res) {
var resRdr, resNom;
if (!res || !res.query || !(resRdr = res.query.redirects)) return deferred.resolve();
if (resNom = res.query.normalized) { // Namespace prefixes are translated in the response (e.g. User: => 利用者:)
resNom.forEach(function(objN) { // Loop through all the translations
resRdr.some(function(objR) {
if (objN.to === objR.from) {
objR.from = objN.from; // Translate back to the inputted page name
return true;
}
});
});
}
deferred.resolve(resRdr);
}).catch(function(code, err) {
deferred.resolve(console.log(err.error.info));
});
return deferred.promise();
};
var apilimit = dragoLib.apiHighLimit() ? 500 : 50;
var deferreds = [];
var redirSrcArr = JSON.parse(JSON.stringify(ep.redirects));
while (redirSrcArr.length) {
deferreds.push(query(redirSrcArr.splice(0, apilimit)));
}
$.when.apply($, deferreds).then(function() {
// Convert the response object to an array of objects
var args = arguments;
var res = Object.keys(args).filter(function(key) { return args[key]; }).map(function(key) { return args[key]; });
res = res.concat.apply([], res);
// Create an array of objects that stores RFD templates to be added to reqText
var RFD = '* {{RFD|SOURCE|GOAL}}\n';
var reqRFD = ep.redirects.reduce(function(acc, src) {
var goal = res.filter(function(obj) { return obj.from === src; });
goal = goal[0] && goal[0].to ? goal[0].to : undefined;
acc.push({
template: dragoLib.replaceAll(RFD, 'SOURCE', src, 'GOAL', goal ? goal : 'undefined'),
source: src,
goal: goal
});
return acc;
}, []);
// Update ep
ep.reqRFD = reqRFD;
def.resolve(ep);
});
return def.promise();
}
/** Preview the RFD to be sumitted */
function preview() {
// Check if the necessary fields are filled and get edit information
var ep = editPrep();
if (!ep) return;
// Preview dialog contour
var previewDiv =
'<div id="rfdh-preview-dialog" title="RFD Helper Preview" style="max-height: 80vh;">' +
'<div id="rfdh-preview-header" style="padding: 0.5em;">' +
'<p id="rfdh-preview-loading">' +
'プレビューを読み込み中' + dragoLib.toggleLoadingSpinner('add') +
'</p>' +
'<div id="rfdh-preview-warning" style="display: none;">' +
'<p>' +
'転送先不明の項目は以下のいずれかに起因します:' +
'</p>' +
'<ol>' +
'<li>リダイレクト元のページ名が間違っている</li>' +
'<li>リダイレクト元として入力されたページがリダイレクトではない</li>' +
'<li>リダイレクト先の取得時に通信に失敗した</li>' +
'</ol>' +
'</div>' +
'</div>' +
'<div id="rfdh-preview-body" style="display: none; font-size: 1.1em; padding-top: 1em; border-top: 1px solid silver;">' +
'<div id="rfdh-preview-text" style="border: 1px solid silver; padding: 0.2em 0.5em; background: white;">' +
// previewHtml
'</div>' +
'<div id="rfdh-preview-summary" style="margin-top: 0.8em; border: 1px solid silver; padding: 0.2em 0.5em; background: white;">' +
// summaryHtml
'</div>' +
'<div id="rfdh-preview-checkbox-div" style="margin: 0.5em 0; display: none;">' +
'<input id="rfdh-preview-checkbox" type="checkbox">' +
'<label for="rfdh-preview-checkbox">プレビューを閉じる際に転送先不明の項目を自動的にリダイレクト元リストから除去</label>' +
'</div>' +
'</div>' +
'</div>';
// Show preview dialog
$('body').append(previewDiv);
$('#rfdh-preview-dialog').dialog({
dialogClass: 'rfdh-dialog-preview',
height: 'auto',
width: $('#content').width() * 0.8,
modal: true,
open: function() {
// Initialize the design of the dialog
dragoLib.dialogCSS($('.rfdh-dialog-preview'), rfdhConfig.headerColor, rfdhConfig.backgroundColor, rfdhConfig.fontSize);
// Preview
updateEp(ep).then(function() { // Add 'reqRFD' to ep
dragoLib.getParsedHtml(mergeReqText(ep), ep.summary).then(function(parsed) { // Convert reqText to HTML
if (parsed) {
if (ep.reqRFD.some(function(obj) { return !obj.goal; })) {
$('#rfdh-preview-checkbox-div').css('display', 'block').children('input').prop('checked', true);
}
var previewHtml = parsed.htmltext;
var summaryHtml = parsed.htmlsummary.replace(/API/g, RFDR);
$('#rfdh-preview-text').append(previewHtml);
$('#rfdh-preview-text a').each(function() { // Loop through all <a> tags in preview
if ($(this).text() === 'undefined') $(this).addClass('rfdh-preview-notarget').text('転送先不明'); // Highlight if undefined
});
$('#rfdh-preview-summary').append(summaryHtml);
$('.autocomment a').css('color', 'gray'); // Change color of section spec in summary
$('#rfdh-preview-dialog a').attr('target', '_blank'); // Open all links on a new tab
$('#rfdh-preview-body').css('display', 'block');
$('#rfdh-preview-loading').remove();
$('#rfdh-preview-warning').css('display', 'block');
dragoLib.centerDialog('#rfdh-preview-dialog');
} else {
$('#rfdh-preview-loading').text('プレビューの読み込みに失敗しました').css('color', 'MediumVioletRed');
dragoLib.centerDialog('#rfdh-preview-dialog');
setTimeout(function() {
$('#rfdh-preview-dialog').dialog('close');
}, 5000);
}
});
});
},
buttons: [{
text: '閉じる',
click: function() {
if ($('#rfdh-preview-checkbox').is(':checked')) {
var $input = $('#rfdh-redirectlist-input');
var inputVal = dragoLib.trim($input.val());
inputVal = inputVal.split('\n');
inputVal = inputVal.filter(function(item) {
return ep.reqRFD.some(function(obj) { return item === obj.source && obj.goal; }); // Remove the undefined
});
$input.val(inputVal.join('\n'));
}
$(this).dialog('close');
$('#rfdh-dialog')
.dialog({buttons: btns})
.find('form').css('display', 'block');
$('#rfdh-editing').remove();
}
}]
});
}
/**
* Merge ep.reqRFD and ep.reqText into a single text
* @param {{}} ep The object must contain a 'reqRFD' and 'reqText' property
* @returns {string}
*/
function mergeReqText(ep) {
return ep.reqRFD.map(function(obj) { return obj.template; }).join('') + ep.reqText;
}
/** Submit RFD: 1st procedure (get redirect goals) */
function submitRequest() {
// Check if the necessary fields are filled
var ep = editPrep();
if (!ep) return;
// Hide the form and buttons and show a message
var $dialog = $('#rfdh-dialog');
$dialog
.append('<p id="rfdh-editing">準備中' + dragoLib.toggleLoadingSpinner('add') + '</p>')
.dialog({buttons: []})
.find('form').css('display', 'none');
updateEp(ep).then(function() {
if (ep.reqRFD.some(function(obj) { return !obj.goal; })) { // If any redirect target failed to be fetched
$('#rfdh-editing').prop('innerHTML', '<span style="color: MediumVioletRed;">転送先不明のリダイレクトを検出しました</span>');
$dialog.dialog({
buttons: [{
text: 'プレビュー',
click: preview
}, {
text: '続行',
click: function() {
$(this).dialog({buttons: []});
submitRequest2(ep);
}
}, {
text: '戻る',
click: function() {
$(this).find('form').css('display', 'block');
$(this).dialog({buttons: btns});
$('#rfdh-editing').remove();
}
}, {
text: '中止',
click: function() {
$(this).remove();
}
}]
});
} else {
submitRequest2(ep);
}
});
}
/**
* Submit RFD: 2nd procedure (edit)
* @param {{}} ep
* @returns
*/
function submitRequest2(ep) {
$('#rfdh-editing').prop('innerHTML', '依頼を提出しています' + dragoLib.toggleLoadingSpinner('add'));
// Get timestamps
dragoLib.getLatestRevision(RFDR).then(function(lr) {
if (!lr) return editDone(ep, 'lr');
lr = lr[0];
// Re-get the title of the section to get just in case
var sectiontitle = dragoLib.getSection5('依頼');
ep.section = sectiontitle;
// Get the content of the section to which the request will be submitted
var sections = dragoLib.parseContentBySection(lr.content);
sections = sections.filter(function(obj) {
return obj.title === sectiontitle;
});
if (sections.length === 0) return editDone(ep, 'sect');
var sectNum = sections[0].index;
// Check for duplicate requests
var templates = dragoLib.findTemplates(lr.content, 'RFD'); // Extract RFD templates
var duplicates = [];
if (templates.length !== 0) {
// duplicates = templates.filter(function(tpml) {
// var params = dragoLib.getTemplateParams(tpml);
// var param1 = params.filter(function(el) { return el.match(/^1\s*=/); });
// param1 = param1.length !== 0 ? param1[0].replace(/^1\s*=/, '').trim() : params[0];
// var isDuplicate = ep.redirects.some(function(title) { return dragoLib.isSameTitle(title, param1); });
// return isDuplicate;
// });
duplicates = templates.reduce(function(acc, tpml) { // Get the redirect sources of duplicate RFD templates
var params = dragoLib.getTemplateParams(tpml);
var param1 = params.filter(function(el) { return el.match(/^1\s*=/); });
var paramUnnamed = params.filter(function(el) { return !el.match(/^[^\s]+\s*=/); });
param1 = param1.length !== 0 ? param1[0].replace(/^1\s*=/, '').trim() : paramUnnamed[0] ? paramUnnamed[0] : null;
if (!param1) return acc;
var isDuplicate = ep.redirects.some(function(title) { return dragoLib.isSameTitle(title, param1); });
if (isDuplicate && acc.indexOf(param1) === -1) acc.push(param1);
return acc;
}, []);
}
ep.duplicates = duplicates;
var reqRFD = ep.reqRFD.filter(function(obj) {
return !duplicates.some(function(title) { return dragoLib.isSameTitle(title, obj.source); });
});
if (reqRFD.length === 0) return editDone(ep, 'dr'); // All the redirects have already been reported as RFD candidates
// Edit page
var params = {
action: 'edit',
title: RFDR,
section: sectNum,
appendtext: '\n\n' + mergeReqText(ep),
basetimestamp: lr.basetimestamp,
starttimestamp: lr.curtimestamp,
summary: ep.summary
};
dragoLib.editPage(params, debuggingMode.causeIntentionalError).then(function(result) {
switch (result) {
case true: // Edit succeeded
return editDone(ep, true);
case false: // Edit failed with an unknown error
return editDone(ep, false);
default: // Edit failed with a known error ('result' stores error info)
return editDone(ep, result);
}
});
});
}
/**
* @param {{}} ep
* @param {string|boolean} type 'dr' when request is cancelled because of duplicates, 'lr' when the latest revision failed to be fetched, 'sect' when section number
* failed to be fetcehd, true when edit succeeded, false when unexpected error occurred on edit, errcode when edit failed
*/
function editDone(ep, type) {
var drText = ep.duplicates.length !== 0 ? ' (' + ep.duplicates.length + '件の重複依頼分は除去されました)' : '',
$msg = $('#rfdh-editing'),
editFailed = true;
switch (type) {
case 'dr':
$msg.prop('innerHTML', '<span style="color: MediumVioletRed;">中止: 指定されたリダイレクトは既に全て依頼されています</span>');
editFailed = false;
break;
case 'lr':
$msg.prop('innerHTML', '<span style="color: MediumVioletRed;">失敗: 報告先の最新版が取得できませんでした</span>');
break;
case 'sect':
$msg.prop('innerHTML', '<span style="color: MediumVioletRed;">失敗: 報告先のセクション情報が取得できませんでした</span>');
break;
case true:
$msg.prop('innerHTML', '<span style="color: MediumSeaGreen;">成功: 依頼が完了しました' + drText + '</span>');
editFailed = false;
break;
case false:
$msg.prop('innerHTML', '<span style="color: MediumSeaGreen;">失敗: ページの編集段階で不明なエラーが発生しました</span>');
break;
default:
$msg.prop('innerHTML', '<span style="color: MediumVioletRed;">失敗: ' + type + '</span>');
}
var $dialog = $('#rfdh-dialog');
var btnsAfterEdit = [];
var onThePage = mw.config.get('wgPageName') === RFDR;
if (!onThePage) {
btnsAfterEdit.push({
text: '報告先',
click: function() {
window.open(mw.util.getUrl(RFDR + '#' + ep.section), '_blank');
}
});
}
btnsAfterEdit.push({
text: '閉じる',
click: function() {
$dialog.dialog('close');
if (onThePage) location.reload(true);
}
});
$dialog.dialog({ buttons: btnsAfterEdit });
if (editFailed) {
$dialog.append(
'<label for="rfdh-editfailed">手動編集用</label>' +
'<textarea id="rfdh-editfailed" rows="5" disabled></textarea>'
);
$('#rfdh-editfailed').val(mergeReqText(ep));
}
}
// ********************************************* EVENT HANDLERS *********************************************
// Clean up the redirect list textarea when the button is hit
$(document).off('click', '#rfdh-redirectlist-cleanup').on('click', '#rfdh-redirectlist-cleanup', getRedirectSources);
// Expand/shrink the summary textarea when the checkbox is (un)checked
$(document).off('click', '#rfdh-summary-checkbox').on('click', '#rfdh-summary-checkbox', function() {
var $textarea = $('#rfdh-summary-input');
var redirectsCnt, summary;
if ($(this).is(':checked')) {
summary = (redirectsCnt = getRedirectSources().length) === 0 ? '' : '+' + redirectsCnt;
$textarea.css('display', 'inline-block').val(summary);
} else {
$textarea.css('display', 'none').val('');
}
dragoLib.centerDialog('#rfdh-dialog');
});
// Remove the dialog html when closed
$(document).off('dialogclose', '#rfdh-dialog, #rfdh-preview-dialog').on('dialogclose', '#rfdh-dialog, #rfdh-preview-dialog', function() {
$(this).remove();
});
// ******************************************************************************************
})();
//</nowiki>