/**
* (fr)
* Ce gadget permet l’ajout d’exemples sans passer en mode édition. Un bouton pour
* ouvrir le formulaire devrait apparaitre à la fin de chaque série d’exemples pour
* chaque définition (seulement si le modèle ] est utilisé).
* ------------------------------------------------------------------------------------
* (en)
* This gadget allows to add examples without entering edit mode. A button to open the
* form should appear at the end of each series of examples for each definition
* (only if ] template is used).
* ------------------------------------------------------------------------------------
* v1.0 2021-09-12 Initial version
* v1.1 2021-09-16 Added input field for "lien" parameter.
* v1.1.1 2021-09-17 Fixed bug when text or translation contained the "=" sign.
* v1.1.2 2021-09-20 Restricted to main and “Reconstruction” namespaces.
* v1.2 2022-11-29 Better handling of multiline examples. Added checkbox to disable
* the translation. Link instead of button to show the form.
* v1.3 2024-03-04 Add buttons to format text (bold and italic).
* v1.3.1 2024-05-18 Add counter to avoid adding "Add Example" button when there are too many examples.
* ------------------------------------------------------------------------------------
* ]
* <nowiki>
*/
$(function () {
"use strict";
if (!wikt.page.hasNamespaceIn()) {
return;
}
console.log("Chargement de Gadget-wikt.add-examples.js…");
var NAME = "Ajouter des exemples";
var VERSION = "1.3";
var COOKIE_KEY_TEXT = "add_examples_text";
var COOKIE_KEY_SOURCE = "add_examples_source";
var COOKIE_KEY_SOURCE_URL = "add_examples_source_url";
var COOKIE_KEY_TRANSLATION = "add_examples_translation";
var COOKIE_KEY_TRANSCRIPTION = "add_examples_transcription";
var MAX_NUMBER_OF_EXAMPLES = 5;
if (typeof max_number_of_examples !== "undefined" && max_number_of_examples instanceof Number) {
MAX_NUMBER_OF_EXAMPLES = max_number_of_examples;
console.log("Using preferred maximum number of examples: " + MAX_NUMBER_OF_EXAMPLES);
}
var api = new mw.Api();
var languages = {};
var sectionNames = {
"adj": ,
"adv": ,
"adv-ind": ,
"adv-int": ,
"adv-pron": ,
"adv-rel": ,
"conj": ,
"conj-coord": ,
"copule": ,
"adj-dém": ,
"dét": ,
"adj-excl": ,
"adj-indéf": ,
"adj-int": ,
"adj-num": ,
"adj-pos": ,
"adj-rel": ,
"art": ,
"art-déf": ,
"art-indéf": ,
"art-part": ,
"nom": ,
"nom-fam": ,
"patronyme": ,
"nom-pr": ,
"nom-sciences": ,
"prénom": ,
"prép": ,
"pronom": ,
"pronom-adj": ,
"pronom-dém": ,
"pronom-indéf": ,
"pronom-int": ,
"pronom-pers": ,
"pronom-pos": ,
"pronom-rel": ,
"racine": ,
"verb": ,
"verb-pr": ,
"interj": ,
"onoma": ,
"aff": ,
"circon": ,
"inf": ,
"interf": ,
"part": ,
"part-num": ,
"post": ,
"préf": ,
"rad": ,
"suf": ,
"pré-verb": ,
"pré-nom": ,
"procl": ,
"loc": ,
"phr": ,
"prov": ,
"quantif": ,
"var-typo": ,
"lettre": ,
"symb": ,
"class": ,
"numeral": ,
"sinogramme": ,
"gismu": ,
"rafsi": ,
};
// Get names of all defined languages
api.get({
action: "query",
format: "json",
titles: "MediaWiki:Gadget-translation editor.js/langues.json",
prop: "revisions",
rvprop: "content",
rvslots: "main",
}).then(function (data) {
for (var pageID in data.query.pages) {
if (data.query.pages.hasOwnProperty(pageID)) {
// noinspection JSUnresolvedVariable
languages = JSON.parse(data.query.pages.revisions.slots.main);
break;
}
}
});
var exampleCounter = 0;
$("ul > li > .example").each(function () {
var $element = $(this);
var $item = $element.parent();
exampleCounter++;
if (!$item.next().length) {
if (exampleCounter >= MAX_NUMBER_OF_EXAMPLES) {
exampleCounter = 0;
return;
}
// Get example’s language
var language = $item.find("bdi").lang;
// Get section and indices of associated definition
var definitionLevel = ;
var $definitionItem = $item.parent().parent();
var $topItem;
do {
definitionLevel.splice(0, 0, $definitionItem.index());
$topItem = $definitionItem;
$definitionItem = $definitionItem.parent().parent();
} while ($definitionItem.prop("tagName") === "LI");
// Keep h3 for skins that do not yet use the new Mediawiki headings structure
var $section = $($topItem.parent().prevAll(".mw-heading3, h3").get(0)).find(".titredef");
definitionLevel.splice(0, 0, $section.attr("id"));
// Remove default edit link if present
var $defaultEditLink = $item.find("span.example > span.stubedit");
if ($defaultEditLink.length) {
$defaultEditLink.remove();
}
// Add a nice button to open the form
var $formItem = $("<li>");
var $button = $("<a href='#'>Ajouter un exemple</a>");
$button.on("click", function () {
if (!$button.form) {
$formItem.append(new Form($item, $button, language, definitionLevel).$element);
}
$button.form.setVisible(true);
return false;
});
$item.after($formItem.append($button));
exampleCounter = 0;
}
});
/**
* Constructor for the edit form.
* @param $lastExample {jQuery} The element corresponding to the example right above the button.
* @param $button {jQuery} The button that shows this form.
* @param language {string} Language for the example.
* @param definitionLevel {Array<string|number>} Indices of the associated definition.
* @constructor
*/
function Form($lastExample, $button, language, definitionLevel) {
var self = this;
this._language = language;
this._definitionLevel = definitionLevel;
this._$lastExample = $lastExample;
this._$button = $button;
this._$button.form = this;
/**
* Create a toolbar for the given text input.
* @param $textInput {jQuery} The text input to associate the toolbar to.
* @return {OO.ui.Toolbar} A new toolbar.
*/
function createToolbar($textInput) {
var toolFactory = new OO.ui.ToolFactory();
var toolGroupFactory = new OO.ui.ToolGroupFactory();
var toolbar = new OO.ui.Toolbar(toolFactory, toolGroupFactory, {actions: true});
/**
* Adds a custom button to the tool factory.
* @param name {string} Button’s name.
* @param icon {string|null} Buttons’s icon name.
* @param progressive {boolean} Wether the icon should be marked as progressive.
* @param title {string} Button’s tooltip text.
* @param onSelect {function} Callback for when the button is clicked.
* @param onUpdateState {function?} Callback for when the button changes state (optional).
* @param displayBothIconAndLabel {boolean?} Whether both the icon and label should be displayed.
*/
function generateButton(name, icon, progressive, title, onSelect, onUpdateState, displayBothIconAndLabel) {
/** @constructor */
function CustomTool() {
CustomTool.super.apply(this, arguments);
}
OO.inheritClass(CustomTool, OO.ui.Tool);
CustomTool.static.name = name;
CustomTool.static.icon = icon;
CustomTool.static.title = title;
if (progressive) {
CustomTool.static.flags = ;
}
CustomTool.static.displayBothIconAndLabel = !!displayBothIconAndLabel;
CustomTool.prototype.onSelect = onSelect;
// noinspection JSUnusedGlobalSymbols
CustomTool.prototype.onUpdateState = onUpdateState || function () {
this.setActive(false);
};
toolFactory.register(CustomTool);
}
generateButton("bold", "bold", false, "Gras", function () {
self.formatText("bold", $textInput);
});
generateButton("italic", "italic", false, "Italique", function () {
self.formatText("italic", $textInput);
});
toolbar.setup([
{
type: "bar",
include: ,
},
]);
return toolbar;
}
this._textInput = new OO.ui.MultilineTextInputWidget();
var textInputLayout = new OO.ui.FieldLayout(this._textInput, {
label: "Texte de l’exemple",
align: "top",
});
this._textInput.on("change", function (value) {
self._applyButton.setDisabled(!value);
})
this._sourceInput = new OO.ui.MultilineTextInputWidget();
var sourceInputLayout = new OO.ui.FieldLayout(this._sourceInput, {
label: "Source de l’exemple",
align: "top",
});
this._sourceURLInput = new OO.ui.TextInputWidget();
var sourceURLInputLayout = new OO.ui.FieldLayout(this._sourceURLInput, {
label: "Adresse web de l’exemple",
align: "top",
help: "Ne renseigner que dans le cas où le lien n’est pas déjà présent dans la référence de la source.",
helpInline: true,
});
this._translationInput = new OO.ui.MultilineTextInputWidget();
var translationInputLayout = new OO.ui.FieldLayout(this._translationInput, {
label: "Traduction en français de l’exemple",
align: "top",
});
this._transcriptionInput = new OO.ui.MultilineTextInputWidget();
var transcriptionInputLayout = new OO.ui.FieldLayout(this._transcriptionInput, {
label: "Transcription de l’exemple",
align: "top",
help: "Ne renseigner que dans le cas où le texte de l’exemple n’est pas écrit avec l’alphabet latin.",
helpInline: true,
});
this._disableTranslationChk = new OO.ui.CheckboxInputWidget();
var disableTranslationChkLayout = new OO.ui.FieldLayout(this._disableTranslationChk, {
label: "Désactiver la traduction",
align: "inline",
help: "Permet d’indiquer que la traduction n’est pas nécessaire (ex\u00a0: moyen français).",
helpInline: true,
});
this._disableTranslationChk.on("change", function (selected) {
self._translationInput.setDisabled(selected);
});
this._applyButton = new OO.ui.ButtonWidget({
label: "Publier",
title: "Publier l’exemple pour cette définition",
flags: ,
disabled: true,
});
this._applyButton.on("click", this.submit.bind(this));
this._cancelButton = new OO.ui.ButtonWidget({
label: "Annuler",
title: "Refermer le formulaire",
flags: ,
});
this._cancelButton.on("click", function () {
self.setVisible(false);
});
this._loadingImage = new OO.ui.LabelWidget({
label: $('<img src="https://upload.wikimedia.org/wikipedia/commons/7/7a/Ajax_loader_metal_512.gif" alt="loading" style="width: 1.5em">'),
});
this._loadingImage.toggle(false);
var textToolbar = createToolbar(this._textInput.$element.find("textarea"));
var sourceToolbar = createToolbar(this._sourceInput.$element.find("textarea"));
var translationToolbar = createToolbar(this._translationInput.$element.find("textarea"));
var transcriptionToolbar = createToolbar(this._transcriptionInput.$element.find("textarea"));
var content = ;
if (language !== "fr") {
content.push(translationToolbar, translationInputLayout, transcriptionToolbar, transcriptionInputLayout, disableTranslationChkLayout);
}
var fieldsLayout = new OO.ui.FieldsetLayout({
label: "Ajout d’un exemple en " + (languages || "langue inconnue"),
items: content,
classes: ,
});
var buttonsLayout = new OO.ui.HorizontalLayout({
items: [
this._applyButton,
this._cancelButton,
this._loadingImage,
]
});
this._frame = new OO.ui.PanelLayout({
id: "add-example-definition-{0}-form".format(this._definitionLevel.join("-")),
classes: ,
expanded: false,
content: [
fieldsLayout,
buttonsLayout,
],
});
textToolbar.initialize();
textToolbar.emit("updateState");
sourceToolbar.initialize();
sourceToolbar.emit("updateState");
}
Form.prototype = {
/**
* Returns the jQuery object for this form.
* @return {jQuery}
*/
get $element() {
return this._frame.$element;
},
/**
* Toggles the visibility of this form.
* @param visible {boolean}
*/
setVisible: function (visible) {
this._$button.toggle(!visible);
this._frame.toggle(visible);
if (visible) {
if (!this._textInput.getValue() && $.cookie(COOKIE_KEY_TEXT)) {
this._textInput.setValue($.cookie(COOKIE_KEY_TEXT));
}
if (!this._sourceInput.getValue() && $.cookie(COOKIE_KEY_SOURCE)) {
this._sourceInput.setValue($.cookie(COOKIE_KEY_SOURCE));
}
if (!this._sourceURLInput.getValue() && $.cookie(COOKIE_KEY_SOURCE_URL)) {
this._sourceURLInput.setValue($.cookie(COOKIE_KEY_SOURCE_URL));
}
if (!this._translationInput.getValue() && $.cookie(COOKIE_KEY_TRANSLATION)) {
this._translationInput.setValue($.cookie(COOKIE_KEY_TRANSLATION));
}
if (!this._transcriptionInput.getValue() && $.cookie(COOKIE_KEY_TRANSCRIPTION)) {
this._transcriptionInput.setValue($.cookie(COOKIE_KEY_TRANSCRIPTION));
}
}
},
/**
* Format the selected text using the given effect.
* @param effect {string} The effect to apply (either "bold" or "italic").
* @param $textInput {jQuery} The text input to format the text of.
*/
formatText: function (effect, $textInput) {
var selectedText = wikt.edit.getSelectedText($textInput);
var replText;
switch (effect) {
case "bold":
replText = "'''" + selectedText + "'''";
break;
case "italic":
replText = "''" + selectedText + "''";
break;
default:
throw new Error("Invalid effect: " + effect);
}
wikt.edit.replaceSelectedText(replText, $textInput);
},
/**
* Clears all fields contained in this forms.
*/
clear: function () {
this._textInput.setValue("");
this._sourceInput.setValue("");
this._sourceURLInput.setValue("");
this._translationInput.setValue("");
this._transcriptionInput.setValue("");
},
/**
* Generates and submits the wikicode then inserts the resulting HTML element if no errors occured.
*/
submit: function () {
var self = this;
self._textInput.setDisabled(true);
self._sourceInput.setDisabled(true);
self._sourceURLInput.setDisabled(true);
self._translationInput.setDisabled(true);
self._transcriptionInput.setDisabled(true);
self._disableTranslationChk.setDisabled(true);
this._applyButton.setDisabled(true);
this._loadingImage.toggle(true);
// noinspection JSUnresolvedFunction
var listMarker = "".padStart(this._definitionLevel.length - 1, "#") + "*";
var text = this._textInput.getValue().trim();
if (text.includes("=")) {
text = "1=" + text;
}
var code = listMarker + " {{exemple|" + text;
if (this._language !== "fr") {
var translation = this._translationInput.getValue().trim();
var transcription = this._transcriptionInput.getValue().trim();
if (translation) {
if (translation.includes("=")) {
translation = "sens=" + translation;
}
code += "\n|" + translation;
}
if (transcription) {
code += "\n|tr=" + transcription;
}
}
var source = this._sourceInput.getValue().trim();
if (source) {
code += "\n|source=" + source;
}
var sourceURL = this._sourceURLInput.getValue().trim();
if (sourceURL) {
code += "\n|lien=" + sourceURL;
}
if (this._definitionLevel.length > 2) {
code += "\n|tête=" + listMarker;
}
if (this._disableTranslationChk.isSelected()) {
code += "\n|pas-trad=1";
}
code += "\n|lang={0}}}".format(this._language);
var escapedLangCode = this._language.replaceAll(" ", "_"); // Language codes may contain spaces
var sectionIDPattern = new RegExp("^" + escapedLangCode + "-(?:(flex)-)?(+)-(\\d+)$");
var match = sectionIDPattern.exec(this._definitionLevel);
var isInflection = match === "flex";
var sectionType = match;
var sectionNum = parseInt(match);
// Insert new example into page’s code
api.get({
action: "query",
titles: mw.config.get("wgPageName"),
prop: "revisions",
rvprop: "content",
rvslots: "main",
}).then(function (data) {
var pageContent;
for (var pageID in data.query.pages) {
if (data.query.pages.hasOwnProperty(pageID)) {
// noinspection JSUnresolvedVariable
pageContent = data.query.pages.revisions.slots.main;
break;
}
}
// Look for correct language section
var langSectionRegex = new RegExp("==\\s*{{langue\\|{0}}}\\s*==".format(self._language));
var langSectionIndex = pageContent.search(langSectionRegex);
if (langSectionIndex === -1) {
error();
return;
}
// Look for correct word type section
var lines = pageContent.slice(langSectionIndex).split("\n");
var sectionRegex = /^===\s*{{S\|(+)\|/;
var targetLineIndex;
for (targetLineIndex = 0; targetLineIndex < lines.length; targetLineIndex++) {
var line = lines;
var match = sectionRegex.exec(line);
if (match && sectionNames.includes(match)
// Parameter "num" is absent if there is only one section for this type
&& (line.includes("|num=" + sectionNum) || sectionNum === 1)
// Check whether the section is an inflection if required
&& (isInflection === line.includes("|flexion"))) {
break;
}
}
if (targetLineIndex === lines.length) {
error();
return;
}
// Look for correct definition
var defIndex = -1;
var level = 1;
for (; targetLineIndex < lines.length; targetLineIndex++) {
var m = /^(#+)/.exec(lines);
if (m) {
if (level === m.length) {
defIndex++;
if (self._definitionLevel === defIndex) {
if (level === self._definitionLevel.length - 1) {
break;
} else {
level++;
defIndex = -1;
}
}
}
}
}
// Look for last example of current definition
var inExample = false;
var stack = 0;
for (targetLineIndex += 1; targetLineIndex < lines.length; targetLineIndex++) {
var line_ = lines;
if (line_.startsWith(listMarker) && line_.includes("{{exemple")) {
inExample = true;
stack = 0;
}
if (inExample) {
// "exemple" template’s arguments may span several lines
// use a stack to detect on which line the template ends
for (var ic = 0; ic < line_.length - 1; ic++) {
var c = line_.charAt(ic) + line_.charAt(ic + 1);
if (c === "{{") {
stack++;
} else if (c === "}}") {
stack--;
}
}
if (stack === 0) {
inExample = false;
}
}
// There should be no empty line between examples
if (!inExample && (!lines || !lines.startsWith(listMarker))) {
targetLineIndex++;
break;
}
}
// Insert new example into page content
var emptyTemplate = /#+\*\s*{{exemple\s*\|\s*\|?\s*lang\s*=+}}/.test(lines);
if (emptyTemplate) {
// Replace empty template with new example
lines.splice(targetLineIndex - 1, 1, code);
} else {
// Insert new example
lines.splice(targetLineIndex, 0, code);
}
// Submit new page content
api.edit(mw.config.get("wgPageName"), function (_) {
return {
text: pageContent.slice(0, langSectionIndex) + lines.join("\n"),
summary: "Ajout d’un exemple avec le gadget «\u00a0{0}\u00a0» (v{1}).".format(NAME, VERSION),
};
}).then(function () {
api.parse(code).done(function (data) {
var $renderedExample = $(data).find("ul > li").html();
var $item;
// Insert rendered example into page
if (emptyTemplate) {
self._$lastExample.html($renderedExample);
$item = self._$lastExample;
} else {
self._$lastExample.after($item = $("<li>").append($renderedExample));
}
$item.css("background-color", "lightgreen");
setTimeout(function () {
$item.css("background-color", "inherit");
}, 1000);
self._$lastExample = $item;
});
self.setVisible(false);
self.clear();
$.removeCookie(COOKIE_KEY_TEXT);
$.removeCookie(COOKIE_KEY_SOURCE);
$.removeCookie(COOKIE_KEY_SOURCE_URL);
$.removeCookie(COOKIE_KEY_TRANSLATION);
$.removeCookie(COOKIE_KEY_TRANSCRIPTION);
reenable();
});
});
function error() {
alert("L’exemple n’a pas pu être publié car la page a probablement été modifiée entre temps. " +
"Veuillez recharger la page et réessayer.");
$.cookie(COOKIE_KEY_TEXT, self._textInput.getValue());
$.cookie(COOKIE_KEY_SOURCE, self._sourceInput.getValue());
$.cookie(COOKIE_KEY_SOURCE_URL, self._sourceURLInput.getValue());
$.cookie(COOKIE_KEY_TRANSLATION, self._translationInput.getValue());
$.cookie(COOKIE_KEY_TRANSCRIPTION, self._transcriptionInput.getValue());
reenable();
}
function reenable() {
self._textInput.setDisabled(false);
self._sourceInput.setDisabled(false);
self._sourceURLInput.setDisabled(false);
self._translationInput.setDisabled(false);
self._transcriptionInput.setDisabled(false);
self._disableTranslationChk.setDisabled(false);
self._applyButton.setDisabled(false);
self._cancelButton.setDisabled(false);
self._loadingImage.toggle(false);
}
},
};
});
// </nowiki>