User:Erutuon/scripts/LanguageSearcher.js

From Wiktionary, the free dictionary
Jump to navigation Jump to search

Note: You may have to bypass your browser’s cache to see the changes. In addition, after saving a sitewide CSS file such as MediaWiki:Common.css, it will take 5-10 minutes before the changes take effect, even if you clear your cache.

  • Mozilla / Firefox / Safari: hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (Command-R on a Macintosh);
  • Konqueror and Chrome: click Reload or press F5;
  • Opera: clear the cache in Tools → Preferences;
  • Internet Explorer: hold Ctrl while clicking Refresh, or press Ctrl-F5.

// have to load apiWrapper before loading this!
// $.getScript("//en.wiktionary.org/w/index.php?title=User:Erutuon/scripts/apiWrapper.js&action=raw")

/* jshint boss: true, esversion: 6, undef: true */
/* globals apiWrapper, mw */

// <nowiki>

(function () {
"use strict";

function makeWikiLink(pageName, text) {
	const link = document.createElement("a");
	link.href = mw.util.getUrl(pageName);
	link.textContent = text || pageName;
	return link;
}

function LanguageSearchResult(name, code, index, words) {
	this.name = name;
	this.code = code;
	this.index = index;
	this.words = words;
}

LanguageSearchResult.prototype = {
	get categoryLink() {
		// Based on getCategoryName in [[Module:languages]].
		const categoryName = this.name.includes("Language")
			? this.name : this.name + " language";
		return makeWikiLink("Category:" + categoryName, this.name);
	},
	get codeElement() {
		const codeElement = document.createElement("code");
		codeElement.textContent = this.code;
		return codeElement;
	},
};

LanguageSearchResult.prototype.print = function () {
	const listItem = document.createElement("li");

	for (const element of [
		this.categoryLink,
		document.createTextNode(" ("),
		this.codeElement,
		document.createTextNode(")")
	])
		listItem.appendChild(element);
		
	return listItem;
};

function LanguageSearcher(storageKey, handleResults, resultSorter) {
	this.storageKey = storageKey;
	this.handleResults = handleResults;
	this.resultSorter = resultSorter;
}

LanguageSearcher.prototype = {
	get storage () {
		return mw.storage.get(this.storageKey);
	},
	set storage (JSON) {
		mw.storage.set(this.storageKey, JSON);
	},
};

LanguageSearcher.prototype.getResults = function (query) {
	const results = [];
	
	query = query.toLowerCase();
	for (const [ name, code ] of Object.entries(window[this.storageKey])) {
		const lowerName = name.toLowerCase();
		let index = lowerName.indexOf(query);
		if (index === -1)
			index = code.indexOf(query);
		if (index !== -1) {
			const words = lowerName.split(/[- ()]+/).concat(code.split("-"));
			results.push(new LanguageSearchResult(name, code, index, words));
		}
	}
	return results;
};

LanguageSearcher.defaultResultSorter = query => (result1, result2) => {
	// put results in the following groups:
	// 1. exact code matches
	if (result1.code === query || result2.code === query)
		return Number(result2.code === query);
	
	// 2. exact matches when "-pro" is added
	if (result1.code === query + "-pro" || result2.code === query + "-pro")
		return Number(result2.code === query + "-pro");
	
	// 3. exact name matches
	if (result1.name === query || result2.name === query)
		return Number(result2.name === query);
	
	// 4. prefix matches
	if ((result1.index === 0) !== (result2.index === 0))
		return result1.index - result2.index;
	
	// 5. whole word matches
	if (result1.words.includes(query) !== result2.words.includes(query))
		return Number(result2.words.includes(query));
	
	// within each group, alphabetize
	return result1.name > result2.name ? 1
		: result1.name < result2.name ? -1
		: 0;
};

LanguageSearcher.prototype.handleQuery = function (query) {
	if (typeof query !== "string")
		throw new TypeError("Expected string");
	
	const results = query !== ""
		? this.getResults(query)
		: [];
	
	if (query !== "")
		results.sort(this.resultSorter
			? this.resultSorter(query)
			: LanguageSearcher.defaultResultSorter(query));
	
	this.handleResults(results);
};

// return true if data was successfully converted from JSON
LanguageSearcher.prototype.loadData = function (nameToCodeJSON) {
	if (nameToCodeJSON) {
		if (typeof nameToCodeJSON !== "string")
			throw new TypeError("Expected string");
		
		try {
			const data = JSON.parse(nameToCodeJSON);
			
			if (typeof data !== "object")
				throw new TypeError("JSON should decode to an object");
			
			window[this.storageKey] = data;
			return true;
		} catch (error) {
			console.log(error);
		}
	}
	
	return false;
};

// name-to-code map process:
// get JSON from Lua => save JSON in localStorage => save object as global variable
LanguageSearcher.prototype.doSearch = function (query) {
	if (query === "" || window[this.storageKey] || this.loadData(this.storage)) {
		this.handleQuery(query);
		return;
	}
	
	apiWrapper.expandTemplates(
		"{{#invoke:languages/javascript-interface|AllCanonicalToCode}}",
		JSON => {
			this.storage = JSON;
			this.loadData(JSON);
			this.handleQuery(query);
		});
};

window.LanguageSearcher = LanguageSearcher;

})();

// </nowiki>