MediaWiki:Gadget-TooltipsEditor.js

/* jshint esversion: 5, forin: true, immed: true, indent: 4, latedef: true, newcap: true, noarg: true, undef: true, undef: true, unused: true, browser: true, jquery: true, onevar: true, eqeqeq: true, multistr: true, maxerr: 999999, -W082, -W084 /* global mw, ace */ $.when(	$.Deferred(function(def) { $(function { 			def.resolve;		}); }), 	mw.loader.using(['mediawiki.util', 'mediawiki.api', 'ext.codeEditor.ace']),	$.Deferred(function(def) { if (mw.libs.QDmodal) { def.resolve(mw.libs.QDmodal); } else { $.ajax({				cache: true,				dataType: "script",				url: "https://dev.fandom.com/load.php?mode=articles&only=scripts&articles=MediaWiki:QDmodal.js"			}).done(function { 				def.resolve(mw.libs.QDmodal);			}); }	}) ).then(function {	// Pages	var allowedPages = [		// "Module:Inventory_slot/Tooltips",		"Module:Inventory_slot/Test", // used only when testing with user script	];	if (!allowedPages.includes(mw.config.get('wgPageName')) || (window.TooltipsEditor && window.TooltipsEditor.loaded)) return;

var api = new mw.Api;

console.log("Loading TooltipsEditor...");

var that; var TooltipsEditor = window.TooltipsEditor = Object.assign(this, {		// variables; undefined variables are just for easier variable tracking		modal: new mw.libs.QDmodal("TooltipsEditor"),		closing: undefined,		actions: [],		loaded: true,		isInMain: false,		data: undefined,		json: undefined,		oldjson: undefined,		oldjsonkeys: undefined,		colorRules: undefined,		editor: undefined,		lastFocusedEditor: undefined,		lastFocusedElement: undefined,

colorConversions: { 0: "black", 1: "dark_blue", 2: "dark_green", 3: "dark_aqua", 4: "dark_red", 5: "dark_purple", 6: "gold", 7: "gray", 8: "dark_gray", 9: "blue", a: "green", b: "aqua", c: "red", d: "light_purple", e: "yellow", f: "white", l: "bold", n: "underline", m: "strikethrough", o: "italic", r: "reset", },		colorConvList: "0123456789abcdef", conversions: { "l": "bold", "m": "strikethrough", "n": "underline", "o": "italic", },		rarityConversions: { "Common": "f", "Uncommon": "a", "Rare": "9", "Epic": "5", "Legendary": "6", "Mythic": "d", "Supreme": "4", "Special": "c", "Very Special": "c", },		shortForm: { "Common": "C", "Uncommon": "U", "Rare": "R", "Epic": "E", "Legendary": "L", "Mythic": "M", "Supreme": "SE", "Special": "SL", "Very Special": "VSL", },		specialchars: ("❤ ❈ ❁ ✦ ☣ ☠ ✎ ∞ ✯ ♣ ❂ ⚔ ⫽ α ✹ ⸕ ☘ 🗲 ❣ ⚚ ⸎ ʬ") .replaceAll(" ", "  ") .split(" ") .map(function(v) {				if (!v.match(" ")) {					return $("", { "class": "TooltipsEditor-insertChar", text: v,						title: "Insert \"" + v + "\"", });				} else {					return v;				}			}), escapes: { regex: /\\(ench?a?n?t?m?e?n?t?|ra?r?i?t?y?|poti?o?n|sta?t?)\{(?: .+?)\}|\\(?:[rntvb]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{1,4}|u\{[0-9a-fA-F]{1,6}\}|[0-2][0-7]{0,2}|3[0-7][0-7]?|[4-7][0-7].)/, token: "backescape.code", },

// helper functions for this.updateActions inOldjson: function (k) { return this.oldjsonkeys.indexOf(k) !== -1; },		throwOldjson: function(k) { if (this.inOldjson(k) && !(k in this.oldjson)) { this.oldjson[k] = {}; Object.assign(this.oldjson[k], this.json[k]); }		},		updateOne: function(k) { if (k) { if (this.inOldjson(k) && !(k in this.json)) this.actions[k] = "remove"; else if (!this.inOldjson(k) && (k in this.json)) this.actions[k] = "add"; else if (!this.inOldjson(k) && !(k in this.json)) { if (k in this.actions) delete this.actions[k]; }				else if (this.oldjson[k] 					&& this.oldjson[k].name === this.json[k].name 					&& this.oldjson[k].title === this.json[k].title 					&& this.oldjson[k].text === this.json[k].text				) { if (k in this.actions) delete this.actions[k]; if (k in this.oldjson) delete this.oldjson[k]; }				else this.actions[k] = "modify"; }		},		revertAction: function { var key = $(this).attr("data-value"); if (key in this.oldjson) { this.json[key] = {}; Object.assign(this.json[key], this.oldjson[key]); delete this.oldjson[key]; }			else if (key in this.json) delete this.json[key]; if (key in this.actions) delete this.actions[key]; mw.notify("for "+key, {title: "Undo Successful", type:"success"}); this.updateActions; if ($("#TooltipsEditor-searchInput").val.trim !== "") $("#TooltipsEditor-searchInput").keyup; },

// main function this.updateActions updateActions: function(keys) { // calling updateActions without keys will refresh the table without additional change // keys can be an array of strings or one string if (keys) { if (typeof(keys) === "string") { this.updateOne(keys); }				else { for (var i = 0; i < keys.length; i++) { var k = keys[i]; this.updateOne(k); }				}			}

var $log = $("#TooltipsEditor-actionLog"); var len = Object.keys(this.actions).length; var ls = ],[],[],[;

$log.empty.append($("", {text: len ? "Unsaved changes: " : "No changes were made", "class": "actions-none"}));

if (len) { Object.keys(this.actions).sort.forEach(function(k) {					switch(this.actions[k]) {						case("add"): {							ls[0].push($("", {								html: [									"Added "+k,									$("", { text: "undo", "class": "actions-undo-button", "data-value": k }).on("click", this.revertAction),									$("", { "class": "actions-edit-button", text: "edit", "data-tooltipTitle": this.json[k] && this.json[k].title && this.json[k].title.replaceAll("&amp;", "&"), "data-tooltipText": this.json[k] && this.json[k].text && this.json[k].text.replaceAll("&amp;", "&"), "data-tooltipLink": this.json[k] && this.json[k].name && this.json[k].name.replaceAll("&amp;", "&"), "data-tooltipKey": k.replaceAll("&amp;", "&") }),									$(" ", {										"class": "actions-preview-button minetip", text: "preview (current)", "data-minetip-text": this.json[k] && this.json[k].text && this.json[k].text.replaceAll("&amp;", "&"), "data-minetip-title": this.json[k] && this.json[k].title && this.json[k].title.replaceAll("&amp;", "&"), })								],								"class": "actions-add"							}));							break;						}						case("modify"): {							ls[1].push($("", {								html: [									"Modified "+k,									$("", { text: "undo", "class": "actions-undo-button", "data-value": k }).on("click", this.revertAction),									$("", { "class": "actions-edit-button", text: "edit", "data-tooltipTitle": this.json[k] && this.json[k].title && this.json[k].title.replaceAll("&amp;", "&"), "data-tooltipText": this.json[k] && this.json[k].text && this.json[k].text.replaceAll("&amp;", "&"), "data-tooltipLink": this.json[k] && this.json[k].name && this.json[k].name.replaceAll("&amp;", "&"), "data-tooltipKey": k.replaceAll("&amp;", "&") }),									$(" ", {										"class": "actions-preview-button minetip", text: "preview (undo)", "data-minetip-text": this.oldjson[k] && this.oldjson[k].text && this.oldjson[k].text.replaceAll("&amp;", "&"), "data-minetip-title": this.oldjson[k] && this.oldjson[k].title && this.oldjson[k].title.replaceAll("&amp;", "&"), }),									$(" ", {										"class": "actions-preview-button minetip", text: "preview (current)", "data-minetip-text": this.json[k] && this.json[k].text && this.json[k].text.replaceAll("&amp;", "&"), "data-minetip-title": this.json[k] && this.json[k].title && this.json[k].title.replaceAll("&amp;", "&"), })								],								"class": "actions-modify"							}));							break;						}						case("remove"): {							ls[2].push($("", {								html: [									"Removed "+k,									$("", { text: "undo", "class": "actions-undo-button", "data-value": k }).on("click", this.revertAction),									$(" ", { "class": "actions-preview-button minetip", text: "preview (undo)", "data-minetip-text": this.oldjson[k] && this.oldjson[k].text && this.oldjson[k].text.replaceAll("&amp;", "&"), "data-minetip-title": this.oldjson[k] && this.oldjson[k].title && this.oldjson[k].title.replaceAll("&amp;", "&"), })								],								"class": "actions-remove"							}));							break;						}					}				}.bind(this)); }

ls.forEach(function(v) { $log.append(v) }); $log.append($("", {text: "Saving changes will also sort all Tooltips in alphabetical order.", "class": "actions-none"})); $("#TooltipsEditor-undoLog").append(ls[3]); //.prepend(ls[3]) $("#TooltipsEditor-totalTooltips").html($(" ", { html: [ "Tooltips Count: "+Object.keys(this.json).length, $(" ", (function { var diff = Object.keys(this.json).length - this.oldjsonkeys.length; if (diff < 0) { return { text: "("+diff+")", css: {color: "red"} }; } else if (diff > 0) { return { text: "(+"+diff+")", css: {color: "lime"} }; } else { return { text: "(0)", css: {color: "gray"} }; }					}.bind(this))), ],				css: {float: "right"}, }));		},

// helper functions for this.generateSearch searchArray: function(arr, search) { var splitSearch = function(str) { var pattern = str.split("").map(function(v) {					return "(?=.*" + mw.RegExp.escape(v) + ")";				}).join(""); var regex = new RegExp(pattern, "i"); var match = str.match(regex);

return match && match[0]; };

return arr.filter(function(v) {				return v.toLowerCase.includes(search.toLowerCase) || splitSearch(v.toLowerCase, search.toLowerCase);			}); },

// main function this.generateSearch generateSearch: function { $("#searchResultsMessage").css({ display: "" });

var $this = $("#TooltipsEditor-searchInput"); var $results = $("#TooltipsEditor-searchResults"); var val = $this.val; var abort = false;

if (val.trim === "") return $results.html(" Enter a search term to start searching. ");

var names = Object.keys(this.json).sort; var results = this.searchArray(names, val);

if (results.length > 100) { results.length = 100; abort = true; }

$results.empty;

results.forEach(function(v) {				$results[0].appendChild($("", {					html: [						$("", { href: mw.util.getUrl(v), title: v,							text: v,							target: "_blank" }),						" (",						 $("", {							"class": "TooltipsEditor-editTooltip",							text: "edit",							"data-tooltipTitle": this.json[v] && this.json[v].title && this.json[v].title.replaceAll("&amp;", "&"),							"data-tooltipText": this.json[v] && this.json[v].text && this.json[v].text.replaceAll("&amp;", "&"),							"data-tooltipLink": this.json[v] && this.json[v].name && this.json[v].name.replaceAll("&amp;", "&"),							"data-tooltipKey": v.replaceAll("&amp;", "&")						}), " &bull; ", $(" ", {							"class": "TooltipsEditor-previewTooltip minetip", 							text: "preview",							"data-minetip-text": this.json[v] && this.json[v].text && this.json[v].text.replaceAll("&amp;", "&"),							"data-minetip-title": this.json[v] && this.json[v].title && this.json[v].title.replaceAll("&amp;", "&"), 						}), " &bull; ", $("", {							"class": "TooltipsEditor-removeTooltip", 							text: "remove",							"data-key": v.replaceAll("&amp;", "&"), 						}), ")",					],				})[0]);			}.bind(this));

if (!results.length) return $results.html(" No tooltip matched your search. "); else if (abort) $results.append(" Showing the first 100 results. "); else $results.append(" Total: "+results.length+(results.length > 1 && " results" || " result" )+". "); },

// helper functions for this.openEditor convertSlashes: function(s) { return s.replaceAll(/(?<!\\)\//g, "\n"); },

// main function this.openEditor openEditor: function(text, title, link, key) { this.isInMain = false; var oldKey = key;

$("#TooltipsEditor-search").css({ display: "none" }); $("#TooltipsEditor-editor").css({ display: "" });

ace.tooltipsTextEditor.resize; ace.tooltipsTitleEditor.resize;

ace.tooltipsTextEditor.setValue(this.convertSlashes(text || "")); ace.tooltipsTitleEditor.setValue(title || ""); $("#TooltipsEditor-link").val(link || ""); $("#TooltipsEditor-key").val(key || "");

$(".qdmodal-button").css({ display: "none" }); $("#TooltipsEditor-save, #TooltipsEditor-preview, #TooltipsEditor-cancel").remove; var $button1 = $(" ", {				"class": "oo-ui-buttonElement",				html: $(" ", { id: "TooltipsEditor-save", text: "Save", "class": "oo-ui-buttonElement-button", click: this.onSave.bind(this, oldKey), }),			});			var $button2 = $(" ", {				"class": "oo-ui-buttonElement",				html: $(" ", { id: "TooltipsEditor-cancel", text: "Cancel", "class": "oo-ui-buttonElement-button", click: function { this.reset(confirm("Go back to main page without saving?")); }.bind(this), }),			});			var $button3 = $(" ", {				"class": "oo-ui-buttonElement",				css: { "float": "right" },				html: $(" ", { id: "TooltipsEditor-preview", "class": "minetip oo-ui-buttonElement-button", text: "Preview", }),			});			$("#TooltipsEditor-editor").append(				$button1,				$button2,				$button3			); this.updatePreview; },

onSave: function(oldKey) { var text = ace.tooltipsTextEditor.getValue, title = ace.tooltipsTitleEditor.getValue, key = $("#TooltipsEditor-key").val, link = $("#TooltipsEditor-link").val;

this.throwOldjson(oldKey); this.throwOldjson(key);

if (!key) return alert("You need to enter a tooltip ID!"); if (!link) return alert("You need to enter the tooltip's link!");

if (oldKey) delete this.json[oldKey]; this.editor.addClass("mw-ajax-loader"); $(".qdmodal-button").css({ display: "" }); $("#TooltipsEditor-editor, #searchResultsMessage").css({ display: "none" });

api.post({ 				action: "parse", 				contentmodel: "wikitext",				text: "",			}).then(function(data) {				this.json[key] = {					text: text ? $(data.parse.text["*"])						.find("p")						.html						.trim						.replace(/&amp;/g, "&")						.replace(/ /g, " ") : undefined,					title: title.trim,					name: link.trim,				};				this.isInMain = true;				this.data = this.json; // TODO: DATA? THIS.DATA?

this.updateActions([oldKey, key]); this.reset(true); }.bind(this));		},

// helper functions for this.MainProcess processResult: function(d) { return JSON.parse(d.replaceAll(/\\(?![\'\"\/])/g, '\\\\'));		},		replaceLines: function(s) {			return s.replaceAll(/(?<!\\)\//g, "\\/").replaceAll(/\n/g, "/");		},		reset: function(confirm) {			if (confirm) {				$("#TooltipsEditor-search").css({ display: "" });				$("#TooltipsEditor-searchResults").empty;				$("#searchResultsMessage, #TooltipsEditor-editor").css({ display: "none" });				$("#TooltipsEditor > section").removeClass("mw-ajax-loader");				ace.tooltipsTextEditor.setValue("");				ace.tooltipsTitleEditor.setValue("");				$("#TooltipsEditor-key, #TooltipsEditor-link").val("");				$(".qdmodal-button").css({ display: "" });				if ($("#TooltipsEditor-searchInput").val.trim !== "") $("#TooltipsEditor-searchInput").keyup;			}		},		processColors: function {			return Object.keys(this.colorConversions).map(function(v, i, a) {				return $("", {					"class": "TooltipsEditor-insertFormat" + ((i === a.length-1) && " TooltipsEditor-last" || ""), text: this.colorConversions[v].replaceAll("_", " ").replaceAll(/(\w)(\w*)/g, function(_, $1, $2) { 						return $1.toUpperCase + $2;					}), "data-insert": "&" + v,				});			}.bind(this)); },		processRarityTexts: function { return Object.keys(this.rarityConversions).map(function(v, i, a) {				return $(" ", { html: [ $("<a>", {							"class": "TooltipsEditor-insertFormat",							text: v,							"data-insert": "&l&" + this.rarityConversions[v] + v.toUpperCase,							style: "font-style: italic;",						}), $("<a>", {							"class": "TooltipsEditor-insertFormat" + ((i === a.length-1) && " TooltipsEditor-last" || ""),							text: this.shortForm[v],							"data-insert": "&" + this.rarityConversions[v],							style: "font-style: italic;",						}), ]				});			}.bind(this)); },		updatePreview: function { $("#TooltipsEditor-preview").attr({				"data-minetip-text": this.replaceLines(ace.tooltipsTextEditor.getValue),				"data-minetip-title": ace.tooltipsTitleEditor.getValue,			}); },		insertText: function(text) { var editor = this.lastFocusedEditor; if (!editor && this.lastFocusedElement) { return this.lastFocusedElement.textSelection("encapsulateSelection", {					pre: text,					peri: "",				}), true; }

editor.insert(text, 1);

return true; },		setupEditor: function(id) { var aceEditor = ace.edit(id); var mode = new (ace.require("ace/mode/javascript").Mode); mode.HighlightRules = ace.require("ace/mode/minecraft-tooltips").MinecraftHighlightRules; aceEditor.session.setMode(mode);

if (id === "TooltipsEditor-text-AceEditor") { aceEditor.setOptions({					wrapBehavioursEnabled: true,					wrap: true,				});

aceEditor.session.on("change", function {					var height = aceEditor.session.getScreenLength						* aceEditor.renderer.lineHeight						+ aceEditor.renderer.scrollBar.getWidth + "px";

$(id).css({ height: height }); aceEditor.resize;

if (aceEditor.session.getScreenWidth > 1000) { $(id).css({ width: "1000px" }); }				});			}

aceEditor.session.on("change", this.updatePreview.bind(this)); aceEditor.on("focus", function {				this.lastFocusedEditor = aceEditor;			}.bind(this));

return aceEditor; },

onMatch: function(str) { var match = this.splitRegex.exec(str); var values = match.slice(1); values.push(match[0]);

var types = this.token.apply(this, values);

if (typeof types === "string") return [{ type: types, value: str }];			var tokens = []; for (var i = 0, l = types.length; i < l; i++) { if (values[i]) tokens[tokens.length] = { type: types[i], value: values[i] };			}			return tokens; },		MinecraftHighlightRules: function { var formats = [];

this.$rules = { start: [{ regex: /&([0-9a-f])/, token: function(code) { this.nextCode = code; that.colorRules["color-" + code].formats = []; return "escape.format.code.color"; },					next: function { return "color-" + this.nextCode; },					onMatch: that.onMatch, }, {					regex: /&r/, token: function { formats = []; return "escape.format.code.color"; },				}, that.escapes, { regex: /&([l-o])/, token: function(code) { formats.push(that.conversions[code]); return "escape.format.code.color"; },				}, {					regex: /[^\0]/, token: function { var ret = "text.formatted"; formats.forEach(function(code) {							ret += ".format_" + code;						}); return ret; },				}],			};

for (var prefix in that.colorRules) { if (that.colorRules.hasOwnProperty(prefix)) { var data = that.colorRules[prefix]; this.$rules[prefix] = data.rules; }			}

this.normalizeRules; },

// main function this.MainProcess MainProcess: function { this.oldjson = {}; this.json = {}; if (arguments.length > 1) { var jsonStr = [];

// Merge result into a single string to limit JSON.parse calls if (Array.isArray(arguments[0])) { Array.from(arguments).forEach(function(v) {						jsonStr.push(v[0]["return"].replace(/^\{|\}$/g, ""));					}); }				else jsonStr.push(arguments[0]["return"].replace(/^\{|\}$/g, "")); // Parse String this.json = this.processResult("{" + jsonStr.join(",") + "}"); } else { this.json = this.processResult(arguments[0]); }

this.editor = $("#TooltipsEditor > section"); this.data = this.json; this.oldjsonkeys = Object.keys(this.json);

ace.define("ace/mode/minecraft-tooltips", [], function(require, exports) {				var oop = require("../lib/oop");				var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules;

this.colorRules = {}; this.colorConvList.split("").forEach(function(code) {					this['color-' + code] = {						formats: [],						rules: [{							token: function(code) {								this.nextCode = code;								return 'escape.format.code.color';							},							regex: /&([0-9a-f])/,							next: function {								return "color-" + this.nextCode;							},							onMatch: that.onMatch,						}, {							regex: /&([l-o])/,							token: function(code) {								this.formats.push(that.conversions[code]);								return 'escape.format.code.text';							},						}, {							token: "language.escape.format.code",							regex: /&r/,							next: "start",						}, that.escapes, {							regex: /[^\0]/,							token: function {								var ret = "format.color.format-" + code + '.code';

this.formats.forEach(function(code) {									ret += '.format_' + code;								}); return ret; },						}],					};

var data = this['color-' + code]; data.rules[0].token = data.rules[0].token.bind(data); data.rules[0].next = data.rules[0].next.bind(data); data.rules[1].token = data.rules[1].token.bind(data); data.rules[4].token = data.rules[4].token.bind(data); }, this.colorRules);

var MinecraftHighlightRules = this.MinecraftHighlightRules;

oop.inherits(MinecraftHighlightRules, TextHighlightRules);

exports.MinecraftHighlightRules = MinecraftHighlightRules; }.bind(this));

this.editor.append(				$(" ", { id: "TooltipsEditor-search", html: [ $(" ", { id: "TooltipsEditor-totalTooltips" }), " Tooltips Editor Main Page ", "Search Existing Tooltip: ", $(" ", {							id: "TooltipsEditor-searchInput",							keyup: this.generateSearch.bind(this),						}), " or ", $(" ", {							"class": "oo-ui-buttonElement",							html: [								$(" ", { id: "TooltipsEditor-addNew", "class": "oo-ui-buttonElement-button", text: "Add New Tooltip", click: function { this.openEditor("", "", "", ""); }.bind(this), }),							],						}),						" ",						$("<ul>", {							id: "TooltipsEditor-actionLog",							css: {								"list-style-type": "none",								"margin-left": "0.8em",							},						}), $(" ", {							id: "TooltipsEditor-undoLog",						}), " ",						$("<b>", { text: "Search Results: ", id: "searchResultsMessage", style: "display: none;" }), $("<ul>", {							id: "TooltipsEditor-searchResults",							css: {								"list-style-type": "square",								"margin-left": "0.8em",							},						}), $(" ", {							css: {"font-style": "italic", float: "right", "margin-bottom": "1em"},							html: [								$(" ", { css: {"margin-left": "0.5em"}, html: ["(", $("<a>", { href: mw.util.getUrl("MediaWiki:Gadget-TooltipsEditor.js"), text: "View JavaScript", target: "_blank" }), ")" ],								}),								$(" ", {									css: {"margin-left": "0.5em"}, html: ["(", $("<a>", { text: "Debug" }).on("click", function { var it = prompt("Please enter an item to see its JSON"); if (it !== null && it !== "") { if (it in this.json) { mw.notify("See results in console", {title: "Debug", type:"warning"}); }											else mw.notify("Debug failed: Item not found.", {title: "Debug", type:"warning"}); }									}), ")" ],								}),							],						}),					],				}),				$(" ", {					id: "TooltipsEditor-editor", style: "display: none;", html: [ "This editor uses minecraft formatting codes (",						$("<a>", { href: "https://minecraft.gamepedia.com/Formatting codes", text: "more info", title: "https://minecraft.gamepedia.com/Formatting codes", }),						") and the template ", $(" ", {							html: [								"", 							],						}), "for extra formatting. See the template\'s documentation for more info.", " ",						$(" ", {							html: [								$(" ", { text: "Toolbox (Click to Insert)", css: {"font-weight": "bold", "font-size": "16px"}}),								$(" ", { html: [ "Formatting", $(" ", { id: "TooltipsEditor-insertFormat", html: this.processColors, "class": "noselect" }), " ",										"Special Character", $(" ", { id: "TooltipsEditor-insertChar", html: this.specialchars, "class": "noselect" }), " ",										"Rarity Text/Color", $(" ", { id: "TooltipsEditor-insertFormat", html: this.processRarityTexts, "class": "noselect" }), ],								}),							],						})," ",						$(" ", { text: "Tooltip ID: ", css: { "font-weight": "bold", "width": "8.7em", display: "inline-block" } }), $(" ", { css: { width: "400px", position: "relative" }, id: "TooltipsEditor-key" }), " ",						$(" ", { text: "Tooltip Link: ", css: { "font-weight": "bold", "width": "8.7em", display: "inline-block" } }), $(" ", { css: { width: "400px", position: "relative" }, id: "TooltipsEditor-link" }), " "," ",						$(" ", { text: "Tooltip Title: ", css: { "font-weight": "bold" } }), $(" ", {							id: "TooltipsEditor-title-AceEditor",							css: {								height: "20px",								"min-width": "600px",								"max-width": "1000px",								"font-size": "14px",								"line-height": "18px",								"border": "1px solid #474747",							}						}), $(" ", { text: "Tooltip Text: ", css: { "font-weight": "bold" } }), $(" ", {							id: "TooltipsEditor-text-AceEditor",							css: {								"font-size": "14px",								"line-height": "18px",								"min-height": "200px",								"max-height": "400px",								"min-width": "600px",								"max-width": "1000px",								"border": "1px solid #474747",							},						}), ],				})			);

$(".TooltipsEditor-insertFormat:not('.TooltipsEditor-last')").after($(" ", { text: " • ", }));			this.editor.removeClass("mw-ajax-loader"); this.updateActions;

$("#TooltipsEditor-key, #TooltipsEditor-link").focus(function {				that.lastFocusedElement = $(this);				that.lastFocusedEditor = null;			});

$(".TooltipsEditor-insertFormat").click(function {				var $this = $(this);				var insert = $this.attr("data-insert");

if (that.lastFocusedEditor) { var editor = that.lastFocusedEditor;

var selection = editor.selection; if (!selection.ranges.length) { editor.insert(insert); } else { selection.ranges.forEach(function(range) {							editor.session.insert({ row: range.start.row, column: range.start.column, }, insert);						}); }					that.lastFocusedEditor.focus; }				else that.lastFocusedElement.focus; });

window.ace.tooltipsTextEditor = this.setupEditor("TooltipsEditor-text-AceEditor"); window.ace.tooltipsTitleEditor = this.setupEditor("TooltipsEditor-title-AceEditor");

$(document.body).on("click", ".TooltipsEditor-insertChar", function(e) {				e.preventDefault;				e.stopImmediatePropagation;

if (that.lastFocusedEditor) { that.insertText($(this).attr("data-insert") || $(this).text); that.lastFocusedEditor.focus; }				else that.lastFocusedElement.focus; });

$(document.body).on("click", ".TooltipsEditor-removeTooltip", function {				var $this = $(this);				var key = $this.attr("data-key");

that.throwOldjson(key); delete that.json[key]; that.updateActions(key); $("#TooltipsEditor-searchInput").keyup; });

$(document.body).on("click", ".TooltipsEditor-editTooltip, .actions-edit-button", function {				var $this = $(this);

that.openEditor(					$this.attr("data-tooltipText"), 					$this.attr("data-tooltipTitle"), 					$this.attr("data-tooltipLink"),					$this.attr("data-tooltipKey")				); });		},

// helper functions for this.onClick luaTableToJson: function(s) { return api.post({				action: "scribunto-console",				title: mw.config.get("wgPageName"),				question: "=mw.text.jsonEncode(p)",				content: s,			}); },		editorCloseHandler: function { this.closing = true;

if (confirm("Are you sure you want to save and close the editor?")) this.modal.hide; else return;

var ret = [];

Object.keys(this.data).sort.forEach(function(k) {				var v = this.data[k];

ret.push([					"\t['" + k.replace(/(['"])/g, "\\$1") + "'] = {",					v.name ? "name = '" + v.name.replace(/\\/g, '\\\\').replace(/(['"])/g, "\\$1") + "', " : "",					v.title ? "title = '" + v.title.replace(/\\/g, '\\\\').replace(/(['"])/g, "\\$1") + "', " : "",					v.text ? "text = '" + v.text.replace(/\\/g, '\\\\').replace(/(['"])/g, "\\$1") + "', " : "",					"},",				].join("")); }.bind(this));

ret = "return {\n" + ret.join("\n") + "\n}\n".replaceAll(/&amp;/g, "&");

api.postWithEditToken({				action: "edit",				text: ret,				title: mw.config.get("wgPageName"),				summary: "Updating tooltips (TooltipsEditor)",				minor: true,			}).always(console.log);

mw.notify("", {title: "Tooltips Saved!", type:"success"}); },

// main function this.onClick onClick: function(tooltips) { this.closing = false;

$(window).on("beforeunload", function {				if (!this.closing)					return "Are you sure you want to exit the page and discard your changes?";			}.bind(this));

this.modal.show({				title: "Tooltips Editor",				onHide: function {					if (!this.closing && confirm("Are you sure you want to exit the editor and discard your changes?")) {						this.closing = true;						return true;					}					else if (this.closing) return true;					else return false;				}.bind(this),				buttons: [{					text: "Save and Close",					handler: this.editorCloseHandler.bind(this),				}],			}); $("#TooltipsEditor > section").attr("class", "mw-ajax-loader");

var promises = []; var split = tooltips.split("\n");

// Split lua table into multiple tables due to size limitation of 500k bytes if (split.length > 1350) { var lines = split; lines.pop; lines.shift;

for (var i = 0; i < lines.length; i += 900) { var ret = "return {\n" + lines.slice(i, i + 900).join("\n") + "\n}"; promises.push(this.luaTableToJson(ret)); }			} else { promises = [this.luaTableToJson(tooltips)]; }

$.when.apply($, promises).then(this.MainProcess.bind(this), function(code, e) {				return alert("Failed to parse Tooltips: ", e), console.warn("Failed to parse Tooltips: ", e);			}).catch(console.warn); },

// entry point init: function { $(' ', {				rel: "stylesheet", 				href: "https://hypixel-skyblock.fandom.com/wiki/MediaWiki:Gadget-TooltipsEditor.css?action=raw&ctype=text/css" 			}).appendTo('head');

$(".editTooltips").click(function {				api.get({ action: "query", format: "json", prop: "revisions", titles: mw.config.get("wgPageName"), formatversion: 2, rvprop: "content", rvslots: "*" }).then(function(d) { var content = d.query.pages[0].revisions[0].slots.main.content;

this.actions = []; this.onClick(content); }.bind(this));			}.bind(this)); },	});

that = TooltipsEditor; // using 'TooltipsEditor' and defining 'that' here so the editor won't complain about it	this.init; }.bind((window.TooltipsEditor = window.TooltipsEditor || Object.create(null)))).catch(console.warn);