User:Thundercraft5/CodeFiddle.js

/* jshint undef: true, jquery: true, maxerr: 9999 */

/* global mw, CodeFiddle, ace */ mw.loader.using(['mediawiki.widgets', 'mediawiki.widgets.SearchInputWidget'], => {	if (mw.config.get('wgPageName') !== 'Special:CodeFiddle' || window.ReadyCodeFiddle) return;	var noop =  => {};	/* Constructor */	var CodeFiddle = function CodeFiddle {		$('body').addClass('code-fiddle-loaded');		/* Patch MW notification to be visible in CodeFiddle */		$('#mw-notification-area').remove.prependTo('body');		$('.mw-notification-area').on({ click { var $this = $(this); $this.removeClass('mw-notification-visible'); },			transistionend { $(this).remove; },		}, '.mw-notification');		this.mw = mw.config.get([ 'wgArticlePath', 'wgSiteName', 'wgServer', 'wgPageName', ]);		this.mw.wgArticlePath = this.mw.wgArticlePath.replace(/\$1/, );		document.title = 'Wiki Code Fiddle | ' + this.mw.wgSiteName + ' | Fandom';		// Push history state to make the page appear in browser history		window.history.pushState(null, document.title, window.location.href);		this.wikiHref = mw.config.get('wgServer') + this.mw.wgArticlePath;		this.onFrameLoad = this.onFrameLoad.bind(this);		this.updateEditorCSS = this.updateEditorCSS.bind(this);		this.wikiHrefText = this.wikiHref.replace(/^.*?:\/{2}/, );

// Setup Output iframe this.$outputFrame = $(' ', {			id: "code-fiddle-output-frame",			src: this.wikiHref,		}); this.addConsoleBindings; this.$inputStyles = $(' ', {			type: "text/css",			id: "code-fiddle-injected-styles",		}); this.$inputScript = $(' ', {			type: "text/javascript",			id: "code-fiddle-injected-script",		}); this.$titleInput = new mw.widgets.TitleInputWidget({			id: "code-fiddle-titleinput",		}); this.$console = $(' ', {			id: "code-fiddle-console-output",			html: [				this.createConsoleMessage('log', 'Welcome to the CodeFiddle console.'),			],		}); // Add load listener to Output frame this.$outputFrame.on('load', this.onFrameLoad); // Console Settings this.consoleSettings = $.extend(this.defaultConsoleSettings, JSON.parse(localStorage.getItem('CodeFiddle-settings') || "{}"));

Object.entries(this.consoleSettings).forEach(([setting, cl]) => {			if (this.consoleSettings[setting]) null;		}); $('body') .find('.main-container') .empty .end .prepend(this.createMainUI				.find('.focusable') // Add use .focusable to make certain elements focusable by tabindexes					.attr('tabindex', -1)					.removeClass('focusable')				.end			); // Setup editors ace.config.set('basePath', '/extensions-ucp/CodeEditor/modules/ace'); this.jsEditor = ace.edit('code-fiddle-js-editor'); this.cssEditor = ace.edit('code-fiddle-css-editor'); this.cssEditor.session.setMode(new (ace.require("ace/mode/css").Mode)); this.jsEditor.session.setMode(new (ace.require("ace/mode/javascript").Mode)); this.activeEditor = this.jsEditor; this.cssEditor.on('change', this.updateEditorCSS); // Add event listeners // Frame controls $('.code-fiddle-reload').click( => {			this.childWindow.location.reload;			this.setFrameLoading;		}); $('.code-fiddle-stop').click( => {			this.blockFrameLoad;		}); // Editor tabs $('.code-fiddle-tab').click(e => {			var $this = $(e.target);			if ($this.hasClass('active')) return;			$('.code-fiddle-tab, .code-fiddle-panel').removeClass('active');			// Find sibling index of tab			const index = $this				.parent				.children				.toArray				.indexOf($this[0]);			// Switch tabs							$('.code-fiddle-panel').eq(index).addClass('active');			$this.addClass('active');		}); $('.code-fiddle-reload').click(this.toggleReloading.bind(this)); $('.code-fiddle-fullscreen').click(this.toFullScreenPreview.bind(this)); // Fullscreen keybind $([window, document]).on('keyup', e => {			e.key = e.key.toLowerCase;			if (e.key === 'f11' && e.ctrlKey) this.toFullScreenPreview;		}); // Block default page reload keybind & change it to ctrl-alt-f5/ctrl-alt $([window, document]).on('keydown', e => {			e.key = e.key.toLowerCase;			// For frame			if ((e.key === 'f5' || e.key === 'r' && e.ctrlKey) && !e.altKey) {				e.preventDefault;				this.childWindow.location.reload;				this.setOutputLoading;			// Allow normal			} else if ((e.key === 'f5' || e.key === 'r' && e.ctrlKey) && e.altKey) {				window.location.reload;			}		}); this.$titleInput.on('enter', => {			this.$outputFrame.attr('src', this.wikiHref + encodeURIComponent(this.$titleInput.getValue) + '?useskin=FandomDesktop&noexternals=1');			this.addConsoleBindings;			this.setOutputLoading;		}); this.setOutputLoading; };

CodeFiddle.prototype = $.extend({		/* Define variables that will be used */		activeEditor: null,		jsEditor: null,		cssEditor: null,		childWindow: null,		wikiHref: null,		wikiHrefText: null,		$outputFrame: null,		$titleInput: null,		$inputStyles: null,		reloading: false,		consoleSettings: null,		defaultConsoleSettings: {			network: false,			errors: true,			warnings: true,			logs: true,			debug: false,			info: false,		},		createMainUI {			return $(' ', { class: "code-fiddle", html: [ this.createSideBarUI, $(' ', {						class: "code-fiddle-output-wrapper",						html: $(' ', { html: [ $(' ', {									class: "code-fiddle-output-header-wrapper",									html: $(' ', { text: "Code Fiddle Output", class: "code-fiddle-output-header", }),								}),								this.createAddressBarUI, $(' ', {									class: "code-fiddle-frame-wrapper",									html: [										this.$outputFrame,										$(' ', { class: "code-fiddle-frame-overlay", html: $(' ', {												class: "fandom-spinner code-fiddle-frame-spinner",												html: '  ',											}), }),									],								}),							],						}),					}),				],			});		},		createSideBarUI {			return $(' ', { class: "code-fiddle-sidebar", html: [ $(' ', {						class: "code-fiddle-header",						html: [							$(' ', { class: "code-fiddle-settings focusable", html: [ $(' ', {										class: "code-fiddle-settings-knob",										}), $('', {										class: "code-fiddle-settings-dropdown",										html: [										],									}), ],							}),							$(' ', {								html: "Wiki Code Fiddle", }),						],					}),					$('', {						class: "code-fiddle-editor-tabs",						html: [							$('', { class: "code-fiddle-tab active", text: "CSS", }),							$('', { class: "code-fiddle-tab", text: "JS", }),							$('', { class: "code-fiddle-tab", text: "Console", }),						],					}),					$(' ', {						class: "code-fiddle-editor-wrapper",						html: [							$(' ', { class: "code-fiddle-editors code-fiddle-panels", html: [ $(' ', {										class: "code-fiddle-panel",										html: [											$(' ', { class: "code-fiddle-editor", id: "code-fiddle-css-editor", }),										],									}),									$(' ', {										class: "code-fiddle-panel active",										html: [											$(' ', { class: "code-fiddle-editor", id: "code-fiddle-js-editor", }),										],									}),									$(' ', {										class: "code-fiddle-panel",										html: this.createConsole,									}), ],							}),						],					}),				],			});		},		createAddressBarUI {			return $(' ', { class: "code-fiddle-addressbar", html: [ $(' ', {						class: "code-fiddle-wiki-wrapper",						html: [							$(' ', { class: "code-fiddle-favicon", html: $(' ', { src: $('link[rel="shortcut icon"]').attr('href'), }), }),							$('', { class: "code-fiddle-wiki", href: this.wikiHref, text: this.wikiHrefText, title: this.wikiHref, target: "_blank", }),						],					}),					$(' ', {						class: "code-fiddle-input-wrapper",						html: this.$titleInput.$element,					}), $(' ', {						class: "code-fiddle-addressbar-controls",						html: [							$(' ', { class: "code-fiddle-reload", html: ' ', title: "Reload the Code Output", }),							$(' ', {								class: "code-fiddle-stop", html: ' ', css: { display: "none", },								title: "Stop loading the Code Output", }),							$(" ", {								class: "code-fiddle-fullscreen", html: ' ', title: "Enter fullscreen for the Code Output", })						],					}),				],			});		},		createConsole {			return [				$(' ', { id: "consolecontrols-toggle", name: "consolecontrols-toggle", type: "checkbox", class: "code-fiddle-consoleoptions-checkbox cf-hidden", checked: true, }),				$(' ', {					class: "code-fiddle-consolecontrols", html: [ $(" ", {							click: this.clearConsole.bind(this),							class: "code-fiddle-consoleclear",							}), $(' ', {							class: "code-fiddle-consoleoptions",							for: "consolecontrols-toggle",						}), ],				}),				$(" ", {					class: "code-fiddle-consoleoptions-dropdown", html: [ $(' ', {							text: "Console Settings",							class: "code-fiddle-consoleoptions-settings",						}), this.createCheckbox(							'Log Network Requests', 							'Log Network requests to the console (XHR/Fetch)', 							'code-fiddle-consolecheckbox-network', {								checkbox: {									change: this.onConsoleCheckboxChange.bind(this),									"data-toggleclass": "network",										},							}						), ],				}),				this.$console,			];		},		createCheckbox(text, title, id, options={}) {			return $(' ', $.extend({				title,				class: "code-fiddle-checkbox-wrapper",				html: [					$(' ', $.extend({						id,						title,						name: id,						type: "checkbox",					}, options.checkbox)),					$(' ', $.extend({						text,						title,						for: id,					}, options.label)),				],				}, options.wrapper));		},		onConsoleCheckboxChange(e) {			var name = e.target.attributes['data-toggleclass'].value;			var cl = "hide-" + name;			this.consoleSettings[name] = e.target.checked;			localStorage.setItem('CodeFiddle-settings', JSON.stringify(this.consoleSettings));			if (!e.target.checked) this.$console.addClass(cl);			else this.$console.removeClass(cl);		},		onFrameLoad {			this.childWindow = this.$outputFrame[0].contentWindow;			$('.code-fiddle-frame-wrapper').removeClass('loading');			this.setupReloadIntercept .setupRequestIntercept .addOnFrameLinkClick .injectEditorCSS .toggleReloading .injectEditorJS; },		setFrameLoading { $('.code-fiddle-frame-wrapper').addClass('loading'); return this; },		blockFrameLoad { this.childWindow.stop; this.toggleReloading; $('.code-fiddle-frame-wrapper').removeClass('loading'); return this; },		setupErrorPipe { $(this.childWindow).on('error', e => {				this.sendConsoleMessage('error', e.message);			}); return this; },		setupRequestIntercept { const oldFetch = this.childWindow.fetch; this.childWindow.fetch = function fetch(url, options={}) { if (!arguments.length) return Promise.reject(new TypeError("Failed to execute 'fetch' on 'Window': 1 argument required, but only 0 present.")); const defaults = { "method": "GET", };				options = $.extend(options, defaults); return oldFetch(url, options).then(res => {					this.sendConsoleMessage('fetch', `Fetch finished loading: ${ options.method.toUpperCase } "${ res.url }"`);					return res;				}, e => {					this.sendConsoleMessage('fetch', `Fetch failed loading: ${ options.method.toUpperCase } "${ url }"`);					throw e;				}); }.bind(this); var oldXHR = this.childWindow.XMLHttpRequest; var that = this;

this.childWindow.XMLHttpRequest = class XMLHttpRequest extends oldXHR { constructor { super; this.addEventListener('load', => {						that.sendConsoleMessage('xhr', `XHR ${this.status >= 200 && this.status <= 300 ? "finished" : "failed"} loading: ${this.data.method} "${this.data.url}"`);					}); this.open = function open(method, url) { const r = oldXHR.prototype.open.apply(this, arguments); this.data = $.extend(this.data, {							method,							url,						}); return r;					}; }			};			return this; },		setupConsolePipe { ['info', 'debug', 'log', 'warn', 'error'].forEach(method => {				var oldMethod = this.childWindow.console[method];				this.childWindow.console[method] = (...arguments) => { // jshint ignore:line					const data = Array.from(arguments);					oldMethod.apply(null, data);					this.$console.append(this.createConsoleMessage.apply(this, [method].concat(data)));				};			}); return this; },		setupReloadIntercept { var oldReload = this.childWindow.location.reload; var oldReplace = this.childWindow.location.replace; this.childWindow.location.reload = => { oldReload; this.setOutputLoading; };			this.childWindow.location.replace = url => { oldReplace(url); this.setOutputLoading; };			return this; },		createConsoleMessage(type) { const data = Array.from(arguments).slice(1); var classes = type.split(' ').map(v => "code-fiddle-consolemessage-" + v).join(' '); return $(' ', {				class: "code-fiddle-consolemessage " + classes,				tabindex: -1,				html: $(' ', { class: "code-fiddle-consolemessage-text", text: data.join(' '), }),			});		},		sendConsoleMessage { return this.$console.append(this.createConsoleMessage.apply(this, arguments)); },		clearConsole { this.$console.empty; return this; },		injectEditorCSS { this.updateEditorCSS; $(this.childWindow.document.head).append(this.$inputStyles); return this; },		injectEditorJS { this.$inputScript.text(this.jsEditor.getValue); $(this.childWindow.document.head).append(this.$inputScript.clone); return this; },		updateEditorCSS { this.$inputStyles.text(this.cssEditor.getValue); return this; },		toggleReloading { if (this.reloading) { $('.code-fiddle-stop').hide; $('.code-fiddle-reload').show; } else { $('.code-fiddle-stop').show; $('.code-fiddle-reload').hide; }			this.reloading = !this.reloading; return this; },		toFullScreenPreview { this.$outputFrame[0].requestFullscreen.catch(noop); return this; },		addConsoleBindings { setTimeout( => {				this.childWindow = this.$outputFrame[0].contentWindow;				console.log(this.childWindow);				this.setupConsolePipe.setupErrorPipe.setupRequestIntercept;			}, 5); },		setOutputLoading { this.setFrameLoading .toggleReloading .addFrameKeyBindings; return this; },		addFrameKeyBindings { $(this.childWindow).on('keyup', e => {				e.preventDefault;				if (e.key.toLowerCase === 'r' && e.ctrlKey) this.childWindow.reload;			}); return this; },		addOnFrameLinkClick { $(this.childWindow.document).on('click', 'a', e => {				var href = $(e.target).attr('href');				if (!href) return;				href = new mw.Uri(href);				var hrefStr = href.toString;				const curHref = new mw.Uri(window.location.href);				const page = new mw.Title(href.path.replace(this.mw.wgArticlePath, ''));

if (curHref.host !== href.host || !hrefStr.includes(this.mw.wgArticlePath) || page.namespace === -1 || hrefStr.includes('action')) { e.preventDefault; open(hrefStr); return; } else { this.$titleInput.setValue(page.toString); this.$outputFrame.attr('src', hrefStr); this.addConsoleBindings; this.setOutputLoading; }			});			return this;		},		createInterval(callback, ms = 0, ...args) {			return {				id: -1,				state: "idle",				promise: $.Deferred,				start {					if (this.state !== "idle") throw new TypeError("Interval is already started or is ended");					this.id = setInterval(callback.bind(this, this), ms, ...args);					this.promise.notify("started", this.id, this);					this.state = "active";					return this;				},				end {					if (this.state !== "active") throw new TypeError("Interval is not active or is not started");					clearInterval(this.id);					this.promise.notify("ended", this.id, this);					this.promise.resolve(this.id, this);					this.state = "done";					return this;				},				valueOf {					return this.id;					},			};				}	}, window.CodeFiddle); window.CodeFiddle = CodeFiddle; window.ReadyCodeFiddle = new CodeFiddle; });