User:Corey/vector.js

From Yugipedia
< User:Corey
Revision as of 17:11, 10 April 2018 by Corey (talk | contribs) (Created page with "* * Main JS function.: (function(window, $, mw, console, delay) { "use strict"; * * Global objects.: const LAST_LOG = '14:22, 8 March 2018 (UTC)'; const c...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Note: After saving, you have to bypass your browser's cache to see the changes. Internet Explorer: hold down Ctrl and click the Refresh or Reload button. Firefox: hold down ⇧ Shift while clicking Reload (or press Ctrl+⇧ Shift+R). Google Chrome and Safari users can just click the Reload button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.

/**
 * Main JS function.
 */
(function(window, $, mw, console, delay) {
	"use strict";

	/**
	 * Global objects.
	 */

	const LAST_LOG = '14:22, 8 March 2018 (UTC)';

	const conf = mw.config.get([
		'wgNamespaceNumber',
		'wgPageName'
	]);
	$.extend(conf, {
		action: mw.util.getParamValue('action'),
		api: function(query, callbacks, method) {
			return new mw.Api()[method || 'get'](query)
				.done(callbacks.done)
				.fail(callbacks.fail)
				.always(callbacks.always);
		}
	});

	const page = {
		hasContent: $('#ca-history').length && conf.wgNamespaceNumber !== -1,
		$content: $('#content'),
		$mwHead: $('#mw-head'),
		$userBar: $('#p-personal').children('ul'),
		$leftNav: $('#left-navigation').find('#p-namespaces').children('ul'),
		$rightNav: $('#right-navigation').find('#p-views').children('ul'),
		$rightMenu: $('#right-navigation').find('#p-cactions').find('ul'),
		$star: $('#ca-watch').length ? $('#ca-watch') : $('#ca-unwatch')
	};

	/** End. */


	/**
	 * Constructors and prototypes.
	 */

	// ------
	// String:
	String.prototype.strip = function(c) {
		return String(this).replace(new RegExp(c || ' ', 'g'), '');
	};

	// ------
	// Query:
	function Query(endpoint, query) {
		// query | url | full | endpoint
		if($.isPlainObject(endpoint)) {
			query = endpoint;
			endpoint = undefined;
		}
		this.query = query || {};
		this.url = this.fetch();
		this.full = this.extend();
		this.endpoint = String(endpoint || '').trim().toLowerCase().strip('.php');
	}
	// Extend prototype:
	$.extend(Query.prototype, {
		// "Private" methods:
		extend: function() {
			return $.extend({}, this.url, this.query);
		},
		fetch: function(parameters) {
			const query = {};
			const getValue = function(parameter) {
				return query[parameter];
			};
			const getValues = function(parameters) {
				return (Array.isArray(parameters)
					? parameters.map(function(v) {
						return getValue(v);
					})
					: getValue(parameters)
				);
			};
			window.location.search.slice(1).split('&').forEach(function(component) {
				if(!component.trim()) return;
				const parts = component.split('=');
				query[window.decodeURIComponent(parts[0])] = window.decodeURIComponent(parts[1] || '');
			});
			return parameters ? getValues(parameters) : query;
		},

		// "Getters":
		getEndpoint: function() {
			return this.endpoint;
		},
		getQuery: function(type) {
			switch((type+'').trim().toLowerCase()) {
				case 'full': return this.full;
				case 'url': return this.url;
				default: return this.query;
			}
		},

		// Other:
		print: function(full) {
			const query = this.getQuery(full && 'full');
			return [
				mw.util.wikiScript(this.getEndpoint()),
				Object.keys(query).map(function(key) {
					return [
						window.encodeURIComponent(key),
						window.encodeURIComponent(query[key])
					].join('=');
				}).join('&')
			].join('?');
		},
	});

	// Static method:
	Query.fetch = Query.prototype.fetch;

	// ------
	// State:
	function State(message) {
		this.message = message || 'Pending...';
		this.status = 'pending';
	}
	$.extend(State.prototype, {
		getMessage: function() {
			return this.message;
		},
		setMessage: function(m) {
			this.message = m || '';
			return this;
		},
		getStatus: function() {
			return this.status;
		},
		setStatus: function(s) { // Private.
			this.status = s || '';
			return this;
		},
		pending: function() {
			return this.setStatus('pending');
		},
		done: function() {
			return this.setStatus('done');
		},
		failed: function() {
			return this.setStatus('failed');
		},
		clear: function() {
			this.setMessage();
			this.setStatus();
			return this;
		},
		reset: function() { // TODO: .assign or something
			this.setMessage('Pending...');
			this.setStatus('pending');
			return this;
		},
		show: function() {
			items.$specialMessages.removeClass('hide')
				.children()
					.removeClass().addClass(this.getStatus())
					.text(this.getMessage());
			return this;
		}
	});

	/** End. */


	/**
	 * Utility functions.
	 */


	/** End. */


	/**
	 * Buttons.
	 */
	
	const items = {
		$searchBar: $('<li>', {
			id: 'pt-search', 
			html: $('#p-search')
		}),
		$specialMessages: $('<li>', {
			id: 'pt-special-messages',
			html: $('<span>', {
				id: 'special-messages',
				class: 'hide'
			})
		}),
		$userBarBottom: $('<div>', {
			id: 'p-bottom'
		}),

		$diff: $('<li>', {
			id: 'ca-diff',
			class: 'collapsible',
			html: $('<span>', {
				html: $('<a>', {
					title: 'Last diff for this page.',
					text: 'Diff'
				})
			})
		}),
		$nullEdit: $('<li>', {
			id: 'ca-null',
			class: 'collapsible',
			html: $('<span>', {
				html: $('<a>', {
					href: '#',
					title: 'Null edit this page.',
					text: 'Null'
				})
			})
		}),

		$raw: $('<li>', {
			id: 'ca-raw',
			html: $('<a>', {
				href: new Query({
					title: conf.wgPageName,
					action: 'raw'
				}).print(),
				title: 'View the raw content.',
				text: 'Raw'
			})
		}),
		$safe: $('<li>', {
			id: 'ca-safe',
			html: $('<a>', {
				href: new Query({
					title: conf.wgPageName,
					safemode: true
				}).print(true),
				title: 'View the page in safemode.',
				text: 'Safe'
			})
		}),
		$debug: $('<li>', {
			id: 'ca-debug',
			html: $('<a>', {
				href: new Query({
					title: conf.wgPageName,
					debug: true
				}).print(true),
				title: 'View page in debug mode.',
				text: 'Debug'
			})
		}),
		$toggle: $('<li>', {
			id: 'ca-toggle',
			html: $('<a>', {
				href: '#',
				title: 'Toggle hidden content.',
				text: 'Toggle'
			})
		})
	};

	// Change "View history" button label to simply "History".
	$('#ca-history').find('a').text('History');

	// Move the search bar and add container for special messages to the user bar:
	page.$userBar
		.prepend(items.$searchBar)
		.append(items.$specialMessages);

	// Place the buttons for the page inside a container in the user bar:
	page.$mwHead.append(
		items.$userBarBottom.append(
			$('#left-navigation')
		).append(
			$('#right-navigation')
		)
	);

	// Add user contributions button to the top of the page:
	page.$leftNav.append(
		$('#t-contributions').attr('id', 'ca-contribs')
		.find('a')
			.wrap('<span>')
			.text('Contributions')
		.end()
	);
	 	
	// Extract the buttons from the right "More" menu and place them next to the other ones:
	page.$rightMenu.children().each(function() {
		const $this = $(this);
		page.$rightNav.append(
			$this.addClass('collapsible').html(function() {
				return $('<span>', {
					html: $this.html()
				});
			})
		);
	});
	
	// Add null edit button (only when reading the page):
	if($('#ca-view.selected').length) {
		page.$rightNav.append(items.$nullEdit);
	}
	
	// Add button to display raw content and last diff (only if the page has content):
	if(page.hasContent) {
		page.$rightMenu.append(items.$raw);
		$('#ca-history').after(items.$diff);
	}
	
	// Add a safemode button and a toggel button to the right "More" menu:
	page.$rightMenu
		.append(items.$safe)
		.append(items.$debug)
		.append(items.$toggle);

	// Place the (un)watch star in the last spot.
	page.$rightNav.append(page.$star);

	/** End. */


	/**
	 * Procedures.
	 */

	// Adapt the top buttons to the diff pages: ("prettyfy")
	$(function selectDiffButton() {
		if(Query.fetch('diff')) {
			$('#ca-diff').addClass('selected');
			$('#ca-view').removeClass('selected');
		}
	});

	// Get lastest diff link:
	$(function getLatestDiffLink() {
		const mainFunctionName = 'getLatestDiffLink'; //arguments.callee.name;
		const query = {
			action: 'query',
			prop: 'revisions',
			titles: conf.wgPageName,
			rvprop: 'ids|user|timestamp|size|parsedcomment|flags',
			rvlimit: 2,
			formatversion: 2,
			format: 'json'
		};
		const callbacks = {
			done: function(data) {
				try {
					const currentRevision = data.query.pages[0].revisions[0];
					items.$diff.off('click')
						.find('a')
							.attr('href', new Query({
								diff: currentRevision.revid
							}).print());
				} catch(e) {
					console.log('%c«' + mainFunctionName + '()» error!', 'color: red;');
					if(e instanceof TypeError) {
						console.log("%cCan't get diff!", 'color: red;', '(known)');
					} else {
						console.error(e);
					}
					console.log(new Query('api', query).print(), data);
					return;
				}
			},
			fail: console.log,
			always: function() {
				console.log('%cDiff getter loaded!', 'color: blue;');
			}
		};
		return conf.api(query, callbacks);
	});

	// Toggle stuff I've hidden:
	$(function toggleWhatIHaveHidden() {
		[	// Add toggle class to all selectors:
			'#siteNotice', '#editpage-copywarn', 
			'.mw-editTools', '.smw-editpage-help',
			'#n-Twitter', '#n-Facebook', '#p-random'
		].forEach(function(selector) {
			$(selector).addClass('toggle');
		});
	});

	// Automatically fill Special:Upload page for card images:
	$(function makeUploadingCardImagesEasier() {

		// Get the card name on the galleries and place it on upload links:
		(function getName(MutationObserver, encode) {

			// Only for "Card Gallery" and "Set Card Galleries" namespaces:
			if(![3004, 3024].includes(conf.wgNamespaceNumber)) {
				return;
			}

			// Function to guess the card name
			// and append it to the empty galleries' links:
			const guessCardName = function() {
				$('.gallerybox').each(function() {
					const $this = $(this);
					const cardName = (function() {
						var brFlag = false;
						if(conf.wgNamespaceNumber === 3004) {
							// Card galleries:
							const $navBoxHeader = $('.navbox-title').children('div');
							return $navBoxHeader.text() + $navBoxHeader.find('a').attr('title').replace(/.+?( \(.*\))?/g, function(_, $1) {
								return $1 || '';
							});
						} else {
							// Set galleries:
							return $this.find('.gallerytext').find('p').contents().filter(function() {
								const isBR = this.nodeName === 'BR';
								brFlag = isBR ? !brFlag : brFlag;
								return brFlag && !isBR;
							}).filter('a').first().text();
						}
					})();

					// Append the name to the url, in the form of «_name=cardName»:
					$this.find('.noFile').attr('href', function(_, href) {
						return [
							href,
							[
								'_name',
								encode(cardName)
							].join('=')
						].join('&');
					});
				});
			};

			// Execute guessing the card name:
			guessCardName();

			// Check if there were changes in the DOM
			// to keep the links with the card name in there (when previewing edits, etc.):
			if(MutationObserver && ['edit', 'submit'].includes(conf.action)) {

				// Wait for the editor to load:
				mw.loader.using('jquery.wikiEditor').then(function() {
					
					// (jQuery) Node to observe:
					const $targetNode = $('.wikiEditor-ui-view-preview').find('.wikiEditor-preview-contents');
			
					// Options for the observer (which mutations to observe).
					// We only want to see when content is loaded from the backend
					// and then appended.
					// Therefore, observe when new child nodes are placed or removed:
					const options = {
						childList: true
					};
			
					// Create an observer instance and observe:
					new MutationObserver(function(mutationsList) {
						// No need to iterate over mutationList,
						// since we just want to execute the following function once.
						delay(function() {
							guessCardName();
						});
					}).observe($targetNode[0], options);

				});

			}

		})(window.MutationObserver, window.encodeURIComponent);

		// Populate Special:Upload descrption with the name from the url:
		(function populate(name) {

			// Fill in the upload description box at Special:Upload:
			$('#wpUploadDescription').val(
				name ? '{{OCG-TCG card image\n| name = ' + name + '\n}}' : null
			);

		})(Query.fetch('_name'));
		
	});

	/** End. */


	/**
	 * Events.
	 */

	// Diff:
	items.$diff.click(function(e) {
		// Prevent default event:
		e.preventDefault();
		// Display state as failed: (if successfull, the Api request done() will "off" this event.)
		new State('Failed to get diff!').failed().show();
	});

	// Null edit:
	items.$nullEdit.click(function(e) {
		// Prevent default event:
		e.preventDefault();
		// Request state:
		const state = new State('Null editing...').show();
		// Request:
		const query = {
			action: 'edit',
			title: conf.wgPageName,
			token: mw.user.tokens.get('editToken'),
			summary: 'Something bad happened!',
			prependtext: ''
		};
		const callbacks = {
			done: function() {
				state.done().setMessage('Null edited!');
				page.$content.load(window.location.href + ' #content > *');
			},
			fail: function(message, errorObject) {
				console.log('%cNull edit error!', 'color: red;', '(message, errorObject)');
				console.log(message, errorObject);
				state.failed().setMessage('Null edit failed! (' + (message === 'http'
					? errorObject.textStatus
					: message
				) + ')');
			},
			always: function() {
				state.show();
			}
		};
		const method = 'post';
		return conf.api(query, callbacks, method);
	});

	// Toggle:
	items.$toggle.click(function(e) {
		e.preventDefault();
		$('.toggle').toggle();
	});

	/** End. */


	/**
	 * Return.
	 */

	console.log(
		'%cUser Vector.js loaded! Last version from ' + LAST_LOG + '.',
		'color: blue;'
	);

	return true;

	/** End. */

})(window, window.jQuery, window.mediaWiki, window.console, (function(window, $) {
	"use strict";
	return function(t, fn) {
		if(!fn && $.isFunction(t)) {
			fn = t;
			t = undefined;
		}
		return window.setTimeout(fn, t);
	};
})(window, window.jQuery));
/** End. */