User:Gary/script installer source.js

// ==================================================================================================== // ============================================== api.js ============================================== // ====================================================================================================

/** * The callback response that retrieves a script's "associated script". */ associatedScriptCallback = function(response) {	if (!response['query'] || !response['query']['pageids'] || response['query']['pageids'][0] == -1) return false; markAssociatedScript(otherPage); // set cookie setConvertedCookie('associated-scripts', [ pageName, otherPage ]); };

/** * Gets the edit token for the INSTALL_PAGE, then sends return data to another function for processing. */ beginScriptInstallation = function(pageToInstallTo, scriptName) {	var api = sajax_init_object; api.open('GET', mw.config.get('wgServer') + mw.config.get('wgScriptPath') + '/api.php?format=json&action=query&prop=info&indexpageids=1&intoken=edit&titles=' + pageToInstallTo, true); // now that we used GET, get the token from the results api.onreadystatechange = function {		if (api.readyState == 4) {			if (api.status == 200) {				var response = eval('(' + api.responseText + ')'); var tokenPage = response['query']['pages'][response['query']['pageids'][0]]; sendScriptInstallation(pageToInstallTo, tokenPage['edittoken'], scriptName); }			else errorMessage('EXT_TOK'); }	};	api.send(null); };

/** * Gets the backlinks to a page, which we later process and then call "installations". */ function getInstallations(callback, page, blcontinue, printToLog) {	wikiApi(callback, 'action=query&rawcontinue=&list=backlinks&bltitle=' + page + '&bllimit=500&blfilterredir=nonredirects&blnamespace=2' + (blcontinue ? '&blcontinue=' + blcontinue : ''), printToLog); };

/** * Get the user's page where their scripts are installed to. */ getInstallPage = function(scriptName, skinPage, markInstalled, functionToRun) {	if (skinPage == scriptName) {		// current script is the script where we install to		var install = document.getElementById('install-this-script'); var node = document.createElement('span'); node.className = 'si-heading'; node.id = 'install-this-script'; node.innerHTML = 'Your scripts are installed here';

return install.parentNode.replaceChild(node, install); }	var api = sajax_init_object; api.open('GET', mw.config.get('wgServer') + mw.config.get('wgScriptPath') + '/api.php?format=json&action=query&prop=revisions&titles=' + skinPage + '&rvprop=content&indexpageids=1', true); api.onreadystatechange = function {			if (api.readyState == 4) {				if (api.status == 200) {					var response = eval('(' + api.responseText + ')'); var pageToInstallTo = response['query']['pages'][response['query']['pageids'][0]]; if (typeof pageToInstallTo['revisions'] == 'undefined') {						errorMessage('NO_REV'); return; }					existingScriptContent = pageToInstallTo['revisions'][0]['*']; if (!functionToRun) indicateScriptInstalled(scriptName, pageToInstallTo['title'], existingScriptContent, markInstalled); else functionToRun(pageToInstallTo['title'], existingScriptContent); }				else if (typeof(failedToConnectToAPI) == 'function') failedToConnectToAPI; }		};	api.send(null); };

/** * The callback used for the Script Library to get all of a user's scripts. * * Also, the callback for finding how many users have a script installed * with blcontinue (for when we reached the 500 max limit on results) */ scriptInstallationsCallback = function(response) {	if (!response['query'] || !response['query']['backlinks']) return; var userCount = countUsersFromJSON(response['query']['backlinks']).length; doScriptInstallation(userCount, (response['query-continue'] ? formatBlContinue(response['query-continue']['backlinks']['blcontinue']) : '')); };

/** * With the token, use POST to submit a page edit. */ function sendScriptInstallation(pageToInstallTo, token, scriptName) {	// Script is already in script page if (findExistingScript(scriptName, existingScriptContent) && siSettings['checkIfScriptIsInstalled']) {		alert('The script is already installed.'); return; }	else indicateScriptIsInstalling; var api = sajax_init_object; var text = existingScriptContent + (existingScriptContent ? '\n' : ) + importScriptTypes[scriptType] + '(\ + scriptName + '\'); // ' + scriptName + ''; // FIXME Indicate "Installing stylesheet" if that is the case. var editSummary = siEditSummary('Installing script ' + scriptName + ''); var parameters = 'action=edit&title=' + encodeURIComponent(pageToInstallTo) + '&text=' + encodeURIComponent(text) + '&token=' + encodeURIComponent(token) + '&summary=' + encodeURIComponent(editSummary) + '&format=json'; api.open('POST', mw.config.get('wgServer') + mw.config.get('wgScriptPath') + '/api.php', true); // tell the user what has happened; either success or failure api.onreadystatechange = function {			if (api.readyState == 4) {				if (api.status == 200) {					// TODO Update the Script Library cookie by adding this script. alert('The script "' + scriptName + '" was installed successfully to "' + pageToInstallTo + '".'); location.reload(true); }				else errorMessage('AL_RES_STAT'); }		};	api.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); api.setRequestHeader('Connection', 'keep-alive'); api.setRequestHeader('Content-length', parameters.length); api.send(parameters); };

/** * Callback response for number of backlinks to a single script. */ singleScriptInstalls = function(response) {	if (!response['query'] || !response['query']['backlinks']) return; var userCount = countUsersFromJSON(response['query']['backlinks']).length; doSingleScriptInstalls(userCount, (response['query-continue'] ? formatBlContinue(response['query-continue']['backlinks']['blcontinue']) : '')); };

/** * Wrapper for Wikipedia's API. */ function wikiApi(callback, parameters, printToLog) {	var url = mw.config.get('wgServer') + mw.config.get('wgScriptPath') + '/api.php?callback=' + callback + '&format=json&' + parameters; importScriptURI(url); if (printToLog) siLog(url); };

// ==================================================================================================== // ============================================ cookies.js ============================================ // ====================================================================================================

/** * Create a cookie. */ function createCookie(name, value, days) {	// override the "days" parameter (for now) var days = ageOfCookies; // convert 'value' hash to string if (value instanceof Array) {		var newValues = []; for (var i = 0; i < value.length; i++) {			if (value[i] instanceof Array) newValues.push(value[i].join(',')); }		var value = newValues.join(encodeURIComponent(';')); }	if (days) {		var date = new Date; date.setTime(date.getTime + (days * 24 * 60 * 60 * 1000)); var expires = '; expires=' + date.toGMTString; }	else var expires = '';

// don't set the cookie if it has the same value as the one that already exists if (readCookie('si-' + name) == value) return false; var name = 'si-' + name; document.cookie = name + '=' + value + expires + '; path=/'; };

/** * Delete a cookie. */ function deleteCookie(name) {	createCookie(name, '', -1); };

/** * Get a cookie, then convert it to a hash. * * @param {string} name Name of cookie, without the preceding "si-". * @param {boolean} asArray Return an array instead. * @param {boolean} fromSetter True if retrieved from the cookie setter function; * 		necessary so that that function can still work properly when SI cookies are disabled. * @return {object} Returns the cookie laid out as an associative array. */ function getConvertedCookie(name, asArray, fromSetter) {	var cookieName = 'si-' + name; var cookie = readCookie(cookieName); if (!cookie || (!siSettings['enableSICookies'] && !fromSetter)) return [];

var allowed = ['associated-scripts', 'installed-by', 'script-library']; if (!has(allowed, name)) return (asArray ? [] : {}); var scripts = decodeURIComponent(cookie).split(';'); if (asArray) var result = []; else var result = {}; var length = 0; for (var i = 0; i < scripts.length; i++) {		var script = scripts[i].split(','); if (name == 'script-library') {			if (asArray) result.push([script[0], script[1], script[2]]); else result[script[0]] = [script[1], script[2]]; }		else {			if (asArray) result.push([script[0], script[1]]); else result[script[0]] = script[1]; }		length++; }	if (typeof(result) == 'object') {		// include the length result['length'] = length; }	return result; };

/** * Reads a cookie. */ function readCookie(name) {	var nameEQ = name + '='; var ca = document.cookie.split(';'); for(var i = 0; i < ca.length; i++) {		var c = ca[i]; while (c.charAt(0) == ' ') c = c.substring(1, c.length); if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length); }	return null; };

/** * Sets a converted cookie. * * @param {string} name Name of cookie * @param {array} value The item to add to the existing cookie. */ function setConvertedCookie(name, value) {	// get the cookie first, only keep maximum 50 items in the cookie var convertedCookie = getConvertedCookie(name, true, true); var convertedCookieHash = getConvertedCookie(name, false, true); // FIXME Don't set this if old script library cookie is the same as the new one. // Set a cookie every time the Script Library loads if (name == 'script-library') {		return createCookie(name, (convertedCookie.length > 0 ? convertedCookie : value)); }	// if new cookie already exists in existing convertedCookie // AND the values are the same, then skip this if (convertedCookieHash[value[0]] && convertedCookieHash[value[0]] == value[1]) return true; // if the new cookie already exists, then remove it first if (name == 'installed-by') {		for (var i = 0; i < convertedCookie.length; i++) {			if (convertedCookie[i][0] == value[0]) {				convertedCookie.splice(i, 1); break; }		}	}	if (convertedCookie.length >= 50) {		// remove the first item convertedCookie.shift; // add newest item at the end convertedCookie.push(value); }	else {		// add newest item at the end convertedCookie.push(value); }

return createCookie(name, convertedCookie); };

// ==================================================================================================== // ============================================= doers.js ============================================= // ====================================================================================================

addTitleTooltips = function {	if (typeof(tooltipText) == 'undefined') return false; // apply TITLE attribute to the ID as well for (var name in tooltipText) {		var id = document.getElementById('si-' + name); if (id) id.title = stripHTML(tooltipText[name].replace(//g, ' ')); } };

/** * Create the list of scripts installed for a user's Script Library */ function buildUserLibrary(title, content) {	var scripts = installedScripts(content); doUserLibrary(scripts); setConvertedCookie('script-library', scripts); };

function countUsersFromJSON(users) {	// TODO +1 user if current user is not included. // remove the forced = 1 from other functions then, but keep the link unlinking. var newUsers = []; for (var i = 0; i < users.length; i++) {		// determine if the title ends in .js and is a skin page // strip .js first var title = users[i]['title']; var parts = title.split('/'); if (parts.length != 2) continue; title = parts[1]; if (!title.indexOf('.')) continue; title = title.substring(0, title.indexOf('.')); if (allSkinsHash[title]) newUsers.push(users[i]); }	return newUsers; };

/** * Used on the Script Library. * Executed directly when a cookie is found. * Otherwise, executed after contacting API and after scriptInstallationsCallback. * Determines which node to update with the help of the "callback-counter" node. * * @param {integer} userCount Number of users. * @param {object} blcontinue An object containing blcontinue information, if available. */ function doScriptInstallation(userCount, blcontinue) {	if (userCount == 0) userCount = 1; // get the current counter var counter = document.getElementById('callback-counter'); var currentCount = counter.firstChild.nodeValue; // insert the number of installations var installs = document.getElementById('installation-' + currentCount); // check if "installs" exists; if not, then fall back to script counter if (!installs) {		var scriptCounter = document.getElementById('si-script-counter'); var scriptNode = document.getElementById(scriptCounter.removeChild(scriptCounter.firstChild).firstChild.nodeValue); currentCount = scriptNode.getElementsByClassName('si-script-counter')[0].firstChild.nodeValue; installs = document.getElementById('installation-' + currentCount); }	else {		// replace the counter counter.replaceChild(document.createTextNode(parseInt(currentCount) + 1), counter.firstChild); }	var script = installs.parentNode.parentNode.id; // insert the new number of users var currentUsers = installs.firstChild.nodeValue; userCount = userCount + (isNaN(parseInt(currentUsers)) ? 0 : parseInt(currentUsers)); var numberOfUsers = document.createTextNode(userCount); installs.replaceChild(numberOfUsers, installs.firstChild);

// pluralize if necessary var plural = document.getElementById('installation-plural-' + currentCount); if (userCount == 1) {		plural.replaceChild(document.createTextNode(' installation'), plural.firstChild); // unlink installation-link-i since we are forcing the number var span = convertIdToSpan('installation-link-' + currentCount); span.title = 'Only YOU have this script installed'; }	// connect to API again to get more backlinks on the same script if (blcontinue) {		var scriptCounter = document.getElementById('si-script-counter'); var newScriptCounter = scriptCounter.appendChild(document.createElement('div')); newScriptCounter.className = 'si-hidden'; newScriptCounter.appendChild(document.createTextNode(wgFormattedNamespaces[blcontinue['namespace']] + ':' + blcontinue['title'])); // get API getInstallations('scriptInstallationsCallback', wgFormattedNamespaces[blcontinue['namespace']] + ':' + blcontinue['title'], blcontinue['full']); }	else {		// update the cookie setConvertedCookie('installed-by', [script, userCount]); } };

function doSingleScriptInstalls(numberOfUsers, blcontinue) {	// TODO Do some "faking" by adding 1 if the user has this script installed // AND the number of installs is 0? Then remove the link to backlinks? var Xpeople = document.getElementById('installed-by-X-people');

// delink the link to the script's backlinks if (numberOfUsers == 0) var span = convertIdToSpan('installed-by-link'); // singularize "people" to "person" for 1 person if (numberOfUsers == 1) document.getElementById('people-plural').replaceChild(document.createTextNode(' person'), document.getElementById('people-plural').firstChild); // calculate number of users by adding it with existing number var currentNumberOfUsers = Xpeople.firstChild.nodeValue; numberOfUsers = numberOfUsers + (isNaN(parseInt(currentNumberOfUsers)) ? 0 : parseInt(currentNumberOfUsers)); // insert the number of users Xpeople.replaceChild(document.createTextNode(numberOfUsers), Xpeople.firstChild); // get API if (blcontinue) getInstallations('singleScriptInstalls', wgFormattedNamespaces[blcontinue['namespace']] + ':' + blcontinue['title'], blcontinue['full']); // update the cookie else setConvertedCookie('installed-by', [(scriptData && scriptData['page'] ? scriptData['page'] : pageName ), numberOfUsers]); };

function failedToConnectToAPI {	// Mark "Checking script" as failed var installThisScript = document.getElementById('install-this-script'); if (installThisScript) installThisScript.replaceChild(document.createTextNode('Failed to connect to the API'), installThisScript.firstChild); };

/** * Formats blcontinue tokens */ function formatBlContinue(origBlcontinue) {	var blcontinue = origBlcontinue.split('|'); return { 'namespace': blcontinue[0], 'title': blcontinue[1], 'id': blcontinue[2], 'full': origBlcontinue }; };

hideTooltip = function(tooltip) {	if (typeof timerIsRunning != 'undefined' && timerIsRunning === true) timerIsRunning = false; var tooltip = document.getElementById('tooltip-' + tooltip); if (tooltip) return tooltip.parentNode.removeChild(tooltip); };

function indicateScriptInstalled(scriptName, pageToInstallTo, content, markInstalled) {	var install = document.getElementById('install-this-script'); // current script is already installed // TODO This should use a cookie for checking if script is already installed or not. if (findExistingScript(scriptName, content) && markInstalled !== false) {		var node = document.createElement('span'); node.className = 'si-heading'; node.id = 'install-this-script'; node.innerHTML = 'You already installed this ' + scriptType + ''; }	// current script is not yet installed else {		var node = document.createElement('a'); node.className = 'si-heading'; node.href = 'javascript:beginScriptInstallation(\ + pageToInstallTo + '\', \ + scriptName + '\');'; node.id = 'install-this-script'; node.title = 'This will install the script to ' + pageToInstallTo + ''; node.appendChild(document.createTextNode('Install this ' + scriptType)); }	install.parentNode.replaceChild(node, install); };

function indicateScriptIsInstalling {	// replace "Install this script" with "Installing..." var span = document.createElement('span'); span.className = 'si-heading'; span.id = 'install-this-script'; // FIXME Indicate "Installing stylesheet" if that is the case. span.appendChild(document.createTextNode('Installing script...')); var install = document.getElementById('install-this-script'); install.parentNode.replaceChild(span, install); };

function markAssociatedScript(otherPage) {	var relatedPage = document.getElementById('si-related-page'); var a = document.createElement('a'); a.href = createLink(otherPage); a.id = 'si-related-page'; a.appendChild(document.createTextNode('Related ' + oppScriptType)); return relatedPage.parentNode.replaceChild(a, relatedPage); };

function parseScriptData {	var scriptDataId = document.getElementById('script-data'); if (!scriptDataId) return false; var data = {}; for (var i = 0; i < documentationData.length; i++) {		var docData = document.getElementById('script-data-' + documentationData[i]); if (docData && docData.firstChild && docData.firstChild.nodeValue) data[documentationData[i]] = docData.firstChild.nodeValue.trim; }		return data; };

showTooltip = function(event, tooltip, extras) {	// if tooltip already exists due to a previous hover, then don't create it again if (document.getElementById('tooltip-' + tooltip)) {		hideTooltip(tooltip); return; }	if (extras) extras = eval(extras); else extras = []; var coords = getMouseCoordinates(event); var text = tooltipText[tooltip]; if (!text) text = ''; // apply extras if (tooltip == 'verified') {		if (extras[0] === true) text = text.replace('Verified:', ' Verified:'); else if (extras[0] === false) text = text.replace('Unverified:', ' Unverified:'); }	var div = document.createElement('div'); div.id = 'tooltip-' + tooltip; div.className = 'si-tooltip'; div.style.left = (coords[0] + 5) + 'px'; div.style.top = (coords[1] + 5) + 'px'; // add the close button div.innerHTML = ' [X] '; div.innerHTML += text; body.appendChild(div); };

function sortScripts(first, second) {	if (first[1]) var a = first[1]; else var a = getBasePage(first[0]).capitalize; if (second[1]) var b = second[1]; else var b = getBasePage(second[0]).capitalize; if (a < b) return -1; else if (a > b) return 1; else return 0; };

// ==================================================================================================== // ============================================ general.js ============================================ // ====================================================================================================

function addClass(element, newClass) {	if (element.className) {		var classes = element.className.split(' '); classes.push(newClass); return element.className = classes.join(' '); }	else return element.className = newClass; };

String.prototype.capitalize = function(string) {	return this.replace(/(^|\s)([a-z])/g, function(m, p1, p2) { return p1 + p2.toUpperCase; } ); };;

function checkURLParam(param) {	if (!document.location.search || document.location.search.length == 0) return false; var params = document.location.search.substr(1).split('&'); for (var i = 0; i < params.length; i++) {		var paramParts = params[i].split('='); if (paramParts[0] == param) return true; }	return false; };

function convertArrayToHash(array) {	var hash = {}; for (var i = 0; i < array.length; i++) hash[array[i]] = true; return hash; };

function convertIdToSpan(id) {	var node = document.getElementById(id); var span = document.createElement('span'); span.id = id; var children = node.childNodes; for (var i = children.length - 1; i >= 0; i--) span.insertBefore(children[i], span.firstChild); node.parentNode.replaceChild(span, node); return span; };

function createLink(title) {	var title = title.trim; if (title.indexOf('http://') == 0) return title; else return wgScript + '?title=' + encodeURIComponent(title); };

function createLinkForBacklinks(script) {	return mw.config.get('wgServer') + mw.config.get('wgScript') + '?title=Special:WhatLinksHere/' + script + '&namespace=2&hideredirs=1&hidetrans=1&limit=50'; };

function errorMessage(code) {	alert(standardErrorMessage + ' (' + code.toUpperCase + ')'); };

function generateTooltip(name, extras) {	return ' (?) '; };

function has(haystack, needle) {	if (haystack instanceof Array) {		for (var i = 0; i < haystack.length; i++) {			if (haystack[i] == needle) return true; }	}	return false; };

function hasClass(element, classToCheck) {	if (typeof(element) == 'undefined' || !element.className) return false; var classes = element.className.split(' '); for (var i = 0; i < classes.length; i++) if (classes[i] == classToCheck) return true; return false; };

function isANumber(number) {	return !isNaN(parseInt(number)); };

function isUnsafe {	if (typeof(unsafeWindow) != 'undefined') return true; else return false; };

function siLog(message, alert) {	if (typeof(console) != 'undefined' && alert !== true) console.log(message); else window.alert(message); };

String.prototype.ltrim = function(stringToTrim) {	return this.replace(/^[\s|\n]+/, ''); };

function objectLength(obj) {   var size = 0, key; for (key in obj) if (obj.hasOwnProperty(key)) size++; return size; };

String.prototype.pluralize = function(count, plural) {	if (plural == null) var plural = this + 's'; return (count == 1 ? this : plural) };

function removeClass(element, oldClass) {	if (!element.className) return false; var classes = element.className.split(' '); var newClasses = []; for (var i = 0; i < classes.length; i++) {		if (classes[i] != oldClass) newClasses.push(classes[i]); }	return element.className = newClasses; };

function reverseHash(oldHash) {	var newHash = {}; for (var key in oldHash) newHash[oldHash[key]] = key; return newHash; };

String.prototype.rtrim = function(stringToTrim) {	return this.replace(/[\s|\n]+$/, ''); };

function sanitizeHTML(data) {	return data.replace(//g, '&gt;'); };

function stripHTML(html) {	var tmp = document.createElement('div'); tmp.innerHTML = html; return tmp.textContent||tmp.innerText; };

String.prototype.trim = function(stringToTrim) {	return this.replace(/^[\s|\n]+|[\s|\n]+$/g, ''); };

String.prototype.truncate = function(maxLength, truncateFromStart) {	if (this.length <= maxLength) return this; if (truncateFromStart) return '...' + this.substring(this.length - maxLength + 3, this.length); else return this.substring(0, maxLength - 3) + '...'; };

// ==================================================================================================== // ============================================ getters.js ============================================ // ====================================================================================================

function findAssociatedScript {	var relatedPage = document.getElementById('si-related-page'); if (!scriptData['page'] || !relatedPage) return false; var split = scriptData['page'].split('/'); var last = split[split.length - 1]; if (last.indexOf('.') == -1 || !linkExtensions[last.substring(last.indexOf('.') + 1, last.length)]) return false; otherPage = scriptData['page'].substring(0, scriptData['page'].indexOf('.', scriptData['page'].indexOf(last)) + 1) + reverseLinkExtensions[oppScriptType]; var associatedScripts = getConvertedCookie('associated-scripts'); if (associatedScripts[pageName]) markAssociatedScript(otherPage); else wikiApi('associatedScriptCallback', 'action=query&prop=info&indexpageids=1&titles=' + otherPage.replace(/ /g, '_')); };

function findExistingScript(name, content) {	// FIXME This function doesn't actually find importScript; only the script name itself. Not necessarily a problem, though. var searchedText = ('\n' + content + '\n').replace(/\\n/g, '\n'); if (searchedText.indexOf(name) == -1) return false; // didn't find the script else if (searchedText.search(new RegExp('\n(.*?)//(.*?)' + name)) != -1) // found it commented out {		searchedText = searchedText.replace(new RegExp('\n(.*?)//(.*?)' + name + '(.*?)\n', 'g'), '\n$1\n'); // remove the commented out versions if (searchedText.search(new RegExp('\n(.*?)' + name)) == -1) return false; // we can no longer find it		else return true; // we found it even though the commented out copies are removed, so it's definitely installed }	else return true; // it's found and it's not commented out };

function getBasePage(page) {	var pages = page.split('/'); if (!pages[1]) return false; var hasExt = pages[pages.length - 1].indexOf('.'); if (hasExt == -1) return false; return pages[pages.length - 1].substring(0, hasExt); };

function getIsViewingSkin {	var basePage = getBasePage(pageName); for (var i = 0; i < allSkins.length; i++) if (allSkins[i] == basePage) return true; return false; };

function getMouseCoordinates(e) {	var posx = 0; var posy = 0; if (!e) var e = window.event; if (e.pageX || e.pageY) {		posx = e.pageX; posy = e.pageY; }	else if (e.clientX || e.clientY) {		posx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; posy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop; }	return [posx, posy]; };

function getOppScriptType {	if (scriptType == 'stylesheet') return 'script'; else return 'stylesheet'; };

function getScriptMetadata(maxLength) {	// get comments classes var classes = {}; classes['multi'] = content.getElementsByClassName('coMULTI'); classes['single'] = content.getElementsByClassName('co1'); var existingMatches = {}; for (var commentClass in classes) {		if (classes[commentClass].length > 100) var shorterLength = 100; else var shorterLength = classes[commentClass].length; for (var i = 0; i < shorterLength; i++) {			var singleClass = classes[commentClass][i]; var value = singleClass.firstChild.nodeValue; // find metadata in the element existingMatches = hasScriptMetadata(value, existingMatches, maxLength); if (existingMatches['done']) break; }		if (existingMatches['done']) break; }	delete existingMatches['done']; return existingMatches; };

function getScriptType(data) {	if (data['type']) {		for (var name in scriptClasses) if (scriptClasses[name] == data['type']) return data['type']; }	var pre = document.getElementsByTagName('pre')[0]; if (!pre) return false; var classes = pre.className.split(' '); var className = classes[0]; return scriptClasses[className] };

function hasAssociatedScript(findScript) {	var script = document.getElementById('mw-script-doc'); if (!script) return false; var links = script.getElementsByTagName('a'); if (links.length >= 3) return links[2].href; else if (isViewingSkin && links.length >= 2 && !hasClass(links[1], 'new')) return links[1].href; else return false; };

function hasDocumentation {	var docs = document.getElementById('mw-script-doc'); if (!docs) return false; if (docs.getElementsByClassName('new').length > 0) return false; // get the documentation page else if (!isViewingSkin) return docs.getElementsByTagName('a')[1].href; };

function hasScriptMetadata(string, existingMatches, maxLength) {	var string = ('\n' + string + '\n').replace(/\\n/g, '\n'); for (var i = 0; i < singleScriptData.length; i++) {		if (existingMatches[singleScriptData[i]]) continue; var matches = string.match(new RegExp('@' + singleScriptData[i] + '\\s+(.*?)\n')); if (matches) existingMatches[singleScriptData[i]] = sanitizeHTML(matches[1].trim); else continue; if (isANumber(maxLength)) existingMatches[singleScriptData[i]] = existingMatches[singleScriptData[i]].truncate(maxLength); }	if (singleScriptData.length == objectLength(existingMatches)) existingMatches['done'] = true; return existingMatches; };

function installedScripts(content) {	var searchedText = ('\n' + content + '\n').replace(/\\n/g, '\n'); // remove comments with an importScript type for (var type in importScriptTypes) searchedText = searchedText.replace(new RegExp('\n(.*?)//(.*?)' + importScriptTypes[type] + '(.*?)\n', 'g'), '\n$1\n');

// collect importScript types, line by line var matches = []; var lines = searchedText.split('\n'); for (var type in importScriptTypes) {		if (type == 'external') continue; for (var i = 0; i < lines.length; i++) {			var pattern = new RegExp('(' + importScriptTypes[type] + '\\s*\\(\\s*[\'|"].*?[\'|"]\\s*\\))'); var match = lines[i].match(pattern); if (match) {				match = match[1].replace(/.*['|"](.*?)['|"].*/, '$1'); var verified = verifiedScripts[match]; if (verified) matches.push([match, verified['name'], verified['documentation']]); else matches.push([match]); }		}	}	return matches.sort(sortScripts); };

function isSiInProduction {	for (var setting in siSettings) {		if (!siSettings[setting]) return false; }	return true; };

// ==================================================================================================== // ============================================== init.js ============================================= // ====================================================================================================

/** * @fileoverview Initiating the script */

/*	@name Installer or Script Installer or Easy Script Installer? @description Simplifies the installation and configuration process for user scripts.

if (typeof(isUnsafe) == 'function' && isUnsafe) {	addPortletLink = unsafeWindow.addPortletLink; appendCSS = unsafeWindow.appendCSS; importScriptURI = unsafeWindow.importScriptURI; sajax_init_object = unsafeWindow.sajax_init_object; skin = unsafeWindow.skin; wgCanonicalNamespace = unsafeWindow.wgCanonicalNamespace; wgPageName = unsafeWindow.wgPageName; wgScript = unsafeWindow.wgScript; wgScriptPath = unsafeWindow.wgScriptPath; wgServer = unsafeWindow.wgServer; wgUserName = unsafeWindow.wgUserName; wgFormattedNamespaces = unsafeWindow.wgFormattedNamespaces; };

if (typeof(ScriptInstaller) == 'undefined') ScriptInstaller = {};

/** * This is the only function loaded in ONLOAD. * This script is intended to work on two pages: script pages (ending in .js) and script documentation pages. * Primarily these two, anyway. */ function scriptInstaller {	if (typeof(isSiInProduction) != 'function' || typeof(doDocumentationPage) != 'function') return false; /*		Useful param disablers: ?installations: does not retrieve number of installations for scripts */	// Settings. These all default to TRUE. // FIXME The following settings should be different when this script is in beta, and especially production mode. siSettings = {}; siSettings['checkIfScriptIsInstalled'] = 1; // If false, don't check if a script is installed // FIXME When this is true, the installations for the Script Library are in the wrong order. siSettings['enableSICookies'] = 0; // If false, then cookies are disabled, so no cached data is retrieved. Data is still cached, however. siSettings['useRealLibraryLink'] = 1; // If false, links to the sandbox siSettings['useSkinPageForInstalls'] = 0; // If false, uses test page instead siIsInProduction = isSiInProduction; siScriptName = 'Script Installer'; // the current script's name maxLengthForScriptMetadata = 250; // maximum length for script metadata on single script pages ageOfCookies = 365; // age for cookies; set to 365, but in reality we still update cookies behind the scenes when they are different from newly acquired data // tooltip text tooltipText = {		'installed-by': 'Only users that install this script on a personal JavaScript page are counted (including yourself).', 'metadata': 'Script metadata: Information about the script that is provided by the script itself.', // Hence, metadata. 'number-of-scripts': 'Scripts installed here: The number of scripts found on this page that are imported with importScript.

Does not include scripts installed using importScriptURI, which are typically scripts not located on the <a href="' + createLink('English Wikipedia') + '">English Wikipedia</a> and are therefore limited in the amount of information that can be obtained about them.', 'personal-user-script': 'Personal user script: A script that contains all the scripts installed by a given user. Installing this will install all scripts that the user has installed themselves.', 'verified': 'Verified: The script has been tested. It is known to work. Unverified: The script has not been tested.' };	// useful variables installPage = (siSettings['useSkinPageForInstalls'] || (!siSettings['useSkinPageForInstalls'] && wgUserName != 'Gary King') ? 'User:' + wgUserName + '/' + skin + '.js' : 'User:Gary King/scripts.js'); // 'class used in PRE': 'what to call the current script\'s type' scriptClasses = { 'css': 'stylesheet', 'javascript': 'script' }; importScriptTypes = { 'script': 'importScript', 'stylesheet': 'importStylesheet', 'external': 'importScriptURI' }; body = document.getElementsByTagName('body')[0]; content = document.getElementById('content'); if (!content) return; // the following are used for highestBox, in this order jsfile = document.getElementById('jsfile'); jsWarning = document.getElementById('jswarning'); clearCache = document.getElementById('clearprefcache'); standardErrorMessage = 'An error occurred. Please try again later.'; pageName = wgPageName.replace(/_/g, ' '); documentationData = ['page', 'type']; scriptData = parseScriptData; scriptType = getScriptType(scriptData); oppScriptType = getOppScriptType; allSkins = ['chick', 'standard', 'cologneblue', 'modern', 'monobook', 'myskin', 'nostalgia', 'simple', 'vector']; allSkinsHash = convertArrayToHash(allSkins); isViewingSkin = getIsViewingSkin; libraryPage = (siSettings['useRealLibraryLink'] ? 'Wikipedia:Script Library' : 'User:Gary King/My Scripts'); linkExtensions = {'css': 'stylesheet', 'js': 'script'}; reverseLinkExtensions = reverseHash(linkExtensions); singleScriptData = ['name', 'description']; jumpToNav = document.getElementById('jump-to-nav'); usersWithBetaAccess = [/*'Gary King', 'Gary Queen'*/]; // only allow verified users to use this script, for now if (!siIsInProduction && usersWithBetaAccess.length > 0) {		var hasAccess = checkIfUserCanAccessSI; if (!hasAccess) {			if (wgUserName != null) alert('You do not have access to the \"' + siScriptName + '\" script while it is in beta mode.'); return false; }	}	// TODO Disable this script with a config setting, for myself only so I can test this script in Greasemonkey while it still exists in my skin.js. // add settings, if different from default, to siteSub. Only on Script Library and single script pages, and only if still in testing mode. if (!siIsInProduction && (wgUserName == 'Gary King') && (pageName == libraryPage || (clearCache && scriptType))) addSISettingsToPage; // add portlet link for script library if (addPortletLink) mw.util.addPortletLink('p-tb', createLink(libraryPage), 'Script Library', 't-script-library', 'Go to the Script Library'); // identify documentation pages if (wgCanonicalNamespace != '' && wgCanonicalNamespace != 'Template') doDocumentationPage; // replace imported scripts using importScript and importScriptURI with clickable links for easier access to them // this works on not only monobook.js, but on script documentation pages, etc. as well if (wgCanonicalNamespace != '') linksReplaced = replaceImportedScriptsWithLinks;

// do script library stuff if viewing the library page if (pageName == libraryPage) doScriptLibrary; // we are viewing an actual script page if (clearCache && scriptType) {		// a global variable; "currentScript' should eventually be replaced with 'pageName'		currentScript = pageName;		// now that we know we are viewing a script, add the install message box		addInstallMessageBox(pageName);		// check if script is already installed or not, and replace text appropriately		getInstallPage(pageName, installPage, (siSettings['checkIfScriptIsInstalled'] ? true : false), false);	} };

if (typeof(isUnsafe) == 'function' && isUnsafe) {	unsafeWindow.addTitleTooltips = addTitleTooltips; unsafeWindow.associatedScriptCallback = associatedScriptCallback; unsafeWindow.beginScriptInstallation = beginScriptInstallation; unsafeWindow.hideTooltip = hideTooltip; unsafeWindow.scriptInstallationsCallback = scriptInstallationsCallback; unsafeWindow.sendScriptInstallation = sendScriptInstallation; unsafeWindow.showTooltip = showTooltip; unsafeWindow.singleScriptInstalls = singleScriptInstalls; };

if (typeof(isUnsafe) == 'function' && typeof(isSiInProduction) == 'function'/* && typeof(verifiedScripts) != 'undefined'*/) {	// On wiki if (typeof(addOnloadHook) != 'undefined') {		if (typeof(siSettings) == 'undefined') {			addOnloadHook(scriptInstaller); addOnloadHook(addTitleTooltips); }	}	// Off wiki else {		if (typeof(siSettings) == 'undefined') {			scriptInstaller; addTitleTooltips; }	} }// ==================================================================================================== // ============================================= layout.js ============================================ // ====================================================================================================

/** * Add the install message box to a single script page */ function addInstallMessageBox(currentScript) {	var boxes = [jsfile, jsWarning, clearCache, jumpToNav.nextSibling]; for (var i = 0; i < boxes.length; i++) {		if (boxes[i]) {			var highestBox = boxes[i]; break; }	}	if (!highestBox) return false; var div = document.createElement('div'); div.id = 'si-message-box'; var checking = document.createElement('span'); checking.className = 'si-heading si-loading'; checking.id = 'install-this-script'; checking.appendChild(document.createTextNode('Checking if script is already installed...'));

// if viewing a user's personal script, then indicate so	var isViewingSkinNode = document.createElement('div'); isViewingSkinNode.className = 'si-text'; isViewingSkinNode.id = 'is-viewing-skin'; if (isViewingSkin && currentScript != installPage) isViewingSkinNode.innerHTML = ' Careful! This is a <a href="' + createLink('Wikipedia:Skin#Customisation (advanced users)') + '">personal user script</a> ' + generateTooltip('personal-user-script') + '.'; // if there are scripts installed on this page, then indicate how many there are var replaced = document.createElement('div'); replaced.id = 'si-number-of-scripts'; // FIXME This says "X scripts installed here" even though they could be stylesheets installed here, too. if (typeof(linksReplaced) == 'object' && linksReplaced.length > 0) replaced.innerHTML = '' + linksReplaced.length + ' ' + 'script'.pluralize(linksReplaced.length) + ' installed here ' + generateTooltip('number-of-scripts'); // FIXME Don't show the following if we're viewing the user's skin.js	var verified = document.createElement('div'); verified.id = 'si-verified'; verified.className = 'si-verification-status ' + (verifiedScripts[currentScript] ? 'si-verified' : 'si-not-verified'); verified.innerHTML = (verifiedScripts[currentScript] ? 'Verified' : 'Unverified') + ' ' + generateTooltip('verified', '[verifiedScripts[currentScript] ? true : false]'); var installedByCookie = getConvertedCookie('installed-by'); if (installedByCookie[currentScript]) var numberOfInstallers = installedByCookie[currentScript]; else var numberOfInstallers = ''; // FIXME Don't show the following if we're viewing the user's skin.js	var installedBy = document.createElement('div'); installedBy.id = 'si-installed-by'; installedBy.className = 'si-installed-by'; installedBy.innerHTML = ' Installed by <a href="' + createLinkForBacklinks(currentScript) + '" id="installed-by-link"> ? people </a> ' + generateTooltip('installed-by'); // also insert other text, below the heading created above var text = document.createElement('div'); text.id = 'script-description'; text.className = 'si-text';

// create menu var menuItems = [];

// find documentation var documentation = hasDocumentation; if (documentation) menuItems.push('<a href="' + documentation + '" id="si-documentation">Documentation</a>'); else if (!scriptData) menuItems.push(' No documentation '); // find stylesheet var script = hasAssociatedScript; if (script) menuItems.push('<a href="' + script + '" id="si-related-page">Related ' + oppScriptType + '</a>'); else menuItems.push(' No ' + oppScriptType + ' '); text.innerHTML = menuItems.join(' · '); // get the script's metadata var metadata = getScriptMetadata(maxLengthForScriptMetadata); var metadataArray = []; for (var data in metadata) metadataArray.push(' ' + data.capitalize + ' : ' + metadata[data] + ' '); // add script metadata var metadata = document.createElement('div'); metadata.id = 'si-metadata'; if (metadataArray.length > 0) metadata.innerHTML = 'Script info ' + generateTooltip('metadata') + ': ' + metadataArray.join(' · '); // order of the message box parts var parts = [checking, isViewingSkinNode, replaced, verified, installedBy, text, metadata]; var partsWithNoFollowingBullet = [checking, isViewingSkinNode, text, metadata]; for (var i = 0; i < parts.length; i++) {		if (!parts[i].firstChild) continue; div.appendChild(parts[i]); var addBullet = true; for (var j = 0; j < partsWithNoFollowingBullet.length; j++) {			var noBullets = partsWithNoFollowingBullet[j]; if (noBullets == parts[i]) {				addBullet = false; break; }		}		if (addBullet) div.appendChild(document.createTextNode(' · ')); }	// insert the message box into the page highestBox.parentNode.insertBefore(div, highestBox); // get number of installations for this script if (numberOfInstallers) doSingleScriptInstalls(numberOfInstallers); else getInstallations('singleScriptInstalls', currentScript); // check if associated script exists via a callback, only for documentation pages findAssociatedScript; };

/** * Adds the script's settings, if different from default, to siteSub */ function addSISettingsToPage {	// Disabled settings in $siScriptName: ... var disabledSettings = []; for (var setting in siSettings) {		if (!siSettings[setting]) disabledSettings.push(' ' + setting + ' '); }	var string = ' Disabled settings in ' + siScriptName + ' : ' + disabledSettings.join(', ') + '. ';	document.getElementById('siteSub').innerHTML = string + document.getElementById('siteSub').innerHTML + '.'; };

/** * Check if the user has access to this script while its in beta mode */ function checkIfUserCanAccessSI {	for (var i = 0; i < usersWithBetaAccess.length; i++) {		if (usersWithBetaAccess[i] == wgUserName) return true; }	return false; };

function doDocumentationPage {	if (!scriptData) return false; addInstallMessageBox(scriptData['page']); getInstallPage(scriptData['page'], installPage, (siSettings['checkIfScriptIsInstalled'] ? true : false), false); };

/** * Draw the Script Library. */ function doScriptLibrary {	// we are viewing the script library page var myLib = document.getElementById('my-scripts'); if (!myLib) return false; // heading var heading = document.createElement('h2'); heading.className = 'installed-scripts-heading'; heading.innerHTML = 'My Scripts'; myLib.appendChild(heading); // descriptive text var text = document.createElement('span'); text.className = 'installed-scripts-description'; text.innerHTML = 'Your scripts ' + generateTooltip('number-of-scripts') + ' are installed at <a href="' + createLink(installPage) + '">' + installPage + '</a>. Script names in bold have been verified ' + generateTooltip('verified') + '. ' + tooltipText['installed-by'] + ' You have the following scripts installed: ';	myLib.appendChild(text); // installed scripts var scripts = document.createElement('div'); scripts.id = 'si-installed-scripts'; var loading = document.createElement('span'); loading.id = 'loading'; loading.className = 'si-scripts-are-loading si-loading'; loading.appendChild(document.createTextNode('Loading...')); scripts.appendChild(loading); myLib.appendChild(scripts); var scriptLibraryCookie = getConvertedCookie('script-library', true); // create the list of scripts if (scriptLibraryCookie.length > 0) // Script Library cookie already exists {		doUserLibrary(scriptLibraryCookie); // FIXME Should be updating the cookie when the script library contents have changed. // Is it comparing the old list of scripts with the one existing in the cookie? var scripts = installedScripts(content); setConvertedCookie('script-library', scripts); }	else // Script Library cookie does not exist, so create it. {		getInstallPage(false, installPage, false, buildUserLibrary); }	// script gallery var galleryHeading = document.createElement('h2'); galleryHeading.className = 'si-script-gallery'; galleryHeading.innerHTML = 'Script Gallery'; myLib.appendChild(galleryHeading); };

function doUserLibrary(scripts) {	var installed = document.getElementById('si-installed-scripts'); if (!installed) return; // remove loading text installed.removeChild(document.getElementById('loading')); var numberOfScriptsInstalled = scripts.length; var counter = document.createElement('span'); counter.className = 'si-hidden'; counter.id = 'callback-counter'; counter.appendChild(document.createTextNode(0)); installed.appendChild(counter); // create counter with a script name var scriptCounter = document.createElement('span'); scriptCounter.id = 'si-script-counter'; scriptCounter.className = 'si-hidden'; installed.appendChild(scriptCounter); for (var i = 0; i < scripts.length; i++) {		var script = scripts[i][0]; var scriptName = scripts[i][1]; var docs = scripts[i][2] ? scripts[i][2] : ''; var node = document.createElement('div'); node.className = 'script-library-item'; node.id = script.replace(/ /g, '_'); // truncate the script name from the beginning if it's too long var scriptLink = '<a href="' + createLink(script) + '">' + (scriptName ? scriptName : script).truncate(50, true) + '</a>'; // bold the script's name if it's verified if (scriptName) scriptLink = ' ' + scriptLink + ' '; if (docs) var documentation = '(<a class="si-documentation-link" href="' + createLink(docs) + '">documentation</a>)'; else var documentation = ''; var installations = '<a class="script-installations" href="' + createLinkForBacklinks(script) + '" id="installation-link-' + i + '"><span id="installation-' + i + '">? <span id="installation-plural-' + i + '">installations </a>'; node.innerHTML = ' ' + scriptLink + ' ' + documentation + ' ' + installations + '<span class="si-hidden si-script-name" id="si-script-name-' + i + '">' + script + ' ' + i + ' '; installed.appendChild(node); // get number of installations if (checkURLParam('installations')) continue; var installedByCookie = getConvertedCookie('installed-by'); if (installedByCookie[script]) doScriptInstallation(installedByCookie[script]); else getInstallations('scriptInstallationsCallback', script); }	var numberOfScripts = document.getElementById('si-number-of-scripts'); numberOfScripts.replaceChild(document.createTextNode(numberOfScriptsInstalled + ' scripts'), numberOfScripts.firstChild); };

function replaceImportedScriptsWithLinks {	// find .js links in code var scriptLinks = content.getElementsByClassName('st0'); var replacedLinks = []; for (var i = 0; i < (scriptLinks.length > 250 ? 250 : scriptLinks.length); i++) {		// trim the script text to get just the actual name var sL = scriptLinks[i]; if (sL.childNodes.length > 1 || !sL.firstChild.nodeValue) continue; var nodeValue = sL.firstChild.nodeValue; var open = nodeValue.trim.replace(/^(['|"]).*/g, '$1').trim;		var close = nodeValue.trim.replace(/^['|"].*(['|"])/g, '$1').trim;		var title = nodeValue.trim.replace(/^['|"]|['|"]$/g, '').trim;

// check if this is an imported script var prevSibling = sL.previousSibling.previousSibling; if (prevSibling && prevSibling.nodeValue) var functionName = prevSibling.nodeValue.trim; else var functionName = ''; var isLegit = false; for (var importType in importScriptTypes) {			if (importType == 'external') continue; if (functionName == importScriptTypes[importType]) isLegit = true; }		if (!isLegit) continue; // create the link to replace the existing text with var a = document.createElement('a'); a.href = createLink(title); a.appendChild(document.createTextNode(title)); var span = document.createElement('span'); span.appendChild(document.createTextNode(open)); span.appendChild(a); span.appendChild(document.createTextNode(close)); sL.replaceChild(span, sL.firstChild); replacedLinks.push(title); }	return replacedLinks; };

// ==================================================================================================== // ============================================== misc.js ============================================= // ====================================================================================================

/** * Create an edit summary */ function siEditSummary(text) {	return text + ' with WP:Script Installer'; };