MediaWiki:Gadget-start.js

Từ điển mở Wiktionary

Chú ý: Sau khi lưu trang này, phải xóa bộ nhớ đệm (cache) của trình duyệt để những thay đổi hiện ra

  • Firefox / Safari: Nhấn giữ phím Shift trong khi nhấn Tải lại (Reload), hoặc nhấn tổ hợp Ctrl-F5 hay Ctrl-R (⌘R trên Mac)
  • Google Chrome: Nhấn tổ hợp Ctrl-Shift-R (⇧⌘R trên Mac)
  • Internet Explorer / Edge: Nhấn giữ phím Ctrl trong khi nhấn Làm tươi (Refresh), hoặc nhấn tổ hợp Ctrl-F5
  • Opera: Nhấn tổ hợp Ctrl-F5.
$(function () {
	var ns = mw.config.get("wgNamespaceNumber");
	if (ns &&
		!(ns === 2 && mw.config.get("wgPageName").match(/\/sandbox$|\/thử$/))) {
		return;
	}
	var action = mw.config.get("wgAction");
	if (!(action === "edit" || action === "submit") ||
		!$(".mw-newarticletext, .mw-newarticletextanon").length) {
		return;
	}
	
	String.prototype.wiktviCapitalize = function () {
		return this[0].toUpperCase() + this.substr(1);
	};
	
	var pageName = mw.config.get("wgPageName").replace(/_/g, " ");
	if (ns) pageName = pageName.replace(/.*\//, "");
	var userLang = mw.config.get("wgUserLanguage");
	var chosenOptions = {
		width: "20em",
		disable_search_threshold: 20,
		placeholder_text_single: "Chọn một",
		placeholder_text_multiple: "Chọn nhiều",
		no_results_text: "Không tìm thấy",
		enable_split_word_search: false,
		search_contains: true };
	var langsByCode = {};
	var steps = {
		lang: {
			id: "lang",
			required: true,
			name: "ngôn ngữ",
			question_fmt: "“{{PAGENAME}}” là tiếng gì?",
			choices: [
				// Languages with 100 or more entries are loaded immediately.
				// The rest come in through ext.gadget.headers.
				["vie", "tiếng Việt"],
				["eng", "tiếng Anh"],
				["nld", "tiếng Hà Lan"],
				["ido", "tiếng Ido"],
				["lit", "tiếng Litva"],
				["nor", "tiếng Na Uy"],
				["rus", "tiếng Nga"],
				["fra", "tiếng Pháp"],
				["spa", "tiếng Tây Ban Nha"],
				["zho", "tiếng Trung Quốc"] ],
			output: function (values) {
				return "{{-" + values.lang + "-}}\n";
			} },
		pron: {
			id: "pron",
			name: "cách phát âm",
			question_fmt: "“{{PAGENAME}}” đọc như thế nào?",
			visible: function (values) {
				return false;
			},
			output: function (values) {
				if (values.lang === "vie") {
					return "{{-pron-}}\n{{vie-pron}}\n\n";
				}
				return "";
			} },
		pos: {
			id: "pos",
			required: true,
			name: "từ loại",
			question_fmt: "“{{PAGENAME}}” là loại từ nào?",
			visible: function (values) {
				return values.lang;
			},
			choices: [
				["noun", "danh từ"],
				["verb", "động từ"],
				["adj", "tính từ"],
				["adv", "phó từ"],
				["prep", "giới từ"],
				["pronoun", "đại từ"],
				["interj", "thán từ"],
				["conj", "liên từ"],
				["prefix", "tiền tố"],
				["suffix", "hậu tố"],
				["place", "địa danh"],
				["phrase", "thành ngữ"],
				["proverb", "tục ngữ"] ],
			output: function (values) {
				return "{{-" + values.pos + "-}}\n";
			} },
		dfn: {
			id: "dfn",
			required: true,
			name: "ngữ nghĩa",
			question_fmt: "Nhập ngữ nghĩa của từ bằng văn bản thuần túy",
			multiple: true,
			ordered: true,
			visible: function (values) {
				return values.lang && values.pos;
			},
			example: function (values) {
				var examples = {
					noun: "Bản vẽ hình thể của một khu vực.",
					verb: "Đùa vui một cách tinh nghịch.",
					adj: "Rất hoạt bát, nhẹ nhàng và mau chóng.",
					adv: "Luôn luôn đều đặn, không gián đoạn.",
					prep: "Thuộc vị trí, địa điểm nào.",
					pronoun: "Ngôi thứ hai khi mình nói với anh ruột hay anh họ.",
					interj: "Tiếng thốt lên để than thở hoặc ngạc nhiên.",
					place: "Thủ đô Vương quốc Anh.",
					proverb: "Thế hệ con phải chịu hậu quả của các hành động của thế hệ cha." };
				var transExamples = {
					noun: "Cá thật mập.",
					verb: "Lè lưỡi.",
					adj: "To lớn.",
					adv: "Thường xuyên.",
					prep: "Ở, tại.",
					pronoun: "Bà ấy, cô ấy, chị ấy.",
					interj: "Ôi trời ơi!",
					prefix: "Trước về thời gian.",
					suffix: "Chỉ đến môn học.",
					place: "Luân Đôn.",
					proverb: "Đời cha ăn mặn đời con khát nước." };
				
				var pos = values.pos;
				return values.lang === "vie" ? examples[pos] : transExamples[pos];
			},
			getOutput: function (values, callback) {
				var text = $.map(values.dfn, function (dfn, i) {
					dfn = $.trim(dfn);
					if (!dfn) return undefined;
					// Capitalize the first word.
					if (dfn[0] !== dfn[0].toUpperCase()) {
						dfn = dfn.wiktviCapitalize();
					}
					// Punctuate the definition.
					if (!dfn.match(/[.…?!)\]]$/)) dfn += ".";
					return "# " + dfn;
				}).join("\n") + "\n\n";
				var didTryToLoad = function () {
					$.wiktviLinkify(text, callback);
				};
				mw.loader.using("ext.gadget.linkify", didTryToLoad, didTryToLoad);
			} },
		syn: {
			id: "syn",
			name: "từ đồng nghĩa",
			multiple: true,
			narrow: true,
			visible: function (values) {
				return values.lang && values.pos;
			},
			output: function (values) {
				if (!values.syn.join("").length) return "";
				return "{{-syn-}}\n" + $.map(values.syn, function (syn, i) {
					return syn ? "* [[" + syn + "]]" : undefined;
				}).join("\n") + "\n\n";
			} },
		trans: {
			id: "trans",
			name: "bản dịch",
			multiple: true,
			narrow: true,
			keyName: "ngôn ngữ",
			visible: function (values) {
				return values.lang === "vie" && values.pos;
			},
			disabledKeys: {
				"mul": true,
				"vie": true },
			output: function (values) {
				var rows = [];
				$.each(values.trans, function (lang, words) {
					if (words) rows.push([lang, words]);
				});
				if (!rows.length) return "";
				rows.sort(function (a, b) {
					var aLang = langsByCode[a[0]] || [];
					var bLang = langsByCode[b[0]] || [];
					return (aLang[2] || "").localeCompare(bLang[2] || "", "vi");
				});
				return "{{-trans-}}\n{{trans-top}}\n" + $.map(rows, function (row, i) {
					var words = row[1].replace(/(\s*[,;()]+\s*)/g, "]]$1[[");
					return "* {{" + row[0] + "}}: [[" + words + "]]";
				}).join("\n") + "\n{{trans-bottom}}\n\n";
			} },
		infl: {
			id: "infl",
			name: "biến cách",
			multiple: true,
			visible: function (values) {
				return (values.lang === "eng" && ["noun", "adj", "adv"].indexOf(values.pos) >= 0) ||
					(values.lang === "spa" && ["noun"].indexOf(values.pos) >= 0) || (values.lang === "fra" && ["noun"].indexOf(values.pos) >= 0);
			},
			build: function (stepDiv, values) {
				if (!values) return;
				var forms;
				// TODO: Spanish and other languages with inflection templates.
				var forms = {
					eng: {
						noun: [
							{name: "Số nhiều", "default": "Mặc định"}
						],
						adj: [
							{name: "So sánh hơn", "default": "Mặc định"},
							{name: "So sánh nhất", "default": "Mặc định"} ] },
					spa: {
						noun: [
							{
								name: "Giống",
								"default": pageName.match(/(?:as?|ción|ciones)$/) ? "f" : "m",
								choices: [
									["m", "đực"],
									["f", "cái"],
									["mf", "tùy"] ] } ] },
					fra: {
						noun: [
							{
								name: "Giống",
								"default": "m",
								choices: [
									["m", "đực"],
									["f", "cái"],
									["mf", "tùy"] ] } ] } };
				if (pageName.match(/(?:c|sh?|zh?)$/)) {
					forms.eng.noun[0].firstSuggestion = pageName + "es";
				}
				else if (pageName.match(/y$/)) {
					forms.eng.noun[0].firstSuggestion = pageName.substr(0, pageName.length - 1) + "ies";
				}
				forms.eng.adv = forms.eng.adj;
				forms = forms[values.lang];
				if (forms) forms = forms[values.pos];
				if (!forms) return;
				
				var firstInput;
				var stepId = this.id;
				$("#wiktvi-start-" + stepId + " .wiktvi-start-custom").detach();
				var list = $("<ul class='wiktvi-start-custom'></ul>");
				var fieldId = "wiktvi-start-" + stepId + "-field";
				$.each(forms, function (i, form) {
					var item = $("<li></li>");
					item.append($(mw.html.element("label", {
						"for": "wiktvi-start-" + stepId + "-field" }, form.name + ":")));
					var value = form.firstSuggestion || form["default"];
					if (values && this.oldLang === values.lang &&
						this.oldPos === values.pos && values.infl[i]) {
						value = values.infl[i];
					}
					var input;
					if (form.choices) {
						input = $(mw.html.element("select", {
							"class": "wiktvi-start-step-field" }));
						$.each(form.choices, function (j, choice) {
							input.append($(mw.html.element("option", {
									value: choice[0],
									selected: choice[0] === (form.firstSuggestion || form["default"]) },
								choice[1].wiktviCapitalize())));
						});
					}
					else {
						input = $(mw.html.element("input", {
							type: "text",
							"class": "wiktvi-start-step-field wiktvi-start-narrow",
							placeholder: form["default"],
							value: value }));
					}
					if (!i) {
						input.attr("id", fieldId);
						firstInput = input;
					}
					item.append(input);
					list.append(item);
				});
				stepDiv.append(list);
				
				list.find("select").chosen(chosenOptions);
				
				this["default"] = $.map(forms, function (form, i) {
					return form["default"];
				});
				this.oldLang = values.lang;
				this.oldPos = values.pos;
				
				return firstInput;
			},
			output: function (values) {
				var output = "{{pn}}";
				switch (values.lang) {
					case "eng": {
						// TODO: Fetch the English Wiktionary’s inflection template parameters.
						if (["noun", "adj", "adv"].indexOf(values.pos) >= 0) {
							output = "{{subst:Thế bản mẫu mục từ/eng-" + values.pos;
							var defaults = this["default"];
							var modified = false;
							$.each(values.infl, function (i, value) {
								if (value && value !== defaults[i]) {
									modified = true;
									return false;
								}
							});
							if (modified) {
								$.each(values.infl, function (i, value) {
									output += "|" + value;
								});
							}
							return output + "}}\n";
						}
						break;
					}
					case "spa": {
						if (values.infl[0] && ["noun"].indexOf(values.pos) >= 0) {
							output = "{{spa-noun|" + values.infl[0] + "}}"
						}
						break;
					}
					case "fra": {
						if (values.infl[0] && ["noun"].indexOf(values.pos) >= 0) {
							output = "{{fra-noun-2|" + values.infl[0] + "}}"
						}
						break;
					}
				}
				return output + "\n";
			} }	};
	steps.trans.keys = steps.lang.choices;
	var ordered_steps = [
		steps.lang, steps.pron, steps.pos, steps.dfn, steps.syn,
		steps.trans, steps.infl ];
	
	var continueBtn;
	
	function getValuesForStep(stepId) {
		var fields = $("#wiktvi-start-" + stepId + " .wiktvi-start-step-field");
		var step = steps[stepId];
		if (step.keys) {
			var values = {};
			fields.each(function (i, field) {
				var key = $(field).prevAll(".wiktvi-start-step-keyfield").val();
				if (key) values[key] = $(field).val();
			});
			return values;
		}
		else if (step.multiple) {
			return fields.map(function (i) {
				return $(this).val();
			}).get();
		}
		return fields.val();
	};
	
	function getValues() {
		var values = {};
		$.each(steps, function (stepId) {
			values[stepId] = getValuesForStep(stepId);
		});
		return values;
	}
	
	function getWikitext(callback) {
		var values = getValues();
		
		var entry = steps.lang.output(values);
		entry += steps.pron.output(values);
		entry += steps.pos.output(values);
		entry += steps.infl.output(values);
		steps.dfn.getOutput(values, function (output) {
			entry += output;
			
			entry += steps.syn.output(values);
			entry += steps.trans.output(values);
			
			// Tag the entry as having come from this tool.
			entry += "{{mẫu}}\n"
			
			// Add categories.
			var langChoice = $.grep(steps.lang.choices, function (choice, i) {
				return choice[0] === values.lang;
			})[0];
			var posChoice = $.grep(steps.pos.choices, function (choice, i) {
				return choice[0] === values.pos;
			})[0];
			if (langChoice && posChoice) {
				entry += "{{catname|" + posChoice[1].wiktviCapitalize() + "|" + langChoice[1] + "}}\n";
			}
			
			callback(entry);
		});
	}
	
	function populateMenus(inputs, choices, disabledValues) {
		if (!disabledValues) disabledValues = {};
		var vals = inputs.map(function (i, input) {
			return $(input).val();
		});
		inputs.empty();
		inputs.append($("<option></option>"));
		$.each(choices, function (j, choice) {
			var option = $(mw.html.element("option", {
				value: choice[0] }, choice[1].wiktviCapitalize()));
			if (disabledValues[choice[0]]) option.prop("disabled", true);
			inputs.append(option);
		});
		inputs.val(function (i) {
			return vals[i];
		});
	}
	
	/**
	 * Returns a bitmap containing the keys that should be disabled for a key
	 * menu in the given step because they are disabled or already taken by
	 * another key menu.
	 */
	function getDisabledKeys(step) {
		var occupiedKeys = {};
		$("#wiktvi-start-" + step.id + " .wiktvi-start-step-keyfield").each(function (i, select) {
			occupiedKeys[$(this).val()] = true;
		});
		if (step.disabledKeys) $.extend(occupiedKeys, step.disabledKeys);
		return occupiedKeys;
	}
	
	function buildKeyInput(step) {
		if (!step.keys) return undefined;
		
		var input = $(mw.html.element("select", {
			"class": "wiktvi-start-step-keyfield wiktvi-start-narrow" }));
		input.attr("data-placeholder", "Chọn một " + step.keyName);
		input.on("chosen:showing_dropdown", function (evt, context) {
			$(this).data("wiktvi-defaultValue", $(this).val());
		});
		input.change(function (evt, context) {
			var inputs = $("#wiktvi-start-" + step.id + " .wiktvi-start-step-keyfield").not(this);
			var oldValue = $(this).data("wiktvi-defaultValue");
			var newValue = context.selected;
			inputs.find("option[value='" + oldValue + "']").prop('disabled', false);
			inputs.find("option[value='" + newValue + "']").prop('disabled', true);
			inputs.trigger("chosen:updated");
		});
		populateMenus(input, step.keys, getDisabledKeys(step));
		
		return input;
	}
	
	/**
	 * If the current text field has text and there are no more text fields after
	 * the current text field in the list, add another text field to the list.
	 */
	function refreshForTextField(currentInput, step) {
		currentInput = $(currentInput);
		var value = currentInput.val();
		var item = currentInput.parent();
		if (value && !item.next("li").length) {
			var nextInput = $(mw.html.element("input", {
				type: "text",
				"class": currentInput.attr("class") }));
			nextInput.on("input", function (evt) {
				refreshForTextField(evt.target, step);
			});
			
			var nextKeyInput = buildKeyInput(step);
			var nextItem = $("<li></li>").append(nextKeyInput).append(nextInput);
			item.parent().append(nextItem);
			
			if (nextKeyInput) nextKeyInput.chosen(chosenOptions);
		}
		
		var canContinue = !continueBtn.prop("disabled");
		if ((value && !canContinue) || (!value && canContinue)) {
			refresh(step);
		}
	}
	
	function prepareInput(input, step) {
		if (!input) return;
		if (step.required) input.prop('required', true);
		input.change(step, refresh);
	}
	
	/**
	 * Shows and hides all the steps based on whether their prerequisites are met,
	 * and updates placeholders where applicable.
	 */
	function refresh(evt) {
		var values = getValues();
		var changedStep = evt && evt.data;
		var didFocus = false;
		var canContinue = true;
		
		$.each(steps, function (stepId, step) {
			if (typeof(step.visible) === "function" && !step.visible(values)) {
				$("#wiktvi-start-" + stepId).slideUp();
//				$("#wiktvi-start-" + stepId + " .wiktvi-start-step-field").val("");
			}
			else {
				var stepDiv = $("#wiktvi-start-" + stepId);
				
				if (changedStep !== step && typeof(step.build) === "function") {
					prepareInput(step.build(stepDiv, values), step);
				}
				
				var input = stepDiv.find(".wiktvi-start-step-field");
				if (typeof(step.example) === "function") {
					input.attr("placeholder", step.example(values) || "");
				}
				
				var didSlideDown;
				if (!didFocus && stepDiv.css("display") === "none" && step.required) {
					didSlideDown = function (evt) {
						input.focus();
					};
					didFocus = true;
				}
				stepDiv.slideDown(didSlideDown);
			}
			
			if (step.required &&
				(values[stepId] === "" ||
				(step.multiple && values[stepId].join("") === ""))) {
				canContinue = false;
			}
		});
		
		continueBtn.button(canContinue ? "enable" : "disable");
	}
	
	function destroy() {
		$("#wiktvi-start-form, .noafterwizard").slideUp(function () {
			$(this).remove();
		});
		$(".noarticletext .nowizard, #editform").slideDown();
		
		$("#ca-edit").removeClass("selected");
		$("#ca-wiktvi-nowizard").addClass("selected");
	}
	
	function build() {
		$(".nowizard, .nowizard .noafterwizard").hide();
		
		var callToAction = $("#wiktvi-calltoaction");
		var origCallToActionText = callToAction.text();
		$(mw.util.addPortletLink("p-views", "#", "Tạo mã nguồn",
			"ca-wiktvi-nowizard", "Tạo mục từ dùng hộp sửa đổi văn bản thuần",
			undefined, "#ca-watch")).click(function (evt) {
			evt.preventDefault();
			callToAction.text(origCallToActionText);
			destroy();
		});
		callToAction.text(origCallToActionText +
			" Điền biểu mẫu ở dưới để viết mục từ mới.");
		
		var startForm = $("<form id='wiktvi-start-form'></form>");
		var spinner;
		startForm.submit(function (evt) {
			evt.preventDefault();
			
			continueBtn.button("disable");
			spinner.show();
			
			getWikitext(function (wikitext) {
				$("#wpTextbox1").val(wikitext);
				$("html, body").animate({scrollTop: 0});
				callToAction.text("Gần xong!");
				destroy();
			});
		});
		$.each(ordered_steps, function (i, step) {
			var stepDiv = $(mw.html.element("div", {
				"class": "wiktvi-start-step",
				id: "wiktvi-start-" + step.id }));
			var name = step.name.wiktviCapitalize();
			stepDiv.append($(mw.html.element("h3", {
				"class": "wiktvi-start-step-name" }, new mw.html.Raw(mw.html.element("label", {
				"for": "wiktvi-start-" + step.id + "-field" }, name)))));
			
			if (step.question_fmt) {
				var question = step.question_fmt.replace(/\{\{PAGENAME\}\}/g, pageName);
				stepDiv.append($(mw.html.element("label", {
					"class": "wiktvi-start-step-question",
					"for": "wiktvi-start-" + step.id + "-field" }, question)));
			}
			
			var input;
			// Menu
			if (step.choices) {
				input = $(mw.html.element("select", {
					"class": "wiktvi-start-step-field",
					id: "wiktvi-start-" + step.id + "-field" }));
				input.attr("data-placeholder",
					(step.multiple ? "Chọn các " : "Chọn một ") + step.name);
				populateMenus(input, step.choices);
				if (step.multiple) input.attr("multiple", true);
				stepDiv.append(input);
			}
			// Custom
			else if (typeof(step.build) === "function") {
				input = step.build(stepDiv);
			}
			// Text field(s)
			else {
				input = $(mw.html.element("input", {
					type: "text",
					"class": "wiktvi-start-step-field",
					id: "wiktvi-start-" + step.id + "-field" }));
				if (step.narrow) input.addClass("wiktvi-start-narrow");
				if (step.multiple) {
					input.on("input", function (evt) {
						refreshForTextField(evt.target, step);
					});
					var keyInput = buildKeyInput(step);
					var list = $(step.ordered ? "<ol></ol>" : "<ul></ul>")
						.append($("<li></li>").append(keyInput).append(input));
					stepDiv.append(list);
				}
				else stepDiv.append(input);
			}
			
			prepareInput(input, step);
			stepDiv.hide();
			startForm.append(stepDiv);
		});
		
		var footer = $("<div id='wiktvi-start-footer'></div>");
		
		spinner = $(mw.html.element("img", {
			id: "wiktvi-start-spinner",
			src: "//upload.wikimedia.org/wikipedia/commons/d/de/Ajax-loader.gif",
			alt: "Đang tải…",
			title: "Đang tải…" }));
		footer.append(spinner.hide());
		
		continueBtn = $(mw.html.element("button", {
			id: "wiktvi-unhide-editform",
			type: "submit" }, "Tiếp"));
		footer.append(continueBtn);
		
		startForm.append(footer);
		
		$("#editform").hide().before(startForm);
		startForm.find("select").chosen(chosenOptions);
		continueBtn.button();
		refresh();
		
		$.wiktviHeaders.get(function (allHeaders) {
			// Replace the initial language list with the complete list.
			var headers = $.extend({}, allHeaders);
			delete headers.vie;
			if (headers.mul) headers.mul.name = "đa ngữ";
			steps.lang.choices = steps.trans.keys = [];
			$.each(headers, function (code, header) {
				var match;
				if (header.level === 1 && (match = header.name.match(/^tiếng (.+)| ngữ$/i))) {
					var name = header.name;
					if (!name.indexOf("Tiếng ")) name = "tiếng " + match[1];
					var choice = [code, name, (match[1] || name)];
					steps.lang.choices.push(choice);
					langsByCode[code] = choice;
				}
			});
			
			// Alphabetize the language list by sort key.
			steps.lang.choices.sort(function (a, b) {
				return a[2].localeCompare(b[2], userLang);
			});
			
			// Vietnamese goes first in language lists but is disabled in the
			// translation section.
			var choice = ["vie", "tiếng Việt", "Việt"];
			steps.lang.choices.unshift(choice);
			langsByCode.vie = choice;
			
			var langMenu = $("#wiktvi-start-lang-field");
			populateMenus(langMenu, steps.lang.choices);
			langMenu.trigger("chosen:updated");
			
			var transMenus = $("#wiktvi-start-trans .wiktvi-start-step-keyfield");
			populateMenus(transMenus, steps.trans.keys, getDisabledKeys(steps.trans));
			transMenus.trigger("chosen:updated");
		});
	}
	
	mw.loader.using(["jquery.chosen", "jquery.ui", "mediawiki.api", "ext.gadget.headers"],
		build);
});