User:So9q/ScriptInstaller.js

From Wikidata
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.
// Adapted from https://en.wiktionary.org/w/index.php?title=User:Erutuon/scripts/ScriptInstaller.js&oldid=54132174
// Modified from [[w:User:Equazcion/ScriptInstaller.js]]
/*jshint maxerr:1048576, strict:true, undef:true, latedef:true, es5:true */
/*global mw, $ */
$.when(mw.loader.using(["mediawiki.util", "mediawiki.api"]), $.ready).then(function() {
	var installerStylesUrl = "//www.wikidata.org/w/index.php?title=User:So9q/ScriptInstaller.css&action=raw&ctype=text/css";
	var namespaceNumber = mw.config.values.wgNamespaceNumber;
	var pageName = mw.config.values.wgPageName;
	var userName = mw.config.values.wgUserName;
	var action = mw.config.values.wgAction;
	var contentLanguage = mw.config.values.wgPageContentLanguage;
	var noticeProject = mw.config.values.wgNoticeProject;
	var contentModel = mw.config.values.wgPageContentModel;
	var jsPage, homePage;
	var isInstalled = false; // This will be changed, if the current script is found to be installed.

	// If current revision id is 0, page has not been created.
	var articleExists = (mw.config.values.wgCurRevisionId > 0);

	// Never do anything in article space, or on a page that hasn't been created.
	if (namespaceNumber === 0 || !articleExists) return;

	function getPath(p) {
		return mw.config.get("wgArticlePath").replace("$1", p);
	}

	function getCommonJs() {
		return new mw.Api().get({
			action: "query",
			titles: "User:" + userName + "/common.js",
			prop: "revisions",
			rvprop: "content",
			indexpageids: 1
		}).then(function(r) {
			var pageid = r.query.pageids[0];
			return r.query.pages[pageid].revisions[0]["*"];
		});
	}

	function uninstallScript(scriptPath) {
		return new mw.Api().edit("User:" + userName + "/common.js", function(response) {
			var wikitext = response.content;
			//find/replace our line with nothing, thereby removing the script
			var toBeReplaced1 = "importScript('" + scriptPath + "')";
			var toBeReplaced2 = 'importScript("' + scriptPath + '")';
			var regex1 = new RegExp("\\n" + mw.util.escapeRegExp(toBeReplaced1) + "[^\\n]*[^\\n]*");
			var regex2 = new RegExp("\\n" + mw.util.escapeRegExp(toBeReplaced2) + "[^\\n]*[^\\n]*");
			var newText = wikitext.replace(regex1, "").replace(regex2, "");
			if (wikitext === newText){
				//throw new Error("");
				return $.Deferred().reject("ScriptInstaller.js failed to make any changes in your common.js.").promise();
			}
			
			return {
				text: newText,
				summary: "[[User:So9q/ScriptInstaller.js|Script Installer]]: Removed [[" + scriptPath + "]]",
			}; 
		});
	}
	var $installerWrapper = $("<div></div>").attr("id", "script-installer-wrapper").prop('hidden', true);
	// Handle .js pages
	if (contentModel === "javascript" &&
		action == 'view' &&
		namespaceNumber > -1) {
		// Exclude users' own common.js and skin.js pages
		var fixedUn = mw.util.escapeRegExp(userName.replace(/ /g, '_'));
		var defaultJSPagesRegex = new RegExp('User:' + fixedUn + '/(?:common|monobook|vector|modern|cologneblue).js');
		if (!defaultJSPagesRegex.test(pageName) &&
			pageName != 'User:So9q/ScriptInstaller.js') {
			// Set a flag so the rest of the script knows we're on a .js page
			jsPage = true;

			// Append the install link to the header, along with a "scriptInstallerLink" class for use by the rest of the script
			$installerWrapper.append('<div id="' + pageName + '" class="scriptInstallerLink jsPage"></div>');
			$('h1#firstHeading').after($installerWrapper);
		}
	}


	// If script links are found on the page, start the music. Detection is based on span tags with "scriptInstallerLink" class, which are added by {{userscript}}.
	// Also run if we're on User:Equazcion/scriptInstaller, where we'll simply provide access to the installed script list and uninstall links
	if (($('div.scriptInstallerLink').length > 0 && action != 'submit' && action != 'edit') ||
		pageName == 'User:So9q/ScriptInstaller.js') {
		//if ( !jsPage ) importStylesheetURI("//en.wiktionary.org/w/index.php?title=User:Erutuon/ScriptInstaller.css&action=raw&ctype=text/css"); // Stylesheet already fetched above on .js pages
		if (pageName == 'User:So9q/ScriptInstaller')
			homePage = true; // Set a flag if we're running the limited User:Erutuon/ScriptInstaller functions

		// Set interface text
		var installerTitle = 'You currently have the following scripts installed in your <a href="' + getPath('Special:MyPage/common.js') + '">common.js</a>. <div class="titleBadge"><a href="https://www.wikidata.org/wiki/User:So9q/ScriptInstaller.js' + '">Script Installer</a></div>';
		var installerMessage = 'Only scripts installed using <span class="nx">importScript</span> are shown here. To uninstall a script, click "uninstall".';
		var installerLink = 'Install';
		var installedBadge = 'Installed';
		var unInstallerLink = 'uninstall';
		var noauto = 'Must be installed manually';

		// Append the box of installed scripts. Hide unless we're on a designated installation page
		$('div#contentSub').after('<div class="scriptInstaller" hidden="hidden"></div>');



		var getInstallerStyles = $.get(installerStylesUrl).then(function(r) {
			mw.util.addCSS(r);
			//this is a SO version
			//$('<link>', {rel:'stylesheet', type:'text/css', 'href':installerStylesUrl}).appendTo('head');
		});

		// Create array for installed script paths
		var installedScripts = [];
		
		$.when(getCommonJs(), getInstallerStyles).then(function(wikitext) {
			$installerWrapper.prop('hidden', false);
			var lines = wikitext.split('\n');

			// Use the regex to iterate through the lines, looking for the ones that ScriptInstaller added
			$.each(lines, function(index, value) {
				var match = new RegExp('importScript\\(\'(.*)\'\\)', "i").exec(value);

				// Put the paths of the matches into the array of installed scripts
				if (match) {
					installedScripts.push(match[1]);
				}
			});

			// If none were found, remove the installed script list box. Otherwise fade-in the box and set up the toggle link
			if (installedScripts.length === 0)
				$('div.scriptInstaller').remove();
			else if (pageName == 'Wikipedia:WikiProject_User_scripts/Scripts' ||
				/:WikiProject_User_scripts$/.test(pageName) ||
				/:User_scripts$/.test(pageName) ||
				jsPage ||
				homePage) {
				// Insert script list toggle link.
				var toggleMessage = (jsPage || homePage) ? 'Show installed script list' : 'Hide installed script list';
				$('.scriptInstallerLink').first()
					.after('<div id="scriptinstallerTog" style="display: inline-block;"><a style="font-weight:bold;font-size:10px" class="scriptinstallerTog" href="#bbx">' + toggleMessage + '</a></div>');

				// The function to set the toggle link to
				var setScriptInstallerToggle = function() {
					$('.scriptinstallerTog').click(function() {
						if ($('.scriptinstallerTog').html() == "Show installed script list") {
							$('.scriptInstaller').fadeIn(500);
							$('.scriptinstallerTog').html('Hide installed script list');
						} else {
							$('.scriptInstaller').fadeOut(200);
							$('.scriptinstallerTog').html('Show installed script list');
						}
					});
				};

				// Set the toggle link function. Also fade the box in initially, if we're at the script listing page.
				if (!jsPage && !homePage) {
					setTimeout(function() {
						$('.scriptInstaller').fadeIn(800, function() {
							setScriptInstallerToggle();
						});
					}, 500);
				} else {
					setScriptInstallerToggle();
				}
			}

			// Start building the code for display of the installed list. Iterate through each installed script in the array
			var installedList = '<ul style="list-style-type:none;">';
			$.each(installedScripts, function(index, value) {

				// For each script already installed, change the install links (into "installed" messages) that are on the current page (if any)
				var scriptNameId = value.replace(/ /g, '_').replace(/[\.%]20/g, '_');
				var scriptNameElement = $('div.scriptInstallerLink[id="' + scriptNameId + '"]');
				if (scriptNameElement.length) {
					isInstalled = true;
					scriptNameElement
						.attr('id', 'installed' + index)
						.addClass('installed')
						.html(installedBadge);
				}
				if (jsPage)
					$('div.scriptInstallerLink[id="' + value.replace(/ /g, '_') + '"]')
					.attr('id', 'installed' + index)
					.addClass('installed')
					.html(installedBadge)
					.css('font-weight', 'bold');

				// Add an HTML list element for each installed script, containing .js and uninstall links
				installedList = installedList + '<li>' +
					'<a class="installedScriptName" href="' + getPath(value) + '">' + decodeURIComponent(value) + '</a> ' +
					'(<a href="#installerLink" class="unInstallerLink">' + unInstallerLink + '</a>)' +
					'</li>';
			});

			// Cap off the list of installed scripts
			installedList = installedList + '</ul>';

			if (isInstalled) {
				$('.scriptInstallerLink').first()
					.after('<div id="uninstallThis" style="display: inline-block;"><a href="#installerLink" class="uninstallThisScript">' + unInstallerLink + '</a></div>');

				$(".uninstallThisScript").each(function() {
					var path = mw.config.get("wgCanonicalNamespace") + ":" + mw.config.get("wgTitle");
					$(this).click(
						function() {
							$('body').append('<div class="overlay" style="background-color:#000;opacity:.4;position:fixed;' +
								'top:0;left:0;width:100%;height:100%;z-index:500;"></div>');

							$('body').prepend('<div class="arcProg" style="font-weight:bold;box-shadow: 7px 7px 5px #000;font-size:0.9em;line-height:1.5em;' +
								'z-index:501;opacity:1;position:fixed;width:50%;left:25%;top:30%;background:#F7F7F7;border:#222 ridge 1px;padding:20px;"></div>');

							$('.arcProg').append('<div>Uninstalling <span style="font-weight:normal;color:#003366;">' + path + '</span>...</div>');

							uninstallScript(path).then(function(postResponse) {
								$('.arcProg').append('<div><span style="color:#00008C">Done!</span> Reloading...</div>');
							}, function(errorText){
								mw.notify(errorText);
							}).always(function(){
								setTimeout(function(){location.reload();}, 500);
							});
						}
					);
				});
			}

			// Build and append the rest if the list box code, and insert our constructed list of installed scripts
			$('.scriptInstaller').html('<div class="installerTitle">' + installerTitle + '</div>' +
				'<div class="container1">' +
				'<div class="installerMessage">' + installerMessage + '</div>' +
				'<div class="uninstallList">' + installedList + '</div>' +
				'</div>');

			// Iterate through each line in the installed list and set the click function for their uninstall links
			$('.scriptInstaller li').each(function() {
				var path = $(this).find('a.installedScriptName').html();
				$(this).find('.unInstallerLink').click(function() {
					$('body').append('<div class="overlay" style="background-color:#000;opacity:.4;position:fixed;' +
						'top:0;left:0;width:100%;height:100%;z-index:500;"></div>');

					$('body').prepend('<div class="arcProg" style="font-weight:bold;box-shadow: 7px 7px 5px #000;font-size:0.9em;line-height:1.5em;' +
						'z-index:501;opacity:1;position:fixed;width:50%;left:25%;top:30%;background:#F7F7F7;border:#222 ridge 1px;padding:20px;"></div>');

					$('.arcProg').append('<div>Uninstalling <span style="font-weight:normal;color:#003366;">' + path + '</span>...</div>');

					uninstallScript(path).then(function(postResponse) {
						$('.arcProg').append('<div><span style="color:#00008C">Done!</span> Reloading...</div>');
						location.reload();
					}, function(errorText){
						mw.notify(errorText);
					}).always(function(){
						setTimeout(function(){location.reload();}, 500);
					});
				});
			});
		});

		// Iterate through each templated (via {{userscript}}) script on the page
		$('div.scriptInstallerLink').each(function() {

			// Get the script path, which the template places in the span's ID
			var path = $(this).attr('id');
			path = path.replace(/.2F/g, '/').replace(/\_/g, ' ');

			// If there's more than one dot left in the path, assume percent encoding was converted to dots. Leave the last dot for ".js"
			if ((path.split(".").length - 1) > 1) {
				var parts = path.split('.');
				path = parts.slice(0, -1).join('%') + '.' + parts.slice(-1);
			}

			// If this path leads to a valid en-wiki .js script in userspace or wikipedia space, add an install link
			if (((path.toLowerCase().substring(0, 5) == "user:") || (path.toLowerCase().substring(0, 10) == 'wikipedia:')) && (path.lastIndexOf('.js') == path.length - 3)) {
				$(this).html('<a href="#installerLink" class="installerLink">' + installerLink + '</a>');

				// Set the click function for the install link
				$(this).find('a.installerLink').click(function() {

					$('body').append('<div class="overlay" style="background-color:#000;opacity:.4;position:fixed;' +
						'top:0;left:0;width:100%;height:100%;z-index:500;"></div>');

					$('body').prepend('<div class="arcProg" style="font-weight:bold;box-shadow: 7px 7px 5px #000;font-size:0.9em;line-height:1.5em;' +
						'z-index:501;opacity:1;position:fixed;width:50%;left:25%;top:30%;background:#F7F7F7;border:#222 ridge 1px;padding:20px;"></div>');

					$('.arcProg').append('<div>Installing <span style="font-weight:normal;color:#003366;">' + path + '</span>...</div>');

					// Set ajax parameters for the ajax post that occurs when the install link is clicked 
					var request1 = {
						action: "edit",
						title: "User:" + userName + "/common.js",
						appendtext: "\nimportScript('" + decodeURIComponent(path) + "'); //Linkback: [[" + decodeURIComponent(path) + "]] Added by Script installer",
						summary: "[[User:So9q/ScriptInstaller.js|Script Installer]]: Added [[" + path + "]]",
						token: mw.user.tokens.get("csrfToken")
					};

					// Send the ajax post, which appends our new importScript line to common.js, then reload the current page
					$.post(mw.config.values.wgScriptPath + "/api.php", request1, function(response1) {
						$('.arcProg').append('<div><span style="color:#00008C">Done!</span> Reloading...</div>');
						location.reload();
					});
				});
			} else {
				// If this is not a valid path to an en-wiki .js script in user or wikipedia space, add a "must install manually" message
				$(this).html(' | <span class="noauto">' + noauto + '</span>');
			}
		});
	}
});