MediaWiki:Gadget-wikt.add-examples-dev.js
Remarque: après avoir sauvegardé, vous devez vider le cache de votre navigateur pour que les changements prennent effet. Mozilla, cliquez sur Actualiser (ou ctrl-r). Internet Explorer / Opera: ctrl-f5. Safari: cmd-r. Konqueror ctrl-r.
/**
* (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 [[Modèle:exemple]] 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 [[Modèle:exemple]] 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-?? Add buttons to format text (bold and italic). TODO update date
* ------------------------------------------------------------------------------------
* [[Catégorie:JavaScript du Wiktionnaire|add-examples-dev.js]]
* <nowiki>
*/
$(function () {
"use strict";
if (!wikt.page.hasNamespaceIn(["", "Reconstruction"])) {
return;
}
console.log("Chargement de Gadget-wikt.add-examples-dev.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 api = new mw.Api();
var languages = {};
var sectionNames = {
"adj": ["adj", "adjectif", "adjectif qualificatif"],
"adv": ["adv", "adverbe"],
"adv-ind": ["adv-ind", "adverbe ind", "adverbe indéfini"],
"adv-int": ["adv-int", "adverbe int", "adverbe interrogatif"],
"adv-pron": ["adv-pron", "adverbe pro", "adverbe pronominal"],
"adv-rel": ["adv-rel", "adverbe rel", "adverbe relatif"],
"conj": ["conj", "conjonction"],
"conj-coord": ["conj-coord", "conjonction coo", "conjonction de coordination"],
"copule": ["copule"],
"adj-dém": ["adj-dém", "adjectif dém", "adjectif démonstratif"],
"dét": ["dét", "déterminant"],
"adj-excl": ["adj-excl", "adjectif exc", "adjectif exclamatif"],
"adj-indéf": ["adj-indéf", "adjectif ind", "adjectif indéfini"],
"adj-int": ["adj-int", "adjectif int", "adjectif interrogatif"],
"adj-num": ["adj-num", "adjectif num", "adjectif numéral"],
"adj-pos": ["adj-pos", "adjectif pos", "adjectif possessif"],
"adj-rel": ["adj-rel", "adjectif rel", "adjectif relatif"],
"art": ["art", "article"],
"art-déf": ["art-déf", "article déf", "article défini"],
"art-indéf": ["art-indéf", "article ind", "article indéfini"],
"art-part": ["art-part", "article par", "article partitif"],
"nom": ["nom", "substantif", "nom commun"],
"nom-fam": ["nom-fam", "nom de famille"],
"patronyme": ["patronyme"],
"nom-pr": ["nom-pr", "nom propre"],
"nom-sciences": ["nom-sciences", "nom science", "nom scient", "nom scientifique"],
"prénom": ["prénom"],
"prép": ["prép", "préposition"],
"pronom": ["pronom"],
"pronom-adj": ["pronom-adj", "pronom-adjectif"],
"pronom-dém": ["pronom-dém", "pronom dém", "pronom démonstratif"],
"pronom-indéf": ["pronom-indéf", "pronom ind", "pronom indéfini"],
"pronom-int": ["pronom-int", "pronom int", "pronom interrogatif"],
"pronom-pers": ["pronom-pers", "pronom-per", "pronom personnel", "pronom réf", "pronom-réfl", "pronom réfléchi"],
"pronom-pos": ["pronom-pos", "pronom pos", "pronom possessif"],
"pronom-rel": ["pronom-rel", "pronom rel", "pronom relatif"],
"racine": ["racine"],
"verb": ["verb", "verbe"],
"verb-pr": ["verb-pr", "verbe pr", "verbe pronominal"],
"interj": ["interj", "interjection"],
"onoma": ["onoma", "onom", "onomatopée"],
"aff": ["aff", "affixe"],
"circon": ["circon", "circonf", "circonfixe"],
"inf": ["inf", "infixe"],
"interf": ["interf", "interfixe"],
"part": ["part", "particule"],
"part-num": ["part-num", "particule num", "particule numérale"],
"post": ["post", "postpos", "postposition"],
"préf": ["préf", "préfixe"],
"rad": ["rad", "radical"],
"suf": ["suf", "suff", "suffixe"],
"pré-verb": ["pré-verb", "pré-verbe"],
"pré-nom": ["pré-nom"],
"procl": ["procl", "proclitique"],
"loc": ["loc", "locution"],
"phr": ["loc-phr", "locution-phrase", "locution phrase", "phrase"],
"prov": ["prov", "proverbe"],
"quantif": ["quantif", "quantificateur"],
"var-typo": ["var-typo", "variante typo", "variante par contrainte typographique"],
"lettre": ["lettre"],
"symb": ["symb", "symbole"],
"class": ["class", "classif", "classificateur"],
"numeral": ["numér", "num", "numéral"],
"sinogramme": ["sinog", "sino", "sinogramme"],
"gismu": ["gismu"],
"rafsi": ["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[pageID].revisions[0].slots.main["*"]);
break;
}
}
});
$("ul > li > .example").each(function () {
var $element = $(this);
var $item = $element.parent();
if (!$item.next().length) {
// Get example’s language
var language = $item.find("bdi[lang]")[0].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");
var $section = $($topItem.parent().prevAll("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));
}
});
/**
* 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;
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 = ["primary", "progressive"];
}
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");
});
generateButton("italic", "italic", false, "Italique", function () {
self.formatText("italic");
});
toolbar.setup([
{
type: "bar",
include: ["bold", "italic"],
},
]);
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: ["progressive", "primary"],
disabled: true,
});
this._applyButton.on("click", this.submit.bind(this));
this._cancelButton = new OO.ui.ButtonWidget({
label: "Annuler",
title: "Refermer le formulaire",
flags: ["destructive"],
});
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 content = [toolbar, textInputLayout, sourceInputLayout, sourceURLInputLayout];
if (language !== "fr") {
content.push(translationInputLayout, transcriptionInputLayout, disableTranslationChkLayout);
}
var fieldsLayout = new OO.ui.FieldsetLayout({
label: "Ajout d’un exemple en " + (languages[this._language] || "langue inconnue"),
items: content,
classes: ["add-example-fieldset"],
});
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: ["add-example-form"],
expanded: false,
content: [
fieldsLayout,
buttonsLayout,
],
});
toolbar.initialize();
toolbar.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 The effect to apply (one of "bold" or "italic").
*/
formatText: function (effect) {
var $textInput = this._textInput.$element.find("textarea");
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.replace(" ", "_"); // Language codes may contain spaces
var sectionIDPattern = new RegExp("^" + escapedLangCode + "-(?:(flex)-)?([\\w-]+)-(\\d+)$");
var match = sectionIDPattern.exec(this._definitionLevel[0]);
var isInflection = match[1] === "flex";
var sectionType = match[2];
var sectionNum = parseInt(match[3]);
// 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[pageID].revisions[0].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\|([\wéèà -]+)\|/;
var targetLineIndex;
for (targetLineIndex = 0; targetLineIndex < lines.length; targetLineIndex++) {
var line = lines[targetLineIndex];
var match = sectionRegex.exec(line);
if (match && sectionNames[sectionType].includes(match[1])
// 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[targetLineIndex]);
if (m) {
if (level === m[1].length) {
defIndex++;
if (self._definitionLevel[level] === 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[targetLineIndex];
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[targetLineIndex + 1] || !lines[targetLineIndex + 1].startsWith(listMarker))) {
targetLineIndex++;
break;
}
}
// Insert new example into page content
var emptyTemplate = /#+\*\s*{{exemple\s*\|\s*\|?\s*lang\s*=[^|}]+}}/.test(lines[targetLineIndex - 1]);
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>