· 4 years ago · Aug 27, 2021, 10:08 AM
1/** <nowiki>
2 * Install this script by pasting the following in your personal JavaScript file:
3
4mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Joeytje50/JWB.js/load.js&action=raw&ctype=text/javascript');
5
6 * Or for users on en.wikipedia.org:
7
8{{subst:lusc|User:Joeytje50/JWB.js/load.js}}
9
10 * Note that this script will only run on the 'Project:AutoWikiBrowser/Script' page.
11 * This script is based on the downloadable AutoWikiBrowser.
12 *
13 * @licence
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
18 *
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License along
25 * with this program; if not, write to the Free Software Foundation, Inc.,
26 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
27 * http://www.gnu.org/copyleft/gpl.html
28 * @version 4.3.0
29 * @author Joeytje50
30 * </nowiki>
31 */
32
33window.JWBdeadman = false; // ADMINS: in case of fire, set this variable to true to disable this entire tool for all users
34
35//TODO: more advanced pagelist-generating options
36//TODO: generate page list based on images on a page
37//TODO: Add feature to perform general cleanup (<table> to {|, fullurl-links to wikilinks, removing underscores from wikilinks)
38//TODO: Add report button to AJAX error alert box
39//TODO: Re-add errored pages to the page list
40
41//Cleanup / modernize:
42// .indexOf('') != -1 -> .includes()
43// mw for requests, instead of ajax
44// Optional?.chaining?.properties
45
46/***** Global object/variables *****/
47window.JWB = {}; //The main global object for the script.
48
49(function() {
50 // Easier way to change import location for local debugging etc.
51 JWB.imports = {
52 'JWB.css': '//en.wikipedia.org/w/index.php?title=User:Joeytje50/JWB.css&action=raw&ctype=text/css',
53 'i18n.js': '//en.wikipedia.org/w/index.php?title=User:Joeytje50/JWB.js/i18n.js&action=raw&ctype=text/javascript',
54 'i18n': {},
55 'RETF.js': '//en.wikipedia.org/w/index.php?title=User:Joeytje50/RETF.js&action=raw&ctype=text/javascript',
56 'worker.js':'//en.wikipedia.org/w/index.php?title=User:Joeytje50/JWB.js/worker.js&action=raw&ctype=text/javascript',
57 };
58
59 let objs = ['page', 'api', 'worker', 'fn', 'pl', 'messages', 'setup', 'settings', 'ns'];
60 for (let i=0;i<objs.length;i++) {
61 JWB[objs[i]] = {};
62 }
63 JWB.summarySuffix = ' (via JWB)';
64 if (document.location.hostname == 'en.wikipedia.org') JWB.summarySuffix = ' (via [[WP:JWB]])';
65 JWB.lang = mw.config.get('wgUserLanguage').replace('-', '_');
66 JWB.contentLang = mw.config.get('wgContentLanguage').replace('-', '_');
67 JWB.index_php = mw.config.get('wgScript');
68 JWB.isStopped = true;
69 JWB.tooltip = window.tooltipAccessKeyPrefix || '';
70 let configext = 'js';
71 if (document.location.hostname.split('.').slice(-2).join('.') == 'wikia.com' || document.location.hostname.split('.').slice(-2).join('.') == 'fandom.com') {
72 //LEGACY: fallback to settings on css for Wikia; uses JSON now.
73 configext = 'css';
74 }
75 JWB.settingspage = 'JWB-settings.'+configext;
76 if (window.hasOwnProperty('JWBSETTINGS')) {
77 JWB.settingspage = JWBSETTINGS+'-settings.'+configext;
78 delete window.JWBSETTINGS; //clean up the global variable
79 }
80 JWB.hasJSON = false; // whether or not the wiki supports JSON userpages (mw 1.31+).
81 JWB.hasSMW = false; // whether or not the wiki has SMW installed.
82})();
83
84/***** User verification *****/
85
86(function() {
87 if (mw.config.get('wgCanonicalNamespace')+':'+mw.config.get('wgTitle') !== 'Project:AutoWikiBrowser/Script' || JWB.allowed === false || mw.config.get('wgUserName') === null) {
88 JWB.allowed = false;
89 return;
90 }
91 mw.loader.load(JWB.imports['JWB.css'], 'text/css');
92 mw.loader.load('mediawiki.diff.styles');
93
94 $.getScript(JWB.imports['i18n.js'], function() {
95 if (JWB.allowed === false) {
96 alert(JWB.msg('not-on-list'));
97 return;
98 }
99 let langs = [];
100 if (JWB.lang !== 'en' && JWB.imports.i18n.hasOwnProperty(JWB.lang)) {
101 langs.push(JWB.imports.i18n[JWB.lang]);
102 JWB.messages[JWB.lang] = JWB.messages[JWB.lang] || null;
103 } else if (JWB.lang !== 'en' && JWB.lang !== 'qqx') {
104 // this only happens if the language file does not exist.
105 JWB.lang = 'en';
106 }
107 if (JWB.contentLang !== 'en' && JWB.contentLang !== JWB.lang && JWB.imports.i18n.hasOwnProperty(JWB.contentLang)) {
108 langs.push(JWB.imports.i18n[JWB.contentLang]);
109 JWB.messages[JWB.contentLang] = JWB.messages[JWB.contentLang] || null;
110 }
111 if (langs.length) {
112 $.when.apply($, langs.map(url => $.getScript(url))).done(function(r) {
113 if (JWB.allowed === true && JWB.messages.length == langs + 1) { // if there are two languages to load, wait for them both.
114 console.log('langs loaded');
115 JWB.init(); //init if verification has already returned true
116 } else if (JWB.allowed === false) {
117 alert(JWB.msg('not-on-list'));
118 }
119 });
120 } else if (JWB.allowed === true) { // no more languages to load.
121 console.log('no langs loaded');
122 JWB.init();
123 }
124 });
125
126 //RegEx Typo Fixing
127 $.getScript(JWB.imports['RETF.js'], function() {
128 $('#refreshRETF').click(RETF.load);
129 });
130
131 if (window.JWBdeadman === true) {
132 window.JWB = false; // disable all access
133 alert("This tool has been temporarily been disabled by Wikipedia admins due to issues it would otherwise cause. Please check back soon to see if it is working again.");
134 return false;
135 } else if (!window.Worker) {
136 // 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.
137 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.");
138 }
139
140 (new mw.Api()).get({
141 action: 'query',
142 titles: 'Project:AutoWikiBrowser/CheckPageJSON',
143 prop: 'info|revisions',
144 meta: 'userinfo|siteinfo',
145 rvprop: 'content',
146 rvlimit: 1,
147 uiprop: 'groups',
148 siprop: 'namespaces|usergroups|extensions',
149 indexpageids: true,
150 format: 'json',
151 }).done(function(response) {
152 if (response.error) {
153 alert('API error: ' + response.error.info);
154 JWB = false; //preventing further access. No verification => no access.
155 return;
156 }
157 JWB.ns = response.query.namespaces; //saving for later
158
159 // This will execute before JWB.init() and therefore before JWB.setup.load() loading the user's settings.
160 let wikigroups = response.query.usergroups;
161 for (var u of wikigroups) {
162 if (u.rights.indexOf('edituserjson') !== -1) {
163 JWB.hasJSON = true;
164 break;
165 }
166 }
167
168 // Check if we've got SMW on this wiki
169 let extensions = response.query.extensions;
170 for (var e of extensions) {
171 if (e.name == "SemanticMediaWiki") {
172 JWB.hasSMW = true;
173 break;
174 }
175 }
176
177 JWB.username = response.query.userinfo.name; //preventing any "hacks" that change wgUserName or mw.config.wgUserName
178 var groups = response.query.userinfo.groups;
179 var page = response.query.pages[response.query.pageids[0]];
180 var users = [];
181 var bots = [];
182 JWB.sysop = groups.indexOf('sysop') !== -1;
183 if (response.query.pageids[0] !== '-1') {
184 var checkPageData = JSON.parse(page.revisions[0]['*']);
185 users = checkPageData.enabledusers;
186 if ("enabledbots" in checkPageData) {
187 bots = checkPageData.enabledbots;
188 }
189 } else {
190 users = false; //fallback when page doesn't exist
191 if (JWB.sysop) { // Check and inform admins if their checkpage is the unsupported format.
192 (new mw.Api()).get({
193 action: 'query',
194 titles: 'Project:AutoWikiBrowser/CheckPage',
195 prop: 'info',
196 indexpageids: true,
197 }).done(function(oldpage){
198 var q = oldpage.query;
199 if (q.pageids[0] != '-1' && !q.pages[q.pageids[0]].hasOwnProperty('redirect')) {
200 // CheckPageJSON does not exist, and CheckPage does exist, and is not a redirect.
201 // This indicates the checkpage needs to be ported to JSON. Notify admins.
202 prompt('Warning: The AWB checkpage found at Project:AutoWikiBrowser/CheckPage is no longer supported.\n'+
203 'Please convert this checkpage to a JSON checkpage. See the URL below for more information.\n'+
204 'After creating the JSON checkpage, you can use "Special:ChangeContentModel" to change the content model to JSON.',
205 'https://en.wikipedia.org/wiki/Wikipedia:AutoWikiBrowser/CheckPage_format');
206 }
207 });
208 }
209 }
210 JWB.bot = groups.indexOf('bot') !== -1 && (users === false || bots.includes(JWB.username));
211 // Temporary global debugging variables
212 JWB.debug = [groups.indexOf('bot'), users === false, bots && bots.indexOf(JWB.username)];
213 if (JWB.username === "Joeytje50" && response.query.userinfo.id === 13299994) {//TEMP: Dev full access to entire interface.
214 JWB.bot = true;
215 users.push("Joeytje50");
216 }
217 if (JWB.sysop)
218 JWB.bot = true;
219 users.push("Joeytje50");
220 }
221 var allLoaded = true;
222 for (var m in JWB.messages) if (JWB.messages[m] === null) allLoaded = false;
223 if (JWB.sysop || response.query.pageids[0] === '-1' || users === false || users.includes(JWB.username) || bots.includes(JWB.username)) {
224 JWB.allowed = true;
225 if (allLoaded) JWB.init(); //init if messages have already loaded
226 } else {
227 if (allLoaded) {
228 //run this after messages have loaded, so the message that shows is in the user's language
229 alert(JWB.msg('not-on-list'));
230 }
231 JWB = false; //prevent further access
232 }
233 }).fail(function(xhr, error) {
234 alert(JWB.msg('verify-error') + '\n' + error);
235 JWB = false; //preventing further access. No verification => no access.
236 });
237})();
238
239/***** API functions *****/
240
241//Main template for API calls
242JWB.api.call = function(data, callback, onerror) {
243 data.format = 'json';
244 if (data.action !== 'query' && data.action !== 'compare' && data.action !== 'ask') {
245 data.bot = true; // mark edits as bot
246 }
247 $.ajax({
248 data: data,
249 dataType: 'json',
250 url: mw.config.get('wgScriptPath') + '/api.php',
251 type: 'POST',
252 success: function(response) {
253 if (response.error) {
254 if (onerror && onerror(response, 'API') === false) return;
255 alert('API error: ' + response.error.info);
256 JWB.stop();
257 } else {
258 callback(response);
259 }
260 },
261 // onerror: if it exists and returns false, do not show error alert. Otherwise, do show alert.
262 error: function(xhr, error) {
263 if (onerror && onerror(error, 'AJAX') === false) return;
264 alert('AJAX error: ' + error);
265 JWB.stop();
266 }
267 });
268};
269
270//Get page diff, and process it for more interactivity
271JWB.api.diff = function(callback) {
272 if (JWB.isStopped) return; // prevent new API calls when stopped
273 JWB.status('diff');
274 var editBoxInput = $('#editBoxArea').val();
275 var redirect = $('input.redirects:checked').val();
276 var data = {
277 action: 'compare',
278 indexpageids: true,
279 fromtitle: JWB.page.name,
280 //toslots: 'main', // TODO: Once this gets supported more widely, convert to the non-deprecated toslots system.
281 //'totext-main': editBoxInput,
282 totext: editBoxInput,
283 topst: true,
284 };
285 if (redirect=='follow') data.redirects = true;
286 JWB.api.call(data, function(response) {
287 var diff;
288 diff = response.compare['*'];
289 if (diff === '') {
290 diff = '<h2>'+JWB.msg('no-changes-made')+'</h2>';
291 } else {
292 diff = '<table class="diff">'+
293 '<colgroup>'+
294 '<col class="diff-marker">'+
295 '<col class="diff-content">'+
296 '<col class="diff-marker">'+
297 '<col class="diff-content">'+
298 '</colgroup>'+
299 '<tbody>'+diff+'</tbody></table>';
300 }
301 $('#resultWindow').html(diff);
302 $('.diff-lineno').each(function() {
303 var lineNumMatch = $(this).html().match(/\d+/);
304 if (lineNumMatch) {
305 $(this).parent().attr('data-line',parseInt(lineNumMatch[0])-1).addClass('lineheader');
306 }
307 });
308 $('table.diff tr').each(function() { //add data-line attribute to every line, relative to the previous one. Used for click event.
309 if (!$(this).next().is('[data-line]') && !$(this).next().has('td.diff-deletedline + td.diff-empty')) {
310 $(this).next().attr('data-line',parseInt($(this).data('line'))+1);
311 } else if ($(this).next().has('td.diff-deletedline + td.diff-empty')) {
312 $(this).next().attr('data-line',$(this).data('line')); //copy over current data-line for deleted lines to prevent them from messing up counting.
313 }
314 });
315 JWB.status('done', true);
316 if (typeof(callback) === 'function') {
317 callback();
318 }
319 }, function(err, type) {
320 if (type == 'API' && err.error.code == 'missingtitle') {
321 // missingtitle is to be expected when editing a page that doesn't exist; just show a message and move on.
322 $('#resultWindow').html('<span style="font-weight:bold;color:red;">'+JWB.msg('page-not-exists')+'</span>');
323 JWB.status('done', true);
324 if (typeof(callback) === 'function') {
325 callback();
326 }
327 return false; // stop propagation of error; do not show alerts.
328 }
329 });
330};
331
332//Retrieve page contents/info, process them, and store information in JWB.page object.
333JWB.api.get = function(pagename) {
334 if (JWB.isStopped) return; // prevent new API calls when stopped
335 JWB.pageCount();
336 if (!JWB.list[0] || JWB.isStopped) {
337 return JWB.stop();
338 }
339 if (pagename === '#PRE-PARSE-STOP') {
340 var curval = $('#articleList').val();
341 $('#articleList').val(curval.substr(curval.indexOf('\n') + 1));
342 $('#preparse').prop('checked', false);
343 JWB.stop();
344 return;
345 }
346 let cgns = JWB.ns[14]['*'];
347 let skipcg = $('#skipCategories').val();
348 // prepend Category: before all categories and turn CSV(,) into CSV(|).
349 skipcg = skipcg.replace(new RegExp('(^|,|\\|)('+cgns+':)?', 'gi'), '|'+cgns+':').substr(1);
350 var redirect = $('input.redirects:checked').val();
351 var data = {
352 action: 'query',
353 prop: 'info|revisions|categories',
354 inprop: 'watched|protection',
355 type: 'csrf|watch',
356 titles: pagename,
357 rvprop: 'content|timestamp|ids',
358 rvlimit: '1',
359 cllimit: 'max',
360 clcategories: skipcg,
361 indexpageids: true,
362 meta: 'userinfo|tokens',
363 uiprop: 'hasmsg'
364 };
365 if (redirect=='follow'||redirect=='skip') data.redirects = true;
366 if (JWB.sysop) {
367 data.list = 'deletedrevs';
368 }
369 JWB.status('load-page');
370 JWB.api.call(data, function(response) {
371 if (response.query.userinfo.hasOwnProperty('messages')) {
372 var view = mw.config.get('wgScriptPath') + '?title=Special:MyTalk';
373 var viewNew = view + '&diff=cur';
374 JWB.status(
375 '<span style="color:red;font-weight:bold;">'+
376 JWB.msg('status-newmsg',
377 '<a href="'+view+'" target="_blank">'+JWB.msg('status-talklink')+'</a>',
378 '<a href="'+viewNew+'" target="_blank">'+JWB.msg('status-difflink')+'</a>')+
379 '</span>', true);
380 alert(JWB.msg('new-message'));
381 JWB.stop();
382 return;
383 }
384 JWB.page = response.query.pages[response.query.pageids[0]];
385 JWB.page.token = response.query.tokens.csrftoken;
386 JWB.page.watchtoken = response.query.tokens.watchtoken;
387 JWB.page.name = JWB.list[0].split('|')[0];
388 var varOffset = JWB.list[0].indexOf('|') !== -1 ? JWB.list[0].indexOf('|') + 1 : 0;
389 JWB.page.pagevar = JWB.list[0].substr(varOffset);
390 JWB.page.content = JWB.page.revisions ? JWB.page.revisions[0]['*'] : '';
391 JWB.page.exists = !response.query.pages["-1"];
392 JWB.page.deletedrevs = response.query.deletedrevs;
393 JWB.page.watched = JWB.page.hasOwnProperty('watched');
394 JWB.page.protections = JWB.page.restrictiontypes;
395
396 if (response.query.redirects) {
397 JWB.page.name = response.query.redirects[0].to;
398 }
399 // check for skips that can be determined before replacing
400 if (!JWB.fn.allowBots(JWB.page.content, JWB.username) || !JWB.fn.allowBots(JWB.page.content)) {
401 // skip if {{bots}} template forbids editing on this page by user OR by JWB in general
402 JWB.log('nobots', JWB.page.name);
403 return JWB.next();
404 } else if (JWB.page.categories !== undefined || // skip because of a matching category as passed via clcategories.
405 ($('#exists-no').prop('checked') && !JWB.page.exists) ||
406 ($('#exists-yes').prop('checked') && JWB.page.exists) ||
407 (redirect==='skip' && response.query.redirects) // variable redirect is defined outside this callback function.
408 ) {
409 // simple skip rules
410 JWB.log('skip', JWB.page.name);
411 return JWB.next();
412 }
413 // Check skip contains rules.
414 var containRegex = $('#containRegex').prop('checked'),
415 containFlags = $('#containFlags').val();
416 var skipContains, skipNotContains;
417 if (containRegex) {
418 JWB.status('check-skips');
419 var skipping = false; // for tracking if match is found in synchronous calls.
420 if ($('#skipContains').val().length) {
421 JWB.worker.match(JWB.page.content, $('#skipContains').val(), containFlags, function(result, err) {
422 console.log('Contains', result, err);
423 if (result !== null && err === undefined) {
424 JWB.log('skip', JWB.page.name);
425 JWB.next(); // next() also cancels the skipNotContains.
426 skipping = true;
427 return;
428 } // else continue with the queued worker job that checks skipNotContains
429 });
430 }
431 if (skipping) {
432 console.log('skipped page before replaces')
433 return;
434 }
435 if ($('#skipNotContains').val().length) {
436 JWB.worker.match(JWB.page.content, $('#skipNotContains').val(), containFlags, function(result, err) {
437 console.log('Not contains', result, err);
438 if (result === null && err === undefined) {
439 JWB.log('skip', JWB.page.name);
440 JWB.next(); // also cancels the replace
441 skipping = true;
442 return;
443 } // else move on to replacing
444 });
445 }
446 if (skipping) {
447 console.log('skipped page before replaces')
448 return;
449 }
450 } else {
451 skipContains = $('#skipContains').val();
452 skipNotContains = $('#skipNotContains').val();
453 if ((skipContains && JWB.page.content.includes(skipContains)) ||
454 (skipNotContains && JWB.page.content.includes(skipNotContains))) {
455 console.log('skipped page before replaces')
456 return JWB.next();
457 }
458 JWB.status('done', true);
459 }
460 JWB.replace(JWB.page.content, function(newContent) {
461 if (JWB.isStopped === true) return;
462 if ($('#skipNoChange').prop('checked') && JWB.page.content === newContent) { //skip if no changes are made
463 JWB.log('skip', JWB.page.name);
464 return JWB.next();
465 } else {
466 JWB.editPage(newContent);
467 }
468 JWB.updateButtons();
469 });
470 });
471};
472
473//Some functions with self-explanatory names:
474JWB.api.submit = function(page) {
475 if (JWB.isStopped) return; // prevent new API calls when stopped
476 JWB.status('submit');
477 var summary = $('#summary').val();
478 if ($('#summary').parent('label').hasClass('viaJWB')) summary += JWB.summarySuffix;
479 if ((typeof page === 'text' && page !== JWB.page.name) || $('#currentpage a').html().replace(/&/g, '&') !== JWB.page.name) {
480 console.log(page, JWB.page.name, $('#currentpage a').html())
481 JWB.stop();
482 alert(JWB.msg('autosave-error', JWB.msg('tab-log')));
483 $('#currentpage').html(JWB.msg('editbox-currentpage', ' ', ' '));
484 return;
485 }
486 var newval = $('#editBoxArea').val();
487 var diffsize = newval.length - JWB.page.content.length;
488 if ($('#sizelimit').val() != 0 && Math.abs(diffsize) > parseInt($('#sizelimit').val())){
489 alert(JWB.msg('size-limit-exceeded', diffsize > 0 ? '+'+diffsize : diffsize));
490 JWB.status('done', true);
491 return;
492 }
493 var data = {
494 title: JWB.page.name,
495 summary: summary,
496 action: 'edit',
497 //tags: 'JWB',
498 basetimestamp: JWB.page.revisions ? JWB.page.revisions[0].timestamp : '',
499 token: JWB.page.token,
500 text: newval,
501 watchlist: $('#watchPage').val()
502 };
503 if ($('#minorEdit').prop('checked')) data.minor = true;
504 JWB.api.call(data, function(response) {
505 JWB.log('edit', response.edit.title, response.edit.newrevid);
506 }, function(error, errtype) {
507 var cont = false;
508 if (errtype == 'API') {
509 cont = confirm("API error: " + error.error.info + "\n" + JWB.msg('confirm-continue'));
510 } else {
511 cont = confirm("AJAX error: " + error + "\n" + JWB.msg('confirm-continue'));
512 }
513 if (!cont) {
514 JWB.stop();
515 }
516 return false; // do not fall back on default error handling
517 });
518 // While the edit is submitting, continue to the next page to edit.
519 JWB.status('done', true);
520 JWB.next();
521};
522JWB.api.preview = function() {
523 if (JWB.isStopped) return; // prevent new API calls when stopped
524 JWB.status('preview');
525 JWB.api.call({
526 title: JWB.page.name,
527 action: 'parse',
528 pst: true,
529 text: $('#editBoxArea').val()
530 }, function(response) {
531 $('#resultWindow').html(response.parse.text['*']);
532 $('#resultWindow div.previewnote').remove();
533 JWB.status('done', true);
534 });
535};
536JWB.api.move = function() {
537 if (JWB.isStopped) return; // prevent new API calls when stopped
538 JWB.status('move');
539 var topage = $('#moveTo').val().replace(/\$x/gi, JWB.page.pagevar);
540 var summary = $('#summary').val();
541 if ($('#summary').parent('label').hasClass('viaJWB')) summary += JWB.summarySuffix;
542 var data = {
543 action: 'move',
544 from: JWB.page.name,
545 to: topage,
546 token: JWB.page.token,
547 reason: summary,
548 ignorewarnings: 'yes'
549 };
550 if ($('#moveTalk').prop('checked')) data.movetalk = true;
551 if ($('#moveSubpage').prop('checked')) data.movesubpages = true;
552 if ($('#suppressRedir').prop('checked')) data.noredirect = true;
553 JWB.api.call(data, function(response) {
554 JWB.log('move', response.move.from, response.move.to);
555 JWB.status('done', true);
556 if (!$('#moveTo').val().match(/\$x/i)) $('#moveTo').val('')[0].focus(); //clear entered move-to pagename if it's not based on the pagevar
557 JWB.next(topage);
558 });
559};
560JWB.api.del = function() {
561 if (JWB.isStopped) return; // prevent new API calls when stopped
562 JWB.status(($('#deletePage').is('.undelete') ? 'un' : '') + 'delete');
563 var summary = $('#summary').val();
564 if ($('#summary').parent('label').hasClass('viaJWB')) summary += JWB.summarySuffix;
565 JWB.api.call({
566 action: (!JWB.page.exists ? 'un' : '') + 'delete',
567 title: JWB.page.name,
568 token: JWB.page.token,
569 reason: summary
570 }, function(response) {
571 JWB.log((!JWB.page.exists ? 'un' : '') + 'delete', (response['delete']||response.undelete).title);
572 JWB.status('done', true);
573 JWB.next(response.undelete && response.undelete.title);
574 });
575};
576JWB.api.protect = function() {
577 if (JWB.isStopped) return; // prevent new API calls when stopped
578 JWB.status('protect');
579 var summary = $('#summary').val();
580 if ($('#summary').parent('label').hasClass('viaJWB')) summary += JWB.summarySuffix;
581 var editprot = $('#editProt').val();
582 var moveprot = $('#moveProt').val() || editprot;
583 var uploadprot = $('#uploadProt').val() || editprot;
584 var protstring = 'edit='+editprot+'|move='+moveprot;
585 if (!JWB.page.exists)
586 protstring = 'create='+editprot;
587 if (JWB.page.protections.includes('upload'))
588 protstring += '|upload='+uploadprot;
589 JWB.api.call({
590 action: 'protect',
591 title: JWB.page.name,
592 token: JWB.page.token,
593 reason: summary,
594 expiry: $('#protectExpiry').val()!==''?$('#protectExpiry').val():'infinite',
595 protections: protstring,
596 }, function(response) {
597 var protactions = '';
598 var prots = response.protect.protections;
599 for (var i=0;i<prots.length;i++) {
600 if (typeof prots[i].edit == 'string') {
601 protactions += ' edit: '+(prots[i].edit || 'all');
602 } else if (typeof prots[i].move == 'string') {
603 protactions += ' move: '+(prots[i].move || 'all');
604 } else if (typeof prots[i].create == 'string') {
605 protactions += ' create: '+(prots[i].create || 'all');
606 } else if (typeof prots[i].upload == 'string') {
607 protactions += ' upload: '+(prots[i].upload || 'all');
608 }
609 }
610 protactions += ' expires: '+prots[0].expiry;
611 JWB.log('protect', response.protect.title, protactions);
612 JWB.status('done', false);
613 JWB.next(response.protect.title);
614 });
615};
616
617JWB.api.watch = function() {
618 JWB.status('watch');
619 var data = {
620 action: 'watch',
621 title: JWB.page.name,
622 token: JWB.page.watchtoken
623 };
624 if (JWB.page.watched) data.unwatch = true;
625 JWB.api.call(data, function(response) {
626 JWB.status('<span style="color:green;">'+
627 JWB.msg('status-watch-'+(JWB.page.watched ? 'removed' : 'added'), "'"+JWB.page.name+"'")+
628 '</span>', true);
629 JWB.page.watched = !JWB.page.watched;
630 $('#watchNow').html( JWB.msg('watch-' + (JWB.page.watched ? 'remove' : 'add')) );
631 });
632};
633
634/***** Pagelist functions *****/
635
636JWB.pl.iterations = 0;
637JWB.pl.done = true;
638
639JWB.pl.stop = function() {
640 if (JWB.pl.done) {
641 JWB.pl.iterations = 0;
642 $('#pagelistPopup [disabled]:not(fieldset [disabled]), #pagelistPopup legend input, #pagelistPopup button').prop('disabled', false);
643 $('#pagelistPopup legend input').trigger('change');
644 $('#pagelistPopup button img').remove();
645 }
646}
647
648JWB.pl.getNSpaces = function() {
649 var list = $('#pagelistPopup [name="namespace"]')[0];
650 return $('#pagelistPopup [name="namespace"]').val().join('|'); //.val() returns an array of selected options.
651};
652
653JWB.pl.getList = function(abbrs, lists, data) {
654 $('#pagelistPopup button, #pagelistPopup input, #pagelistPopup select, #pagelistPopup button').prop('disabled', true);
655 JWB.pl.iterations++;
656 if (data.ask !== undefined) {
657 JWB.pl.SMW(data.ask); // execute SMW call in parallel
658 JWB.pl.done = false;
659 data.ask = undefined;
660 }
661 if (!abbrs.length) {
662 JWB.pl.done = true;
663 return; // don't execute the rest; only a SMW query was entered.
664 }
665 data.action = 'query';
666 var nspaces = JWB.pl.getNSpaces();
667 for (var i=0;i<abbrs.length;i++) {
668 if (nspaces) data[abbrs[i]+'namespace'] = data[abbrs[i]+'namespace'] || nspaces; // if namespaces are already set, use that instead (for apnamespace)
669 data[abbrs[i]+'limit'] = 'max';
670 }
671 let linksList = lists.indexOf('links')
672 if (linksList !== -1) {
673 data.prop = 'links';
674 lists.splice(linksList, 1)
675 }
676 data.list = lists.join('|');
677 console.log('generating:', data);
678 JWB.api.call(data, function(response) {
679 var maxiterate = 100; //allow up to 100 consecutive requests at a time to avoid overloading the server.
680 if (!response.query) response.query = {};
681 if (response.watchlistraw) response.query.watchlistraw = response.watchlistraw; //adding some consistency
682 var plist = [];
683 if (response.query.pages) {
684 var links;
685 for (var id in response.query.pages) {
686 links = response.query.pages[id].links;
687 for (var i=0;i<links.length;i++) {
688 plist.push(links[i].title);
689 }
690 }
691 }
692 for (var l in response.query) {
693 if (l === 'pages') continue;
694 for (var i=0;i<response.query[l].length;i++) {
695 plist.push(response.query[l][i].title);
696 }
697 }
698 //add the result to the pagelist immediately, as opposed to saving it all up and adding in 1 go like AWB does
699 $('#articleList').val($.trim($('#articleList').val()) + '\n' + plist.join('\n'));
700 JWB.pageCount();
701 var cont = response.continue;
702 console.log("Continue",JWB.pl.iterations, cont);
703 if (cont && JWB.pl.iterations <= maxiterate) {
704 var lists = [];
705 if (response.query) { //compatibility with the code I wrote for the old query-continue. TODO: make this unnecessary?
706 for (var list in response.query) {
707 lists.push(list); //add to the new array of &list= values
708 }
709 }
710 var abbrs = [];
711 for (var abbr in cont) {
712 data[abbr] = cont[abbr]; //add the &xxcontinue= value to the data
713 if (abbr != 'continue') {
714 abbrs.push(abbr.replace('continue','')); //find out what xx is and add it to the list of abbrs
715 }
716 }
717 JWB.pl.getList(abbrs, lists, data); //recursive function to get every page of a list
718 } else {
719 if (JWB.pl.iterations > maxiterate) {
720 JWB.status('pl-over-lim', true);
721 } else {
722 JWB.status('done', true);
723 }
724 JWB.pl.stop(); // if JWB.pl.done == true show stopped interface. Otherwise mark as done.
725 JWB.pl.done = true;
726 }
727 }, function() { //on error, simply reset and let the user work with what he has
728 JWB.status('done', true);
729 JWB.pl.stop();
730 JWB.pl.done = true;
731 });
732};
733
734JWB.pl.SMW = function(query) {
735 var data = {
736 action: 'ask',
737 query: query
738 };
739 JWB.api.call(data, function(response) {
740 console.log(response);
741 let list = response.query.results;
742 let pagevar = response.query.printrequests[1];
743 let pagevar_type = pagevar && pagevar.typeid;
744 if (pagevar) {
745 // either pagevar === undefined, or it's the first printrequest.
746 pagevar = pagevar.label;
747 }
748 let plist = [];
749 for (let l in list) {
750 let page = list[l];
751 let name = page.fulltext;
752 let suff;
753 if (pagevar) try {
754 let val = page.printouts[pagevar][0];
755 if (!val) continue; // this page does not contain this property.
756 switch (pagevar_type) {
757 case '_boo':
758 suff = val == 't'; // true if 't' else false;
759 break;
760 case '_wpg':
761 suff = val.fulltext;
762 break;
763 case '_dat':
764 // val.raw is also available but the unconventional format makes it a lot less convenient.
765 suff = val.timestamp;
766 break;
767 case '_qty':
768 suff = val.value + ' ' + val.unit;
769 break;
770 case '_mlt_rec':
771 // I doubt this is used anywhere, but it's not too hard to support.
772 suff = val.Text.item[0];
773 break;
774 case '_ref_rec':
775 // not supported; references contain too many properties.
776 break;
777 default:
778 suff = val;
779 }
780 } catch(e) {
781 console.error(e); // show error but ignore. Something is wrong in SMW query/api.
782 }
783 if (suff) {
784 plist.push(name + '|' + suff);
785 } else {
786 plist.push(name);
787 }
788 }
789 $('#articleList').val($.trim($('#articleList').val()) + '\n' + plist.join('\n'));
790 JWB.pageCount();
791 JWB.pl.stop(); // if JWB.pl.done == true show stopped interface. Otherwise mark as done.
792 JWB.pl.done = true;
793 });
794}
795
796//JWB.pl.getList(['wr'], ['watchlistraw'], {}) for watchlists
797JWB.pl.generate = function() {
798 var $fields = $('#pagelistPopup fieldset').not('[disabled]');
799 $('#pagelistPopup').find('button[type="submit"]').append('<img src="//upload.wikimedia.org/wikipedia/commons/d/de/Ajax-loader.gif" width="15" height="15" alt="'+JWB.msg('status-alt')+'"/>');
800 var abbrs = [],
801 lists = [],
802 data = {'continue': ''};
803 $fields.each(function() {
804 var list = $(this).find('legend input').attr('name');
805 var abbr;
806 if (list === 'linksto') { //Special case since this fieldset features 3 merged lists in 1 fieldset
807 if (!$('[name="title"]').val()) return;
808 $('[name="backlinks"], [name="embeddedin"], [name="imageusage"]').filter(':checked').each(function() {
809 var val = this.value;
810 abbrs.push(val);
811 lists.push(this.name);
812 data[val+'title'] = $('[name="title"]').val();
813 data[val+'filterredir'] = $('[name="filterredir"]:checked').val();
814 if ($('[name="redirect"]').prop('checked')) data[val+'redirect'] = true;
815 });
816 } else if (list === 'smwask') {
817 data.ask = $(this).find('#smwquery').val();
818 } else { //default input system
819 if ($(this).find('#psstrict').prop('checked')) {
820 // different list if prefixsearch is strict
821 let $input = $(this).find('#psstrict')
822 list = $input.attr('name');
823 abbr = $input.val();
824 } else {
825 abbr = $(this).find('legend input').val();
826 }
827 lists.push(list);
828 abbrs.push(abbr);
829 $(this).find('input').not('legend input').each(function() {
830 if ((this.type === 'checkbox' || this.type === 'radio') && this.checked === false) return;
831 if (this.id == 'psstrict') return; // ignore psstrict; it only affects how pssearch is handled
832 var name, val;
833 if (this.id == 'cmtitle') {
834 // making sure the page has a Category: prefix, in case the user left it out
835 let cgns = JWB.ns[14]['*']; // name for Category: namespace
836 if (!this.value.startsWith(cgns+':')) {
837 this.value = cgns+':'+this.value;
838 }
839 }
840 if (this.id == 'pssearch' && this.name == 'apprefix') {
841 // apprefix needs namespace separate from pagename
842 name = this.name;
843 let split = this.value.split(':')
844 val = split[1] || split[0];
845 let nsid = 0;
846 if (split[1]) { // if a namespace is given
847 for (let ns in JWB.ns) {
848 if (JWB.ns[ns]['*'] == split[0]) {
849 nsid = JWB.ns[ns].id;
850 break;
851 }
852 }
853 }
854 data.apnamespace = nsid;
855 } else {
856 name = this.name;
857 val = this.value;
858 }
859 if (data.hasOwnProperty(name)) {
860 data[name] += '|'+val;
861 } else {
862 data[name] = val;
863 }
864 });
865 console.log(abbrs, lists, data);
866 }
867 });
868 if (abbrs.length || data.ask) JWB.pl.getList(abbrs, lists, data);
869 else JWB.pl.stop();
870};
871
872/***** Setup functions *****/
873
874JWB.setup.save = function(name) {
875 name = name || prompt(JWB.msg('setup-prompt', JWB.msg('setup-prompt-store')), $('#loadSettings').val());
876 if (name === null) return;
877 var self = JWB.settings[name] = {
878 string: {},
879 bool: {},
880 replaces: []
881 };
882 //inputs with a text value
883 $('textarea, input[type="text"], input[type="number"], select').not('.replaces input, #editBoxArea, #settings *').each(function() {
884 if (typeof $(this).val() == 'string') {
885 self.string[this.id] = this.value.replace(/\n{2,}/g,'\n');
886 } else {
887 self.string[this.id] = $(this).val();
888 }
889 });
890 self.replaces = [];
891 $('.replaces').each(function() {
892 if ($(this).find('.replaceText').val() || $(this).find('.replaceWith').val()) {
893 self.replaces.push({
894 replaceText: $(this).find('.replaceText').val(),
895 replaceWith: $(this).find('.replaceWith').val(),
896 useRegex: $(this).find('.useRegex').prop('checked'),
897 regexFlags: $(this).find('.regexFlags').val(),
898 ignoreNowiki: $(this).find('.ignoreNowiki').prop('checked')
899 });
900 }
901 });
902 $('input[type="radio"], input[type="checkbox"]').not('.replaces input').each(function() {
903 self.bool[this.id] = this.checked;
904 });
905 if (!$('#loadSettings option[value="'+name+'"]').length) {
906 $('#loadSettings').append('<option value="'+name+'">'+name+'</option>');
907 }
908 $('#loadSettings').val(name);
909 console.log(self);
910};
911
912JWB.setup.apply = function(name) {
913 name = name && JWB.settings[name] ? name : 'default';
914 var self = JWB.settings[name];
915 $('#loadSettings').val(name);
916 $('.replaces + .replaces').remove(); //reset find&replace inputs
917 $('.replaces input[type="text"]').val('');
918 $('.useRegex').each(function() {this.checked = false;});
919 $('#pagelistPopup legend input').trigger('change'); //fix checked state of pagelist generating inputs
920 for (var a in self.string) {
921 $('#'+a).val(self.string[a]);
922 }
923 for (var b in self.bool) {
924 $('#'+b).prop('checked', self.bool[b]);
925 }
926 var cur;
927 for (var c=0;c<self.replaces.length;c++) {
928 if ($('.replaces').length <= c) $('#moreReplaces')[0].click();
929 cur = self.replaces[c];
930 for (var d in cur) {
931 if (cur[d] === true || cur[d] === false) {
932 $('.replaces').eq(c).find('.'+d).prop('checked', cur[d]);
933 } else {
934 $('.replaces').eq(c).find('.'+d).val(cur[d]);
935 }
936 }
937 }
938 $('.useRegex, #containRegex,'+
939 '#pagelistPopup legend input,'+
940 '#viaJWB, #enableRETF').trigger('change'); //reset disabled inputs
941};
942
943JWB.setup.getObj = function() {
944 var settings = [];
945 for (var i in JWB.settings) {
946 if (i != '_blank') {
947 settings.push('"' + i + '": ' + JSON.stringify(JWB.settings[i]));
948 }
949 }
950 return '{\n\t' + settings.join(',\n\t').split('{{subst:').join('{{#JWB-SAFESUBST:#') + '\n}';
951};
952
953JWB.setup.submit = function() {
954 var name = prompt(JWB.msg('setup-prompt', JWB.msg('setup-prompt-save')), $('#loadSettings').val());
955 if (name === null) return;
956 if ($.trim(name) === '') name = 'default';
957 JWB.setup.save(name);
958 JWB.status('setup-submit');
959 JWB.api.call({
960 action: 'query',
961 meta: 'tokens',
962 }, function(response) {
963 let edittoken = response.query.tokens.csrftoken;
964 JWB.api.call({
965 title: 'User:'+JWB.username+'/'+JWB.settingspage,
966 summary: JWB.msg(['setup-summary', JWB.contentLang]),
967 action: 'edit',
968 token: edittoken,
969 text: JWB.setup.getObj(),
970 minor: true
971 }, function(response) {
972 JWB.status('done', true);
973 JWB.log('edit', response.edit.title, response.edit.newrevid);
974 });
975 });
976};
977
978//TODO: use blob uri
979JWB.setup.download = function() {
980 var name = prompt(JWB.msg('setup-prompt', JWB.msg('setup-prompt-save')), $('#loadSettings').val());
981 if (name === null) return;
982 if ($.trim(name) === '') name = 'default';
983 JWB.setup.save(name);
984 JWB.status('setup-dload');
985 var url = 'data:application/json;base64,' + btoa(unescape(encodeURIComponent(JWB.setup.getObj())));
986 var elem = $('#download-anchor')[0];
987 if (HTMLAnchorElement.prototype.hasOwnProperty('download')) { //use download attribute when possible, for its ability to specify a filename
988 elem.href = url;
989 elem.click();
990 setTimeout(function() {elem.removeAttribute('href');}, 2000);
991 } else { //fallback to iframes for browsers with no support for download="" attributes
992 elem = $('#download-iframe')[0];
993 elem.src = url.replace('application/json', 'application/octet-stream');
994 setTimeout(function() {elem.removeAttribute('src');}, 2000);
995 }
996 JWB.status('done', true);
997};
998
999JWB.setup.import = function(e) {
1000 e.preventDefault();
1001 file = (e.dataTransfer||this).files[0];
1002 if ($(this).is('#import')) { //reset input
1003 this.outerHTML = this.outerHTML;
1004 $('#import').change(JWB.setup.import);
1005 }
1006 if (!window.hasOwnProperty('FileReader')) {
1007 alert(JWB.msg('old-browser'));
1008 JWB.status('old-browser', '<a target="_blank" href="'+JWB.index_php+'?title=Special:MyPage/'+JWB.settingspage+'">/'+JWB.settingspage+'</a>');
1009 return;
1010 }
1011 if (file.name.split('.').pop().toLowerCase() !== 'json') {
1012 alert(JWB.msg('not-json'));
1013 return;
1014 }
1015 JWB.status('Processing file');
1016 var reader = new FileReader();
1017 reader.readAsText(file);
1018 reader.onload = function(e) {
1019 JWB.status('done', true);
1020 try {
1021 //Exclusion regex based on http://stackoverflow.com/a/23589204/1256925
1022 //Removes all JS comments from the file, except when they're between quotes.
1023 var c = reader.result;
1024 var data = JSON.parse(c.replace(/("[^"]*")|(\/\*[\w\W]*\*\/|\/\/[^\n]*)/g, function(match, g1, g2) {
1025 if (g1) return g1;
1026 }));
1027 } catch(e) {
1028 alert(JWB.msg('json-err', e.message, JWB.msg('json-err-upload')));
1029 console.log(e); //also log the error for further info
1030 return;
1031 }
1032 JWB.setup.extend(data);
1033 };
1034
1035 JWB.status('Processing file');
1036};
1037
1038JWB.setup.load = function() {
1039 JWB.status('setup-load');
1040 var user = JWB.username||mw.config.get('wgUserName');
1041 var oldtitle = "User:" + user + '/'+JWB.settingspage; // page title for what was used before version 4.0
1042 var newtitle = "User:" + user + '/JWB-settings.json'; // new page title for all settings pages.
1043 var titles = oldtitle;
1044 // if the old title isn't JWB-settings.json, also query the new title.
1045 if (oldtitle !== newtitle && JWB.hasJSON) {
1046 titles += '|' + newtitle;
1047 }
1048 JWB.api.call({
1049 action: 'query',
1050 titles: titles,
1051 prop: 'info|revisions',
1052 meta: 'tokens',
1053 rvprop: 'content',
1054 indexpageids: true
1055 }, function(response) {
1056 if (JWB === false) return; //user is not allowed to use JWB
1057 var firstrun = !JWB.setup.initialised;
1058 JWB.setup.initialised = true;
1059 var edittoken = response.query.tokens.csrftoken;
1060
1061 // determine correct page to get settings from
1062 var pages = response.query.pages,
1063 ids = response.query.pageids;
1064 var page, exists = true;
1065 if (ids.length == 2) {
1066 var page0 = pages[ids[0]],
1067 page1 = pages[ids[1]];
1068 var oldpage, newpage;
1069 if (page0.title == oldtitle) {
1070 oldpage = page0;
1071 newpage = page1;
1072 } else {
1073 oldpage = page1;
1074 newpage = page0;
1075 }
1076 if (oldpage.missing === undefined && oldpage.redirect === undefined) {
1077 // old page exists and is not a redirect
1078 if (newpage.missing === undefined) {
1079 // both old AND new page exist; throw error and load neither page.
1080 let jsredir = "https://www.mediawiki.org/wiki/Help:Redirects#JavaScript_page_redirect";
1081 prompt(JWB.msg('duplicate-settings', oldtitle, newtitle, jsredir), jsredir);
1082 exists = false;
1083 } else {
1084 // old page exists but new page doesn't; move the page to the new location.
1085 JWB.setup.moveNew(oldtitle, newtitle, edittoken);
1086 JWB.settingspage = 'JWB-settings.json';
1087 return;
1088 }
1089 } else {
1090 // Old page either doesn't exist or is a redirect. Don't bother with it.
1091 page = newpage;
1092 exists = (page.missing === undefined);
1093 JWB.settingspage = 'JWB-settings.json';
1094 }
1095 } else {
1096 page = pages[ids[0]];
1097 exists = (page.missing === undefined);
1098 }
1099 if (!exists) {
1100 // settings page does not exist; don't load anything
1101 if (JWB.allowed && firstrun) JWB.setup.save('default'); //this runs when this callback returns after the init has loaded.
1102 return;
1103 }
1104 var data = page.revisions[0]['*'].split('{{#JWB-SAFESUBST:#').join('{{subst:');
1105 if (!data) {
1106 // settings page is empty; don't load anything.
1107 if (JWB.allowed && firstrun) JWB.setup.save('default'); //this runs when this callback returns after the init has loaded.
1108 return;
1109 }
1110 try {
1111 data = JSON.parse(data);
1112 } catch(e) {
1113 alert(JWB.msg('json-err', e.message, JWB.msg('json-err-page', JWB.settingspage)) || 'JSON error:\n'+e.message);
1114 JWB.setup.save('default');
1115 return;
1116 }
1117 JWB.setup.extend(data);
1118 JWB.status('done', true);
1119 });
1120};
1121
1122JWB.setup.moveNew = function(from, to, token) {
1123 (new mw.Api()).post({
1124 action: 'move',
1125 from: from,
1126 to: to,
1127 token: token,
1128 reason: JWB.msg(['setup-move-summary', JWB.contentLang]),
1129 noredirect: true, // if possible, suppress redirects; the old page will no longer be needed if the new page exists.
1130 movesubpages: true, // if any
1131 movetalk: true, // if any
1132 ignorewarnings: true,
1133 }).done(function(response) {
1134 if (response.error === undefined) {
1135 JWB.log('move', from, to);
1136 JWB.settingspage = to.split('/')[1];
1137 alert(JWB.msg('moved-settings', from, to, JWB.msg('tab-log')));
1138 JWB.setup.load(); // load settings from newly moved page.
1139 }
1140 });
1141}
1142
1143JWB.setup.extend = function(obj) {
1144 $.extend(JWB.settings, obj);
1145 if (!JWB.settings.hasOwnProperty('default')) {
1146 JWB.setup.save('default');
1147 }
1148 for (var i in JWB.settings) {
1149 if ($('#loadSettings').find('option[value="'+i+'"]').length) continue;
1150 $('#loadSettings').append('<option value="'+i+'">'+i+'</option>');
1151 }
1152 JWB.setup.apply($('#loadSettings').val());
1153};
1154
1155JWB.setup.del = function() {
1156 var name = $('#loadSettings').val();
1157 if (name === '_blank') return alert(JWB.msg('setup-delete-blank'));
1158 var temp = {};
1159 temp[name] = JWB.settings[name];
1160 JWB.setup.temp = $.extend({}, temp);
1161 delete JWB.settings[name];
1162 $('#loadSettings').val('default');
1163 if (name === 'default') {
1164 JWB.setup.apply('_blank');
1165 JWB.setup.save('default');
1166 JWB.status(['del-default', '<a href="javascript:JWB.setup.undelete();">'+JWB.msg('status-del-undo')+'</a>'], true);
1167 } else {
1168 $('#loadSettings').find('[value="'+name+'"]').remove();
1169 JWB.setup.apply();
1170 JWB.status(['del-setup', name, '<a href="javascript:JWB.setup.undelete();">'+JWB.msg('status-del-undo')+'</a>'], true);
1171 }
1172};
1173JWB.setup.undelete = function() {
1174 JWB.setup.extend(JWB.setup.temp);
1175 JWB.status('done', true);
1176};
1177
1178/***** Main other functions *****/
1179
1180//Show status message status-`action`, or status-`action[0]` with arguments `action[1:]`
1181JWB.status = function(action, done) {
1182 if (JWB.bot && $('#autosave').prop('checked') && !JWB.isStopped) {
1183 $('#summary, .editbutton, #movePage, #deletePage, #protectPage, #skipPage').prop('disabled', true); //Disable summary when auto-saving
1184 } else {
1185 $('#summary, .editbutton, #movePage, #deletePage, #protectPage, #skipPage').prop('disabled', !done); //Disable box when not done (so busy loading). re-enable when done loading.
1186 }
1187 var status;
1188 if (action instanceof Array) {
1189 action[0] = 'status-'+action[0];
1190 status = JWB.msg.apply(this, action)
1191 } else {
1192 status = JWB.msg('status-'+action);
1193 }
1194 if (status === false) return;
1195 if (status) {
1196 if (!done) { //spinner if not done
1197 status += ' <img src="//upload.wikimedia.org/wikipedia/commons/d/de/Ajax-loader.gif" width="15" height="15" alt="'+JWB.msg('status-alt')+'"/>';
1198 }
1199 } else {
1200 status = action;
1201 }
1202 $('#status').html(status);
1203 JWB.pageCount();
1204 return action=='done';
1205};
1206
1207JWB.pageCount = function() {
1208 if (JWB.allowed === false||!$('#articleList').length) return;
1209 $('#articleList').val(($('#articleList').val()||'').replace(/(^[ \t]*$\n)*/gm, ''));
1210 JWB.list = $('#articleList').val().split('\n');
1211 var count = JWB.list.length;
1212 if (count === 1 && JWB.list[0] === '') count = 0;
1213 $('#totPages').html(count);
1214};
1215
1216//Perform all specified find&replace actions
1217JWB.replace = function(input, callback) {
1218 JWB.status('replacing');
1219 if (!JWB.worker.isWorking() && JWB.worker.supported) {
1220 // if the worker is not already working, then re-init to make sure we've not got any broken leftovers from the previous page
1221 JWB.worker.init();
1222 }
1223 JWB.newContent = input;
1224 JWB.pageCount();
1225 var varOffset = JWB.list[0].indexOf('|') !== -1 ? JWB.list[0].indexOf('|') + 1 : 0;
1226 JWB.page.pagevar = JWB.list[0].substr(varOffset);
1227 $('.replaces').each(function() {
1228 var $this = $(this);
1229 var replaceText = $this.find('.replaceText').val(),
1230 replaceWith = $this.find('.replaceWith').val();
1231 if (replaceText.length == 0 && replaceWith.length == 0) return; // don't bother replacing 2 empty strings.
1232 var regexFlags = $this.find('.regexFlags').val();
1233 var replace = replaceText.replace(/\$x/gi, JWB.page.pagevar).replace(/\\{2}/g, '\\').replace(/\\n/g,'\n') || '$';
1234 var useRegex = replaceText.length == 0 || $this.find('.useRegex').prop('checked');
1235 if (useRegex && regexFlags.indexOf('_') !== -1) {
1236 replace = replace.replace(/[ _]/g, '[ _]'); //replaces any of [Space OR underscore] with a match for spaces or underscores.
1237 replace = replace.replace(/(\[[^\]]*)\[ _\]/g, '$1 _'); //in case a [ _] was placed inside another [] match, remove the [].
1238 regexFlags = regexFlags.replace('_', '');
1239 }
1240 //apply replaces where \n and \\ work in both regular text and regex mode.
1241 var rWith = replaceWith.replace(/\$x/gi, JWB.page.pagevar).replace(/\\{2}/g, '\\').replace(/\\n/g,'\n');
1242 if (rWith.length === 0 && replace === '$') return;
1243 try {
1244 let replaceDone = function(result, err) {
1245 console.log('done replacing', result, err);
1246 if (err === undefined) {
1247 JWB.newContent = result;
1248 if (JWB.worker.queue.length == 0 && JWB.worker.supported) {
1249 // all workers are done
1250 JWB.status('done', true);
1251 callback(JWB.newContent);
1252 }
1253 } else if (err == 'Timeout exceeded') {
1254 if (JWB.worker.queue.length == 0 && JWB.worker.supported) {
1255 // all workers have exceeded their time and/or have finished
1256 JWB.status('done', true);
1257 callback(JWB.newContent); // newContent remains unmodified due to timeout.
1258 }
1259 }
1260 }
1261 if ($this.find('.ignoreNowiki').prop('checked')) {
1262 if (!useRegex) {
1263 replace = replace.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
1264 regexFlags = 'g';
1265 }
1266 JWB.worker.unparsedReplace("~"+"~~JWB.newContent", replace, regexFlags, rWith, replaceDone);
1267 } else if (useRegex) {
1268 JWB.worker.replace("~"+"~~JWB.newContent", replace, regexFlags, rWith, replaceDone);
1269 } else {
1270 JWB.newContent = JWB.newContent.split(replace).join(rWith); //global replacement without having to escape all special chars.
1271 }
1272 } catch(e) {
1273 console.log('Regex error:', e)
1274 JWB.stop();
1275 return JWB.status('regex-err', false);
1276 }
1277 });
1278 if ($('#enableRETF').prop('checked')) {
1279 JWB.newContent = RETF.replace(JWB.newContent);
1280 }
1281 if (!JWB.worker.isWorking()) {
1282 // no workers were called
1283 JWB.status('done', true);
1284 callback(JWB.newContent);
1285 }
1286};
1287
1288JWB.skipRETF = function() {
1289 if (!$('#enableRETF').prop('checked')) return; // RETF is not enabled to begin with
1290 if (JWB.isStopped === true) return; // don't mess with the edit box when stopped
1291 $('#enableRETF').prop('checked', false);
1292 JWB.replace(JWB.page.content, function(newContent) {
1293 JWB.editPage(newContent);
1294 JWB.updateButtons();
1295 $('#enableRETF').prop('checked', true);
1296 });
1297}
1298
1299// Edit the current page and pre-fill the newContent.
1300JWB.editPage = function(newContent) {
1301 $('#editBoxArea').val(newContent);
1302 $('#currentpage').html(JWB.msg('editbox-currentpage', JWB.page.name, encodeURIComponent(JWB.page.name)));
1303 if ($('#preparse').prop('checked')) {
1304 $('#articleList').val($.trim($('#articleList').val()) + '\n' + JWB.list[0]); //move current page to the bottom
1305 JWB.next();
1306 return;
1307 } else if (JWB.bot && $('#autosave').prop('checked')) {
1308 JWB.api.diff(function() {
1309 //timeout will take #throttle's value * 1000, if it's a number above 0. Currently defaults to 0.
1310 setTimeout(JWB.api.submit, Math.max(+$('#throttle').val() || 0, 0) * 1000, JWB.page.name);
1311 });
1312 } else {
1313 JWB.api.diff();
1314 }
1315}
1316
1317//Adds a line to the logs tab.
1318JWB.log = function(action, page, info) {
1319 var d = new Date();
1320 var pagee = encodeURIComponent(page);
1321 var extraInfo = '', actionStat = '';
1322 switch (action) {
1323 case 'edit':
1324 if (typeof info === 'undefined') {
1325 action = 'null-edit';
1326 actionStat = 'nullEdits';
1327 } else {
1328 extraInfo = ' (<a target="_blank" href="'+JWB.index_php+'?title='+pagee+'&diff='+info+'">diff</a>)';
1329 actionStat = 'pagesSaved';
1330 }
1331 break;
1332 case 'nobots':
1333 action = 'bot-skip';
1334 extraInfo = ' (<a target="_blank" href="https://en.wikipedia.org/wiki/Template:Bots">{{bots}}</a>)';
1335 // no break;
1336 case 'skip':
1337 actionStat = 'pagesSkipped';
1338 break;
1339 case 'move':
1340 extraInfo = ' to <a target="_blank" href="/wiki/'+encodeURIComponent(info)+'" title="'+info+'">'+info+'</a>';
1341 break;
1342 case 'protect':
1343 extraInfo = info;
1344 break;
1345 }
1346 actionStat = '#' + (actionStat || 'otherActions');
1347 $(actionStat).html(+$(actionStat).html() + 1);
1348 $('#actionlog tbody')
1349 .append('<tr>'+
1350 '<td>'+(JWB.fn.pad0(d.getHours())+':'+JWB.fn.pad0(d.getMinutes())+':'+JWB.fn.pad0(d.getSeconds()))+'</td>'+
1351 '<th>'+action+'</th>'+
1352 '<td><a target="_blank" href="/wiki/'+pagee+'" title="'+page+'">'+page+'</a>'+ extraInfo +'</td>'+
1353 '</tr>')
1354 .parents('.JWBtabc').scrollTop($('#actionlog tbody').parents('.JWBtabc')[0].scrollHeight);
1355};
1356
1357//Move to the next page in the list
1358JWB.next = function(nextPage) {
1359 // cancel any still ongoing regex match/replace functions, since we're moving on to another page.
1360 JWB.worker.cancelAll();
1361 if ($.trim(nextPage) && !$('#skipAfterAction').prop('checked')) {
1362 nextPage = $.trim(nextPage) + '\n';
1363 } else {
1364 nextPage = '';
1365 }
1366 $('#articleList').val($('#articleList').val().replace(/^.*\n?/, nextPage));
1367 JWB.list.splice(0,1);
1368 JWB.pageCount();
1369 JWB.api.get(JWB.list[0].split('|')[0]);
1370};
1371
1372//Stop everything, reset inputs and editor
1373JWB.stop = function() {
1374 console.trace('stopped');
1375 $('#stopbutton,'+
1376 '.editbutton,'+
1377 '#watchNow,'+
1378 '.JWBtabc[data-tab="2"] .editbutton,'+
1379 '#watchNow'+
1380 '.JWBtabc[data-tab="4"] button,'+
1381 '#skipRETF').prop('disabled', true);
1382 $('#startbutton, #articleList,'+
1383 '.JWBtabc[data-tab="1"] button,'+
1384 '#replacesPopup button,'+
1385 '#replacesPopup input,'+
1386 '.JWBtabc input, select').prop('disabled', false);
1387 $('#resultWindow').html('');
1388 $('#editBoxArea').val('');
1389 $('#currentpage').html(JWB.msg('editbox-currentpage', ' ', ' '));
1390 JWB.pl.done = true;
1391 JWB.pl.stop();
1392 JWB.status('done', true);
1393 JWB.isStopped = true;
1394};
1395
1396//Start AutoWikiBrowsing
1397JWB.start = function() {
1398 JWB.pageCount();
1399 if (JWB.list.length === 0 || (JWB.list.length === 1 && !JWB.list[0])) {
1400 alert(JWB.msg('no-pages-listed'));
1401 } else if ($('#skipNoChange').prop('checked') && !$('.replaceText').val() && !$('.replaceWith').val() && !$('#enableRETF').prop('checked')) {
1402 alert(JWB.msg('infinite-skip-notice'));
1403 } else {
1404 JWB.isStopped = false;
1405 if ($('#preparse').prop('checked')) {
1406 if (!$('#articleList').val().match('#PRE-PARSE-STOP')) {
1407 $('#articleList').val($.trim($('#articleList').val()) + '\n#PRE-PARSE-STOP'); //mark where to stop pre-parsing
1408 }
1409 } else {
1410 $('#preparse-reset').click();
1411 }
1412 $('#stopbutton, .editbutton, #watchNow, .JWBtabc[data-tab="2"] button, .JWBtabc[data-tab="4"] button, #skipRETF').prop('disabled', false);
1413 $('#startbutton, #articleList, .JWBtabc[data-tab="1"] button, #replacesPopup button, #replacesPopup input, .JWBtabc input, select').prop('disabled', true);
1414 if (!JWB.bot || !$('#autosave').prop('checked')) {
1415 // keep summary / watchlist options enabled when not in autosave mode
1416 $('#minorEdit, #summary, #viaJWB, #watchPage').prop('disabled', false);
1417 }
1418 JWB.api.get(JWB.list[0].split('|')[0]);
1419 }
1420};
1421
1422JWB.updateButtons = function() {
1423 if (!JWB.page.exists && $('#deletePage').is('.delete')) {
1424 $('#deletePage').removeClass('delete').addClass('undelete').html('Undelete');
1425 JWB.fn.blink('#deletePage'); //Indicate the button has changed
1426 } else if (JWB.page.exists && $('#deletePage').is('.undelete')) {
1427 $('#deletePage').removeClass('undelete').addClass('delete').html('Delete');
1428 JWB.fn.blink('#deletePage'); //Indicate the button has changed
1429 }
1430 if (!JWB.page.exists) {
1431 $('#movePage').prop('disabled', true);
1432 } else {
1433 $('#movePage').prop('disabled', false);
1434 }
1435 $('#watchNow').html( JWB.msg('watch-' + (JWB.page.watched ? 'remove' : 'add')) );
1436};
1437
1438/***** Web Worker functions *****/
1439JWB.worker.supported = !!window.Worker; // if window.Worker exists, we can use workers. Unless CSP blocks us.
1440JWB.worker.queue = [];
1441
1442// 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.
1443// See https://stackoverflow.com/q/66188950/1256925 for this exact question
1444JWB.worker.load = function(callback) {
1445 if (JWB.worker.blob) return callback(); // already successfully built
1446 $.getScript(JWB.imports['worker.js'], function() {
1447 // Firefox does not understand try..catch for content security policy violations, so define the worker functions regardless of the blob support.
1448 JWB.worker.functions = JWB.worker.function();
1449 // the loaded script just defined JWB.worker.function; convert it to a blob url
1450 // Based on https://stackoverflow.com/a/33432215/1256925
1451 if (JWB.worker.supported) try {
1452 let blob = new Blob(['('+JWB.worker.function.toString()+')()'], {type: 'text/javascript'});
1453 JWB.worker.blob = URL.createObjectURL(blob);
1454 callback();
1455 } catch(e) {
1456 if (e.code == 18) {
1457 JWB.worker.supported = false;
1458 }
1459 }
1460 });
1461}
1462
1463// Create a worker to be able to preform regex operations without hanging the current process.
1464// Based on https://stackoverflow.com/q/66153487/1256925
1465JWB.worker.init = function() {
1466 JWB.worker.load(function() {
1467 JWB.worker.worker = new Worker(JWB.worker.blob);
1468 JWB.worker.callback = undefined; // explicitly set to the implicit value of undefined.
1469 JWB.worker.timeout = 0;
1470 JWB.worker.queue = [];
1471 JWB.worker.worker.onmessage = function(e) {
1472 clearTimeout(JWB.worker.timeout);
1473 JWB.worker.timeout = 0;
1474 if (JWB.isStopped) {
1475 // we're stopped; clear the queue and stop.
1476 JWB.worker.queue = [];
1477 } else if (JWB.worker.callback !== undefined) {
1478 JWB.worker.callback(e.data.result, e.data.err);
1479 } else {
1480 console.error("Worker finished without callback set:", e.data, e);
1481 }
1482 JWB.worker.next(true);
1483 }
1484 });
1485};
1486
1487// Boolean; check if the worker is currently occupied.
1488JWB.worker.isWorking = function() {
1489 return JWB.worker.callback !== undefined;
1490};
1491
1492// Cancel current worker's task (e.g. due to timeout)
1493JWB.worker.terminate = function() {
1494 console.log('terminating');
1495 let w = JWB.worker;
1496 w.worker.terminate();
1497 w.callback(undefined, 'Timeout exceeded');
1498 let queue = w.queue; // save old queue
1499 w.init(); // re-init this worker, since the previous one is presumed dead (and terminated).
1500 w.queue = queue; // restore queue
1501};
1502
1503// Cancel all workers (e.g. due to no longer needing the worker's queued services)
1504JWB.worker.cancelAll = function() {
1505 JWB.worker.queue = [];
1506 if (JWB.worker.worker) JWB.worker.worker.terminate(); // do not call the callback.
1507}
1508
1509// Set worker to work, or queue the worker task.
1510JWB.worker.do = function(msg, callback) {
1511 if (JWB.worker.isWorking()) {
1512 JWB.worker.queue.push({msg: msg, callback: callback});
1513 } else {
1514 var timelimit = parseInt($('#timelimit').val()) || 3000;
1515 JWB.worker.callback = callback;
1516 // Expand "JWB.string" into JWB['string']; to allow the string to be loaded at execution time instead of queue time.
1517 // Start with 3x ~ because that cannot exist as the start of an actual page
1518 if (msg.str && msg.str.indexOf('~'+'~~JWB.') === 0) msg.str = JWB[msg.str.substr(7)]; // For now, 1-deep expansion is sufficient.
1519 JWB.worker.worker.postMessage(msg);
1520 JWB.worker.timeout = setTimeout(function() {
1521 if (!JWB.worker.isWorking()) {
1522 console.error('Worker error');
1523 JWB.worker.next(true);
1524 return;
1525 }
1526 JWB.worker.terminate();
1527 JWB.worker.next(true);
1528 }, timelimit);
1529 }
1530};
1531
1532// Execute the next task in the queue
1533JWB.worker.next = function(force = false) {
1534 if (force) {
1535 // force means the function that's calling next() has handled the previous worker task. Clean up after it.
1536 JWB.worker.callback = undefined;
1537 } else if (JWB.worker.isWorking()) {
1538 // still working and the calling function did not specify proper exit of the previous task yet.
1539 return false;
1540 }
1541 if (JWB.worker.queue.length === 0) return true;
1542 var q = JWB.worker.queue.shift();
1543 JWB.worker.do(q.msg, q.callback);
1544};
1545
1546/***** Functions using workers *****/
1547JWB.worker.match = function(str, pattern, flags, callback) {
1548 if (JWB.worker.supported) {
1549 JWB.worker.do({cmd: 'match', str, pattern, flags}, callback);
1550 } else {
1551 if (str && str.indexOf('~'+'~~JWB.') === 0) str = JWB[str.substr(7)]; // For now, 1-deep expansion is sufficient.
1552 JWB.worker.functions.match(str, pattern, flags, callback);
1553 }
1554};
1555
1556JWB.worker.replace = function(str, pattern, flags, rWith, callback) {
1557 if (JWB.worker.supported) {
1558 JWB.worker.do({cmd: 'replace', str, pattern, flags, rWith}, callback);
1559 } else {
1560 if (str && str.indexOf('~'+'~~JWB.') === 0) str = JWB[str.substr(7)]; // For now, 1-deep expansion is sufficient.
1561 JWB.worker.functions.replace(str, pattern, flags, rWith, callback);
1562 }
1563};
1564
1565JWB.worker.unparsedReplace = function(str, pattern, flags, rWith, callback) {
1566 if (JWB.worker.supported) {
1567 JWB.worker.do({cmd: 'unparsedreplace', str, pattern, flags, rWith}, callback);
1568 } else {
1569 if (str && str.indexOf('~'+'~~JWB.') === 0) str = JWB[str.substr(7)]; // For now, 1-deep expansion is sufficient.
1570 JWB.worker.functions.unparsedreplace(str, pattern, flags, rWith, callback);
1571 }
1572};
1573
1574/***** General functions *****/
1575//Clear all existing timers to prevent them from getting errors
1576JWB.fn.clearAllTimeouts = function() {
1577 var i = setTimeout(function() {
1578 return void(0);
1579 }, 1000);
1580 for (var n=0;n<=i;n++) {
1581 clearTimeout(n);
1582 clearInterval(n);
1583 }
1584 console.log('Cleared all running intervals up to index',i);
1585};
1586
1587//Filter an array to only contain unique values.
1588JWB.fn.uniques = function(arr) {
1589 var a = [];
1590 for (var i=0, l=arr.length; i<l; i++) {
1591 if (a.indexOf(arr[i]) === -1 && arr[i] !== '') {
1592 a.push(arr[i]);
1593 }
1594 }
1595 return a;
1596};
1597
1598// code taken directly from [[Template:Bots]] and changed structurally (not functionally) for readability. The user in this case is "JWB" to deny this script.
1599// the user parameter is still kept as an optional parameter to maintain functionality as given on that template page.
1600JWB.fn.allowBots = function(text, user = "JWB") {
1601 var usr = user.replace(/([\(\)\*\+\?\.\-\:\!\=\/\^\$])/g, "\\$1");
1602 if (!new RegExp("\\{\\{\\s*(nobots|bots[^}]*)\\s*\\}\\}", "i").test(text))
1603 return true;
1604 if (new RegExp("\\{\\{\\s*bots\\s*\\|\\s*deny\\s*=\\s*([^}]*,\\s*)*" + usr + "\\s*(?=[,\\}])[^}]*\\s*\\}\\}", "i").test(text))
1605 return false
1606 else
1607 return new RegExp("\\{\\{\\s*((?!nobots)|bots(\\s*\\|\\s*allow\\s*=\\s*((?!none)|([^}]*,\\s*)*" + usr +
1608 "\\s*(?=[,\\}])[^}]*|all))?|bots\\s*\\|\\s*deny\\s*=\\s*(?!all)[^}]*|bots\\s*\\|\\s*optout=(?!all)[^}]*)\\s*\\}\\}", "i").test(text);
1609}
1610
1611
1612//Prepends zeroes until the number has the desired length of len (default 2)
1613JWB.fn.pad0 = function(n, len = 2) {
1614 n = n.toString();
1615 return n.length < len ? Array(len-n.length+1).join('0')+n : n;
1616};
1617
1618JWB.fn.blink = function(el,t) {
1619 t=t?t:500;
1620 $(el).prop('disabled', true)
1621 .children().animate({opacity:'0.1'},t-100)
1622 .animate({opacity:'1'},t)
1623 .animate({opacity:'0.1'},t-100)
1624 .animate({opacity:'1'},t);
1625 setTimeout("$('"+el+"').prop('disabled', false)",t*4-400);
1626};
1627
1628JWB.fn.setSelection = function(el, start, end, dir) {
1629 dir = dir||'none'; //Default value
1630 end = end||start; //If no end is specified, assume the caret is placed without creating text selection.
1631 if (el.setSelectionRange) {
1632 el.focus();
1633 el.setSelectionRange(start, end, dir);
1634 } else if (el.createTextRange) {
1635 var rng = el.createTextRange();
1636 rng.collapse(true);
1637 rng.moveStart('character', start);
1638 rng.moveEnd('character', end);
1639 rng.select();
1640 }
1641};
1642
1643JWB.fn.scrollSelection = function(el, index) { //function to fix scrolling to selection - doesn't do that automatically.
1644 var newEl = document.createElement('textarea'); //create a new textarea to simulate the same conditions
1645 var elStyle = getComputedStyle(el);
1646 newEl.style.height = elStyle.height; //copy over size-influencing styles
1647 newEl.style.width = elStyle.width;
1648 newEl.style.lineHeight = elStyle.lineHeight;
1649 newEl.style.fontSize = elStyle.fontSize;
1650 newEl.value = el.value.substr(0,index);
1651 document.body.appendChild(newEl); //needs to be added to the HTML for the scrollHeight and clientHeight to work.
1652 if (newEl.scrollHeight != newEl.clientHeight) {
1653 el.scrollTop = newEl.scrollHeight - 2;
1654 } else {
1655 el.scrollTop = 0;
1656 }
1657 newEl.remove(); //clean up the mess I've made
1658};
1659
1660//i18n function
1661JWB.msg = function(message) {
1662 var args = arguments;
1663 var lang = JWB.lang;
1664 if (typeof message === 'object') {
1665 lang = message[1];
1666 message = message[0];
1667 }
1668 if (lang == 'qqx') return message;
1669 if (!JWB.messages || !JWB.messages.en) return '\u29FC'+message+'\u29FD'; // same surrounding <> as used in mw.msg();
1670 var msg;
1671 if (JWB.messages.hasOwnProperty(lang) && JWB.messages[lang].hasOwnProperty(message)) {
1672 msg = JWB.messages[lang][message];
1673 } else {
1674 msg = (JWB.messages.en.hasOwnProperty(message)) ? JWB.messages.en[message] : '\u29FC'+message+'\u29FD';
1675 }
1676 msg = msg.replace(/\$(\d+)/g, function(match, num) {
1677 return args[+num] || match;
1678 });
1679 return msg;
1680};
1681
1682/***** Init *****/
1683
1684JWB.init = function() {
1685 console.log(JWB.messages.en, !!JWB.messages.en);
1686 JWB.setup.load();
1687 JWB.worker.init();
1688 JWB.fn.clearAllTimeouts();
1689
1690 var findreplace = '<div class="replaces">'+
1691 '<label style="display:block;">'+JWB.msg('label-replace')+' <input type="text" class="replaceText"/></label>'+
1692 '<label style="display:block;">'+JWB.msg('label-rwith')+' <input type="text" class="replaceWith"/></label>'+
1693 '<div class="regexswitch">'+
1694 '<label><input type="checkbox" class="useRegex"> '+JWB.msg('label-useregex')+'</label>'+
1695 '<a class="re101" href="http://regex101.com/#javascript" target="_blank">?</a>'+
1696 '<label class="divisor" title="'+JWB.msg('tip-regex-flags')+'" style="display:none;">'+
1697 JWB.msg('label-regex-flags')+' <input type="text" class="regexFlags" value="g"/>'+ //default: global replacement
1698 '</label>'+
1699 '<br/>'+
1700 '</div>'+
1701 '<label title="'+JWB.msg('tip-ignore-comment')+'">'+
1702 '<input type="checkbox" class="ignoreNowiki"> '+JWB.msg('label-ignore-comment')+
1703 '</label>'+
1704 '</div>';
1705
1706 var NSList = '<select multiple name="namespace" id="namespacelist">';
1707 for (var i in JWB.ns) {
1708 if (parseInt(i) < 0) continue; //No Special: or Media: in the list
1709 NSList += '<option value="'+JWB.ns[i].id+'" selected>'+(JWB.ns[i]['*'] || '('+JWB.msg('namespace-main')+')')+'</option>';
1710 }
1711 NSList += '</select>';
1712
1713 /***** Interface *****/
1714
1715 document.title = 'AutoWikiBrowser Script'+(document.title.split('-')[1] ? ' -'+document.title.split('-')[1] : '');
1716 $('body').html(
1717 '<article id="resultWindow"></article>'+
1718 '<main id="inputsWindow">'+
1719 '<div id="inputsBox">'+
1720 '<aside id="articleBox">'+
1721 '<b>'+JWB.msg('pagelist-caption')+'</b>'+
1722 '<textarea id="articleList"></textarea>'+
1723 '</aside>'+
1724 '<section id="tabs">'+
1725 '<nav class="tabholder">'+
1726 '<span class="JWBtab" data-tab="1">'+JWB.msg('tab-setup')+'</span> '+
1727 '<span class="JWBtab active" data-tab="2">'+JWB.msg('tab-editing')+'</span> '+
1728 '<span class="JWBtab" data-tab="3">'+JWB.msg('tab-skip')+'</span> '+
1729 (JWB.sysop?'<span class="JWBtab" data-tab="4">'+JWB.msg('tab-other')+'</span> ':'')+
1730 ' <span class="JWBtab log" data-tab="5">'+JWB.msg('tab-log')+'</span> '+
1731 '</nav>'+
1732 '<section class="JWBtabc" data-tab="1"></section>'+
1733 '<section class="JWBtabc active" data-tab="2"></section>'+
1734 '<section class="JWBtabc" data-tab="3"></section>'+
1735 (JWB.sysop?'<section class="JWBtabc" data-tab="4"></section>':'')+
1736 '<section class="JWBtabc log" data-tab="5"></section>'+
1737 '<footer id="status">done</footer>'+
1738 '</section>'+
1739 '<aside id="editBox">'+
1740 '<b>'+JWB.msg('editbox-caption')+' - <span id="currentpage">'+JWB.msg('editbox-currentpage', ' ', ' ')+'</span></b>'+
1741 '<textarea id="editBoxArea"></textarea>'+
1742 '</aside>'+
1743 '</div>'+
1744 '</main>'+
1745 '<footer id="stats">'+
1746 JWB.msg('stat-pages')+' <span id="totPages">0</span>; '+
1747 JWB.msg('stat-save')+' <span id="pagesSaved">0</span>; '+
1748 JWB.msg('stat-null')+' <span id="nullEdits">0</span>; '+
1749 JWB.msg('stat-skip')+' <span id="pagesSkipped">0</span>; '+
1750 JWB.msg('stat-other')+' <span id="otherActions">0</span>; '+
1751 '</footer>'+
1752 '<div id="overlay" style="display:none;"></div>'+
1753 '<section class="JWBpopup" id="replacesPopup" style="display:none;">'+
1754 '<button id="moreReplaces">'+JWB.msg('button-more-fields')+'</button>'+
1755 '<br>'+findreplace+
1756 '</section>'+
1757 '<section class="JWBpopup" id="pagelistPopup" style="display:none;">'+
1758 '<form action="#" id="pl-form"></form>'+
1759 '</section>'
1760 );
1761
1762 $('.JWBtabc[data-tab="1"]').html(
1763 '<fieldset id="pagelist">'+
1764 '<legend>'+JWB.msg('label-pagelist')+'</legend>'+
1765 '<button id="removeDupes">'+JWB.msg('button-remove-dupes')+'</button> '+
1766 '<button id="sortArticles">'+JWB.msg('button-sort')+'</button>'+
1767 '<br>'+
1768 '<label title="'+JWB.msg('tip-preparse')+'">'+
1769 '<input type="checkbox" id="preparse"> '+JWB.msg('preparse')+
1770 '</label>'+
1771 '<span class="divisor"></span>'+
1772 '<button id="preparse-reset" title="'+JWB.msg('tip-preparse-reset')+'">'+JWB.msg('preparse-reset')+'</button>'+
1773 '<br>'+
1774 '<button id="pagelistButton">'+JWB.msg('pagelist-generate')+'</button>'+
1775 '</fieldset>'+
1776 '<fieldset id="settings">'+
1777 '<legend>'+JWB.msg('label-settings')+'</legend>'+
1778 '<button id="saveAs" title="'+JWB.msg('tip-store-setup')+'">'+JWB.msg('store-setup')+'</button>'+
1779 '<br>'+
1780 '<label>'+
1781 JWB.msg('load-settings') + ' '+
1782 '<select id="loadSettings">'+
1783 '<option value="default" selected>default</option>'+
1784 '<option value="_blank">'+JWB.msg('blank-setup')+'</option>'+
1785 '</select>'+
1786 '</label>'+
1787 '<span class="divisor"></span>'+
1788 '<button id="deleteSetup" title="'+JWB.msg('tip-delete-setup')+'">'+JWB.msg('delete-setup')+'</button>'+
1789 '<hr>'+
1790 '<button id="saveToWiki">'+JWB.msg('save-setup')+'</button>'+
1791 '<span class="divisor"></span>'+
1792 '<button id="download">'+JWB.msg('download-setup')+'</button>'+
1793 '<hr>'+
1794 '<label class="button" id="importLabel" title="'+JWB.msg('tip-import-setup')+'">'+
1795 '<input type="file" id="import" accept=".json">'+
1796 JWB.msg('import-setup')+
1797 '</label>'+
1798 '<span class="divisor"></span>'+
1799 '<button id="updateSetups" title="'+JWB.msg('tip-update-setup', JWB.settingspage)+'">'+JWB.msg('update-setup')+'</button>'+
1800 '<div id="downloads">'+
1801 '<a download="JWB-settings.json" target="_blank" id="download-anchor"></a>'+
1802 '<iframe id="download-iframe"></iframe>'+
1803 '</div>'+
1804 '</fieldset>'+
1805 '<fieldset id="limits">'+
1806 '<legend>'+JWB.msg('label-limits')+'</legend>'+
1807 '<label class="timelimit-label" title="'+JWB.msg('tip-time-limit')+'">'+
1808 JWB.msg('time-limit')+
1809 '<input type="number" id="timelimit" value="3000" min="0">'+
1810 '</label>'+
1811 '<label title="'+JWB.msg('tip-diff-size-limit')+'">'+
1812 JWB.msg('diff-size-limit')+
1813 '<input type="number" id="sizelimit" value="0" min="0">'+
1814 '</label>'+
1815 '</fieldset>'
1816 );
1817 $('.JWBtabc[data-tab="2"]').html(
1818 '<label class="minorEdit"><input type="checkbox" id="minorEdit" checked> '+JWB.msg('minor-edit')+'</label>'+
1819 '<label class="editSummary viaJWB">'+JWB.msg('edit-summary')+' <input class="fullwidth" type="text" id="summary" maxlength="500"></label>'+
1820 ' <input type="checkbox" id="viaJWB" checked title="'+JWB.msg('tip-via-JWB')+'">'+
1821 '<select id="watchPage">'+
1822 '<option value="watch">'+JWB.msg('watch-watch')+'</option>'+
1823 '<option value="unwatch">'+JWB.msg('watch-unwatch')+'</option>'+
1824 '<option value="nochange" selected>'+JWB.msg('watch-nochange')+'</option>'+
1825 '<option value="preferences">'+JWB.msg('watch-preferences')+'</option>'+
1826 '</select>'+
1827 '<span class="divisor"></span>'+
1828 '<button id="watchNow" disabled accesskey="w">'+
1829 JWB.msg('watch-add')+
1830 '</button>'+
1831 '<br>'+
1832 (JWB.bot?
1833 '<label><input type="checkbox" id="autosave"> '+JWB.msg('auto-save')+'</label>'+
1834 '<label title="'+JWB.msg('tip-save-interval')+'" class="divisor">'+
1835 JWB.msg('save-interval', '<input type="number" min="0" value="0" style="width:50px" id="throttle" disabled>')+
1836 '</label>'+
1837 '<br>'
1838 :'')+
1839 '<span id="startstop">'+
1840 '<button id="startbutton" accesskey="a">'+JWB.msg('editbutton-start')+'</button>'+
1841 '<br>'+
1842 '<button id="stopbutton" disabled accesskey="q">'+JWB.msg('editbutton-stop')+'</button> '+
1843 '</span>'+
1844 '<button class="editbutton" id="skipButton" disabled accesskey="n">'+JWB.msg('editbutton-skip')+'</button>'+
1845 '<button class="editbutton" id="submitButton" disabled accesskey="s">'+JWB.msg('editbutton-save')+'</button>'+
1846 '<br>'+
1847 '<button class="editbutton" id="previewButton" disabled accesskey="p">'+JWB.msg('editbutton-preview')+'</button>'+
1848 '<button class="editbutton" id="diffButton" disabled accesskey="d">'+JWB.msg('editbutton-diff')+'</button>'+
1849 '<button id="replacesButton">'+JWB.msg('button-open-popup')+'</button>'+
1850 findreplace+
1851 '<hr>'+
1852 '<label><input type="checkbox" id="enableRETF"> '+
1853 JWB.msg('label-enable-RETF',
1854 '<a href="/wiki/Project:AutoWikiBrowser/Typos" target="_blank">'+
1855 JWB.msg('label-RETF')+
1856 '</a>')+
1857 '</label>'+
1858 ' <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/2/2a/Gnome-view-refresh.svg/20px-Gnome-view-refresh.svg.png"'+
1859 'id="refreshRETF" title="'+JWB.msg('tip-refresh-RETF')+'">'+
1860 '<br/>'+
1861 '<button id="skipRETF" title="'+JWB.msg('tip-skip-RETF')+'" disabled>'+JWB.msg('skip-RETF')+'</button>'
1862 );
1863 $('.JWBtabc[data-tab="3"]').html(
1864 '<fieldset>'+
1865 '<legend>'+JWB.msg('label-redirects')+'</legend>'+
1866 '<label title="'+JWB.msg('tip-redirects-follow')+'">'+
1867 '<input type="radio" class="redirects" value="follow" name="redir" id="redir-follow"> '+JWB.msg('redirects-follow')+' '+
1868 '</label>'+
1869 '<label title="'+JWB.msg('tip-redirects-skip')+'">'+
1870 '<input type="radio" class="redirects" value="skip" name="redir" id="redir-skip"> '+JWB.msg('redirects-skip')+' '+
1871 '</label>'+
1872 '<label title="'+JWB.msg('tip-redirects-edit')+'">'+
1873 '<input type="radio" class="redirects" value="edit" name="redir" id="redir-edit" checked> '+JWB.msg('redirects-edit')+''+
1874 '</label>'+
1875 '</fieldset>'+
1876 '<fieldset>'+
1877 '<legend>'+JWB.msg('label-skip-when')+'</legend>'+
1878 '<label><input type="checkbox" id="skipNoChange"> '+JWB.msg('skip-no-change')+'</label>'+
1879 '<br>'+
1880 '<label><input type="radio" id="exists-yes" name="exists" value="yes"> '+JWB.msg('skip-exists-yes')+'</label>'+
1881 '<label><input type="radio" id="exists-no" name="exists" value="no" checked> '+JWB.msg('skip-exists-no')+'</label>'+
1882 '<label><input type="radio" id="exists-neither" name="exists" value="neither">'+JWB.msg('skip-exists-neither')+'</label>'+
1883 (JWB.sysop?'<br><label><input type="checkbox" id="skipAfterAction" checked> '+JWB.msg('skip-after-action')+'</label>':'')+
1884 '<hr/>'+
1885 '<label>'+JWB.msg('skip-contains')+' <input class="fullwidth" type="text" id="skipContains"></label>'+
1886 '<label>'+JWB.msg('skip-not-contains')+' <input class="fullwidth" type="text" id="skipNotContains"></label>'+
1887 '<div class="regexswitch">'+
1888 '<label><input type="checkbox" id="containRegex"> '+JWB.msg('label-useregex')+'</label>'+
1889 '<a class="re101" href="http://regex101.com/#javascript" target="_blank">?</a>'+
1890 '<label class="divisor" title="'+JWB.msg('tip-regex-flags')+'" style="display:none;">'+
1891 JWB.msg('label-regex-flags')+' <input type="text" id="containFlags"/>'+
1892 '</label>'+
1893 '</div>'+
1894 '<hr/>'+
1895 '<label title="'+JWB.msg('skip-cg-prefix')+'">'+JWB.msg('skip-category')+' <input class="fullwidth" type="text" id="skipCategories"></label>'+
1896 '</fieldset>'
1897 );
1898 if (JWB.sysop) $('.JWBtabc[data-tab="4"]').html(
1899 '<fieldset>'+
1900 '<legend>'+JWB.msg('move-header')+'</legend>'+
1901 '<label><input type="checkbox" id="suppressRedir"> '+JWB.msg('move-redir-suppress')+'</label>'+
1902 '<br>'+
1903 JWB.msg('move-also')+' '+
1904 '<label><input type="checkbox" id="movetalk"> '+JWB.msg('move-talk-page')+'</label> '+
1905 '<label><input type="checkbox" id="movesubpage"> '+JWB.msg('move-subpage')+'</label>'+
1906 '<br>'+
1907 '<label>'+JWB.msg('move-new-name')+' <input type="text" id="moveTo"></label>'+
1908 '</fieldset>'+
1909 '<fieldset>'+
1910 '<legend>'+JWB.msg('protect-header')+'</legend>'+
1911 JWB.msg('protect-edit')+
1912 ' <select id="editProt">'+
1913 '<option value="all" selected>'+JWB.msg('protect-none')+'</option>'+
1914 '<option value="autoconfirmed">'+JWB.msg('protect-autoconf')+'</option>'+
1915 '<option value="sysop">'+JWB.msg('protect-sysop')+'</option>'+
1916 '</select> '+
1917 '<br>'+
1918 JWB.msg('protect-move')+
1919 ' <select id="moveProt">'+
1920 '<option value="" selected>('+JWB.msg('protect-like-edit')+')</option>'+
1921 '<option value="all">'+JWB.msg('protect-none')+'</option>'+
1922 '<option value="autoconfirmed">'+JWB.msg('protect-autoconf')+'</option>'+
1923 '<option value="sysop">'+JWB.msg('protect-sysop')+'</option>'+
1924 '</select> '+
1925 '<br>'+
1926 JWB.msg('protect-upload')+
1927 ' <select id="uploadProt">'+
1928 '<option value="" selected>('+JWB.msg('protect-like-edit')+')</option>'+
1929 '<option value="all">'+JWB.msg('protect-none')+'</option>'+
1930 '<option value="autoconfirmed">'+JWB.msg('protect-autoconf')+'</option>'+
1931 '<option value="sysop">'+JWB.msg('protect-sysop')+'</option>'+
1932 '</select> '+
1933 '<br>'+
1934 '<label>'+JWB.msg('protect-expiry')+' <input type="text" id="protectExpiry"/></label>'+
1935 '</fieldset>'+
1936 '<button id="movePage" disabled accesskey="m">'+JWB.msg('editbutton-move')+'</button> '+
1937 '<button id="deletePage" disabled accesskey="x">'+JWB.msg('editbutton-delete')+'</button> '+
1938 '<button id="protectPage" disabled accesskey="z">'+JWB.msg('editbutton-protect')+'</button> '+
1939 '<button id="skipPage" disabled title="['+JWB.tooltip+'n]">'+JWB.msg('editbutton-skip')+'</button>'
1940 );
1941 $('.JWBtabc[data-tab="5"]').html('<table id="actionlog"><tbody></tbody></table>');
1942 $('#pagelistPopup form').html(
1943 '<div id="ns-filter" title="'+JWB.msg('tip-ns-select')+'">' + JWB.msg('label-ns-select') + NSList + '</div>'+
1944 '<fieldset>'+
1945 '<legend><label><input type="checkbox" id="categorymembers" name="categorymembers" value="cm"> '+JWB.msg('legend-cm')+'</label></legend>'+
1946 '<label title="'+JWB.msg('tip-cm')+'">'+JWB.msg('label-cm')+' <input type="text" name="cmtitle" id="cmtitle"></label>'+
1947 '<div>'+JWB.msg('cm-include')+' '+
1948 '<label><input type="checkbox" id="cmtype-page" name="cmtype" value="page" checked> '+JWB.msg('cm-include-pages')+'</label>'+
1949 '<label><input type="checkbox" id="cmtype-subcg" name="cmtype" value="subcat" checked> '+JWB.msg('cm-include-subcgs')+'</label>'+
1950 '<label><input type="checkbox" id="cmtype-file" name="cmtype" value="file" checked> '+JWB.msg('cm-include-files')+'</label>'+
1951 '</div>'+
1952 '</fieldset>'+
1953 '<fieldset>'+
1954 '<legend><label><input type="checkbox" name="linksto" id="linksto"> '+JWB.msg('legend-linksto')+'</label></legend>'+
1955 '<label>'+JWB.msg('label-linksto')+' <input type="text" name="title" id="linksto-title"></label>'+
1956 '<div>'+JWB.msg('links-include')+' '+
1957 '<label><input type="checkbox" id="backlinks" name="backlinks" value="bl" checked> '+JWB.msg('links-include-links')+'</label>'+
1958 '<label><input type="checkbox" id="embeddedin" name="embeddedin" value="ei"> '+JWB.msg('links-include-templ')+'</label>'+
1959 '<label><input type="checkbox" id="imageusage" name="imageusage" value="iu"> '+JWB.msg('links-include-files')+'</label>'+
1960 '</div>'+
1961 '<div>'+JWB.msg('links-redir')+' '+
1962 '<label><input type="radio" id="rfilter-redir" name="filterredir" value="redirects"> '+JWB.msg('links-redir-redirs')+'</label>'+
1963 '<label><input type="radio" id="rfilter-nonredir" name="filterredir" value="nonredirects"> '+JWB.msg('links-redir-noredirs')+'</label>'+
1964 '<label><input type="radio" id="rfilter-all" name="filterredir" value="all" checked> '+JWB.msg('links-redir-all')+'</label>'+
1965 '</div>'+
1966 '<label title="'+JWB.msg('tip-link-redir')+'">'+
1967 '<input type="checkbox" name="redirect" value="true" checked id="linksto-redir"> '+JWB.msg('label-link-redir')+
1968 '</label>'+
1969 '</fieldset>'+
1970 '<fieldset>'+
1971 '<legend><label><input type="checkbox" id="prefixsearch" name="prefixsearch" value="ps"> '+JWB.msg('legend-ps')+'</label></legend>'+
1972 '<label>'+JWB.msg('label-ps')+' <input type="text" name="pssearch" id="pssearch"></label>'+
1973 '<label title="'+JWB.msg('tip-ps-strict')+'"><input type="checkbox" name="allpages" value="ap" id="psstrict" checked> '+JWB.msg('label-ps-strict')+'</label>'+
1974 '</fieldset>'+
1975 '<fieldset>'+
1976 '<legend><label><input type="checkbox" id="watchlistraw" name="watchlistraw" value="wr"> '+JWB.msg('legend-wr')+'</label></legend>'+
1977 JWB.msg('label-wr')+
1978 '</fieldset>'+
1979 '<fieldset>'+
1980 '<legend><label><input type="checkbox" id="proplinks" name="links" value="pl"> '+JWB.msg('legend-pl')+'</label></legend>'+
1981 '<label title="'+JWB.msg('tip-pl')+'">'+JWB.msg('label-pl')+' <input type="text" id="titles" name="titles"></label>'+
1982 '</fieldset>'+
1983 '<fieldset>'+
1984 '<legend><label><input type="checkbox" id="proplinks" name="search" value="sr"> '+JWB.msg('legend-sr')+'</label></legend>'+
1985 '<label title="'+JWB.msg('tip-sr')+'\n'+JWB.msg('placeholder-sr', 'insource:', 'intitle:')+'">'+
1986 JWB.msg('label-sr')+' <input type="text" id="srsearch" name="srsearch" placeholder="'+JWB.msg('placeholder-sr', 'insource:', 'intitle:')+'">'+
1987 '</label>'+
1988 '</fieldset>'+
1989 '<fieldset class="listSMW">'+
1990 '<legend><label><input type="checkbox" id="smwask" name="smwask" value="smw"> '+JWB.msg('legend-smw', JWB.msg('smw-slow'))+'</label></legend>'+
1991 '<textarea id="smwquery" name="smwquery" placeholder="'+JWB.msg('label-smw', '\n|limit=500')+'"></textarea>'+
1992 '</fieldset>'+
1993 '<button type="submit">'+JWB.msg('pagelist-generate')+'</button>'
1994 );
1995 if (JWB.hasSMW) {
1996 $('#pagelistPopup').addClass('hasSMW')
1997 }
1998 $('body').addClass('AutoWikiBrowser'); //allow easier custom styling of JWB.
1999 $('[accesskey]').each(function() {
2000 let lbl = this.accessKeyLabel || this.accessKey; // few browsers support accessKeyLabel, so fallback to accessKey.
2001 $(this).attr('title', '['+lbl+']');
2002 });
2003
2004 /***** Setup *****/
2005 JWB.setup.save('_blank'); //default setup
2006 if (JWB.settings.hasOwnProperty('default')) {
2007 JWB.setup.apply();
2008 } else if (JWB.setup.initialised) {
2009 // If we already initialised, create the default settings profile.
2010 JWB.setup.save('default');
2011 }
2012 JWB.setup.extend({});
2013
2014 /***** Event handlers *****/
2015
2016 //Alert user when leaving the tab, to prevent accidental closing.
2017 onbeforeunload = function() {
2018 return "Closing this tab will cause you to lose all progress.";
2019 };
2020 ondragover = function(e) {
2021 e.preventDefault();
2022 };
2023 document.addEventListener("securitypolicyviolation", function(e) {
2024 console.log('violated CSP:', e);
2025 if (e.blockedURI == 'blob') {
2026 JWB.worker.supported = false; // tell the next JWB.worker.init() that it shouldn't even try.
2027 } else if (JWB && JWB.msg) {
2028 alert(JWB.msg('csp-error', e.violatedDirective))
2029 }
2030 })
2031
2032 $('.JWBtab').click(function() {
2033 $('.active').removeClass('active');
2034 $(this).addClass('active');
2035 $('.JWBtabc[data-tab="'+$(this).attr('data-tab')+'"]').addClass('active');
2036 });
2037
2038 function showRegexFlags() {
2039 // >>this<< is the element that's triggered
2040 $(this).parent().nextAll('label').toggle(this.checked);
2041 }
2042 $('body').on('change', '#useRegex, #containRegex, .useRegex', showRegexFlags);
2043
2044 $('#preparse-reset').click(function() {
2045 $('#articleList').val($('#articleList').val().replace(/#PRE-PARSE-STOP/g,'').replace(/\n\n/g, '\n'));
2046 });
2047 $('#saveAs').click(function() {
2048 JWB.setup.save();
2049 });
2050 $('#loadSettings').change(function() {
2051 JWB.setup.apply(this.value);
2052 });
2053 $('#download').click(JWB.setup.download);
2054 $('#saveToWiki').click(JWB.setup.submit);
2055 $('#import').change(JWB.setup.import);
2056 ondrop = JWB.setup.import;
2057 $('#updateSetups').click(JWB.setup.load);
2058 $('#deleteSetup').click(JWB.setup.del);
2059
2060 if (window.RETF) {
2061 $('#refreshRETF').click(RETF.load);
2062 $('#skipRETF').click(JWB.skipRETF)
2063 $('#enableRETF').change(function() {
2064 $('#skipRETF').css('visibility', this.checked ? 'visible' : 'hidden');
2065 });
2066 }
2067
2068 $('#replacesButton, #pagelistButton').click(function() {
2069 var popup = this.id.slice(0, -6); //omits the 'Button' in the id by cutting off the last 6 characters
2070 $('#'+popup+'Popup, #overlay').show();
2071 });
2072 $('#overlay').click(function() {
2073 $('#replacesPopup, #pagelistPopup, #overlay').hide();
2074 JWB.pl.done = true;
2075 JWB.pl.stop();
2076 });
2077 $('#moreReplaces').click(function() {
2078 $('#replacesPopup').append(findreplace);
2079 });
2080 $('#replacesPopup').on('keydown', '.replaces:last', function(e) {
2081 if (e.which === 9) $('#moreReplaces')[0].click();
2082 });
2083
2084 $('#pl-form').submit(function(e) {
2085 e.preventDefault();
2086 JWB.pl.generate();
2087 return false;
2088 });
2089 $('#pagelistPopup legend input').change(function() {
2090 //remove disabled attr when checked, add when not.
2091 $(this).parents('fieldset').find('input').not('legend input').prop('disabled', !this.checked);
2092 $(this).parents('fieldset').prop('disabled', !this.checked);
2093 }).trigger('change');
2094 $('#psstrict').change(function() {
2095 if (this.checked) {
2096 $('#pssearch').attr('name', 'apprefix');
2097 } else {
2098 $('#pssearch').attr('name', 'pssearch');
2099 }
2100 }).trigger('change');
2101
2102 $('#resultWindow').on('click', 'tr[data-line]:not(.lineheader) *', function(e) {
2103 var line = +$(e.target).closest('tr[data-line]').data('line');
2104 var index = $('#editBoxArea').val().split('\n').slice(0, line-1).join('\n').length;
2105 $('#editBoxArea')[0].focus();
2106 JWB.fn.setSelection($('#editBoxArea')[0], index+1);
2107 JWB.fn.scrollSelection($('#editBoxArea')[0], index);
2108 });
2109
2110 $('#removeDupes').click(function() {
2111 $('#articleList').val(JWB.fn.uniques($('#articleList').val().split('\n')).join('\n'));
2112 JWB.pageCount();
2113 });
2114 $('#sortArticles').click(function() {
2115 $('#articleList').val($('#articleList').val().split('\n').sort().join('\n'));
2116 JWB.pageCount();
2117 });
2118
2119 $('#watchNow').click(JWB.api.watch);
2120 $('#autosave').change(function() {
2121 $('#throttle').prop('disabled', !this.checked);
2122 });
2123
2124 $('#viaJWB').change(function() {
2125 $('#summary').parent('label')
2126 .toggleClass('viaJWB', this.checked)
2127 .attr('maxlength', 500 - this.checked*JWB.summarySuffix.length); // Change the max size of the allowed summary according to having a suffix or not.
2128 });
2129 $('#startbutton').click(JWB.start);
2130 $('#stopbutton').click(JWB.stop);
2131 $('#submitButton').click(JWB.api.submit);
2132 $('#previewButton').click(JWB.api.preview);
2133 $('#diffButton').click(JWB.api.diff);
2134
2135 $('#skipButton, #skipPage').click(function() {
2136 JWB.log('skip', JWB.list[0].split('|')[0]);
2137 JWB.next();
2138 });
2139
2140 if (JWB.sysop) {
2141 $('#movePage').click(function() {
2142 if ($('#moveTo').val().length === 0) {
2143 return alert(JWB.msg('alert-no-move'));
2144 }
2145 JWB.api.move();
2146 });
2147 $('#protectPage').click(JWB.api.protect);
2148 $('#deletePage').click(JWB.api.del);
2149 }
2150};
2151
2152//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.
2153if (JWB.allowed === false) JWB = false;