User:NOUG4AT/JWB.js

From Valve Developer Community
< User:NOUG4AT
Revision as of 23:45, 25 January 2024 by NOUG4AT (talk | contribs)
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.
/** <nowiki>
 * Install this script by pasting the following in your personal JavaScript file:

mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Joeytje50/JWB.js/load.js&action=raw&ctype=text/javascript');

 * Or for users on en.wikipedia.org:

{{subst:lusc|User:Joeytje50/JWB.js/load.js}}

 * Note that this script will only run on the 'Project:AutoWikiBrowser/Script' page.
 * 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
 * @version 4.4.2
 * @author Joeytje50
 * </nowiki>
 */

window.JWBdeadman = false; // ADMINS: in case of fire, set this variable to true to disable this entire tool for all users

//TODO: more advanced pagelist-generating options
//TODO: generate page list based on images on a page
//TODO: Add feature to perform general cleanup (<table> to {|, fullurl-links to wikilinks, removing underscores from wikilinks)
//TODO: Add report button to AJAX error alert box
//TODO: Re-add errored pages to the page list
//TODO: Automatically generate full list of protection levels
//TODO: Add 'Ignore quotes' alongside 'Ignore unparsed content'; for example: Ignore [] Unparsed content [] Quotes
//TODO: Read Wikipedia:AutoWikiBrowser/Config for when not to apply typo fixes
//TODO: RETF disable any \b and some other cases
//TODO: Fix deletedrevs deprecated API

//Cleanup / modernize:
// .indexOf('') != -1 -> .includes()
// mw for requests, instead of ajax
// Optional?.chaining?.properties

/***** 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':	'//en.wikipedia.org/w/index.php?title=User:Joeytje50/JWB.css&action=raw&ctype=text/css',
		'i18n.js':	'//en.wikipedia.org/w/index.php?title=User:Joeytje50/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':'//en.wikipedia.org/w/index.php?title=User:Joeytje50/JWB.js/worker.js&action=raw&ctype=text/javascript',
	};
	
	let objs = ['page', 'api', 'worker', 'fn', 'pl', 'messages', 'setup', 'settings', 'ns'];
	for (let i=0;i<objs.length;i++) {
		JWB[objs[i]] = {};
	}
	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
	}
	JWB.hasJSON = false; // whether or not the wiki supports JSON userpages (mw 1.31+).
	JWB.hasSMW = false; // whether or not the wiki has SMW installed.
})();

/***** 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');
	new mw.Api().loadMessagesIfMissing( [ 'pagecategorieslink', 'pagecategorieslink' ] );

	JWB.langs = [];

	$.getScript(JWB.imports['i18n.js'], function() {
		if (JWB.allowed === false) {
			JWB.checkInit(); // deny access without attempting to load other languages.
			return;
		}

		if (JWB.lang !== 'en' && JWB.imports.i18n.hasOwnProperty(JWB.lang)) {
			JWB.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)) {
			JWB.langs.push(JWB.imports.i18n[JWB.contentLang]);
			JWB.messages[JWB.contentLang] = JWB.messages[JWB.contentLang] || null;
		}

		if (JWB.langs.length) {
			$.when.apply($, JWB.langs.map(url => $.getScript(url))).done(function(r) {
				JWB.checkInit();
			});
		} else { // no more languages to load.
			console.log('no languages needed loading');
			JWB.checkInit();
		}
	});
	
	//RegEx Typo Fixing
	$.getScript(JWB.imports['RETF.js'], function() {
			$('#refreshRETF').click(RETF.load);
	});

	if (window.JWBdeadman === true) {
		window.JWB = false; // disable all access
		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.");
		return false;
	} else 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',
		titles: 'Project:AutoWikiBrowser/CheckPageJSON',
		prop: 'info|revisions',
		meta: 'userinfo|siteinfo',
		rvprop: 'content',
		rvlimit: 1,
		uiprop: 'groups',
		siprop: 'namespaces|usergroups|extensions',
		indexpageids: true,
		format: 'json',
	}).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
		
		// This will execute before JWB.init() and therefore before JWB.setup.load() loading the user's settings.
		let wikigroups = response.query.usergroups;
		for (var u of wikigroups) {
			if (u.rights.indexOf('edituserjson') !== -1) {
				JWB.hasJSON = true;
				break;
			}
		}
		
		// Check if we've got SMW on this wiki
		let extensions = response.query.extensions;
		for (var e of extensions) {
			if (e.name == "SemanticMediaWiki") {
				JWB.hasSMW = true;
				break;
			}
		}
		
		JWB.username = response.query.userinfo.name; //preventing any "hacks" that change wgUserName or mw.config.wgUserName
		var groups = response.query.userinfo.groups;
		var page = response.query.pages[response.query.pageids[0]];
		var users = [];
		var bots = [];
		JWB.sysop = groups.indexOf('sysop') !== -1;
		if (response.query.pageids[0] !== '-1') {
			var checkPageData = JSON.parse(page.revisions[0]['*']);
			users = checkPageData.enabledusers;
			if ("enabledbots" in checkPageData) {
				bots = checkPageData.enabledbots;
			}
		} else {
			users = false; //fallback when page doesn't exist
			if (JWB.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.bot = groups.indexOf('bot') !== -1 && (users === false || bots.includes(JWB.username));
		// Temporary global debugging variables
		JWB.debug = [groups.indexOf('bot'), users === false, bots && bots.indexOf(JWB.username)];
		if (JWB.username === "Joeytje50" && response.query.userinfo.id === 13299994) {//TEMP: Dev full access to entire interface.
			JWB.bot = true;
			users.push("Joeytje50");
		}
		if (JWB.sysop || response.query.pageids[0] === '-1' || users === false || users.includes(JWB.username) || bots.includes(JWB.username)) {
			JWB.allowed = true;
			JWB.checkInit(); //init if everything necessary has been 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')) {
				$(this).next().attr('data-line',$(this).data('line')); //copy over current data-line for deleted lines 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.
		}
	});
};