User:NguoiDungKhongDinhDanh/JWB.js
Jump to navigation
Jump to search
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
- Opera: Press Ctrl-F5.
/***************************************************************************
* This script is based on the downloadable AutoWikiBrowser.
*
* @licence
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* Original author: [[:wikipedia:User:Joeytje50]]
*
* For attribution: //en.wikipedia.org/wiki/User:Joeytje50/JWB.js
***************************************************************************/
/***** Global object/variables *****/
window.JWB = {}; //The main global object for the script.
(function() {
// Easier way to change import location for local debugging etc.
JWB.imports = {
'JWB.css' : '//dev.fandom.com/wiki/User:NguoiDungKhongDinhDanh/JWB.css?action=raw&ctype=text/css',
'i18n.js' : '//dev.fandom.com/wiki/User:NguoiDungKhongDinhDanh/JWB.js/i18n.js?action=raw&ctype=text/javascript',
'i18n' : {},
'RETF.js' : '//en.wikipedia.org/w/index.php?title=User:Joeytje50/RETF.js&action=raw&ctype=text/javascript',
'worker.js' : '//dev.fandom.com/wiki/User:NguoiDungKhongDinhDanh/JWB.js/worker.js?action=raw&ctype=text/javascript',
};
let objs = ['page', 'api', 'worker', 'fn', 'pl', 'messages', 'setup', 'settings', 'ns', 'protection', 'test'];
for (let i=0;i<objs.length;i++) {
JWB[objs[i]] = {};
}
// <del>Set null as value instead of completely removing it. I may need it in the future.</del> [[:wikipedia:vi:Special:Diff/68297434]].
JWB.summarySuffix = ' (via JWB)';
// if (document.location.hostname == 'en.wikipedia.org') JWB.summarySuffix = ' (via [[WP:JWB]])';
JWB.lang = mw.config.get('wgUserLanguage').replace('-', '_');
JWB.contentLang = mw.config.get('wgContentLanguage').replace('-', '_');
JWB.index_php = mw.config.get('wgScript');
JWB.isStopped = true;
JWB.tooltip = window.tooltipAccessKeyPrefix || '';
let configext = 'js';
if (document.location.hostname.split('.').slice(-2).join('.') == 'wikia.com' ||
document.location.hostname.split('.').slice(-2).join('.') == 'fandom.com') {
//LEGACY: fallback to settings on css for Wikia; uses JSON now.
configext = 'css';
}
JWB.settingspage = 'JWB-settings.'+configext;
if (window.hasOwnProperty('JWBSETTINGS')) {
JWB.settingspage = JWBSETTINGS+'-settings.'+configext;
delete window.JWBSETTINGS; //clean up the global variable
}
// .floodFlag should be null and string while .hasFlood should go after .flood and before .floodFlag, but forgive my OCD.
JWB.test.changeable = false; // Whether or not the user can add & remove it from self.
JWB.test.floodFlag = false; // Whether or not the flood user group has a name.
JWB.test.hasFlood = false; // Whether or not the wiki has flood user group.
JWB.test.hasJSON = false; // whether or not the wiki supports JSON userpages (mw 1.31+).
JWB.test.hasSMW = false; // whether or not the wiki has SMW installed.
JWB.test.flood = false; // Whether or not the user is holding JWB.test.floodFlag.
JWB.test.flag = false; // Whether or not the user can remove JWB.test.floodFlag from self.
JWB.test.hid = false; // Whether or not the user can hide their edits from RC.
})();
/***** User verification *****/
(function() {
if (mw.config.get('wgCanonicalNamespace')+':'+mw.config.get('wgTitle') !== 'Project:AutoWikiBrowser/Script' ||
JWB.allowed === false ||
mw.config.get('wgUserName') === null) {
JWB.allowed = false;
return;
}
mw.loader.load(JWB.imports['JWB.css'], 'text/css');
mw.loader.load('mediawiki.diff.styles');
$.getScript(JWB.imports['i18n.js'], function() {
if (JWB.allowed === false) {
alert(JWB.msg('not-on-list'));
return;
}
let langs = [];
if (JWB.lang !== 'en' && JWB.imports.i18n.hasOwnProperty(JWB.lang)) {
langs.push(JWB.imports.i18n[JWB.lang]);
JWB.messages[JWB.lang] = JWB.messages[JWB.lang] || null;
} else if (JWB.lang !== 'en' && JWB.lang !== 'qqx') {
// this only happens if the language file does not exist.
JWB.lang = 'en';
}
if (JWB.contentLang !== 'en' && JWB.contentLang !== JWB.lang && JWB.imports.i18n.hasOwnProperty(JWB.contentLang)) {
langs.push(JWB.imports.i18n[JWB.contentLang]);
JWB.messages[JWB.contentLang] = JWB.messages[JWB.contentLang] || null;
}
if (langs.length) {
$.when.apply($, langs.map(url => $.getScript(url))).done(function(r) {
if (JWB.allowed === true && JWB.messages.length == langs + 1) { // if there are two languages to load, wait for them both.
console.log('langs loaded');
JWB.init(); //init if verification has already returned true
} else if (JWB.allowed === false) {
alert(JWB.msg('not-on-list'));
}
});
} else if (JWB.allowed === true) { // no more languages to load.
console.log('no langs loaded');
JWB.init();
}
});
//RegEx Typo Fixing
$.getScript(JWB.imports['RETF.js'], function() {
$('#refreshRETF').click(RETF.load);
});
if (!window.Worker) {
// https://caniuse.com/webworkers - this should not happen for any sensible human being.
// Either you're on IE<10, or you're just testing my patience.
alert('Web Workers are not supported in this browser. Please use a more modern browser to use JWB. '+
'Most matching and replacing features are not supported in this browser.');
}
(new mw.Api()).get({
action: 'query',
meta: 'siteinfo|userinfo', // allmessages|
prop: 'info|revisions',
titles: 'Project:AutoWikiBrowser/CheckPageJSON',
indexpageids: true,
rvlimit: 1,
rvprop: 'content',
// amincludelocal: true,
// amlang: JWB.lang,
// ammessages: '*',
// amprefix: 'restriction-level-',
siprop: 'extensions|general|namespaces|restrictions|usergroups',
uiprop: 'changeablegroups|groups|rights',
format: 'json',
formatversion: 2
}).done(function(response) {
if (response.error) {
alert('API error: ' + response.error.info);
JWB = false; //preventing further access. No verification => no access.
return;
}
JWB.ns = response.query.namespaces; //saving for later
JWB.protection.other = {};
JWB.protection.other.function = {};
// JWB.protection.other.levels = [];
JWB.protection.types = response.query.restrictions.types; // Idem.
JWB.protection.levels = response.query.restrictions.levels; // Idem.
JWB.protection.casc = response.query.restrictions.cascadinglevels; // Idem.
JWB.protection.other.types = JWB.protection.types; // Saving original ones.
JWB.protection.types = // Splicing "create" out as it will be determined at .api.protect.
JWB.protection.types.slice(0, JWB.protection.types.indexOf('create'))
.concat(JWB.protection.types.slice(JWB.protection.types.indexOf('create') + 1));
/* Getting restriction-level- messages and add them to JWB.messages. Abandoned due to their lengthy nature.
var am = response.query.allmessages;
for (let i = 0; i < JWB.protection.levels.length; i++) { // Getting message names.
if (JWB.protection.levels[i] === '') continue;
JWB.protection.other.levels.push('restriction-level-' + JWB.protection.levels[i]);
}
for (var j = 0; j < JWB.protection.other.levels.length; j++) {
for (var k = 0; k < am.length; k++) {
console.warn(am[k].normalizedname);
console.warn(JWB.protection.other.levels[j]);
if (am[k].normalizedname === 'restriction-level-all') {
continue;
} else if (am[k].normalizedname === JWB.protection.other.levels[j]) {
JWB.messages[JWB.lang]['protect-' + JWB.protection.other.levels[j].slice(18)] = am[k]['*'];
}
}
}
*/
// This will execute before JWB.init() and therefore before JWB.setup.load() loading the user's settings.
// Check if there is another group with "bot" right and user can add and remove it from themself.
let wikigroups = response.query.usergroups;
var groups = response.query.userinfo.groups;
let cagroups = response.query.userinfo.changeablegroups;
let cagarray = ['add', 'remove', 'add-self', 'remove-self'];
let changeable = function(g) {
return ((cagroups[cagarray[0]].includes(g) && cagroups[cagarray[1]].includes(g)) ||
(cagroups[cagarray[2]].includes(g) && cagroups[cagarray[3]].includes(g)));
};
let revocable = function(g) {
return cagroups[cagarray[2]].includes(g) && cagroups[cagarray[3]].includes(g);
};
for (var u of wikigroups) {
if (u.rights.includes('editmyuserjson') || u.rights.includes('edituserjson')) {
JWB.test.hasJSON = true;
}
if (u.rights.includes('bot') && u !== 'bot' && changeable(u)) {
JWB.test.hasFlood = true;
JWB.test.floodFlag = u;
}
if (JWB.test.hasJSON && JWB.test.hasFlood) {
break;
}
}
if (JWB.test.floodFlag === false && changeable('bot')) {
JWB.test.hasFlood = true;
JWB.test.floodFlag = 'bot';
}
if (groups.includes(JWB.test.floodFlag)) {
JWB.test.flood = true;
if (changeable(JWB.test.floodFlag)) {
JWB.test.changeable = true;
} else if (revocable(JWB.test.floodFlag)) {
JWB.test.flag = true;
}
}
// Check if we've got SMW on this wiki
let extensions = response.query.extensions;
for (var e of extensions) {
if (e.name == 'SemanticMediaWiki') {
JWB.test.hasSMW = true;
break;
}
}
// Check if user already has "bot" right.
let rights = response.query.userinfo.rights;
for (var r in rights) {
if (r === 'bot') {
JWB.test.hid = true;
break;
}
}
JWB.username = response.query.userinfo.name; //preventing any "hacks" that change wgUserName or mw.config.wgUserName
JWB.userid = response.query.userinfo.id; // For use in JWB.api.right (line 670+).
var page = response.query.pages[0];
var server = response.query.general.servername;
var users = [];
var bots = [];
var exempt = {
'global' : ['NguoiDungKhongDinhDanh', 'Tocdoso1Bot'],
'unlimited' : {
'vi.wikipedia.org' : ['NhacNy2412Bot']
},
'local' : {
'vi.wikipedia.org' : ['Hộp cát', 'NhacNy2412', 'Không hề giả trân']
}
};
JWB.test.dev = exempt.global.includes(JWB.username);
JWB.test.unlimited = (typeof exempt.unlimited[server] !== 'undefined' ? exempt.unlimited[server].includes(JWB.username) : false);
JWB.test.main = JWB.test.dev || JWB.test.unlimited;
JWB.test.sysop = groups.includes('sysop') || groups.includes('eliminator') || JWB.test.dev;
if (response.query.pageids[0] !== '-1') {
var checkPageData = JSON.parse(page.revisions[0].content);
users = checkPageData.enabledusers;
if ("enabledbots" in checkPageData) {
bots = checkPageData.enabledbots;
}
} else {
users = false; //fallback when page doesn't exist
if (JWB.test.sysop) { // Check and inform admins if their checkpage is the unsupported format.
(new mw.Api()).get({
action: 'query',
titles: 'Project:AutoWikiBrowser/CheckPage',
prop: 'info',
indexpageids: true,
}).done(function(oldpage){
var q = oldpage.query;
if (q.pageids[0] != '-1' && !q.pages[q.pageids[0]].hasOwnProperty('redirect')) {
// CheckPageJSON does not exist, and CheckPage does exist, and is not a redirect.
// This indicates the checkpage needs to be ported to JSON. Notify admins.
prompt('Warning: The AWB checkpage found at Project:AutoWikiBrowser/CheckPage is no longer supported.\n'+
'Please convert this checkpage to a JSON checkpage. See the URL below for more information.\n'+
'After creating the JSON checkpage, you can use "Special:ChangeContentModel" to change the content model to JSON.',
'https://en.wikipedia.org/wiki/Wikipedia:AutoWikiBrowser/CheckPage_format');
}
});
}
}
JWB.test.bot = (groups.includes('bot') && (users === false || bots.includes(JWB.username)) || JWB.test.main || JWB.test.hid);
if (typeof exempt.local[server] !== 'undefined' ? exempt.local[server].includes(JWB.username) : false) {
users.push(JWB.username);
}
var allLoaded = true;
for (var m in JWB.messages) if (JWB.messages[m] === null) allLoaded = false;
if (JWB.test.sysop || response.query.pageids[0] === '-1' || users === false ||
users.includes(JWB.username) || bots.includes(JWB.username) || JWB.test.main) {
JWB.allowed = true;
if (allLoaded) JWB.init(); //init if messages have already loaded
} else {
if (allLoaded) {
//run this after messages have loaded, so the message that shows is in the user's language
alert(JWB.msg('not-on-list'));
}
JWB = false; //prevent further access
}
}).fail(function(xhr, error) {
alert(JWB.msg('verify-error') + '\n' + error);
JWB = false; //preventing further access. No verification => no access.
});
})();
/***** API functions *****/
//Main template for API calls
JWB.api.call = function(data, callback, onerror) {
data.format = 'json';
if (data.action !== 'query' && data.action !== 'compare' && data.action !== 'ask') {
data.bot = true; // mark edits as bot
}
$.ajax({
data: data,
dataType: 'json',
url: mw.config.get('wgScriptPath') + '/api.php',
type: 'POST',
success: function(response) {
if (response.error) {
if (onerror && onerror(response, 'API') === false) return;
alert('API error: ' + response.error.info);
JWB.stop();
} else {
callback(response);
}
},
// onerror: if it exists and returns false, do not show error alert. Otherwise, do show alert.
error: function(xhr, error) {
if (onerror && onerror(error, 'AJAX') === false) return;
alert('AJAX error: ' + error);
JWB.stop();
}
});
};
//Get page diff, and process it for more interactivity
JWB.api.diff = function(callback) {
if (JWB.isStopped) return; // prevent new API calls when stopped
JWB.status('diff');
var editBoxInput = $('#editBoxArea').val();
var redirect = $('input.redirects:checked').val();
var data = {
action: 'compare',
indexpageids: true,
fromtitle: JWB.page.name,
//toslots: 'main', // TODO: Once this gets supported more widely, convert to the non-deprecated toslots system.
//'totext-main': editBoxInput,
totext: editBoxInput,
topst: true,
};
if (redirect=='follow') data.redirects = true;
JWB.api.call(data, function(response) {
var diff;
diff = response.compare['*'];
if (diff === '') {
diff = '<h2>'+JWB.msg('no-changes-made')+'</h2>';
} else {
diff = '<table class="diff">'+
'<colgroup>'+
'<col class="diff-marker">'+
'<col class="diff-content">'+
'<col class="diff-marker">'+
'<col class="diff-content">'+
'</colgroup>'+
'<tbody>'+diff+'</tbody></table>';
}
$('#resultWindow').html(diff);
$('.diff-lineno').each(function() {
var lineNumMatch = $(this).html().match(/\d+/);
if (lineNumMatch) {
$(this).parent().attr('data-line', parseInt(lineNumMatch[0])-1).addClass('lineheader');
}
});
$('table.diff tr').each(function() { //add data-line attribute to every line, relative to the previous one. Used for click event.
if (!$(this).next().is('[data-line]') && !$(this).next().has('td.diff-deletedline + td.diff-empty')) {
$(this).next().attr('data-line', parseInt($(this).data('line'))+1);
} else if ($(this).next().has('td.diff-deletedline + td.diff-empty')) { //copy over current data-line for deleted lines
$(this).next().attr('data-line', $(this).data('line')); // to prevent them from messing up counting.
}
});
JWB.status('done', true);
if (typeof(callback) === 'function') {
callback();
}
}, function(err, type) {
if (type == 'API' && err.error.code == 'missingtitle') {
// missingtitle is to be expected when editing a page that doesn't exist; just show a message and move on.
$('#resultWindow').html('<span style="font-weight:bold;color:red;">'+JWB.msg('page-not-exists')+'</span>');
JWB.status('done', true);
if (typeof(callback) === 'function') {
callback();
}
return false; // stop propagation of error; do not show alerts.
}
});
};
//Retrieve page contents/info, process them, and store information in JWB.page object.
JWB.api.get = function(pagename) {
if (JWB.isStopped) return; // prevent new API calls when stopped
JWB.pageCount();
if (!JWB.list[0] || JWB.isStopped) {
return JWB.stop();
}
if (pagename === '#PRE-PARSE-STOP') {
var curval = $('#articleList').val();
$('#articleList').val(curval.substr(curval.indexOf('\n') + 1));
$('#preparse').prop('checked', false);
JWB.stop();
return;
}
let cgns = JWB.ns[14].name;
let skipcg = $('#skipCategories').val();
// prepend Category: before all categories and turn CSV(,) into CSV(|).
skipcg = skipcg.replace(new RegExp('(^|,|\\|)('+cgns+':)?', 'gi'), '|'+cgns+':').substr(1);
var redirect = $('input.redirects:checked').val();
var data = {
action: 'query',
prop: 'info|revisions|categories',
inprop: 'watched|protection',
type: 'csrf|watch',
titles: pagename,
rvprop: 'content|timestamp|ids',
rvlimit: '1',
cllimit: 'max',
clcategories: skipcg,
indexpageids: true,
meta: 'userinfo|tokens',
uiprop: 'hasmsg'
};
if (redirect=='follow'||redirect=='skip') data.redirects = true;
if (JWB.test.sysop && !JWB.test.dev) { // This causes all JWB.api.get to throw API error since non-sysops cannot query deleted revs.
data.list = 'deletedrevs';
}
JWB.status('load-page');
JWB.api.call(data, function(response) {
if (response.query.userinfo.hasOwnProperty('messages')) {
var view = mw.config.get('wgScriptPath') + '?title=Special:MyTalk';
var viewNew = view + '&diff=cur';
JWB.status(
'<span style="color:red;font-weight:bold;">'+
JWB.msg('status-newmsg',
'<a href="'+view+'" target="_blank">'+JWB.msg('status-talklink')+'</a>',
'<a href="'+viewNew+'" target="_blank">'+JWB.msg('status-difflink')+'</a>')+
'</span>', true);
alert(JWB.msg('new-message'));
JWB.stop();
return;
}
JWB.page = response.query.pages[response.query.pageids[0]];
JWB.page.token = response.query.tokens.csrftoken;
JWB.page.watchtoken = response.query.tokens.watchtoken;
JWB.page.name = JWB.list[0].split('|')[0];
var varOffset = JWB.list[0].includes('|') ? JWB.list[0].indexOf('|') + 1 : 0;
JWB.page.pagevar = JWB.list[0].substr(varOffset);
JWB.page.content = JWB.page.revisions ? JWB.page.revisions[0]['*'] : '';
JWB.page.exists = !response.query.pages['-1'];
JWB.page.deletedrevs = response.query.deletedrevs;
JWB.page.watched = JWB.page.hasOwnProperty('watched');
JWB.page.protections = JWB.page.restrictiontypes;
if (response.query.redirects) {
JWB.page.name = response.query.redirects[0].to;
}
// check for skips that can be determined before replacing
if (!JWB.fn.allowBots(JWB.page.content, JWB.username) || !JWB.fn.allowBots(JWB.page.content)) {
// skip if {{bots}} template forbids editing on this page by user OR by JWB in general
JWB.log('nobots', JWB.page.name);
return JWB.next();
} else if (JWB.page.categories !== undefined || // skip because of a matching category as passed via clcategories.
($('#exists-no').prop('checked') && !JWB.page.exists) ||
($('#exists-yes').prop('checked') && JWB.page.exists) ||
(redirect==='skip' && response.query.redirects) // variable redirect is defined outside this callback function.
) {
// simple skip rules
JWB.log('skip', JWB.page.name);
return JWB.next();
}
// Check skip contains rules.
var containRegex = $('#containRegex').prop('checked'),
containFlags = $('#containFlags').val();
var skipContains, skipNotContains;
if (containRegex) {
JWB.status('check-skips');
var skipping = false; // for tracking if match is found in synchronous calls.
if ($('#skipContains').val().length) {
JWB.worker.match(JWB.page.content, $('#skipContains').val(), containFlags, function(result, err) {
console.log('Contains', result, err);
if (result !== null && err === undefined) {
JWB.log('skip', JWB.page.name);
JWB.next(); // next() also cancels the skipNotContains.
skipping = true;
return;
} // else continue with the queued worker job that checks skipNotContains
});
}
if (skipping) {
console.log('skipped page before replaces')
return;
}
if ($('#skipNotContains').val().length) {
JWB.worker.match(JWB.page.content, $('#skipNotContains').val(), containFlags, function(result, err) {
console.log('Not contains', result, err);
if (result === null && err === undefined) {
JWB.log('skip', JWB.page.name);
JWB.next(); // also cancels the replace
skipping = true;
return;
} // else move on to replacing
});
}
if (skipping) {
console.log('skipped page before replaces');
return;
}
} else {
skipContains = $('#skipContains').val();
skipNotContains = $('#skipNotContains').val();
if ((skipContains && JWB.page.content.includes(skipContains)) ||
(skipNotContains && !JWB.page.content.includes(skipNotContains))) {
console.log('skipped page before replaces');
return JWB.next();
}
JWB.status('done', true);
}
JWB.replace(JWB.page.content, function(newContent) {
if (JWB.isStopped === true) return;
if ($('#skipNoChange').prop('checked') && JWB.page.content === newContent) { //skip if no changes are made
JWB.log('skip', JWB.page.name);
return JWB.next();
} else {
JWB.editPage(newContent);
}
JWB.updateButtons();
});
});
};
//Some functions with self-explanatory names:
JWB.api.submit = function(page) {
if (JWB.isStopped) return; // prevent new API calls when stopped
JWB.status('submit');
var summary = $('#summary').val();
if ($('#summary').parent('label').hasClass('viaJWB')) summary += JWB.summarySuffix;
if ((typeof page === 'string' && page !== JWB.page.name) || $('#currentpage a').html().replace(/&/g, '&') !== JWB.page.name) {
console.log(page, JWB.page.name, $('#currentpage a').html())
JWB.stop();
alert(JWB.msg('autosave-error', JWB.msg('tab-log')));
$('#currentpage').html(JWB.msg('editbox-currentpage', ' ', ' '));
return;
}
var newval = $('#editBoxArea').val();
var diffsize = newval.length - JWB.page.content.length;
if ($('#sizelimit').val() != 0 && Math.abs(diffsize) > parseInt($('#sizelimit').val())){
alert(JWB.msg('size-limit-exceeded', diffsize > 0 ? '+'+diffsize : diffsize));
JWB.status('done', true);
return;
}
var data = {
title: JWB.page.name,
summary: summary,
action: 'edit',
//tags: 'JWB',
basetimestamp: JWB.page.revisions ? JWB.page.revisions[0].timestamp : '',
token: JWB.page.token,
text: newval,
watchlist: $('#watchPage').val()
};
if ($('#minorEdit').prop('checked')) data.minor = true;
JWB.api.call(data, function(response) {
JWB.log('edit', response.edit.title, response.edit.newrevid);
}, function(error, errtype) {
var cont = false;
if (errtype == 'API') {
cont = confirm('API error: ' + error.error.info + '\n' + JWB.msg('confirm-continue'));
} else {
cont = confirm('AJAX error: ' + error + '\n' + JWB.msg('confirm-continue'));
}
// JWB.log('skip', response.edit.title); // TODO: Readd err page
if (!cont) {
JWB.stop();
}
return false; // do not fall back on default error handling
});
// While the edit is submitting, continue to the next page to edit.
JWB.status('done', true);
JWB.next();
};
JWB.api.preview = function() {
if (JWB.isStopped) return; // prevent new API calls when stopped
JWB.status('preview');
JWB.api.call({
title: JWB.page.name,
action: 'parse',
pst: true,
text: $('#editBoxArea').val()
}, function(response) {
$('#resultWindow').html(response.parse.text['*']);
$('#resultWindow div.previewnote').remove();
JWB.status('done', true);
});
};
JWB.api.move = function() {
if (JWB.isStopped) return; // prevent new API calls when stopped
JWB.status('move');
var topage = $('#moveTo').val().replace(/\$x/gi, JWB.page.pagevar);
var summary = $('#summary').val();
if ($('#summary').parent('label').hasClass('viaJWB')) summary += JWB.summarySuffix;
var data = {
action: 'move',
from: JWB.page.name,
to: topage,
token: JWB.page.token,
reason: summary,
ignorewarnings: 'yes'
};
if ($('#moveTalk').prop('checked')) data.movetalk = true;
if ($('#moveSubpage').prop('checked')) data.movesubpages = true;
if ($('#suppressRedir').prop('checked')) data.noredirect = true;
JWB.api.call(data, function(response) {
JWB.log('move', response.move.from, response.move.to);
JWB.status('done', true);
if (!$('#moveTo').val().match(/\$x/i)) $('#moveTo').val('')[0].focus(); //clear entered move-to pagename if it's not based on the pagevar
JWB.next(topage);
});
};
JWB.api.del = function() {
if (JWB.isStopped) return; // prevent new API calls when stopped
JWB.status(($('#deletePage').is('.undelete') ? 'un' : '') + 'delete');
var summary = $('#summary').val();
if ($('#summary').parent('label').hasClass('viaJWB')) summary += JWB.summarySuffix;
JWB.api.call({
action: (!JWB.page.exists ? 'un' : '') + 'delete',
title: JWB.page.name,
token: JWB.page.token,
reason: summary
}, function(response) {
JWB.log((!JWB.page.exists ? 'un' : '') + 'delete', (response['delete']||response.undelete).title);
JWB.status('done', true);
JWB.next(response.undelete && response.undelete.title);
});
};
JWB.api.protect = function() {
if (JWB.isStopped) return; // prevent new API calls when stopped
JWB.status('protect');
var summary = $('#summary').val();
var prot = [];
var cascade = $('#cascadingProtection').prop('checked');
if ($('#summary').parent('label').hasClass('viaJWB')) summary += JWB.summarySuffix;
for (let i = 0; i < JWB.protection.types.length; i++) {
prot.push(JWB.protection.types[i] + '=' + $('#' + JWB.protection.types[i] + 'Prot').val() || $('#editProt').val());
}
for (let i = 0; i < prot.length; i++) {
if (!JWB.page.exists) {
if (prot[i].match(/(?:move|delete|upload)=/)) {
prot[i] = '';
} else if (prot[i].match(/edit=/)) {
prot[i] = prot[i].replace(/^edit/, 'create');
}
} else {
if (prot[i].match(/create=/)) {
prot[i] = '';
}
}
if (!JWB.page.protections.includes('upload')) {
if (prot[i].match(/upload=/)) {
prot[i] = '';
}
}
}
prot = prot.filter(function(e) {
return e != '';
});
JWB.api.call({
action: 'protect',
title: JWB.page.name,
token: JWB.page.token,
reason: summary,
expiry: $('#protectExpiry').val() !== '' ? $('#protectExpiry').val() : 'indefinite',
cascade: cascade,
protections: prot.join('|')
}, function(response) {
var protactions = [];
var prots = response.protect.protections;
var casc = (typeof response.protect.cascade === 'string' ? true : false);
for (var i = 0; i < prots.length; i++) {
for (var j = 0; j < JWB.protection.other.types.length; j++) {
if (typeof prots[i][JWB.protection.other.types[j]] === 'string') {
protactions.push(JWB.protection.other.types[j] + ': ' + (prots[i][JWB.protection.other.types[j]] || JWB.msg('protect-none')));
} else {
continue;
}
}
}
if (casc) protactions.push(JWB.msg('log-protect-cascading') + ' ' + JWB.msg('log-protect-casctrue'));
protactions.push(JWB.msg('log-protect-expiry') + ' ' + (prots[0].expiry !== 'infinite' ? prots[0].expiry : JWB.msg('log-protect-indef')));
JWB.log('protect', response.protect.title, protactions.join('\n'));
JWB.status('done', false);
JWB.next(response.protect.title);
});
};
JWB.api.watch = function() {
JWB.status('watch');
var data = {
action: 'watch',
title: JWB.page.name,
token: JWB.page.watchtoken
};
if (JWB.page.watched) data.unwatch = true;
JWB.api.call(data, function(response) {
JWB.status(['watch-'+(JWB.page.watched ? 'removed' : 'added'), JWB.page.name], true);
JWB.page.watched = !JWB.page.watched;
$('#watchNow').html(JWB.msg('watch-' + (JWB.page.watched ? 'remove' : 'add')));
});
};
JWB.api.right = function(b) {
JWB.status('right');
JWB.api.call({
action: 'query',
meta: 'tokens',
type: 'userrights',
format: 'json',
formatversion: 2
}, function(response) {
var token = response.query.tokens.userrightstoken;
var type = (b ? 'add' : 'remove');
var data = {
action: 'userrights',
user: '#' + JWB.userid,
reason: $('#rightSummary').val(),
token: token,
format: 'json',
formatversion: 2
}
data[type] = JWB.test.floodFlag;
JWB.api.call(data, function(response) {
var change = response.userrights[type + (type.endsWith('e') ? 'd' : 'ed')],
info = (b ? true : false);
if (change.length === 0) info = null;
JWB.log('flag', JWB.ns[2].name + ':' + JWB.username, info);
JWB.status('done', true);
});
});
}
/* Overlay div resolves nearly everything, except for one: Scrolling. Source: //adhominem.w3spaces.com/JWB.html
// Highlight the first pagename (CSS seems to be insufficient). See //stackoverflow.com/questions/70959738 and .css line 481.
// For attribution: //stackoverflow.com/questions/3282505#3536762
JWB.highlight = function() {
JWB.pageCount();
pages = $('.articleListTrue').val();
pages = pages
.replace(/([^\n]+)/m, '<span class="firstpage">$1</span>')
.replace(/\n([^\n]+)\n([^\n]+)/gm, '\n$1\n<span class="evenpages">$2</span>')
.replace(/\n/g, '<br>');
$('.articleListFalse').html(s);
};
*/
JWB.remove = function() {
var replace = $(this).parentsUntil('.JWBpopup');
var boolify = function(b) {
try {
var c = b.val();
return true;
} catch (e) {
return false;
}
};
var input = function(b, r = replace) {
var input;
if (r.find('.replaceText')[0] && r.find('.replaceText').val()) input = r.find('.replaceText');
else if (r.find('.replaceWith')[0] && r.find('.replaceWith').val()) input = r.find('.replaceWith');
if (boolify(input)) {
if (!b) return input;
else return input.val();
} else {
return false;
}
};
if (input(true)) {
input(false).focus().select();
} else {
replace.slideUp('slow', function() {
replace.remove();
});
}
}
// For attribution: //loading.io/css (CC0)
// See .css line 480+.
JWB.loader = function(s, c, i) {
var f = function(n) {
return Math.round(n * 10) / 10;
};
var e = function() {
var r = '';
for (let j = 0; j < 12; j++) {
r += '<div class="loader-items" style="animation: loader 1.2s linear infinite;' +
' transform: rotate(' + (j * 30) + 'deg); animation-delay: ' + f((-1.1 + (j * 0.1))) + 's;"></div>';
}
return r;
};
var l = '<div class="loader-wrapper"' + (i ? ' id="' + i + '"' : '') + ' data-size="' + s + '" ' +
'style="position: relative; right: 10px;' + (c ? ' ' + c : '') + '">' +
'<div class="loader" style="transform: scale(' + (s / 80) + '); ' +
'width: ' + (s * 2) + 'px; height: ' + s + 'px;">' +
e() +
'</div>' +
'</div>';
return l;
}
/***** Pagelist functions *****/
JWB.pl.iterations = 0;
JWB.pl.done = true;
JWB.pl.stop = function() {
if (JWB.pl.done) {
JWB.pl.iterations = 0;
$('#pagelistPopup [disabled]:not(fieldset [disabled]), #pagelistPopup legend input, #pagelistPopup button').prop('disabled', false);
$('#pagelistPopup legend input').trigger('change');
$('#pagelistPopup button[type="submit"] + .loader-wrapper').remove();
}
}
JWB.pl.getNSpaces = function() {
var list = $('#pagelistPopup [name="namespace"]')[0];
return $('#pagelistPopup [name="namespace"]').val().join('|'); //.val() returns an array of selected options.
};
JWB.pl.getList = function(abbrs, lists, data) {
$('#pagelistPopup button, #pagelistPopup input, #pagelistPopup select, #pagelistPopup button').prop('disabled', true);
JWB.pl.iterations++;
if (data.ask !== undefined) {
JWB.pl.SMW(data.ask); // execute SMW call in parallel
JWB.pl.done = false;
data.ask = undefined;
}
if (!abbrs.length) {
JWB.pl.done = true;
return; // don't execute the rest; only a SMW query was entered.
}
data.action = 'query';
var nspaces = JWB.pl.getNSpaces();
for (var i=0;i<abbrs.length;i++) { // if namespaces are already set, use that instead (for apnamespace)
if (nspaces) data[abbrs[i]+'namespace'] = data[abbrs[i]+'namespace'] || nspaces;
data[abbrs[i]+'limit'] = 'max';
}
let linksList = lists.indexOf('links');
if (linksList !== -1) {
data.prop = 'links';
lists.splice(linksList, 1)
}
data.list = lists.join('|');
console.log('generating:', data);
JWB.api.call(data, function(response) {
var maxiterate = 100; //allow up to 100 consecutive requests at a time to avoid overloading the server.
if (!response.query) response.query = {};
if (response.watchlistraw) response.query.watchlistraw = response.watchlistraw; //adding some consistency
var plist = [];
if (response.query.pages) {
var links;
for (var id in response.query.pages) {
links = response.query.pages[id].links;
for (var i=0;i<links.length;i++) {
plist.push(links[i].title);
}
}
}
for (var l in response.query) {
if (l === 'pages') continue;
for (var i=0;i<response.query[l].length;i++) {
plist.push(response.query[l][i].title);
}
}
//add the result to the pagelist immediately, as opposed to saving it all up and adding in 1 go like AWB does
$('#articleList').val($.trim($('#articleList').val()) + '\n' + plist.join('\n'));
JWB.pageCount();
var cont = response.continue;
console.log('Continue', JWB.pl.iterations, cont);
if (cont && JWB.pl.iterations <= maxiterate) {
var lists = [];
if (response.query) { //compatibility with the code I wrote for the old query-continue. TODO: make this unnecessary?
for (var list in response.query) {
lists.push(list); //add to the new array of &list= values
}
}
var abbrs = [];
for (var abbr in cont) {
data[abbr] = cont[abbr]; //add the &xxcontinue= value to the data
if (abbr != 'continue') {
abbrs.push(abbr.replace('continue', '')); //find out what xx is and add it to the list of abbrs
}
}
JWB.pl.getList(abbrs, lists, data); //recursive function to get every page of a list
} else {
if (JWB.pl.iterations > maxiterate) {
JWB.status('pl-over-lim', true);
} else {
JWB.status('done', true);
}
JWB.pl.stop(); // if JWB.pl.done == true show stopped interface. Otherwise mark as done.
JWB.pl.done = true;
}
}, function() { //on error, simply reset and let the user work with what he has
JWB.status('done', true);
JWB.pl.stop();
JWB.pl.done = true;
});
};
JWB.pl.SMW = function(query) {
var data = {
action: 'ask',
query: query
};
JWB.api.call(data, function(response) {
console.log(response);
let list = response.query.results;
let pagevar = response.query.printrequests[1];
let pagevar_type = pagevar && pagevar.typeid;
if (pagevar) {
// either pagevar === undefined, or it's the first printrequest.
pagevar = pagevar.label;
}
let plist = [];
for (let l in list) {
let page = list[l];
let name = page.fulltext;
let suff;
if (pagevar) try {
let val = page.printouts[pagevar][0];
if (!val) continue; // this page does not contain this property.
switch (pagevar_type) {
case '_boo':
suff = val == 't'; // true if 't' else false;
break;
case '_wpg':
suff = val.fulltext;
break;
case '_dat':
// val.raw is also available but the unconventional format makes it a lot less convenient.
suff = val.timestamp;
break;
case '_qty':
suff = val.value + ' ' + val.unit;
break;
case '_mlt_rec':
// I doubt this is used anywhere, but it's not too hard to support.
suff = val.Text.item[0];
break;
case '_ref_rec':
// not supported; references contain too many properties.
break;
default:
suff = val;
}
} catch(e) {
console.error(e); // show error but ignore. Something is wrong in SMW query/api.
}
if (suff) {
plist.push(name + '|' + suff);
} else {
plist.push(name);
}
}
$('#articleList').val($.trim($('#articleList').val()) + '\n' + plist.join('\n'));
JWB.pageCount();
JWB.pl.stop(); // if JWB.pl.done == true show stopped interface. Otherwise mark as done.
JWB.pl.done = true;
});
}
//JWB.pl.getList(['wr'], ['watchlistraw'], {}) for watchlists
JWB.pl.generate = function() {
var $fields = $('#pagelistPopup fieldset').not('.disabled');
$('#pagelistPopup').find('button[type="submit"]').after(JWB.loader(15, 'bottom: 3px;'));
var abbrs = [],
lists = [],
data = {'continue': ''};
$fields.each(function() {
var list = $(this).find('legend input').attr('name');
var abbr;
if (list === 'linksto') { //Special case since this fieldset features 3 merged lists in 1 fieldset
if (!$('[name="title"]').val()) return;
$('[name="backlinks"], [name="embeddedin"], [name="imageusage"]').filter(':checked').each(function() {
var val = this.value;
abbrs.push(val);
lists.push(this.name);
data[val+'title'] = $('[name="title"]').val();
data[val+'filterredir'] = $('[name="filterredir"]:checked').val();
if ($('[name="redirect"]').prop('checked')) data[val+'redirect'] = true;
});
} else if (list === 'smwask') {
data.ask = $(this).find('#smwquery').val();
} else { //default input system
if ($(this).find('#psstrict').prop('checked')) {
// different list if prefixsearch is strict
let $input = $(this).find('#psstrict')
list = $input.attr('name');
abbr = $input.val();
} else {
abbr = $(this).find('legend input').val();
}
lists.push(list);
abbrs.push(abbr);
$(this).find('input').not('legend input').each(function() {
if ((this.type === 'checkbox' || this.type === 'radio') && this.checked === false) return;
if (this.id == 'psstrict') return; // ignore psstrict; it only affects how pssearch is handled
var name, val;
if (this.id == 'cmtitle') {
// making sure the page has a Category: prefix, in case the user left it out
let cgns = JWB.ns[14].name; // name for Category: namespace
if (!this.value.startsWith(cgns+':')) {
this.value = cgns+':'+this.value;
}
}
if (this.id == 'pssearch' && this.name == 'apprefix') {
// apprefix needs namespace separate from pagename
name = this.name;
let split = this.value.split(':')
val = split[1] || split[0];
let nsid = 0;
if (split[1]) { // if a namespace is given
for (let ns in JWB.ns) {
if (JWB.ns[ns]['*'] == split[0]) {
nsid = JWB.ns[ns].id;
break;
}
}
}
data.apnamespace = nsid;
} else {
name = this.name;
val = this.value;
}
if (data.hasOwnProperty(name)) {
data[name] += '|'+val;
} else {
data[name] = val;
}
});
console.log(abbrs, lists, data);
}
});
if (abbrs.length || data.ask) JWB.pl.getList(abbrs, lists, data);
else JWB.pl.stop();
};
/***** Setup functions *****/
JWB.setup.save = function(name) {
name = name || prompt(JWB.msg('setup-prompt', JWB.msg('setup-prompt-store')), $('#loadSettings').val());
if (name === null) return;
var self = JWB.settings[name] = {
string: {},
bool: {},
replaces: []
};
//inputs with a text value
$('textarea, input[type="text"], input[type="number"], select').not('.replaces input, #editBoxArea, #settings *').each(function() {
if (typeof $(this).val() == 'string') {
self.string[this.id] = this.value.replace(/\n{2,}/g, '\n');
} else {
self.string[this.id] = $(this).val();
}
});
self.replaces = [];
$('.replaces').each(function() {
if ($(this).find('.replaceText').val() || $(this).find('.replaceWith').val()) {
self.replaces.push({
replaceText: $(this).find('.replaceText').val(),
replaceWith: $(this).find('.replaceWith').val(),
useRegex: $(this).find('.useRegex').prop('checked'),
regexFlags: $(this).find('.regexFlags').val(),
ignoreNowiki: $(this).find('.ignoreNowiki').prop('checked')
});
}
});
$('input[type="radio"], input[type="checkbox"]').not('.replaces input').each(function() {
self.bool[this.id] = this.checked;
});
if (!$('#loadSettings option[value="'+name+'"]').length) {
$('#loadSettings').append('<option value="'+name+'">'+name+'</option>');
}
$('#loadSettings').val(name);
console.log(self);
};
JWB.setup.apply = function(name) {
name = name && JWB.settings[name] ? name : JWB.msg('default-setup');
var self = JWB.settings[name];
$('#loadSettings').val(name);
$('.replaces + .replaces').remove(); //reset find&replace inputs
$('.replaces input[type="text"]').val('');
$('.useRegex').each(function() {this.checked = false;});
$('#pagelistPopup legend input').trigger('change'); //fix checked state of pagelist generating inputs
for (var a in self.string) {
$('#'+a).val(self.string[a]);
}
for (var b in self.bool) {
$('#'+b).prop('checked', self.bool[b]);
}
var cur;
for (var c=0;c<self.replaces.length;c++) {
if ($('.replaces').length <= c) $('#moreReplaces')[0].click();
cur = self.replaces[c];
for (var d in cur) {
if (cur[d] === true || cur[d] === false) {
$('.replaces').eq(c).find('.'+d).prop('checked', cur[d]);
} else {
$('.replaces').eq(c).find('.'+d).val(cur[d]);
}
}
}
$('.useRegex, #containRegex, '+
'#pagelistPopup legend input, '+
'#viaJWB, #enableRETF').trigger('change'); //reset disabled inputs
};
JWB.setup.getObj = function() {
var settings = [];
for (var i in JWB.settings) {
if (i != '_blank') {
settings.push('"' + i + '": ' + JSON.stringify(JWB.settings[i]));
}
}
return '{\n\t' + settings.join(',\n\t') + '\n}';
};
JWB.setup.submit = function() {
var name = prompt(JWB.msg('setup-prompt', JWB.msg('setup-prompt-save')), $('#loadSettings').val());
if (name === null) return;
if ($.trim(name) === '') name = 'default';
JWB.setup.save(name);
JWB.status('setup-submit');
JWB.api.call({
action: 'query',
meta: 'tokens',
}, function(response) {
let edittoken = response.query.tokens.csrftoken;
JWB.api.call({
title: 'User:'+JWB.username+'/'+JWB.settingspage,
summary: JWB.msg(['setup-summary', JWB.contentLang]),
action: 'edit',
token: edittoken,
text: JWB.setup.getObj(),
minor: true
}, function(response) {
JWB.status('done', true);
JWB.log('edit', response.edit.title, response.edit.newrevid);
});
});
};
//TODO: use blob uri
JWB.setup.download = function() {
var name = prompt(JWB.msg('setup-prompt', JWB.msg('setup-prompt-save')), $('#loadSettings').val());
if (name === null) return;
if ($.trim(name) === '') name = 'default';
JWB.setup.save(name);
JWB.status('setup-dload');
var url = 'data:application/json;base64,' + btoa(unescape(encodeURIComponent(JWB.setup.getObj())));
var elem = $('#download-anchor')[0];
if (HTMLAnchorElement.prototype.hasOwnProperty('download')) { //use download attribute when possible, for its ability to specify a filename
elem.href = url;
elem.click();
setTimeout(function() {elem.removeAttribute('href');}, 2000);
} else { //fallback to iframes for browsers with no support for download="" attributes
elem = $('#download-iframe')[0];
elem.src = url.replace('application/json', 'application/octet-stream');
setTimeout(function() {elem.removeAttribute('src');}, 2000);
}
JWB.status('done', true);
};
JWB.setup.import = function(e) {
e.preventDefault();
file = (e.dataTransfer||this).files[0];
if ($(this).is('#import')) { //reset input
this.outerHTML = this.outerHTML;
$('#import').change(JWB.setup.import);
}
if (!window.hasOwnProperty('FileReader')) {
alert(JWB.msg('old-browser'));
JWB.status('old-browser', '<a target="_blank" href="'+JWB.index_php+'?title=Special:MyPage/'+JWB.settingspage+'">/'+JWB.settingspage+'</a>');
return;
}
if (file.name.split('.').pop().toLowerCase() !== 'json') {
alert(JWB.msg('not-json'));
return;
}
JWB.status('Processing file');
var reader = new FileReader();
reader.readAsText(file);
reader.onload = function(e) {
JWB.status('done', true);
try {
//Exclusion regex based on http://stackoverflow.com/a/23589204/1256925
//Removes all JS comments from the file, except when they're between quotes.
var c = reader.result;
var data = JSON.parse(c.replace(/("[^"]*")|(\/\*[\w\W]*\*\/|\/\/[^\n]*)/g, function(match, g1, g2) {
if (g1) return g1;
}));
} catch(e) {
alert(JWB.msg('json-err', e.message, JWB.msg('json-err-upload')));
console.log(e); //also log the error for further info
return;
}
JWB.setup.extend(data);
};
JWB.status('Processing file');
};
JWB.setup.load = function() {
JWB.status('setup-load');
var user = JWB.username||mw.config.get('wgUserName');
var oldtitle = 'User:' + user + '/'+JWB.settingspage; // page title for what was used before version 4.0
var newtitle = 'User:' + user + '/JWB-settings.json'; // new page title for all settings pages.
var titles = oldtitle;
// if the old title isn't JWB-settings.json, also query the new title.
if (oldtitle !== newtitle && JWB.test.hasJSON) {
titles += '|' + newtitle;
}
JWB.api.call({
action: 'query',
titles: titles,
prop: 'info|revisions',
meta: 'tokens',
rvprop: 'content',
indexpageids: true
}, function(response) {
if (JWB === false) return; //user is not allowed to use JWB
var firstrun = !JWB.setup.initialised;
JWB.setup.initialised = true;
var edittoken = response.query.tokens.csrftoken;
// determine correct page to get settings from
var pages = response.query.pages,
ids = response.query.pageids;
var page, exists = true;
if (ids.length == 2) {
var page0 = pages[ids[0]],
page1 = pages[ids[1]];
var oldpage, newpage;
if (page0.title == oldtitle) {
oldpage = page0;
newpage = page1;
} else {
oldpage = page1;
newpage = page0;
}
if (oldpage.missing === undefined && oldpage.redirect === undefined) {
// old page exists and is not a redirect
if (newpage.missing === undefined) {
// both old AND new page exist; throw error and load neither page.
let jsredir = '//www.mediawiki.org/wiki/Help:Redirects#JavaScript_page_redirect';
prompt(JWB.msg('duplicate-settings', oldtitle, newtitle, jsredir), jsredir);
exists = false;
} else {
// old page exists but new page doesn't; move the page to the new location.
JWB.setup.moveNew(oldtitle, newtitle, edittoken);
JWB.settingspage = 'JWB-settings.json';
return;
}
} else {
// Old page either doesn't exist or is a redirect. Don't bother with it.
page = newpage;
exists = (page.missing === undefined);
JWB.settingspage = 'JWB-settings.json';
}
} else {
page = pages[ids[0]];
exists = (page.missing === undefined);
}
if (!exists) {
// settings page does not exist; don't load anything
if (JWB.allowed && firstrun) JWB.setup.save('default'); //this runs when this callback returns after the init has loaded.
return;
}
var data = page.revisions[0]['*'];
if (!data) {
// settings page is empty; don't load anything.
if (JWB.allowed && firstrun) JWB.setup.save('default'); //this runs when this callback returns after the init has loaded.
return;
}
try {
data = JSON.parse(data);
} catch(e) {
alert(JWB.msg('json-err', e.message, JWB.msg('json-err-page', JWB.settingspage)) || 'JSON error:\n'+e.message);
JWB.setup.save('default');
return;
}
JWB.setup.extend(data);
JWB.status('done', true);
});
};
JWB.setup.moveNew = function(from, to, token) {
(new mw.Api()).post({
action: 'move',
from: from,
to: to,
token: token,
reason: JWB.msg(['setup-move-summary', JWB.contentLang]),
noredirect: true, // if possible, suppress redirects; the old page will no longer be needed if the new page exists.
movesubpages: true, // if any
movetalk: true, // if any
ignorewarnings: true,
}).done(function(response) {
if (response.error === undefined) {
JWB.log('move', from, to);
JWB.settingspage = to.split('/')[1];
alert(JWB.msg('moved-settings', from, to, JWB.msg('tab-log')));
JWB.setup.load(); // load settings from newly moved page.
}
});
}
JWB.setup.extend = function(obj) {
$.extend(JWB.settings, obj);
if (!JWB.settings.hasOwnProperty('default')) {
JWB.setup.save('default');
}
for (var i in JWB.settings) {
if ($('#loadSettings').find('option[value="'+i+'"]').length) continue;
$('#loadSettings').append('<option value="'+i+'">'+i+'</option>');
}
JWB.setup.apply($('#loadSettings').val());
};
JWB.setup.del = function() {
var name = $('#loadSettings').val();
if (name === '_blank') return alert(JWB.msg('setup-delete-blank'));
var temp = {};
temp[name] = JWB.settings[name];
JWB.setup.temp = $.extend({}, temp);
delete JWB.settings[name];
$('#loadSettings').val('default');
if (name === 'default') {
JWB.setup.apply('_blank');
JWB.setup.save('default');
JWB.status(['del-default', '<a href="javascript:JWB.setup.undelete();">'+JWB.msg('status-del-undo')+'</a>'], true);
} else {
$('#loadSettings').find('[value="'+name+'"]').remove();
JWB.setup.apply();
JWB.status(['del-setup', name, '<a href="javascript:JWB.setup.undelete();">'+JWB.msg('status-del-undo')+'</a>'], true);
}
};
JWB.setup.undelete = function() {
JWB.setup.extend(JWB.setup.temp);
JWB.status('done', true);
};
/***** Main other functions *****/
//Show status message status-`action`, or status-`action[0]` with arguments `action[1:]`
JWB.status = function(action, done) {
if (JWB.test.bot && $('#autosave').prop('checked') && !JWB.isStopped) { // Disable summary when auto-saving
$('#summary, .editbutton, #otherButtons button:not(.starts, .stops), #rightGrant, #rightRevoke, #editBoxArea').prop('disabled', true);
} else { // Disable box when not done (so busy loading). re-enable when done loading.
$('#summary, #rightSummary, #rightGrant, #rightRevoke').prop('disabled', !done);
}
var status;
if (action instanceof Array) {
action[0] = 'status-'+action[0];
status = JWB.msg.apply(this, action)
} else {
status = JWB.msg('status-'+action);
}
if (status === false) return;
if (status) {
if (!done) { //spinner if not done
status += JWB.loader(10, 'bottom: 4px; margin-left: 3px;');
}
} else {
status = action;
}
$('#status').html(status);
JWB.pageCount();
return action=='done';
};
JWB.pageCount = function() {
if (JWB.allowed === false||!$('#articleList').length) return;
$('#articleList').val(($('#articleList').val()||'')
.replace(/(?<!\|.*)[ \t_]+/gm, ' ') // Duplicated spaces, underscores and tabs in names.
.replace(/^ +| +(?=\|)|(?<!\|.*) +$/gm, '') // Leading and trailing spaces in names.
.replace(/^\|.*$/gm, '') // Vars without corresponding names.
.replace(/\n{2,}/gm, '\n').replace(/(?<=^.+$)\n(?![^ ])|(?<![^ ])\n(?![^ ])|(?<![^ ])\n(?=^.+$)/gm, '') // Blank lines.
);
JWB.list = $('#articleList').val().split('\n');
var count = JWB.list.length;
if (count === 1 && JWB.list[0] === '') count = 0;
$('#totPages').html(count);
};
//Perform all specified find&replace actions
JWB.replace = function(input, callback) {
if (JWB.isStopped) return;
JWB.status('replacing');
if (!JWB.worker.isWorking() && JWB.worker.supported) {
// if the worker is not already working, then re-init to make sure we've not got any broken leftovers from the previous page
JWB.worker.init();
}
JWB.newContent = input;
JWB.pageCount();
var varOffset = JWB.list[0].includes('|') ? JWB.list[0].indexOf('|') + 1 : 0;
JWB.page.pagevar = JWB.list[0].substr(varOffset);
$('.replaces').each(function() {
var $this = $(this);
var replaceText = $this.find('.replaceText').val(),
replaceWith = $this.find('.replaceWith').val();
if (replaceText.length == 0 && replaceWith.length == 0) return; // don't bother replacing 2 empty strings.
var regexFlags = $this.find('.regexFlags').val();
var replace = replaceText.replace(/\$x/gi, JWB.page.pagevar).replace(/\\{2}/g, '\\').replace(/\\n/g, '\n') || '$';
var useRegex = replaceText.length == 0 || $this.find('.useRegex').prop('checked');
if (useRegex && regexFlags.includes('_')) {
replace = replace.replace(/[ _]/g, '[ _]'); //replaces any of [Space OR underscore] with a match for spaces or underscores.
replace = replace.replace(/(\[[^\]]*)\[ _\]/g, '$1 _'); //in case a [ _] was placed inside another [] match, remove the [].
regexFlags = regexFlags.replace('_', '');
}
//apply replaces where \n and \\ work in both regular text and regex mode.
var rWith = replaceWith.replace(/\$x/gi, JWB.page.pagevar).replace(/\\{2}/g, '\\').replace(/\\n/g, '\n');
if (rWith.length === 0 && replace === '$') return;
try {
let replaceDone = function(result, err) {
console.log('done replacing', result, err);
if (err === undefined) {
JWB.newContent = result;
if (JWB.worker.queue.length == 0 && JWB.worker.supported) {
// all workers are done
JWB.status('done', true);
callback(JWB.newContent);
}
} else if (err == 'Timeout exceeded') {
if (JWB.worker.queue.length == 0 && JWB.worker.supported) {
// all workers have exceeded their time and/or have finished
JWB.status('done', true);
callback(JWB.newContent); // newContent remains unmodified due to timeout.
}
}
}
if ($this.find('.ignoreNowiki').prop('checked')) {
if (!useRegex) {
replace = replace.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
regexFlags = 'g';
}
JWB.worker.unparsedReplace("~"+"~~JWB.newContent", replace, regexFlags, rWith, replaceDone);
} else if (useRegex) {
JWB.worker.replace("~"+"~~JWB.newContent", replace, regexFlags, rWith, replaceDone);
} else {
JWB.newContent = JWB.newContent.split(replace).join(rWith); //global replacement without having to escape all special chars.
}
} catch(e) {
console.log('Regex error:', e)
JWB.stop();
return JWB.status('regex-err', false);
}
});
if ($('#enableRETF').prop('checked')) {
JWB.newContent = RETF.replace(JWB.newContent);
}
if (!JWB.worker.isWorking()) {
// no workers were called
JWB.status('done', true);
callback(JWB.newContent);
}
};
JWB.skipRETF = function() {
if (!$('#enableRETF').prop('checked')) return; // RETF is not enabled to begin with
if (JWB.isStopped === true) return; // don't mess with the edit box when stopped
$('#enableRETF').prop('checked', false);
JWB.replace(JWB.page.content, function(newContent) {
JWB.editPage(newContent);
JWB.updateButtons();
$('#enableRETF').prop('checked', true);
});
}
// Edit the current page and pre-fill the newContent.
JWB.editPage = function(newContent) {
$('#editBoxArea').val(newContent);
$('#currentpage').html(JWB.msg('editbox-currentpage', JWB.page.name, encodeURIComponent(JWB.page.name)));
if ($('#preparse').prop('checked')) {
$('#articleList').val($.trim($('#articleList').val()) + '\n' + JWB.list[0]); //move current page to the bottom
JWB.next();
return;
} else if (JWB.test.bot && $('#autosave').prop('checked')) {
JWB.api.diff(function() {
//timeout will take #throttle's value * 1000, if it's a number above 0. Currently defaults to 0.
setTimeout(JWB.api.submit, Math.max(+$('#throttle').val() || 0, 0) * 1000, JWB.page.name);
});
} else {
JWB.api.diff();
}
}
//Adds a line to the logs tab.
JWB.log = function(action, page, info) {
if (page === '') return; // Skipping force logging a null title.
var d = new Date();
var pagee = encodeURIComponent(page);
var extraInfo = '', actionStat = '';
switch (action) {
case 'edit':
if (typeof info === 'undefined') {
action = 'null';
actionStat = 'nullEdits';
} else {
extraInfo = '(<a target="_blank" href="'+JWB.index_php+'?title='+pagee+'&diff='+info+'">'+JWB.msg('log-diff')+'</a>)';
actionStat = 'pagesSaved';
}
break;
case 'nobots':
action = 'bots';
extraInfo = '(<a target="_blank" href="https://en.wikipedia.org/wiki/Template:Bots">'+JWB.msg('log-botskip')+'</a>)';
// no break;
case 'skip':
actionStat = 'pagesSkipped';
break;
case 'move':
extraInfo = '(<a target="_blank" href="/wiki/'+encodeURIComponent(info)+'" title="'+JWB.msg('log-info')+'">'+JWB.msg('log-movedto')+'</a>)';
break;
case 'delete':
action = 'delt';
break;
case 'undelete':
action = 'udel';
break;
case 'protect':
action = 'prot';
extraInfo = '(<a title="'+info+'">'+JWB.msg('log-protect')+'</a>)';
break;
case 'flag':
if (info === true) {
extraInfo = '(<a title="'+JWB.msg('log-right-granted', JWB.test.floodFlag)+'">+</a>)';
}
if (info === false) {
extraInfo = '(<a title="'+JWB.msg('log-right-revoked', JWB.test.floodFlag)+'">–</a>)';
}
if (info === null) {
action = 'skip';
extraInfo = '(<a title="'+JWB.msg('log-right-nochange')+'">=</a>)';
}
break;
}
actionStat = '#' + (actionStat || 'otherActions');
$(actionStat).html(+$(actionStat).html() + 1);
$('#actionlog tbody').append(
'<tr>'+
'<td class="timeStamps">'+(JWB.fn.pad0(d.getHours())+':'+JWB.fn.pad0(d.getMinutes())+':'+JWB.fn.pad0(d.getSeconds()))+'</td>'+
'<th>'+action+'</th>'+
'<td class="pageNames">'+
'<a target="_blank" href="/wiki/'+pagee.replace(/ /g, '_')+'" class="reAdd-'+action+'" title="'+page+'">'+page+'</a>'+
'</td>'+
'<td class="extraInfo">'+ extraInfo +'</td>'+
'<td class="reAddButtons">'+
'<button class="reAddButton" data-readd="'+ escape(page)+ '">'+JWB.msg('readd-button')+'</button>'+
'</td>'+
'</tr>'
).parents('.JWBtabc').scrollTop($('#actionlog tbody').parents('.JWBtabc')[0].scrollHeight);
if ($('#startLog').prop('disabled')) { // Turn "reAdd" buttons off while editing. See also JWB.start (line ~1430).
$('.reAddButton:not(disabled)').prop('disabled', true);
}
};
//Move to the next page in the list
JWB.next = function(nextPage) {
// cancel any still ongoing regex match/replace functions, since we're moving on to another page.
JWB.worker.cancelAll();
if ($.trim(nextPage) && !$('#skipAfterAction').prop('checked')) {
nextPage = $.trim(nextPage) + '\n';
} else {
nextPage = '';
}
$('#articleList').val($('#articleList').val().replace(/^.*\n?/, nextPage));
JWB.list.splice(0, 1);
JWB.pageCount();
JWB.api.get(JWB.list[0].split('|')[0]);
// JWB.highlight(); // See line 614.
};
//Stop everything, reset inputs and editor
JWB.stop = function() {
console.trace('stopped');
// Things to turn off.
$('#stopbutton, #stopOther, #stopLog, '+ // Stop buttons.
'.JWBtabc[data-tab="2"] .editbutton, #watchNow, #skipRETF, '+ // Edit Save, Skip, Prev, Diff, Watch, RETF.
'.JWBtabc[data-tab="4"] button:not(.starts, .stops), '+ // Other Move, Del, Prot, Skip.
'.JWBtabc[data-tab="-2"] button').prop('disabled', true); // Readd Readd.
// Things to turn on.
$('#startbutton, #startOther, #startLog, '+ // Start buttons.
'#articleList, .JWBtabc[data-tab="1"] button, #import, '+ // Setup things + list.
'#rightGrant, #rightRevoke, .reAddButton, #readdClick, '+ // Edit Summary, Other RightG/R, Log ReAdd.
'#replacesPopup button, #replacesPopup input, #replacesPopup .replaces, '+ // Popup things.
'.JWBtabc input, .JWBtabc select').prop('disabled', false); // All inputs and selects
$('#importLabel').removeClass('disabled'); // This need a line for its own as it is <label class="button"> (line 1830+).
if (!!$('#replacesPopup .replaces.sortable')[0]) $('#replacesPopup').sortable('enable');
$('#replacesPopup .replaces').removeClass('disabled');
$('#resultWindow').html('');
$('#editBoxArea').val('');
$('#currentpage').html(JWB.msg('editbox-currentpage', ' ', ' '));
JWB.pl.done = true;
JWB.pl.stop();
JWB.status('done', true);
JWB.isStopped = true;
};
//Start AutoWikiBrowsing
JWB.start = function() {
JWB.pageCount(); // See $('#articleList').focusout() (line 2100+).
var replacebool = function() {
var bool = false;
$('.replaceText').each(function() {
if ($(this).val()) bool = true;
});
$('.replaceWith').each(function() {
if ($(this).val()) bool = true;
});
return bool;
}
if (JWB.list.length === 0 || (JWB.list.length === 1 && !JWB.list[0])) {
alert(JWB.msg('no-pages-listed'));
} else if ($('#skipNoChange').prop('checked') && !replacebool() && !$('#enableRETF').prop('checked')) {
alert(JWB.msg('infinite-skip-notice'));
} else {
JWB.isStopped = false;
if ($('#preparse').prop('checked')) {
if (!$('#articleList').val().match('#PRE-PARSE-STOP')) {
$('#articleList').val($.trim($('#articleList').val()) + '\n#PRE-PARSE-STOP'); //mark where to stop pre-parsing
}
} else {
$('#preparse-reset').click();
}
// Things to turn on.
$('#stopbutton, #stopOther, #stopLog, '+ // Stop buttons
'.JWBtabc[data-tab="2"] .editbutton, #watchNow, #skipRETF, '+ // Edit Save, Skip, Prev, Diff, Watch, RETF.
'.JWBtabc[data-tab="4"] button:not(.starts, .stops), '+ // Other Move, Del, Prot, Skip.
'.JWBtabc[data-tab="-2"] button').prop('disabled', false); // Readd Readd.
// Things to turn off.
$('#startbutton, #startOther, #startLog, '+ // Start buttons
'#articleList, .JWBtabc[data-tab="1"] button:not(#articleListTop, #articleListBot), #import, '+ // Setup things + list apart from TopBot.
'#rightGrant, #rightRevoke, .reAddButton, #readdClick, '+
'#replacesPopup button, #replacesPopup input, #replacesPopup .replaces, '+
'.JWBtabc input, .JWBtabc select').prop('disabled', true);
$('#importLabel').addClass('disabled'); // This need a line for its own as it is <label class="button"> (line 1830+).
if (!!$('#replacesPopup .replaces.sortable')[0]) $('#replacesPopup').sortable('disable'); // No drag'n'drop when editing.
$('#replacesPopup .replaces').addClass('disabled');
if (!JWB.test.bot || !$('#autosave').prop('checked')) {
// keep summary / watchlist options enabled when not in autosave mode
$('#minorEdit, #summary, #viaJWB, #watchPage, #rightGrant, #rightRevoke, '+
'.JWBtabc[data-tab="4"] select, .JWBtabc[data-tab="4"] input, '+
'.JWBtabc[data-tab="4"] checkbox, #editBoxArea').prop('disabled', false);
}
JWB.api.get(JWB.list[0].split('|')[0]);
}
};
JWB.updateButtons = function() {
if (!JWB.page.exists && $('#deletePage').is('.delete')) {
$('#deletePage').removeClass('delete').addClass('undelete').html(JWB.msg('editbutton-undelete'));
JWB.fn.blink('#deletePage'); //Indicate the button has changed
} else if (JWB.page.exists && $('#deletePage').is('.undelete')) {
$('#deletePage').removeClass('undelete').addClass('delete').html(JWB.msg('editbutton-delete'));
JWB.fn.blink('#deletePage'); //Indicate the button has changed
}
if (!JWB.test.bot || !$('#autosave').prop('checked')) {
if (!JWB.page.exists) {
$('#movePage').prop('disabled', true);
} else {
$('#movePage').prop('disabled', false);
}
}
$('#watchNow').html( JWB.msg('watch-' + (JWB.page.watched ? 'remove' : 'add')) );
};
/***** Web Worker functions *****/
JWB.worker.supported = !!window.Worker; // if window.Worker exists, we can use workers. Unless CSP blocks us.
JWB.worker.queue = [];
// Load function required to properly load the worker, since directly using `new Worker(url)`
// for cross-origin URLs does not work even with CORS/CSP rules all allowing it.
// See https://stackoverflow.com/q/66188950/1256925 for this exact question
JWB.worker.load = function(callback) {
if (JWB.worker.blob) return callback(); // already successfully built
$.getScript(JWB.imports['worker.js'], function() {
// Firefox does not understand try..catch for content security policy violations, so define the worker functions regardless of the blob support.
JWB.worker.functions = JWB.worker.function();
// the loaded script just defined JWB.worker.function; convert it to a blob url
// Based on https://stackoverflow.com/a/33432215/1256925
if (JWB.worker.supported) try {
let blob = new Blob(['('+JWB.worker.function.toString()+')()'], {type: 'text/javascript'});
JWB.worker.blob = URL.createObjectURL(blob);
callback();
} catch(e) {
if (e.code == 18) {
JWB.worker.supported = false;
}
}
});
}
// Create a worker to be able to preform regex operations without hanging the current process.
// Based on https://stackoverflow.com/q/66153487/1256925
JWB.worker.init = function() {
JWB.worker.load(function() {
JWB.worker.worker = new Worker(JWB.worker.blob);
JWB.worker.callback = undefined; // explicitly set to the implicit value of undefined.
JWB.worker.timeout = 0;
JWB.worker.queue = [];
JWB.worker.worker.onmessage = function(e) {
clearTimeout(JWB.worker.timeout);
JWB.worker.timeout = 0;
if (JWB.isStopped) {
// we're stopped; clear the queue and stop.
JWB.worker.queue = [];
} else if (JWB.worker.callback !== undefined) {
JWB.worker.callback(e.data.result, e.data.err);
} else {
console.error("Worker finished without callback set:", e.data, e);
}
JWB.worker.next(true);
}
});
};
// Boolean; check if the worker is currently occupied.
JWB.worker.isWorking = function() {
return JWB.worker.callback !== undefined;
};
// Cancel current worker's task (e.g. due to timeout)
JWB.worker.terminate = function() {
console.log('terminating');
let w = JWB.worker;
w.worker.terminate();
w.callback(undefined, 'Timeout exceeded');
let queue = w.queue; // save old queue
w.init(); // re-init this worker, since the previous one is presumed dead (and terminated).
w.queue = queue; // restore queue
};
// Cancel all workers (e.g. due to no longer needing the worker's queued services)
JWB.worker.cancelAll = function() {
JWB.worker.queue = [];
if (JWB.worker.worker) JWB.worker.worker.terminate(); // do not call the callback.
}
// Set worker to work, or queue the worker task.
JWB.worker.do = function(msg, callback) {
if (JWB.worker.isWorking()) {
JWB.worker.queue.push({msg: msg, callback: callback});
} else {
var timelimit = parseInt($('#timelimit').val()) || 3000;
JWB.worker.callback = callback;
// Expand "JWB.string" into JWB['string']; to allow the string to be loaded at execution time instead of queue time.
// Start with 3x ~ because that cannot exist as the start of an actual page
if (msg.str && msg.str.indexOf('~'+'~~JWB.') === 0) msg.str = JWB[msg.str.substr(7)]; // For now, 1-deep expansion is sufficient.
JWB.worker.worker.postMessage(msg);
JWB.worker.timeout = setTimeout(function() {
if (!JWB.worker.isWorking()) {
console.error('Worker error');
JWB.worker.next(true);
return;
}
JWB.worker.terminate();
JWB.worker.next(true);
}, timelimit);
}
};
// Execute the next task in the queue
JWB.worker.next = function(force = false) {
if (force) {
// force means the function that's calling next() has handled the previous worker task. Clean up after it.
JWB.worker.callback = undefined;
} else if (JWB.worker.isWorking()) {
// still working and the calling function did not specify proper exit of the previous task yet.
return false;
}
if (JWB.worker.queue.length === 0) return true;
var q = JWB.worker.queue.shift();
JWB.worker.do(q.msg, q.callback);
};
/***** Functions using workers *****/
JWB.worker.match = function(str, pattern, flags, callback) {
if (JWB.worker.supported) {
JWB.worker.do({cmd: 'match', str, pattern, flags}, callback);
} else {
if (str && str.indexOf('~'+'~~JWB.') === 0) str = JWB[str.substr(7)]; // For now, 1-deep expansion is sufficient.
JWB.worker.functions.match(str, pattern, flags, callback);
}
};
JWB.worker.replace = function(str, pattern, flags, rWith, callback) {
if (JWB.worker.supported) {
JWB.worker.do({cmd: 'replace', str, pattern, flags, rWith}, callback);
} else {
if (str && str.indexOf('~'+'~~JWB.') === 0) str = JWB[str.substr(7)]; // For now, 1-deep expansion is sufficient.
JWB.worker.functions.replace(str, pattern, flags, rWith, callback);
}
};
JWB.worker.unparsedReplace = function(str, pattern, flags, rWith, callback) {
if (JWB.worker.supported) {
JWB.worker.do({cmd: 'unparsedreplace', str, pattern, flags, rWith}, callback);
} else {
if (str && str.indexOf('~'+'~~JWB.') === 0) str = JWB[str.substr(7)]; // For now, 1-deep expansion is sufficient.
JWB.worker.functions.unparsedreplace(str, pattern, flags, rWith, callback);
}
};
/***** General functions *****/
//Clear all existing timers to prevent them from getting errors
JWB.fn.clearAllTimeouts = function() {
var i = setTimeout(function() {
return void(0);
}, 1000);
for (var n=0;n<=i;n++) {
clearTimeout(n);
clearInterval(n);
}
console.log('Cleared all running intervals up to index', i);
};
//Filter an array to only contain unique values.
JWB.fn.uniques = function(arr) {
var a = [];
for (var i=0, l=arr.length; i<l; i++) {
if (!a.includes(arr[i]) && arr[i] !== '') {
a.push(arr[i]);
}
}
return a;
};
// Filter an array to remove non-duplicated values and uniquify the rest.
JWB.fn.duplicates = function(arr) {
var a = [];
var b = [];
for (let i = 0; i < arr.length; i++) {
if (!a.includes(arr[i])) {
a.push(arr[i]);
} else if (!b.includes(arr[i])) {
b.push(arr[i]);
}
}
return b;
};
// code taken directly from [[Template:Bots]] and changed structurally (not functionally) for readability.
// The user in this case is "JWB" to deny this script.
// the user parameter is still kept as an optional parameter to maintain functionality as given on that template page.
JWB.fn.allowBots = function(text, user = 'JWB') {
var usr = user.replace(/([\(\)\*\+\?\.\-\:\!\=\/\^\$])/g, '\\$1');
if (!new RegExp('\\{\\{\\s*(nobots|bots[^}]*)\\s*\\}\\}', 'i').test(text))
return true;
if (new RegExp("\\{\\{\\s*bots\\s*\\|\\s*deny\\s*=\\s*([^}]*,\\s*)*" + usr + "\\s*(?=[,\\}])[^}]*\\s*\\}\\}", "i").test(text))
return false
else
return new RegExp('\\{\\{\\s*((?!nobots)|bots(\\s*\\|\\s*allow\\s*=\\s*((?!none)|([^}]*,\\s*)*' + usr +
'\\s*(?=[,\\}])[^}]*|all))?|bots\\s*\\|\\s*deny\\s*=\\s*(?!all)[^}]*|bots\\s*\\|\\s*optout=(?!all)[^}]*)\\s*\\}\\}', 'i').test(text);
}
//Prepends zeroes until the number has the desired length of len (default 2)
JWB.fn.pad0 = function(n, len = 2) {
n = n.toString();
return n.length < len ? Array(len-n.length+1).join('0')+n : n;
};
JWB.fn.blink = function(el, t) {
t=t?t:500;
$(el).prop('disabled', true)
.children().animate({opacity:'0.1'}, t-100)
.animate({opacity:'1'}, t)
.animate({opacity:'0.1'}, t-100)
.animate({opacity:'1'}, t);
setTimeout("$('"+el+"').prop('disabled', false)", t*4-400);
};
JWB.fn.setSelection = function(el, start, end, dir) {
dir = dir||'none'; //Default value
end = end||start; //If no end is specified, assume the caret is placed without creating text selection.
if (el.setSelectionRange) {
el.focus();
el.setSelectionRange(start, end, dir);
} else if (el.createTextRange) {
var rng = el.createTextRange();
rng.collapse(true);
rng.moveStart('character', start);
rng.moveEnd('character', end);
rng.select();
}
};
JWB.fn.scrollSelection = function(el, index) { //function to fix scrolling to selection - doesn't do that automatically.
var newEl = document.createElement('textarea'); //create a new textarea to simulate the same conditions
var elStyle = getComputedStyle(el);
newEl.style.height = elStyle.height; //copy over size-influencing styles
newEl.style.width = elStyle.width;
newEl.style.lineHeight = elStyle.lineHeight;
newEl.style.fontSize = elStyle.fontSize;
newEl.value = el.value.substr(0, index);
document.body.appendChild(newEl); //needs to be added to the HTML for the scrollHeight and clientHeight to work.
if (newEl.scrollHeight != newEl.clientHeight) {
el.scrollTop = newEl.scrollHeight - 2;
} else {
el.scrollTop = 0;
}
newEl.remove(); //clean up the mess I've made
};
//i18n function
JWB.msg = function(message) {
var args = arguments;
var lang = JWB.lang;
if (typeof message === 'object') {
lang = message[1];
message = message[0];
}
if (lang == 'qqx') return '(' + message + ')';
if (!JWB.messages || !JWB.messages.en) return '\u29FC'+message+'\u29FD'; // same surrounding <> as used in mw.msg();
var msg;
if (JWB.messages.hasOwnProperty(lang) && JWB.messages[lang].hasOwnProperty(message)) {
msg = JWB.messages[lang][message];
} else {
msg = (JWB.messages.en.hasOwnProperty(message)) ? JWB.messages.en[message] : '\u29FC'+message+'\u29FD';
}
msg = msg.replace(/\$(\d+)/g, function(match, num) {
return args[+num] || match;
});
return msg;
};
/***** Init *****/
JWB.init = function() {
console.log(JWB.messages.en, !!JWB.messages.en);
JWB.setup.load();
JWB.worker.init();
JWB.fn.clearAllTimeouts();
var findreplace = function(a) {
var sortable = JWB.test.sortable;
var findreplace = '<div class="replaces'+(sortable ? ' sortable' : '')+'"'+(a === false ? ' style="display: none;"' : '')+'>'+
'<label class="replaceTextLabel inputFlex">'+
'<span class="flexCenter">'+JWB.msg('label-rtext')+'</span>'+
'<input type="text" class="replaceText"/>'+
'</label>'+
'<label class="replaceWithLabel inputFlex">'+
'<span class="flexCenter">'+JWB.msg('label-rwith')+'</span>'+
'<input type="text" class="replaceWith"/>'+
'</label>'+
'<div class="switches">'+
(typeof a === 'undefined' ? '' :
'<button class="removeThis">'+JWB.msg('button-remove-this')+'</button>'
)+
'<div class="regexswitch">'+
'<label data-label="checkbox">'+
'<input type="checkbox" class="useRegex">'+
'<span class="flexBaseline">'+JWB.msg('label-useregex')+'</span>'+
'</label>'+
/* (typeof a !== 'undefined' ? '' :
'<a class="re101" href="http://regex101.com/#javascript" target="_blank">?</a>'
)+ */
'<div class="divisorWrapper" style="display: none;"><span class="divisor"></span></div>'+
'<label class="regexFlagsLabel" title="'+JWB.msg('tip-regex-flags')+'" style="display: none;">'+
'<span class="flexCenter">'+JWB.msg('label-regex-flags')+'</span>'+
'<input type="text" class="regexFlags" value="g"/>'+ //default: global replacement
'</label>'+
'<br/>'+
'</div>'+
'<div class="ignoreswitch">'+
'<label data-label="checkbox" title="'+JWB.msg('tip-ignore-comment')+'">'+
'<input type="checkbox" class="ignoreNowiki">'+
'<span class="flexBaseline">'+JWB.msg('label-ignore-comment')+'</span>'+
'</label>'+
'</div>'+
'</div>'
'</div>';
return findreplace;
}
var NSList = '<select multiple name="namespace" id="namespacelist">';
for (var i in JWB.ns) {
if (parseInt(i) < 0) continue; //No Special: or Media: in the list
NSList += '<option value="'+JWB.ns[i].id+'" selected>'+(JWB.ns[i].name || '('+JWB.msg('namespace-main')+')')+'</option>';
}
NSList += '</select>';
JWB.protection.other.function.levels = function(type) {
var list = '';
list += (type === 'edit' ? '' : '<option value="" selected>('+JWB.msg('protect-like-edit')+')</option>');
for (let i = 0; i < JWB.protection.levels.length; i++) {
list +=
'<option value="'+
(JWB.protection.levels[i] == '' ? 'all' : JWB.protection.levels[i]) + '"' +
(JWB.protection.levels[i] == '' && type === 'edit' ? ' selected' : '') +
'>' + // JWB.msg('protect-' + JWB.protection.levels[i]) as output for false? See JWB.protection.other.levels.
(JWB.protection.levels[i].length === 0 ? JWB.msg('protect-none') : JWB.protection.levels[i]) +
'</option>';
}
return list;
}
JWB.protection.other.function.types = function() {
var list = [];
for (let i = 0; i < JWB.protection.types.length; i++) {
if (JWB.protection.types[i] == 'create') continue;
list.push(
'<div class="otherOptionLabels">'+
'<label for="'+JWB.protection.types[i]+'Prot">'+JWB.msg('protect-' + JWB.protection.types[i])+'</label>'+
'<select class="protectSelect" id="'+JWB.protection.types[i]+'Prot">'+
JWB.protection.other.function.levels(JWB.protection.types[i])+
'</select>'+
'</div>'
);
}
return list.join('');
}
/***** Interface *****/
document.title = 'AutoWikiBrowser Script'+(document.title.split('-')[1] ? ' -'+document.title.split('-')[1] : '');
$('body').html(
'<article id="resultWindow"></article>'+
'<main id="inputsWindow">'+
'<div id="inputsBox">'+
'<aside id="articleBox">'+
'<b>'+JWB.msg('pagelist-caption')+'</b>'+
'<textarea id="articleList"></textarea>'+
'</aside>'+
'<section id="tabs">'+
'<nav class="tabholder">'+
'<span class="JWBtab" data-tab="1">'+JWB.msg('tab-setup')+'</span> '+
'<span class="JWBtab active" data-tab="2">'+JWB.msg('tab-editing')+'</span> '+
'<span class="JWBtab" data-tab="3">'+JWB.msg('tab-skip')+'</span> '+
'<span class="JWBtab" data-tab="4">'+JWB.msg('tab-other')+'</span> '+
'<span class="JWBtab" data-tab="-2">'+JWB.msg('tab-readd')+'</span>'+
'<span class="JWBtab log" data-tab="-1">'+JWB.msg('tab-log')+'</span> '+
'</nav>'+
'<section class="JWBtabc" data-tab="1"></section>'+
'<section class="JWBtabc active" data-tab="2"></section>'+
'<section class="JWBtabc" data-tab="3"></section>'+
'<section class="JWBtabc" data-tab="4"></section>'+
'<section class="JWBtabc log" data-tab="-2"></section>'+
'<section class="JWBtabc log" data-tab="-1"></section>'+
'<footer id="statusBar"><div id="status">'+JWB.msg('status-done')+'</div></footer>'+
'</section>'+
'<aside id="editBox">'+
'<b>'+JWB.msg('editbox-caption')+' - <span id="currentpage">'+JWB.msg('editbox-currentpage', ' ', ' ')+'</span></b>'+
'<textarea id="editBoxArea" disabled></textarea>'+
'</aside>'+
'</div>'+
'</main>'+
'<footer id="stats">'+
JWB.msg('stat-pages')+' <span id="totPages">0</span>; '+
JWB.msg('stat-save')+' <span id="pagesSaved">0</span>; '+
JWB.msg('stat-null')+' <span id="nullEdits">0</span>; '+
JWB.msg('stat-skip')+' <span id="pagesSkipped">0</span>; '+
JWB.msg('stat-other')+' <span id="otherActions">0</span>; '+
'</footer>'+
'<div id="overlay" style="display: none;"></div>'+
'<section class="JWBpopup" id="replacesPopup" style="display: none;">'+
'<button class="stickyButtons" id="moreReplaces">'+JWB.msg('button-more-fields')+'</button>'+
'<hr id="moreReplacesBorder">'+findreplace(true)+
'</section>'+
'<section class="JWBpopup" id="pagelistPopup" style="display: none;">'+
'<form action="#" id="pl-form"></form>'+
'</section>'
);
$('.JWBtabc[data-tab="1"]').html(
'<fieldset id="pagelist">'+
'<legend>'+JWB.msg('label-pagelist')+'</legend>'+
'<div class="collapsibleFieldset">'+
'<div id="removeDupesUniqs">'+
'<label for="removeDupesUniqsButtons">'+JWB.msg('label-remove')+'</label>'+
'<span id="removeDupesUniqsButtons">'+
'<button id="removeDupes" title="'+JWB.msg('tip-remove-dupes')+'">'+JWB.msg('button-remove-dupes')+'</button>'+
'<button id="removeUniqs" title="'+JWB.msg('tip-remove-uniqs')+'">'+JWB.msg('button-remove-uniqs')+'</button>'+
'<span class="divisor"></span>'+
'<button id="removeUndo" title="'+JWB.msg('tip-remove-undo')+'">'+JWB.msg('button-remove-undo')+'</button>'+
'</span>'+
'</div>'+
'<div id="preparseSettings">'+
'<label data-label="checkbox" title="'+JWB.msg('tip-preparse')+'">'+
'<input type="checkbox" id="preparse">'+
'<span class="flexBaseline">'+JWB.msg('preparse')+'</span>'+
'</label>'+
'<span class="divisor"></span>'+
'<button id="preparse-reset" title="'+JWB.msg('tip-preparse-reset')+'">'+JWB.msg('preparse-reset')+'</button>'+
'</div>'+
'<div id="articleListButtons">'+
'<button id="pagelistButton">'+JWB.msg('pagelist-generate')+'</button>'+
'<span class="divisor"></span>'+
'<span id="articleListTopBot">'+
'<button id="articleListTop">'+JWB.msg('button-pagelist-top')+'</button>'+
'<button id="articleListBot">'+JWB.msg('button-pagelist-bot')+'</button>'+
'</span>'+
'<span class="divisor"></span>'+
'<button id="sortArticles">'+JWB.msg('button-sort')+'</button>'+
'</div>'+
'</div>'+
'</fieldset>'+
'<fieldset id="settings">'+
'<legend>'+JWB.msg('label-settings')+'</legend>'+
'<div class="collapsibleFieldset">'+
'<div>'+
'<button id="saveAs" title="'+JWB.msg('tip-store-setup')+'">'+JWB.msg('store-setup')+'</button>'+
'<span class="divisor"></span>'+
'<label class="button" id="importLabel" title="'+JWB.msg('tip-import-setup')+'">'+
'<input type="file" id="import" accept=".json">'+
JWB.msg('import-setup')+
'</label>'+
'<span class="divisor"></span>'+
'<button id="updateSetups" title="'+JWB.msg('tip-update-setup', JWB.settingspage)+'">'+JWB.msg('update-setup')+'</button>'+
'</div>'+
'<hr>'+
'<div>'+
'<label>'+
JWB.msg('load-settings')+
'<select id="loadSettings">'+
'<option value="default" selected>'+JWB.msg('default-setup')+'</option>'+
'<option value="_blank">'+JWB.msg('blank-setup')+'</option>'+
'</select>'+
'</label>'+
'<span class="divisor"></span>'+
'<button id="deleteSetup" title="'+JWB.msg('tip-delete-setup')+'">'+JWB.msg('delete-setup')+'</button>'+
'</div>'+
'<hr>'+
'<div>'+
'<button id="saveToWiki">'+JWB.msg('save-setup')+'</button>'+
'<span class="divisor"></span>'+
'<button id="download">'+JWB.msg('download-setup')+'</button>'+
'<div id="downloads">'+
'<a download="JWB-settings.json" target="_blank" id="download-anchor"></a>'+
'<iframe id="download-iframe"></iframe>'+
'</div>'+
'</div>'+
'</div>'+
'</fieldset>'+
'<fieldset id="limits">'+
'<legend>'+JWB.msg('label-limits')+'</legend>'+
'<div class="collapsibleFieldset">'+
'<label class="timelimit-label" title="'+JWB.msg('tip-time-limit')+'">'+
JWB.msg('time-limit')+
'<input type="number" id="timelimit" value="3000" min="0">'+
'</label>'+
'<label title="'+JWB.msg('tip-diff-size-limit')+'">'+
JWB.msg('diff-size-limit')+
'<input type="number" id="sizelimit" value="0" min="0">'+
'</label>'+
'</div>'+
'</fieldset>'
);
$('.JWBtabc[data-tab="2"]').html(
'<label data-label="checkbox" class="minorEdit">'+
'<input type="checkbox" id="minorEdit" checked>'+
'<span class="flexBaseline">'+JWB.msg('minor-edit')+'</span>'+
'</label>'+
'<label class="editSummary' + (JWB.test.dev ? '' : ' viaJWB') + '">'+JWB.msg('edit-summary')+
'<br/> <input class="fullwidth" type="text" id="summary" maxlength="' + (JWB.test.dev ? 500 : 490) + '"></label>'+
// ' <input type="checkbox" id="viaJWB" title="'+JWB.msg('tip-via-JWB')+'">'+ // Commenting this out. See line 45.
'<div id="watchOption">'+
'<select id="watchPage">'+
'<option value="watch">'+JWB.msg('watch-watch')+'</option>'+
'<option value="unwatch">'+JWB.msg('watch-unwatch')+'</option>'+
'<option value="nochange" selected>'+JWB.msg('watch-nochange')+'</option>'+
'<option value="preferences">'+JWB.msg('watch-preferences')+'</option>'+
'</select>'+
'<span class="divisor"></span>'+
'<button id="watchNow" disabled accesskey="w">'+
JWB.msg('watch-add')+
'</button>'+
'</div>'+
(!JWB.test.bot ? '' :
'<div id="throttleOption">'+
'<label data-label="checkbox">'+
'<input type="checkbox" id="autosave">'+
'<span class="flexBaseline">'+JWB.msg('auto-save')+'</span> '+
'</label>'+
'<div class="divisorWrapper"><span class="divisor"></span></div>'+
'<label id="throttleLabel" title="'+JWB.msg('tip-save-interval')+'">'+
JWB.msg('save-interval', '<input type="number" min="0" value="0" id="throttle" disabled>')+
'</label>'+
'</div>'
)+
'<span class="startstops" id="startstop">'+
'<button class="starts" id="startbutton" accesskey="a">'+JWB.msg('editbutton-start')+'</button>'+
'<button class="stops" id="stopbutton" disabled accesskey="q">'+JWB.msg('editbutton-stop')+'</button> '+
'</span>'+
'<div id="mainButtons">'+
'<div>'+
'<button class="editbutton" id="skipButton" disabled accesskey="n">'+JWB.msg('editbutton-skip')+'</button>'+
'<button class="editbutton" id="submitButton" disabled accesskey="s">'+JWB.msg('editbutton-save')+'</button>'+
'</div>'+
'<div>'+
'<button class="editbutton" id="previewButton" disabled accesskey="p">'+JWB.msg('editbutton-preview')+'</button>'+
'<button class="editbutton" id="diffButton" disabled accesskey="d">'+JWB.msg('editbutton-diff')+'</button>'+
'</div>'+
'</div>'+
'<button id="replacesButton">'+JWB.msg('button-open-popup')+'</button>'+
findreplace()+
'<hr>'+
'<div id="RETF">'+
'<label data-label="checkbox">'+
'<input type="checkbox" id="enableRETF">'+
'<span class="flexBaseline">'+JWB.msg(
'label-enable-RETF',
'<a href="/wiki/Project:AutoWikiBrowser/Typos" target="_blank">'+
JWB.msg('label-RETF')+
'</a>'
)+'</span>'+
'</label>'+
' <img src="//upload.wikimedia.org/wikipedia/commons/3/39/Feather-core-refresh-cw.svg" width="12" height="12"'+
'id="refreshRETF" title="'+JWB.msg('tip-refresh-RETF')+'">'+
'</div>'+
'<br/>'+
'<button id="skipRETF" title="'+JWB.msg('tip-skip-RETF')+'" disabled>'+JWB.msg('skip-RETF')+'</button>'
);
$('.JWBtabc[data-tab="3"]').html(
'<fieldset>'+
'<legend>'+JWB.msg('label-redirects')+'</legend>'+
'<div class="collapsibleFieldset">'+
'<div class="radioFlex">'+
'<label data-label="radio" title="'+JWB.msg('tip-redirects-follow')+'">'+
'<input type="radio" class="redirects" value="follow" name="redir" id="redir-follow">'+
'<span class="flexBaseline">'+JWB.msg('redirects-follow')+'</span>'+
'</label>'+
'<label data-label="radio" title="'+JWB.msg('tip-redirects-skip')+'">'+
'<input type="radio" class="redirects" value="skip" name="redir" id="redir-skip">'+
'<span class="flexBaseline">'+JWB.msg('redirects-skip')+'</span>'+
'</label>'+
'<label data-label="radio" title="'+JWB.msg('tip-redirects-edit')+'">'+
'<input type="radio" class="redirects" value="edit" name="redir" id="redir-edit" checked>'+
'<span class="flexBaseline">'+JWB.msg('redirects-edit')+'</span>'+
'</label>'+
'</div>'+
'</div>'+
'</fieldset>'+
'<fieldset>'+
'<legend>'+JWB.msg('label-skip-when')+'</legend>'+
'<div class="collapsibleFieldset">'+
'<label data-label="checkbox">'+
'<input type="checkbox" id="skipNoChange">'+
'<span class="flexBaseline">'+JWB.msg('skip-no-change')+'</span>'+
'</label>'+
'<div class="radioFlex">'+
'<label data-label="radio">'+
'<input type="radio" id="exists-yes" name="exists" value="yes">'+
'<span class="flexBaseline">'+JWB.msg('skip-exists-yes')+'</span>'+
'</label> '+
'<label data-label="radio">'+
'<input type="radio" id="exists-neither" name="exists" value="neither">'+
'<span class="flexBaseline">'+JWB.msg('skip-exists-neither')+'</span>'+
'</label>'+
'<label data-label="radio">'+
'<input type="radio" id="exists-no" name="exists" value="no" checked>'+
'<span class="flexBaseline">'+JWB.msg('skip-exists-no')+'</span>'+
'</label> '+
'</div>'+
(JWB.test.sysop?
'<label data-label="checkbox">'+
'<input type="checkbox" id="skipAfterAction" checked>'+
'<span class="flexBaseline">'+JWB.msg('skip-after-action')+'</span>'+
'</label>'
:'')+
'<hr/>'+
'<label>'+JWB.msg('skip-contains')+'<input class="fullwidth" type="text" id="skipContains"></label>'+
'<label>'+JWB.msg('skip-not-contains')+'<input class="fullwidth" type="text" id="skipNotContains"></label>'+
'<div class="regexswitch">'+
'<label data-label="checkbox">'+
'<input type="checkbox" id="containRegex">'+
'<span class="flexBaseline">'+JWB.msg('label-useregex')+'</span>'+
'</label>'+
// '<a class="re101" href="http://regex101.com/#javascript" target="_blank">?</a>'+
'<div class="divisorWrapper" style="display: none;"><span class="divisor"></span></div>'+
'<label class="regexFlagsLabel" title="'+JWB.msg('tip-regex-flags')+'" style="display: none;">'+
'<span class="flexCenter">'+JWB.msg('label-regex-flags')+'</span>'+
'<input type="text" id="containFlags"/>'+
'</label>'+
'</div>'+
'<hr/>'+
'<label title="'+JWB.msg('skip-cg-prefix')+'">'+JWB.msg('skip-category')+
'<input class="fullwidth" type="text" id="skipCategories"></label>'+
'</div>'+
'</fieldset>'
);
if (JWB.test.sysop || (JWB.test.hasFlood || JWB.test.flag || JWB.test.dev)) $('.JWBtabc[data-tab="4"]').html(
'<fieldset>'+
'<legend>'+JWB.msg('move-header')+'</legend>'+
'<div class="collapsibleFieldset">'+
'<div class="otherOptionLabels">'+
'<label for="movealsoOptions">'+JWB.msg('move-also')+'</label>'+
'<span id="movealsoOptions">'+
'<label data-label="checkbox">'+
'<input type="checkbox" id="movetalk" checked>'+
'<span class="flexBaseline">'+JWB.msg('move-talk-page')+'</span>'+
'</label> '+
'<label data-label="checkbox">'+
'<input type="checkbox" id="movesubpage">'+
'<span class="flexBaseline">'+JWB.msg('move-subpage')+'</span>'+
'</label>'+
'</span>'+
'</div>'+
'<div class="otherOptionLabels">'+
'<label for="moveTo">'+JWB.msg('move-new-name')+'</label>'+
'<input type="text" id="moveTo">'+
'</div>'+
'<label data-label="checkbox">'+
'<input type="checkbox" id="suppressRedir">'+
'<span class="flexBaseline">'+JWB.msg('move-redir-suppress')+'</span>'+
'</label>'+
'</div>'+
'</fieldset>'+
'<fieldset>'+
'<legend>'+JWB.msg('protect-header')+'</legend>'+
'<div class="collapsibleFieldset">'+
JWB.protection.other.function.types()+
'<div class="otherOptionLabels">'+
'<label for="protectExpiry">'+JWB.msg('protect-expiry')+'</label>'+
'<input type="text" id="protectExpiry"/>'+
'</div>'+
'<label data-label="checkbox">'+
'<input type="checkbox" id="cascadingProtection">'+
'<span class="flexBaseline">'+JWB.msg('protect-cascading')+'</span>'+
'</label>'+
'</div>'+
'</fieldset>'+
(JWB.test.hasFlood || JWB.test.flag || JWB.test.dev ?
'<fieldset>'+
'<legend>'+JWB.msg('right-header')+'</legend>'+
'<div class="collapsibleFieldset" style="display: none;">'+ // Prevent misclicking.
'<div class="otherOptionLabels">'+
'<label for="rightSummary">'+JWB.msg('edit-summary')+'</label>'+
'<span id="rightGrantRevoke">'+
'<input type="text" id="rightSummary"/>'+
'<button id="rightGrant" title="'+JWB.msg('right-grant', JWB.test.floodFlag)+'">+</button>'+
'<button id="rightRevoke" title="'+JWB.msg('right-revoke', JWB.test.floodFlag)+'">–</button>'+
'</span>'+
'</div>'+
'</div>'+
'</fieldset>'
: '' ) +
'<div id="otherButtons">'+
'<button id="movePage" disabled accesskey="m">'+JWB.msg('editbutton-move')+'</button> '+
'<button class="delete" id="deletePage" disabled accesskey="x">'+JWB.msg('editbutton-delete')+'</button> '+
'<button id="protectPage" disabled accesskey="z">'+JWB.msg('editbutton-protect')+'</button> '+
'<button id="skipPage" disabled title="['+JWB.tooltip+'n]">'+JWB.msg('editbutton-skip')+'</button>'+
'<button class="starts" id="startOther">'+JWB.msg('editbutton-start')+'</button> '+
'<button class="stops" id="stopOther" disabled>'+JWB.msg('editbutton-stop')+'</button> '+
'</div>'
);
$('.JWBtabc[data-tab="-2"]').html(
'<fieldset>'+
'<legend>'+JWB.msg('readd-header')+'</legend>'+
'<div class="collapsibleFieldset">'+
'<div class="readdLine">'+
'<label data-label="checkbox">'+
'<input type="checkbox" class="readdCheckboxes" id="readd-edit">'+
'<span class="flexBaseline">'+JWB.msg('readd-edit')+'</span>'+
'</label>'+
'<label data-label="checkbox">'+
'<input type="checkbox" class="readdCheckboxes" id="readd-null">'+
'<span class="flexBaseline">'+JWB.msg('readd-null')+'</span>'+
'</label>'+
'<label data-label="checkbox">'+
'<input type="checkbox" class="readdCheckboxes" id="readd-move">'+
'<span class="flexBaseline">'+JWB.msg('readd-move')+'</span>'+
'</label>'+
'</div>'+
'<div class="readdLine">'+
'<label data-label="checkbox">'+
'<input type="checkbox" class="readdCheckboxes" id="readd-skip">'+
'<span class="flexBaseline">'+JWB.msg('readd-skip')+'</span>'+
'</label>'+
'<label data-label="checkbox">'+
'<input type="checkbox" class="readdCheckboxes" id="readd-bots">'+
'<span class="flexBaseline">'+JWB.msg('readd-bots')+'</span>'+
'</label>'+
'<label data-label="checkbox">'+
'<input type="checkbox" class="readdCheckboxes" id="readd-flag">'+
'<span class="flexBaseline">'+JWB.msg('readd-flag')+'</span>'+
'</label>'+
'</div>'+
'<div class="readdLine">'+
'<label data-label="checkbox">'+
'<input type="checkbox" class="readdCheckboxes" id="readd-delt">'+
'<span class="flexBaseline">'+JWB.msg('readd-delete')+'</span>'+
'</label>'+
'<label data-label="checkbox">'+
'<input type="checkbox" class="readdCheckboxes" id="readd-udel">'+
'<span class="flexBaseline">'+JWB.msg('readd-undelete')+'</span>'+
'</label>'+
'<label data-label="checkbox">'+
'<input type="checkbox" class="readdCheckboxes" id="readd-prot">'+
'<span class="flexBaseline">'+JWB.msg('readd-protect')+'</span>'+
'</label>'+
'</div>'+
'</div>'+
'</fieldset>'+
'<button type="submit" id="readdClick">'+JWB.msg('readd-button')+'</button>'
);
$('.JWBtabc[data-tab="-1"]').html( // Make these sticky. See clearLog event handlers (line 2240+) and .css line 353.
'<span class="stickyButtons startstops" id="startstopLog">'+
'<button class="starts" id="startLog">'+JWB.msg('editbutton-start')+'</button>'+
'<button class="stops" id="stopLog" disabled>'+JWB.msg('editbutton-stop')+'</button> '+
'</span>'+
'<button class="stickyButtons clearLogButtons" id="clearLogButton">'+ JWB.msg('log-clear') +'</button>'+
'<span class="stickyButtons" id="clearLogButtonYesNoButtons">'+
'<button class="stickyButtons clearLogButtons clearLogButtonYesNo" id="clearLogButtonYes">'+ JWB.msg('log-clear-yes') +'</button>'+
'<button class="stickyButtons clearLogButtons clearLogButtonYesNo" id="clearLogButtonNo">'+ JWB.msg('log-clear-no') +'</button>'+
'</span>'+
'<table id="actionlog">'+
'<tbody></tbody>'+
'</table>'
);
$('#pagelistPopup form').html(
'<div id="ns-filter" title="'+JWB.msg('tip-ns-select')+'">' + JWB.msg('label-ns-select') + NSList + '</div>'+
'<fieldset>'+
'<legend>'+
'<label data-label="checkbox">'+
'<input type="checkbox" id="categorymembers" name="categorymembers" value="cm">'+
'<span class="flexBaseline">'+JWB.msg('legend-cm')+'</span>'+
'</label>'+
'</legend>'+
'<label class="inputFlex" title="'+JWB.msg('tip-cm')+'">'+
'<span class="flexCenter">'+JWB.msg('label-cm')+'</span>'+
'<input type="text" name="cmtitle" id="cmtitle">'+
'</label>'+
'<div class="checkboxFlex">'+
'<span class="flexCenter">'+JWB.msg('cm-include')+'</span>'+
'<div>'+
'<label data-label="checkbox">'+
'<input type="checkbox" id="cmtype-page" name="cmtype" value="page" checked>'+
'<span class="flexBaseline">'+JWB.msg('cm-include-pages')+'</span>'+
'</label>'+
'<label data-label="checkbox">'+
'<input type="checkbox" id="cmtype-subcg" name="cmtype" value="subcat" checked>'+
'<span class="flexBaseline">'+JWB.msg('cm-include-subcgs')+'</span>'+
'</label>'+
'<label data-label="checkbox">'+
'<input type="checkbox" id="cmtype-file" name="cmtype" value="file" checked>'+
'<span class="flexBaseline">'+JWB.msg('cm-include-files')+'</span>'+
'</label>'+
'</div>'+
'</div>'+
'</fieldset>'+
'<fieldset>'+
'<legend>'+
'<label data-label="checkbox">'+
'<input type="checkbox" name="linksto" id="linksto">'+
'<span class="flexBaseline">'+JWB.msg('legend-linksto')+'</span>'+
'</label>'+
'</legend>'+
'<label class="inputFlex">'+
'<span class="flexCenter">'+JWB.msg('label-linksto')+'</span>'+
'<input type="text" name="title" id="linksto-title">'+
'</label>'+
'<div class="checkboxFlex">'+
'<span class="flexCenter">'+JWB.msg('links-include')+'</span>'+
'<div>'+
'<label data-label="checkbox">'+
'<input type="checkbox" id="backlinks" name="backlinks" value="bl" checked>'+
'<span class="flexBaseline">'+JWB.msg('links-include-links')+'</span>'+
'</label>'+
'<label data-label="checkbox">'+
'<input type="checkbox" id="embeddedin" name="embeddedin" value="ei">'+
'<span class="flexBaseline">'+JWB.msg('links-include-templ')+'</span>'+
'</label>'+
'<label data-label="checkbox">'+
'<input type="checkbox" id="imageusage" name="imageusage" value="iu">'+
'<span class="flexBaseline">'+JWB.msg('links-include-files')+'</span>'+
'</label>'+
'</div>'+
'</div>'+
'<div class="radioFlex">'+
'<span class="flexCenter">'+JWB.msg('links-redir')+'</span>'+
'<div>'+
'<label data-label="radio">'+
'<input type="radio" id="rfilter-redir" name="filterredir" value="redirects">'+
'<span class="flexBaseline">'+JWB.msg('links-redir-redirs')+'</span>'+
'</label>'+
'<label data-label="radio">'+
'<input type="radio" id="rfilter-nonredir" name="filterredir" value="nonredirects">'+
'<span class="flexBaseline">'+JWB.msg('links-redir-noredirs')+'</span>'+
'</label>'+
'<label data-label="radio">'+
'<input type="radio" id="rfilter-all" name="filterredir" value="all" checked>'+
'<span class="flexBaseline">'+JWB.msg('links-redir-all')+'</span>'+
'</label>'+
'</div>'+
'</div>'+
'<label data-label="checkbox" title="'+JWB.msg('tip-link-redir')+'">'+
'<input type="checkbox" name="redirect" value="true" checked id="linksto-redir">'+
'<span class="flexBaseline">'+JWB.msg('label-link-redir')+'</span>'+
'</label>'+
'</fieldset>'+
'<fieldset>'+
'<legend>'+
'<label data-label="checkbox">'+
'<input type="checkbox" id="prefixsearch" name="prefixsearch" value="ps">'+
'<span class="flexBaseline">'+JWB.msg('legend-ps')+'</span>'+
'</label>'+
'</legend>'+
'<label class="inputFlex">'+
'<span class="flexCenter">'+JWB.msg('label-ps')+'</span>'+
'<input type="text" name="pssearch" id="pssearch">'+
'</label>'+
'<label data-label="checkbox" title="'+JWB.msg('tip-ps-strict')+'">'+
'<input type="checkbox" name="allpages" value="ap" id="psstrict" checked>'+
'<span class="flexBaseline">'+JWB.msg('label-ps-strict')+'</span>'+
'</label>'+
'</fieldset>'+
'<fieldset>'+
'<legend>'+
'<label data-label="checkbox">'+
'<input type="checkbox" id="watchlistraw" name="watchlistraw" value="wr">'+
'<span class="flexBaseline">'+JWB.msg('legend-wr')+'</span>'+
'</label>'+
'</legend>'+
JWB.msg('label-wr')+
'</fieldset>'+
'<fieldset>'+
'<legend>'+
'<label data-label="checkbox">'+
'<input type="checkbox" id="proplinks" name="links" value="pl">'+
'<span class="flexBaseline">'+JWB.msg('legend-pl')+'</span>'+
'</label>'+
'</legend>'+
'<labe class="inputFlex" title="'+JWB.msg('tip-pl')+'">'+
'<span class="flexCenter">'+JWB.msg('label-pl')+'</span>'+
'<input type="text" id="titles" name="titles">'+
'</label>'+
'</fieldset>'+
'<fieldset>'+
'<legend>'+
'<label data-label="checkbox">'+
'<input type="checkbox" id="proplinks" name="search" value="sr">'+
'<span class="flexBaseline">'+JWB.msg('legend-sr')+'</span>'+
'</label>'+
'</legend>'+
'<label class="inputFlex" title="'+JWB.msg('tip-sr')+'\n'+JWB.msg('placeholder-sr', 'insource:', 'intitle:')+'">'+
'<span class="flexCenter">'+JWB.msg('label-sr')+'</span>'+
'<input type="text" id="srsearch" name="srsearch" placeholder="'+JWB.msg('placeholder-sr', 'insource:', 'intitle:')+'">'+
'</label>'+
'</fieldset>'+
'<fieldset class="listSMW">'+
'<legend>'+
'<label data-label="checkbox">'+
'<input type="checkbox" id="smwask" name="smwask" value="smw">'+
'<span class="flexBaseline">'+JWB.msg('legend-smw', JWB.msg('smw-slow'))+'</span>'+
'</label>'+
'</legend>'+
'<textarea id="smwquery" name="smwquery" placeholder="'+JWB.msg('label-smw', '\n|limit=500')+'"></textarea>'+
'</fieldset>'+
'<button type="submit">'+JWB.msg('pagelist-generate')+'</button>'
);
if (JWB.test.hasSMW) {
$('#pagelistPopup').addClass('hasSMW');
} else {
$('.listSMW').remove();
}
$('body').addClass('AutoWikiBrowser'); //allow easier custom styling of JWB.
$('[accesskey]').each(function() {
let lbl = this.accessKeyLabel || this.accessKey; // few browsers support accessKeyLabel, so fallback to accessKey.
$(this).attr('title', '['+lbl+']');
});
/***** Setup *****/
JWB.setup.save('_blank'); //default setup
if (JWB.settings.hasOwnProperty('default')) {
JWB.setup.apply();
} else if (JWB.setup.initialised) {
// If we already initialised, create the default settings profile.
JWB.setup.save('default');
}
JWB.setup.extend({});
/***** Event handlers *****/
//Alert user when leaving the tab, to prevent accidental closing.
onbeforeunload = function() {
return 'Closing this tab will cause you to lose all progress.';
};
ondragover = function(e) {
e.preventDefault();
};
document.addEventListener('securitypolicyviolation', function(e) {
console.log('violated CSP:', e);
if (e.blockedURI == 'blob') {
JWB.worker.supported = false; // tell the next JWB.worker.init() that it shouldn't even try.
} else if (JWB && JWB.msg) {
console.log(JWB.msg('csp-error', e.violatedDirective)); // Disabling this annoying thing. No alerts, logging only.
}
});
$('.JWBtab').click(function() {
$('.active').removeClass('active');
$(this).addClass('active');
$('.JWBtabc[data-tab="'+$(this).attr('data-tab')+'"]').addClass('active');
});
$('fieldset > legend').click(function() {
$(this).parent().children('.collapsibleFieldset').slideToggle();
});
$('#articleListTop').click(function() {
$('#articleList').scrollTop(0);
});
$('#articleListBot').click(function() {
$('#articleList').scrollTop($($('#articleList')[0]).prop('scrollHeight') - $('#articleList').height());
});
$('#articleList').focusout(JWB.pageCount);
function showRegexFlags() {
// >>this<< is the element that's triggered
$(this).parent().nextAll('.divisorWrapper, .regexFlagsLabel').toggle(this.checked);
}
$('body').on('change', '#useRegex, #containRegex, .useRegex', showRegexFlags);
$('#preparse-reset').click(function() {
$('#articleList').val($('#articleList').val().replace(/#PRE-PARSE-STOP/g, '').replace(/\n\n/g, '\n'));
});
$('#saveAs').click(JWB.setup.save);
$('#loadSettings').change(function() {
JWB.setup.apply(this.value);
JWB.pageCount();
$('#throttle').prop('disabled', !$('#autosave').prop('checked'));
$('#cascadingProtection').prop('disabled', !JWB.protection.casc.includes($('#editProt').val()));
});
$('#download').click(JWB.setup.download);
$('#saveToWiki').click(JWB.setup.submit);
$('#import').change(JWB.setup.import);
ondrop = JWB.setup.import;
$('#updateSetups').click(JWB.setup.load);
$('#deleteSetup').click(JWB.setup.del);
if (window.RETF) {
$('#refreshRETF').click(function() {
RETF.load();
$('#refreshRETF').hide().after(JWB.loader(12, 'display: none; bottom: 4px;', 'loadedRETF'));
$('#loadedRETF').fadeIn('fast');
window.setTimeout(function() {
$('#loadedRETF').fadeOut('fast', function() {
$('#loadedRETF').remove();
$('#refreshRETF').fadeIn('fast');
});
}, 2000);
});
$('#skipRETF').click(JWB.skipRETF)
$('#enableRETF').change(function() {
$('#skipRETF').toggle(this.checked);
});
}
$('#replacesButton, #pagelistButton').click(function() {
var popup = this.id.slice(0, -6); //omits the 'Button' in the id by cutting off the last 6 characters
$('#'+popup+'Popup, #overlay').fadeIn('fast');
});
$('#overlay').click(function() {
$('#replacesPopup, #pagelistPopup, #overlay').fadeOut('fast');
JWB.pl.done = true;
JWB.pl.stop();
});
try { // Drag'n'drop list of .replaces.
$('#replacesPopup').sortable({
delay: 100,
distance: 10,
items: '> .replaces',
placeholder: 'fieldsetPlaceholder',
revert: true,
scroll: false,
zIndex: 999
});
$('#replacesPopup .replaces').addClass('sortable');
JWB.test.sortable = true;
} catch (e) { // Should not throw errors.
console.log(e);
JWB.test.sortable = false;
}
$('#moreReplaces').click(function() {
$('#replacesPopup').append(findreplace(false));
$('.replaces[style*="display: none;"]').slideDown();
});
/* $('#replacesPopup').on('keydown', '.replaces:last', function(e) { // Should no longer be needed as the button is now sticky.
if (e.which === 9) $('#moreReplaces')[0].click();
}); */
$('#replacesPopup').on('click', '.removeThis', JWB.remove);
$('#pl-form').submit(function(e) {
e.preventDefault();
JWB.pl.generate();
return false;
});
$('#pagelistPopup legend input').change(function() {
//remove disabled attr when checked, add when not.
$(this).parents('fieldset').find('input, textarea').not('legend input').prop('disabled', !this.checked);
$(this).parents('fieldset').attr('class', (!this.checked ? 'disabled' : ''));
}).trigger('change');
$('#psstrict').change(function() {
if (this.checked) {
$('#pssearch').attr('name', 'apprefix');
} else {
$('#pssearch').attr('name', 'pssearch');
}
}).trigger('change');
$('#resultWindow').on('click', 'tr[data-line]:not(.lineheader) *', function(e) {
var line = +$(e.target).closest('tr[data-line]').data('line');
var index = $('#editBoxArea').val().split('\n').slice(0, line-1).join('\n').length;
$('#editBoxArea')[0].focus();
JWB.fn.setSelection($('#editBoxArea')[0], index+1);
JWB.fn.scrollSelection($('#editBoxArea')[0], index);
});
$('#removeDupes').click(function() {
JWB.temp = $('#articleList').val();
$('#articleList').val(JWB.fn.uniques($('#articleList').val().split('\n')).join('\n'));
JWB.pageCount();
});
$('#removeUniqs').click(function() {
JWB.temp = $('#articleList').val();
$('#articleList').val(JWB.fn.duplicates($('#articleList').val().split('\n')).join('\n'));
JWB.pageCount();
});
$('#removeUndo').click(function() {
$('#articleList').val(JWB.temp);
JWB.pageCount();
});
$('#sortArticles').click(function() {
$('#articleList').val($('#articleList').val().split('\n').sort().join('\n'));
JWB.pageCount();
});
$('#watchNow').click(JWB.api.watch);
$('#autosave').change(function() {
$('#throttle').prop('disabled', !this.checked);
});
/* $('#viaJWB').change(function() { // Change the max size of the allowed summary according to having a suffix or not.
$('#summary').parent('label')
.toggleClass('viaJWB', this.checked)
.attr('maxlength', 500 - this.checked*JWB.summarySuffix.length);
}); */
$('.starts').click(JWB.start);
$('.stops').click(JWB.stop);
$('#submitButton').click(JWB.api.submit);
$('#previewButton').click(JWB.api.preview);
$('#diffButton').click(JWB.api.diff);
$('#skipButton, #skipPage').click(function() {
JWB.log('skip', JWB.list[0].split('|')[0]);
JWB.next();
});
if (JWB.test.sysop) {
$('#movePage').click(function() {
if (!JWB.isStopped && $('#moveTo').val().length === 0) {
return alert(JWB.msg('alert-no-move'));
} else if (!JWB.isStopped) {
JWB.api.move();
}
});
$('#protectPage').click(JWB.api.protect);
if (!JWB.protection.casc.includes($('#editProt').val())) $('#cascadingProtection').prop('disabled', true);
$('#editProt').on('change', function() {
if (JWB.protection.casc.includes($('#editProt').val())) $('#cascadingProtection').prop('disabled', false);
else $('#cascadingProtection').prop('checked', false).prop('disabled', true);
});
$('#deletePage').click(JWB.api.del);
}
if (JWB.test.hasFlood || JWB.test.flag || JWB.test.dev) {
var righttype = // Flagged with JWB.test.floodFlag/not flagged yet, grantable&revocable/revocable only.
(JWB.test.flood && JWB.test.changeable ? // Flagged and g&r-able.
'Grant'
: // Not flagged or revocable only.
(JWB.test.flag ? // Flagged, revocable.
'Grant'
: // Not flagged but g&r-able or vice versa.
(JWB.test.changeable ? // Not flagged, g&r-able.
'Revoke'
: // Not flagged but revocable only!?
'Revoke'
)
)
);
$('#right' + righttype).hide();
$('#rightGrant, #rightRevoke').click(function() {
$('#rightGrant, #rightRevoke').toggle();
});
$('#rightGrant').click(function() {
JWB.api.right(true);
});
$('#rightRevoke').click(function() {
var confirmation;
if (!JWB.test.dev) {
confirmation = confirm(JWB.msg('right-revoke-confirm'));
if (!confirmation) return;
}
JWB.api.right(false);
if (JWB.test.flag && confirmation && !JWB.test.dev) {
if (confirmation) $('#rightRevoke').parents('fieldset').slideUp('slow', function() {
$('#rightRevoke').parents('fieldset').remove();
if (!JWB.test.sysop) {
$('[data-tab="4"]').remove();
$('span[data-tab="2"]').click();
}
});
}
});
}
if (!JWB.test.sysop && (JWB.test.hasFlood || JWB.test.flag || JWB.test.dev)) {
$('section[data-tab="4"] fieldset:nth-child(1), section[data-tab="4"] fieldset:nth-child(2), '+
'#otherButtons').remove();
} else if (!JWB.test.sysop) {
$('[data-tab="4"]').remove();
}
// Tab -2 checkboxes.
if (!JWB.test.sysop)
$('.readdLine:nth-child(1) > label:nth-child(3), .readdLine:nth-child(3)').remove();
if (!(JWB.test.hasFlood || JWB.test.flag || JWB.test.dev))
$('.readdLine:nth-child(2) > label:nth-child(3)').remove();
$('#readdClick').click(function() {
var types = ['edit', 'null', 'move', 'skip', 'bots', 'flag', 'delt', 'udel', 'prot'];
var readd = [];
for (let i = 0; i < types.length; i++) {
if ($('#readd-' + types[i]).prop('checked')) readd.push('.reAdd-' + types[i]);
}
$('#articleList').val($(readd.join(', ')).text() + '\n' + $('#articleList').val());
JWB.pageCount();
});
$('#clearLogButtonYes').click(function() {
$('#actionlog tbody').empty();
});
$('#clearLogButton, .clearLogButtonYesNo').click(function() {
$('#clearLogButton, #clearLogButtonYes, #clearLogButtonNo').toggle();
});
$('#actionlog').on('click', '.reAddButton', function() {
$('#articleList').val(unescape($(this).attr('data-readd')) + '\n' + $('#articleList').val());
});
};
// Disable JWB altogether when it's loaded on a page other than Project:AutoWikiBrowser/Script.
// This script shouldn't be loaded on any other page in the first place.
if (JWB.allowed === false) JWB = false;