User:BrandonXLF/sandbox.js

From Wikipedia, the free encyclopedia
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
(function() {
	function loadScripts() {
		var requests = [null, null], // Force $.when to return arrays
			enabledScripts = [],
			js = '',
			scripts = JSON.parse(mw.user.options.get('userjs-scriptmanager-scripts') || '[]');

		for (var i = 0; i < scripts.length; i++) {
			var script = scripts[i];
			if (script.enabled && (script.conds ? eval(script.conds) : true) && script.name !== 'User:BrandonXLF/sandbox.js') {
				enabledScripts.push(script.name);
				js += script.vars + '\n';
			}
		}

		for (var i = 0; i < enabledScripts.length; i += 50) {
			requests.push(new mw.Api().get({
				action: 'query',
				prop: 'revisions',
				titles: enabledScripts.slice(i, i + 50).join('|'),
				rvprop: 'content',
				rvslots: 'main',
				formatversion: 2
			}));
		}

		$.when.apply(null, requests).then(function() {
			for (var i = 2; i < arguments.length; i++) {
				arguments[i][0].query.pages.forEach(function(page) {
					if (page.revisions) {
						js += '/* ' + page.title + ' */\n' + page.revisions[0].slots.main.content;
					} else {
						console.warn('Failed to load revision for script ' + page.title);
					}
				});
			}

			var script = document.createElement('script');
			script.text = js;
			document.head.appendChild(script);
		});
	}

	function manageScripts(e) {
		e.preventDefault();
		
		function ScriptGroup(items) {
			ScriptGroup.super.call(this);

			OO.ui.mixin.DraggableGroupElement.call(this, {
				$group: this.$element,
				items: items
			});
		}

		OO.inheritClass(ScriptGroup, OO.ui.Widget);
		OO.mixinClass(ScriptGroup, OO.ui.mixin.DraggableGroupElement);

		function ScriptWidget(data, dialog) {
			var widget = this;

			ScriptWidget.super.call(this, {data: data});
			OO.ui.mixin.IconElement.call(this, {icon: 'draggable'});
			OO.ui.mixin.LabelElement.call(this, {label: data.name});
			OO.ui.mixin.DraggableElement.call(this, {$handle: this.$icon});

			this.toggle = new OO.ui.ButtonWidget({
				framed: false,
				label: widget.data.enabled ? 'Disable' : 'Enable'
			});

			this.toggle.on('click', function() {
				widget.data.enabled = !widget.data.enabled;
				widget.toggle.setLabel(widget.data.enabled ? 'Disable' : 'Enable');
				widget.$label.toggleClass('userscript-disabled', !widget.data.enabled);
			});

			this.remove = new OO.ui.ButtonWidget({
				framed: false,
				flags: 'destructive',
				label: 'Remove'
			});

			this.remove.on('click', function() {
				widget.getElementGroup().removeItems([widget]);
				dialog.updateSize();
			});

			this.$label.toggleClass('userscript-disabled', !this.data.enabled);

			this.$element.append(
				this.$icon,
				this.$label,
				new OO.ui.ButtonGroupWidget({
					items: [this.toggle, this.remove]
				}).$element
			);

			this.$element.addClass('userscript-listing');
			this.$label.addClass('userscript-label');
		}

		OO.inheritClass(ScriptWidget, OO.ui.Widget);
		OO.mixinClass(ScriptWidget, OO.ui.mixin.IconElement);
		OO.mixinClass(ScriptWidget, OO.ui.mixin.LabelElement);
		OO.mixinClass(ScriptWidget, OO.ui.mixin.DraggableElement);

		function ManagerDialog(config) {
			ManagerDialog.super.call(this, config);
		}

		OO.inheritClass(ManagerDialog, OO.ui.ProcessDialog);

		ManagerDialog.static.name = 'manageuserscripts';
		ManagerDialog.static.title = 'Manage user scripts';
		ManagerDialog.static.actions = [
			{
				label: 'Close',
				flags: ['safe', 'close'],
				modes: ['list']
			},
			{
				label: 'Save',
				flags: ['primary', 'progressive'],
				modes: ['list'],
				action: 'save'
			},
			{
				label: 'Add new script',
				flags: ['safe'],
				modes: ['list'],
				action: 'add'
			},
			{
				label: 'Import scripts',
				flags: ['safe'],
				modes: ['list'],
				action: 'import'
			},
			{
				label: 'Back',
				flags: ['safe', 'back'],
				modes: ['add'],
				action: 'add-back'
			},
			{
				label: 'Continue',
				flags: ['primary', 'progressive'],
				modes: ['add'],
				action: 'add-continue'
			},
		];

		ManagerDialog.prototype.initialize = function() {
			ManagerDialog.super.prototype.initialize.apply(this, arguments);
			
			var widgets = [],
				scripts = JSON.parse(mw.user.options.get('userjs-scriptmanager-scripts') || '[]');

			for (var i = 0; i < scripts.length; i++) {
				widgets.push(new ScriptWidget(scripts[i], this));
			}

			this.listPanel = new OO.ui.PanelLayout({
				padded: true,
				expanded: false
			});
			this.addNewPanel = new OO.ui.PanelLayout({
				padded: true,
				expanded: false
			});
			this.group = new ScriptGroup(widgets);
			this.panels = new OO.ui.StackLayout();
			this.addNewInput = new OO.ui.TextInputWidget();
			this.addNewLayout = new OO.ui.FieldLayout(this.addNewInput, {
				align: 'top',
				label: 'Enter script title'
			});
			
			this.listPanel.$element.append(this.group.$element);
			this.addNewPanel.$element.append(this.addNewLayout.$element);
			this.panels.addItems([this.listPanel, this.addNewPanel]);
			this.$body.append(this.panels.$element);
		};
		
		ManagerDialog.prototype.getSetupProcess = function(data) {
			return ManagerDialog.super.prototype.getSetupProcess.call(this, data).next(function() {
				this.panels.setItem(this.listPanel);
				this.actions.setMode('list');
			}, this);
		};
		
		ManagerDialog.prototype.showErrors = function(errors) {
			ManagerDialog.super.prototype.showErrors.call(this, errors);
			
			this.actions.setAbilities({
				'add-continue': true
			});
			
			this.updateSize();
		};
		
		ManagerDialog.prototype.showAddNewError = function(error) {
			this.addNewLayout.setErrors(error ? [error] : []);
			this.updateSize();
		};

		ManagerDialog.prototype.getActionProcess = function(action) {
			var dialog = this;

			return new OO.ui.Process(function() {
				if (!action) return dialog.close();

				if (action == 'add') {
					dialog.panels.setItem(dialog.addNewPanel);
					dialog.actions.setMode('add');
					dialog.addNewInput.setValue('');
					dialog.showAddNewError();
				} else if (action == 'add-back') {
					dialog.panels.setItem(dialog.listPanel);
					dialog.actions.setMode('list');
				} else if (action == 'add-continue') {
					var name = dialog.addNewInput.getValue();

					if (!name) return dialog.showAddNewError('Name is required.');

					return new mw.Api().get({
						action: 'query',
						titles: name,
						prop: 'info',
						formatversion: 2
					}).then(function(res) {
						var info = res.query.pages[0];
						
						if (info.missing) return dialog.showAddNewError('Script does not exist.');
						if (info.ns !== 2 && info.ns !== 8) return dialog.showAddNewError('Script must be in the User or MediaWiki namespaces.');
						if (info.contentmodel !== 'javascript') return dialog.showAddNewError('Script must have the JavaScript content model.');

						var items = dialog.group.getItems();
						for (var i = 0; i < items.length; i++) {
							if (items[i].getData().name == info.title) return dialog.showAddNewError('A script with the name is already added.');
						}

						dialog.showAddNewError();
						dialog.group.addItems([
							new ScriptWidget({name: info.title, enabled: true}, dialog)
						]);
						dialog.panels.setItem(dialog.listPanel);
						dialog.actions.setMode('list');
					});
				} else if (action == 'save') {
					var items = dialog.group.getItems(),
						scripts = [];

					for (var i = 0; i < items.length; i++) {
						scripts.push(items[i].getData());
					}

					return new mw.Api().saveOption('userjs-scriptmanager-scripts', JSON.stringify(scripts)).then(function() {
						mw.user.options.set('userjs-scriptmanager-scripts', JSON.stringify(scripts));
						dialog.close();
					});
				} else if (action == 'import') {
					var skins = ['common', 'monobook', 'minerva', 'vector', 'cologneblue', 'timeless'],
						skinScripts = [];

					for (var i = 0; i < skins.length; i++) {
						skinScripts.push('User:' + mw.config.get('wgUserName') + '/' + skins[i] + '.js');
					}

					return new mw.Api().get({
						action: 'query',
						prop: 'revisions',
						titles: skinScripts.join('|'),
						rvprop: 'content',
						rvslots: 'main',
						formatversion: 2
					}).then(function(res) {
						for (var i = 0; i < res.query.pages.length; i++) {
							var re = /(\/\/|)importScript\('(.*)'\)/g,
								match,
								page = res.query.pages[i];

							if (!page.revisions) continue;

							while (true) {
								match = re.exec(page.revisions[0].slots.main.content);
								
								if (!match) break;

								// Normalize title
								var title = new mw.Title(match[2]).getPrefixedText(),
									items = dialog.group.getItems(),
									duplicate = false;

								for (var j = 0; j < items.length; j++) {
									// Script is already added, skip
									if (items[j].getData().name == title) {
										duplicate = true;
										break;
									}
								}

								if (duplicate) continue;

								dialog.group.addItems([new ScriptWidget({
									name: title,
									enabled: !match[1]
								}, dialog)]);
							}
						}
					});
				}
			});
		};

		var windowManager = new OO.ui.WindowManager(),
			dialog = new ManagerDialog({
				size: 'large'
			});

		$(document.body).append(windowManager.$element);
		windowManager.addWindows([dialog]);
		windowManager.openWindow(dialog);
		
		mw.loader.addStyleTag(
			'.userscript-listing { display: block; position: relative; }' +
			'.userscript-listing.oo-ui-iconElement { padding-left: 2.5em; }' +
			'.userscript-listing.oo-ui-iconElement .oo-ui-iconElement-icon { position: absolute; left: 0.6em; }' +
			'.userscript-listing > * { vertical-align: middle; }' +
			'.userscript-disabled { text-decoration: line-through; }' +
			'.userscript-label  { margin-right: 1em; }'
		);
	}

	mw.loader.using('mediawiki.api').then(function() {
		loadScripts();
	});

	var portletLinks = mw.util.addPortletLink('p-personal', '#', 'Scripts', 'scriptmanager', 'Manage user scripts', '', '#pt-watchlist');
	if (portletLinks) portletLinks.addEventListener('click', manageScripts);
})();