MediaWiki:Gadget-WikitextAutocompleter.js: Difference between revisions

From Zelda Wiki, the Zelda encyclopedia
Jump to navigation Jump to search
m (Ensure it only runs on an Edit page)
(Pushed/Mainspaced sandbox changes - More general, more functionality, though under simplified assumptions, not fully tested: https://zelda.gamepedia.com/index.php?title=User%3AKokoroSenshi%2Fcommon.js&type=revision&diff=630041&oldid=629917)
Line 1: Line 1:
/**  
/**
  * Autocomplete/Dropdown list  
  * Autocomplete/Dropdown list
  * Uses https://yuku-t.com/textcomplete/
  * Uses https://yuku-t.com/textcomplete/
  *  
  *
  * Notes:
  * Notes:
  * - Won't work with CodeEditor since it doesn't use a textarea but that's fine  
  * - Won't work with CodeEditor since it doesn't use a textarea but that's fine
  *  since CodeEditor won't be used for wikitext
  *  since CodeEditor won't be used for wikitext
  * - $.getScript() is used since mw.loader.load() doesn't wait for the code to load
  * - $.getScript() is used since mw.loader.load() doesn't wait for the code to load
Line 10: Line 10:
  * Bugs:
  * Bugs:
  * - The regex fails if there is an instance of {{Color| before it in the textarea? tho it's fine in the example webpage
  * - The regex fails if there is an instance of {{Color| before it in the textarea? tho it's fine in the example webpage
  * - In addition, whenyou start a "{{Color|", then pop over to a different "{{Color|", it'll show dropdown, but append to the first "{{Color|""
  * - In addition, when you start a "{{Color|", then pop over to a different "{{Color|", it'll show dropdown, but append to the first "{{Color|""
  *  
  *
  */
  */
if (mw.config.get("wgAction") == 'edit') {
if (mw.config.get("wgAction") == 'edit') {
/* Make the strategy/ies to add */
var Strategies = {};
var Strategies = {};
Strategies.Template = {};
Strategies.Templates = [];
Strategies.Template.Color = new TemplateStrategy("Color");
Strategies.Templates.push(new TemplateStrategy(
"Color",
function() {
var self = this;
$.get( "https://zelda.gamepedia.com/Template:Color?action=raw", function( data ) {
/** Store parameter options in instance variable */
self.params["1"] = readColorArrayFromString(data);
/** Callback */
self.registerStrategies();
});
var readColorArrayFromString = function(text) {
var colorArray = [];
text.split("</includeonly>")[0]
.split("#switch:{{{1\|}}}")[1]
.split("\|#default")[0]
.match(/\|[a-zA-Z0-9 ]*/g)
.forEach(function(value, index){
var colorName = value.slice(1);
colorArray.push(colorName);
});
return colorArray;
};
}
));
Strategies.Templates.push(new TemplateStrategy(
"KSTest",
function() { // Need to have self/this for params and registerStrategies()
var self = this;
/** Store parameter options in instance variable */
self.params["1"] = ["testparam1value1","testparam1value2","testparam1value3"];
self.params["2"] = ["testparam2value1","testparam2value2","testparam2value3"];
self.params["3"] = ["testparam3value1","testparam3value2","testparam3value3"];
 
/** Callback */
self.registerStrategies();
}
));
/* Load Textcomplete then register the strategy/ies */
$(document).ready(function() {
$(document).ready(function() {
console.log( "Loading textcomplete..." );
getTextcompleteScript(
$.getScript( "https://unpkg.com/textcomplete/dist/textcomplete.min.js", function( data, textStatus, jqxhr ) {
Strategies.Templates // Array of TemplateStrategy objects to register each of
console.log( data ); // Data returned
);
console.log( textStatus ); // Success
console.log( jqxhr.status ); // 200
console.log( "Loaded textcomplete. (Warning: May not be executed yet)" );
// (Global) Textarea object: https://github.com/yuku-t/textcomplete/issues/114#issuecomment-318352383
Textarea = Textcomplete.editors.Textarea;
Strategies.Template.Color.register();
});
} );
} );
}
}
function getTextcompleteScript(templateStrategyArray) {
console.log( "Loading textcomplete..." );
$.getScript( "https://unpkg.com/textcomplete/dist/textcomplete.min.js", function( data, textStatus, jqxhr ) {
console.log( [ data, textStatus, jqxhr.status ] ); // Data returned, Success, 200
console.log( "Loaded textcomplete. (Warning: May not be executed yet)" );
// (Global) Textarea object: https://github.com/yuku-t/textcomplete/issues/114#issuecomment-318352383
Textarea = Textcomplete.editors.Textarea;
templateStrategyArray.forEach(function(templateStrategy){ templateStrategy.register(); });
});
}
// Been going over OOP in JavaScript: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain#Example
// Been going over OOP in JavaScript: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain#Example


Line 40: Line 90:


/**
/**
  * Constructor? (and non-prototype definitions)
  * Constructor (and non-prototype definitions)
  * (instance variables can't be accessed without "this." etc.)
  * (instance variables can't be accessed without "this." etc.)
  */
  */
function TemplateStrategy(templateName) {
function TemplateStrategy(templateName, getParametersFunction) {
var params = {};
var self = this;
/*********************
** Public variables **
**********************/
this.templateName = templateName;
this.params = {}; // params.<name_of_param> = array of options // should each param have a 'description' variable too? applicable to both preset options and open-ended
this.strategies = [];
/********************
** Public methods **
********************/
/**
* register method
*/
this.register = function() {
this.register = function() {
ajaxGetParams(processParams);
self.getParameters(); // Must have callback to registerStrategies()
};
};
var ajaxGetParams = function (callback) {
this.getParameters = getParametersFunction;
$.get( "https://zelda.gamepedia.com/Template:Color?action=raw", function( data ) {
params.color = getColorArray(data);
callback();
});
};
var getColorArray = function (text) {
this.registerStrategies = function() {
var colorArray = [];
var params = self.params;
text.split("</includeonly>")[0]
var strategies = self.strategies;
.split("#switch:{{{1\|}}}")[1]
.split("\|#default")[0]
/** Validate parameters */
.match(/\|[a-zA-Z0-9 ]*/g)
.forEach(function(value, index){
var colorName = value.slice(1);
colorArray.push(colorName);
});
return colorArray;
};
var processParams = function () {
console.log("params: ");
console.log("params: ");
console.log(params);
console.log(params);
if (params.length === 0) throw (this + ": params is empty");
var colorStrategy = {
/**
id: "Template:" + templateName,
* Turn each parameter into a strategy; assume no named parameters for now
match: new RegExp("(){{" + templateName + "\\|([a-zA-Z 0-9+\\-\\_\\|}]*)$","m"), //TODO: Need to keep the first capture group for it to work? Is it because term in search is the 2nd capture group?
*/
search: function (term, callback) {
for (var paramName in params) {
var nameArray = params.color.filter(function (currentValue) { return currentValue.startsWith(term); });
if (params.hasOwnProperty(paramName)) {
callback(nameArray); // List of possible completions ('names')
console.log(paramName + " -> " + params[paramName]);
},
// Assume paramName is a number (as a String)
template: function (name) {
var precedingParamRegex = "";
var displayName = name;
var paramNum = parseInt(paramName);
return displayName; // What to display in the list
for (var i = 1; i < paramNum; i++) {
},
precedingParamRegex += "\\|[^\\|]*";
replace: function (name) {
}
var replacementString = "$1{{" + templateName + "\|" + name + '}}'; // The replace string before 'name' is the same as the RegExp string
return replacementString; // What to replace the matched typed text with
var strategy = self.createStrategy(
templateName,
paramName,
new RegExp("({{" + templateName + precedingParamRegex + "\\|)([^\\|]*)$","m"),
params[paramName],
function(name) { return name; },
function(name) { return "$1" + name; } // Removed the '}}' for generality for now; maybe will make exceptions for single or no parameter templates?
);
strategies.push(strategy);
paramName = "1"; //paramName is taken as the last value set, for all the strats^????
}
}
};
}
console.log("colorStrategy: ");
console.log(colorStrategy);
var editor = new Textarea(document.getElementById('wpTextbox1'))
var editor = new Textarea(document.getElementById('wpTextbox1'))
Line 102: Line 160:
}
}
  , textcomplete = new Textcomplete(editor, options);
  , textcomplete = new Textcomplete(editor, options);
 
textcomplete.register([colorStrategy]);
textcomplete.register(strategies);
};
};
this.createStrategy = function(name, param, match, paramArray, template, replace) {
var strategy =
{
id: "Template:" + name + "_" + param,
match: match,
search: function (term, callback) { // term is probably the 2nd capture group of the match regexp
var nameArray = paramArray.filter(function(currentValue) { return currentValue.startsWith(term); });
callback(nameArray); // List of possible completions ('names')
},
template: function (name) {
var displayName = template(name);
return displayName; // What to display in the list
},
replace: function (name) {
var replacementString = replace(name);
return replacementString; // What to replace the matched typed text with
}
};
return strategy;
};
}
}

Revision as of 14:33, 4 May 2018

/**
 * Autocomplete/Dropdown list
 * Uses https://yuku-t.com/textcomplete/
 *
 * Notes:
 * - Won't work with CodeEditor since it doesn't use a textarea but that's fine
 *   since CodeEditor won't be used for wikitext
 * - $.getScript() is used since mw.loader.load() doesn't wait for the code to load
 *   mw.loader.load('https://unpkg.com/textcomplete@0.13.1/dist/textcomplete.min.js');
 * Bugs:
 * - The regex fails if there is an instance of {{Color| before it in the textarea? tho it's fine in the example webpage
 * - In addition, when you start a "{{Color|", then pop over to a different "{{Color|", it'll show dropdown, but append to the first "{{Color|""
 *
 */
if (mw.config.get("wgAction") == 'edit') {
	
	/* Make the strategy/ies to add */
	var Strategies = {};
	Strategies.Templates = [];
	Strategies.Templates.push(new TemplateStrategy(
		"Color",
		function() {
			var self = this;
			
			$.get( "https://zelda.gamepedia.com/Template:Color?action=raw", function( data ) {
				
				/** Store parameter options in instance variable */
				self.params["1"] = readColorArrayFromString(data);
				
				/** Callback */
				self.registerStrategies();
			});
			
			var readColorArrayFromString = function(text) {
				var colorArray = [];
				text.split("</includeonly>")[0]
					.split("#switch:{{{1\|}}}")[1]
					.split("\|#default")[0]
					.match(/\|[a-zA-Z0-9 ]*/g)
					.forEach(function(value, index){
					var colorName = value.slice(1);
					colorArray.push(colorName);
				});
				return colorArray;
			};
			
		}
	));
	Strategies.Templates.push(new TemplateStrategy(
		"KSTest",
		function() { // Need to have self/this for params and registerStrategies()
			var self = this;
			
			/** Store parameter options in instance variable */
			self.params["1"] = ["testparam1value1","testparam1value2","testparam1value3"];
			self.params["2"] = ["testparam2value1","testparam2value2","testparam2value3"];
			self.params["3"] = ["testparam3value1","testparam3value2","testparam3value3"];

			/** Callback */
			self.registerStrategies();
			
		}
	));
	
	/* Load Textcomplete then register the strategy/ies */
	$(document).ready(function() {
		getTextcompleteScript(
			Strategies.Templates // Array of TemplateStrategy objects to register each of
		);
	} );
	
}

function getTextcompleteScript(templateStrategyArray) {
	console.log( "Loading textcomplete..." );
	$.getScript( "https://unpkg.com/textcomplete/dist/textcomplete.min.js", function( data, textStatus, jqxhr ) {
		console.log( [ data, textStatus, jqxhr.status ] ); // Data returned, Success, 200
		console.log( "Loaded textcomplete. (Warning: May not be executed yet)" );
		// (Global) Textarea object: https://github.com/yuku-t/textcomplete/issues/114#issuecomment-318352383
		Textarea = Textcomplete.editors.Textarea;
		templateStrategyArray.forEach(function(templateStrategy){ templateStrategy.register(); });
	});
}

// Been going over OOP in JavaScript: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain#Example

/**
 * Strategy Object
 */

/**
 * Constructor (and non-prototype definitions)
 * (instance variables can't be accessed without "this." etc.)
 */
function TemplateStrategy(templateName, getParametersFunction) {
	
	var self = this;
	
	/*********************
	** Public variables **
	**********************/
	this.templateName = templateName;	
	this.params = {}; // params.<name_of_param> = array of options // should each param have a 'description' variable too? applicable to both preset options and open-ended
	this.strategies = [];
	
	/********************
	** Public methods **
	********************/
	
	/**
	 * register method
	 */
	this.register = function() {
		self.getParameters(); // Must have callback to registerStrategies()
	};
	
	this.getParameters = getParametersFunction;
	
	this.registerStrategies = function() {
		var params = self.params;
		var strategies = self.strategies;
		
		/** Validate parameters */
		console.log("params: ");
		console.log(params);
		
		/**
		 * Turn each parameter into a strategy; assume no named parameters for now
		 */
		for (var paramName in params) {
			if (params.hasOwnProperty(paramName)) {
				console.log(paramName + " -> " + params[paramName]);
				// Assume paramName is a number (as a String)
				var precedingParamRegex = "";
				var paramNum = parseInt(paramName);
				for (var i = 1; i < paramNum; i++) {
					precedingParamRegex += "\\|[^\\|]*";
				}
				
				var strategy = self.createStrategy(
					templateName,
					paramName,
					new RegExp("({{" + templateName + precedingParamRegex + "\\|)([^\\|]*)$","m"),
					params[paramName],
					function(name) { return name; },
					function(name) { return "$1" + name; } // Removed the '}}' for generality for now; maybe will make exceptions for single or no parameter templates?
				);
				strategies.push(strategy);
				paramName = "1"; //paramName is taken as the last value set, for all the strats^????
			}
		}
		
		
		var editor = new Textarea(document.getElementById('wpTextbox1'))
		  , options = {
				dropdown: {
					maxCount: 500,
					style: { 'margin-top': (-parseFloat($('body').css('margin-top')))+'px' }
				}
			}
		  , textcomplete = new Textcomplete(editor, options);
		
		textcomplete.register(strategies);
		
	};
	
	this.createStrategy = function(name, param, match, paramArray, template, replace) {
		var strategy =
			{
				id: "Template:" + name + "_" + param,
				match: match,
				search: function (term, callback) { // term is probably the 2nd capture group of the match regexp
					var nameArray = paramArray.filter(function(currentValue) { return currentValue.startsWith(term); });
					callback(nameArray); // List of possible completions ('names')
				},
				template: function (name) {
					var displayName = template(name);
					return displayName; // What to display in the list
				},
				replace: function (name) {
					var replacementString = replace(name);
					return replacementString; // What to replace the matched typed text with
				}
			};
		return strategy;
	};
	
}