Utilisateur:Seudo/wikidatafy.dev.js

[[File:|10px|link=Catégorie:{{{1}}}|alt=]]
La bibliothèque libre.

Note : après avoir enregistré vos modifications, il se peut que vous deviez forcer le rechargement complet du cache de votre navigateur pour voir les changements.

  • Firefox / Safari : Maintenez la touche Maj (Shift) en cliquant sur le bouton Actualiser ou pressez Ctrl-F5 ou Ctrl-R (⌘-R sur un Mac) ;
  • Google Chrome : Appuyez sur Ctrl-Maj-R (⌘-Shift-R sur un Mac) ;
  • Internet Explorer : Maintenez la touche Ctrl en cliquant sur le bouton Actualiser ou pressez Ctrl-F5 ;
  • Opera : Allez dans Menu → Settings (Opera → Préférences sur un Mac) et ensuite à Confidentialité & sécurité → Effacer les données d'exploration → Images et fichiers en cache.
// @english: In case you saw a modification made by this tool in Wikidata, this is an experimental tool
// to enhance information about Wikisource documents at Wikidata. It is not a bot but a Javascript gadget,
// so it should not cause a major disruptance. However it may contain bugs or not be used properly.
// In case you notice something really wrong, feel free to modify this page and remove everything to
// limit the damage (but please send me a message...)
// 
// ////////////////////////////////////////////////////////////////
// Outil permettant de :
// - récupérer de Wikidata la liste des œuvres et éditions d'un auteur 
//   qui ne sont pas déjà mentionnées dans la page Auteur => cette
//   fonctionnalité est pour l'instant désactivée, car les données de 
//   Wikidata sont rarement d'une qualité suffisante pour que ce soit
//   utile ;
// - corriger les données Wikidata des œuvres d'un auteur, via une
//   interface qu'on peut lancer depuis la page de l'auteur (en mode
//   édition uniquement, pour des raisons techniques) ;
// - afficher en haut d'une page de l'espace principal (transclusion),
//   le cas échéant, une liste de propositions d'améliorations de la fiche
//   Wikidata correspondant à cette page. Pour les cas les plus simples,
//   la correction peut être faite directement, sans avoir à aller sur Wikidata.
//
// Mode d'emploi :
// - inclure ce fichier dans common.js :
//     importScript('User:Seudo/wikidatafy.js')
// 1) Ouvrir une page Auteur en mode édition.
// - Dans le menu « Plus » , cliquer sur « (WDfy) Cohérence des données ».
// - Une fenêtre suggère des améliorations à faire le cas échéant dans Wikidata. 
//   Dans certains cas elles peuvent être faites en cliquant sur 
//   « Mettre à jour Wikidata » (sous votre responsabilité !), la plupart du temps un lien
//   vers Wikidata est fourni.
// 2) Ouvrir une page de l'espace principal contenant la transclusion d'un 
//   ouvrage.
// - Si l'outil détecte des problèmes potentiels dans la fiche Wikidata 
//   (absence totale de fiche Wikidata, données importantes non renseignées...)
//   il affiche un cadre rouge en haut de la page avec l'indication de ces problèmes
//   et, dans les cas les plus simples, un bouton permettant de corriger directement
//   Wikidata.
//
// Si on ne souhaite pas que le cadre rouge apparaisse à chaque chargement d'une page de l'espace principal, 
// charger le script de la manière suivante (avec le paramètre « auto=non ») :
//      mw.loader.load( '/w/index.php?title=User:Seudo/wikidatafy.js&auto=non&action=raw&ctype=text/javascript' );
//

// Numéro de version (qui peut être cherché dans l'historique)
version = 0.3
mw.loader.using( [ 'oojs', 'oojs-ui', 'mediawiki.util' ] ).done( function () {

WIKIDATAFY_WIKISOURCE_URL = "https://fr.wikisource.org"; // Valeur par défaut

const script_name = "wikidatafy.js";

// User Agent envoyé pour les requêtes à l'API
const api_user_agent = `Seudo/${script_name} ${version} ([[s:fr:User:Seudo/${script_name}]]; https://fr.wikisource.org/wiki/User_talk:Seudo)`

// Pour ne pas surcharger le serveur
const maxCountFS = 50;

// Hauteur des boutons d'édition Wikidata
const g_btn_height = "1.6em";

// Mise en forme pour un message de succès
const css_successmsg = "font-weight: bold;";

// Mise en forme pour un message d'erreur
const css_errormsg = "font-weight: bold; color: red;";

const WD_API_URL = "https://www.wikidata.org/w/api.php";

// Fin de la configuration
///////////////////////////////////////////////////////////

// Renvoit un message dans une langue plus ou moins adaptée
const WIKIDATAFY_WIKISOURCE_URL_FR = "https://fr.wikisource.org";
const i18n_messages = {
    "Accédez à l'élément Wikidata": { "en": "Go to the Wikidata element" },
    "chargez le gadget WEF": { "en": "load the WEF gadget" },
    "Il manque une référence à l'œuvre correspondant à cette édition (propriété {1} : « {2} »)": {
        "en": "A reference is missing to the work this edition refers to (property {1} : '{2}')"
    },
    "édition ou traduction de": {
        "en": "edition or translation of"
    },
    "ou bien": { 
        "en": "or" 
    },
    "puis cliquez sur WEF: FRBR Edition pour éditer l'élément Wikidata sans sortir de Wikisource": {
        "en": "then clic on WEF: FRBR Edition to edit the Wikidata element without leaving Wikisource"
    }
};
function i18n(msg, params=[]) {
    // On ne gère que deux langues
    var lang = (WIKIDATAFY_WIKISOURCE_URL == WIKIDATAFY_WIKISOURCE_URL_FR ? "fr" : "en");
    if(lang != "fr" && msg in i18n_messages) {
        msg = i18n_messages[msg][lang];
        for(i = 0; i < params.length; i++)
            msg = msg.replace("{" + (i+1) + "}", params[i]);
    }
    return msg;
}

// Lancement automatique au chargement d'une page
var getScriptURL = (function() {
    var scripts = document.getElementsByTagName('script');
    for(s of scripts) {
      let src = s.src;
      if(src && src.includes("/" + script_name + "&"))
          return function() { return src; };
    }
    return function() { return null; };
})();

var automatique = true;

///////////////////////////////////////////////////////////
// Fonctions utilitaires générales

function escapeHtml(unsafe) {
    return unsafe
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#039;");
}

function url_last_part(url) {
    if (typeof(url) != "string")
        throw new Error("" + url + " devrait être une chaîne");
    return url.replace(/.*\//, "");
}

function getAjax(url, settings={}) {
    settings['Api-User-Agent'] = api_user_agent;
    return $.ajax(url, settings);
}

function escape_regex(s) {
    return s.replaceAll('(', '\\\(')
    .replaceAll(')', '\\\)')
    .replaceAll('|', '\\\|')
    .replaceAll('*', '\\\*')
    .replaceAll('?', '\\\?');
}

function pluriel(nb) {
    return (nb >= 2 ? "s" : "");
}

// Renvoit une version "canonique" des chaînes de 
// caractères pour des comparaisons
function canonique(s) {
    return s.replaceAll('&nbsp;', ' ')
            .replaceAll(/\s+/g, ' ')
            .trim(); 
}

///////////////////////////////////////////////////////////
// Constantes et fonctions utilitaires pour Wikidata.

var wdproperties = {};
const p_nature = "P31";
wdproperties[p_nature] = "nature de l'élément"
const p_auteur = "P50";
wdproperties[p_auteur] = "auteur ou autrice";
const p_traducteur = "P655";
wdproperties[p_traducteur] = "traducteur ou traductrice";
const p_editeur = "P123";
wdproperties[p_editeur] = "publié par";

const p_langue = "P407";
wdproperties[p_langue] = "langue de l'œuvre, du nom ou du terme";
const p_edition_de = "P629";
wdproperties[p_edition_de] = "édition ou traduction de";
const p_edition = "P747";
wdproperties[p_edition] = "édition";
const p_facsimile = "P996";
wdproperties[p_facsimile] = "fac-similé";
const p_titre = "P1476";
wdproperties[p_titre] = "titre";

function get_property_label(prop) {
    if (prop in wdproperties)
        return wdproperties[prop];
    else
        throw new Error(`Erreur interne : ${prop} est une propriété inconnue`);
}


var wdentities = {};
const q_francais = "Q150";
wdentities[q_francais] = "français";
const q_edition = "Q3331189";
wdentities[q_edition] = "version, édition ou traduction";
const q_traduction = "Q39811647"; // traduction (sous-classe du précédent)
wdentities[q_traduction] = "traduction";

// Codes des langues considérées comme relevant du français 
// (cf. Module:Œuvre/Langue française)
const langues_fr_wdids = [
  q_francais,      // français
  "Q35222",    // ancien français
  "Q1473289",  // moyen français
  "Q3100376"   // français classique
];

function get_entity_label(entity) {
    if (entity in wdentities)
        return wdentities[entity];
    else
        throw new Error(`Erreur interne : ${entity} est une entité inconnue`);
}

var badges_avancement = {
    "Q28064618": 1, // document numérique
    "Q20748094": 1, // texte incomplet
    "Q20748091": 2, // texte non corrigé
    "Q20748092": 4, // texte relu et corrigé
    "Q20748093": 5 // texte validé
}

// Renvoi la qualité indiquée dans une page de transclusion (via le modèle TextQuality, qui insère une catégorie)
function get_quality_in_page() {
    let res = null;
    if(RLCONF && RLCONF["wgCategories"]) {
        RLCONF["wgCategories"].forEach(categorie => {
            if(Avancement.hasQuality(categorie))
                res = categorie;
        });
    }
    return res;
}

function get_mw_api() {
    return new mw.Api({
        ajax: {
            headers: { 'Api-User-Agent': api_user_agent }
        }
    });
}

function get_wikidata_id() {
    // Plusieurs méthodes, selon le type de page, la skin, etc.
    let wd_id = mw.config.get("wgWikibaseItemId");
    if(wd_id)
        return wd_id;
    
    const re = new RegExp("https://www.wikidata.org/wiki/Special:EntityPage/(.*)");
    let lien = $("#t-wikibase a");
    if(lien.length > 0) {
        wd_id = $("#t-wikibase a").attr("href").match(re)[1];
        return wd_id;
    }
    
    return null;
}

// Envoie une requête SPARQL et passe le résultat à une fonction
// sous forme d'objet JSON
function send_sparql(query, callback) {
    console.log(query);
    let url = "https://query.wikidata.org/sparql?format=json&query=" + encodeURIComponent(query);
    fetch(url, { 
        method: 'POST' // Juste pour éviter le cache : cf. https://www.wikidata.org/wiki/Wikidata:SPARQL_query_service/query_optimization#Dealing_with_cached_queries
    })
    .then(response => response.json())
    .then(data => callback(data));
}

// Renvoit la valeur d'une donnée renvoyée par SPARQL,
// qui présente des structures diverses
function get_value(x) {
    if (typeof(x) == "string")
        return x;
    else if ("value" in x)
        return x["value"];
    else {
        console.log(x);
        throw new Error("Valeur non reconnue : " + x);
    }
}

// Cherche à renvoyer la "valeur" d'un snak (ex: mainsnak = {...}) en fonction de son type
function snak_value(snak) {
    // snaktype peut valoir "value", "somevalue" (valeur inconnue) ou "novalue".
    if(snak.snaktype == "value") {
        if(snak.datatype == "wikibase-item" && snak.datavalue && snak.datavalue.type == "wikibase-entityid"
           && snak.datavalue.value["entity-type"] == "item")
            return snak.datavalue.value.id;
        else if(snak.datatype == "commonsMedia" && snak.datavalue.type == "string")
            return snak.datavalue.value;
        else if(snak.datatype == "monolingualtext" && snak.datavalue.type == "monolingualtext")
            return snak.datavalue.value.text;
        else {
            // Autres types à implémenter en tant que de besoin
            console.error("Type de snak non reconnu : " + JSON.stringify(snak));
            return null;
        }
    }
    else {
        console.log("Snak sans valeur ou avec une valeur inconnue : " + JSON.stringify(snak));
        return null;
    }
}

function nettoyer_sparql_res(bindings) {
    let items = {};
    const multiples = ["nature", "natureLabel", "editionDe", "langue", "facsimile", "badge"]; // Champs à valeurs multiples
    bindings.forEach(item => {
        const item_id = url_last_part(get_value(item["item"]));
        if (item_id in items) {
            const autre = items[item_id];
            for (key in item) {
                if (!item[key])
                    continue;
                const val = get_value(item[key]);
                if (multiples.includes(key)) {
                    if (key in autre) {
                        if (!autre[key].includes(val))
                            autre[key].push(val);
                    } else
                        autre[key] = [val];
                } else if (!key in autre || !autre[key])
                    autre[key] = val;
            }
        } else {
            const newitem = {};
            for (key in item) {
                const val = get_value(item[key]);
                if (multiples.includes(key))
                    newitem[key] = [val];
                else
                    newitem[key] = val;
            }
            items[item_id] = newitem;
        }
    });
    return items;
}

///////////////////////////////////////////////////////////
// Vérifie la cohérence des informations relatives aux oeuvres dans Wikidata
// et affiche les résultats dans une boîte de dialogue

function verif_wd_oeuvres_auteur(data) {
    console.log(data);

    if (!data["results"] || !data["results"]["bindings"]) {
        OO.ui.alert("Pas de résultats");
        return;
    }

    // D'abord un nettoyage : la requête SPARQL renvoit certains items en plusieurs
    // versions, dont certains incomplètes
    let items = nettoyer_sparql_res(data["results"]["bindings"]);

    // Variables de maintenance de Wikidata
    let editions_sans_oeuvre = {};
    let oeuvre2editions = {};
    let edition2oeuvre = {};
    let editions_incompletes = {};
    let facsimiles_manquants = {};

    // Relie une œuvre à l'avancement le plus élevé de ses éditions
    // let avancement_oeuvres = {};

    // Pour vérifier qu'il est renseigné dans Wikidata comme dans Wikisource
    // let avancement_editions = [];

    for (let item_id in items) {
        const item = items[item_id];
        let nature_ids = item["nature"].map(x => url_last_part(x));
        const has_nature_edition = nature_ids.includes(q_edition)
             || nature_ids.includes(q_traduction);

        if (has_nature_edition && item["wsname"] && !item["facsimile"])
            facsimiles_manquants[item_id] = [item];

        if (item["editionDe"]) {
            // c'est une édition
            let manquants = [];
            if(!item["langue"] || item["langue"].length == 0)
                manquants.push([p_langue, get_property_label(p_langue)]);
            if(manquants.length > 0)
                editions_incompletes[item_id] = [item, manquants];


            item["editionDe"].forEach(editionDe => {
                const oeuvre = url_last_part(editionDe);
                if (!edition2oeuvre[item_id])
                    edition2oeuvre[item_id] = [];
                edition2oeuvre[item_id].push(oeuvre);
            });
        } else {
            // c'est une œuvre
            let editions = [];
            item["editions"].split(/\s+/).forEach(edition => {
                if (edition.length > 0)
                    editions.push(url_last_part(edition));
            });
            oeuvre2editions[item_id] = editions;
        }
    }

    for (let item_id in items) {
        let item = items[item_id];
        let msg = [];
        let nature_ids = item["nature"].map(x => url_last_part(x));

        // On se limite pour l'instant aux œuvres, définies comme
        // les éléments qui ont au moins une édition (restrictif : suppose
        // que tout est parfait dans Wikidata)
        // if(!(item["editions"] && item["editions"].length > 0))
        //    continue;

        // On n'affiche pas un document qui n'a ni transclusion, ni fac-similé
        if (!item["wsname"] && !item["facsimile"])
            continue;

        // On n'affiche pas certains éléments qui ne correspondent manifestement
        // pas à des ouvrages intéressant Wikisource

        const non_oeuvres = [
            "Q7777570" // production théâtrale
        ];
        let keep = true;
        non_oeuvres.forEach(id => {
            if (nature_ids.includes(id))
                keep = false;
        });
        if (!keep)
            continue;

        const has_nature_edition = nature_ids.includes(q_edition)
             || nature_ids.includes(q_traduction);

        // On n'affiche les éditions non rattachées à une œuvre que
        // si elles sont en français
        if (has_nature_edition
             && "langue" in item && !(item["langue"].map(url_last_part).includes("Q150")))
            continue;

        // On recense les éditions en français défectueuses
        if (has_nature_edition && !item["editionDe"])
            editions_sans_oeuvre[item_id] = item;
    };

    display_resultats(items, editions_sans_oeuvre,
        oeuvre2editions, edition2oeuvre, editions_incompletes,
        facsimiles_manquants);
}

// Affiche des données récupérées de SPARQL dans le champ de saisie
function display_oeuvres(data) {
    console.log(data);
    const editbox = $("#wpTextbox1");
    if (!editbox) {
        throw new Error("Champ d'édition absent");
    }
    const content = editbox.val();

    if (!data["results"] || !data["results"]["bindings"]) {
        OO.ui.alert("Pas de résultats");
        return;
    }

    let documents = [];
    let cur_document = null;

    // D'abord un nettoyage : la requête SPARQL renvoit certains items en plusieurs
    // versions, dont certains incomplètes
    let items = nettoyer_sparql_res(data["results"]["bindings"]);

    // Relie une œuvre à l'avancement le plus élevé de ses éditions
    let avancement_oeuvres = {};

    for (let item_id in items) {
        const item = items[item_id];
        let nature_ids = item["nature"].map(x => url_last_part(x));
        const has_nature_edition = nature_ids.includes(q_edition)
             || nature_ids.includes(q_traduction);

        if (item["editionDe"]) {
            // c'est une édition

            item["editionDe"].forEach(editionDe => {
                const oeuvre = url_last_part(editionDe);
                if(item["badge"]) {
                    item["badge"].forEach(badge => {
                        let badge_id = url_last_part(badge);
                        if(badge_id in badges_avancement) {
                            const avancement = badges_avancement[badge_id];
                            if (!(oeuvre in avancement_oeuvres)
                                 || avancement_oeuvres[oeuvre] < avancement)
                                avancement_oeuvres[oeuvre] = avancement;
                        }
                    });
                }
            });
        } 
    }

    for (let item_id in items) {
        let item = items[item_id];
        let msg = [];
        let nature_ids = item["nature"].map(x => url_last_part(x));

        // On se limite pour l'instant aux œuvres, définies comme
        // les éléments qui ont au moins une édition (restrictif : suppose
        // que tout est parfait dans Wikidata)
        // if(!(item["editions"] && item["editions"].length > 0))
        //    continue;

        // On n'affiche pas un document qui n'a ni transclusion, ni fac-similé
        if (!item["wsname"] && !item["facsimile"])
            continue;

        // On n'affiche pas certains éléments qui ne correspondent manifestement
        // pas à des ouvrages intéressant Wikisource

        const non_oeuvres = [
            "Q7777570" // production théâtrale
        ];
        let keep = true;
        non_oeuvres.forEach(id => {
            if (nature_ids.includes(id))
                keep = false;
        });
        if (!keep)
            continue;

        const has_nature_edition = nature_ids.includes(q_edition)
             || nature_ids.includes(q_traduction);

        // On n'affiche les éditions non rattachées à une œuvre que
        // si elles sont en français
        if (has_nature_edition
             && "langue" in item && !(item["langue"].map(url_last_part).includes("Q150")))
            continue;

        // On recense les éditions en français défectueuses
        // if (has_nature_edition && !item["editionDe"])
        //     editions_sans_oeuvre[item_id] = item;

        let label = item["itemLabel"];
        // S'il y a une page sur Wikisource, on regarde si l'élément est présent
        // dans un appel à Document ou L2S. Sinon, on l'affiche sans lien.
        let doc = null;
        if (item["wsname"]) {
            const wsname = item["wsname"];
            const wsname_regexp = escape_regex(wsname);
            const regex1 = new RegExp('{{Document\\s*\\\|([^}]*\\\||)\\s*(titre|éditions)=' + wsname_regexp + "(\\\||})", "i");
            const regex2 = new RegExp('{{Document\\s*\\\|([^}]*\\\||)\\s*(titre|éditions)=\\\[\\\[' + wsname_regexp + "(\\\||\\\]\\\])", "i");
            const regex3 = new RegExp('{{(L2S|Livre2Scanné)\\s*\\\|\\s*' + wsname_regexp + "(\\\||})", "i");
            if (content.search(regex1) < 0 && content.search(regex2) < 0 && content.search(regex3) < 0) {
                // Document non encore mentionné dans la page : on le rajoute
                doc = {
                    "wsname": wsname
                };
            }
        } else
            doc = {};

        if (doc) {
            doc["_type"] = has_nature_edition ? "édition" : "œuvre";
            doc["id"] = item_id;
            doc["titre"] = label;
            let forme;
            if (item["formeDeLOeuvreLabel"]) {
                const s = item["formeDeLOeuvreLabel"];
                forme = s[0].toUpperCase() + s.substr(1);
            } else
                forme = null;
            if (item["yearPublication"])
                doc["date"] = item["yearPublication"];
            else if (item["yearFondation"])
                doc["date"] = item["yearFondation"];
            else if (item["yearPremiereRepresentation"])
                doc["date"] = item["yearPremiereRepresentation"];
            // Attention, SPARQL renvoit l'année suivante si elle est négative !
            if(doc["date"] && doc["date"][0] == "-")
                doc["date"] = "-" + (1 + parseInt(doc["date"].substring(1)));
            doc["nature"] = item["natureLabel"].join(", ");
            doc["genre"] = forme;
            if(item["facsimile"]) {
                // On fait un lien seulement vers le premier fac-similé
                const uri = decodeURI(item["facsimile"][0]);
                doc["livre"] = url_last_part(uri);
            }
            if(item["badge"]) {
                item["badge"].forEach(badgeurl => {
                    let badge_id = url_last_part(badgeurl);
                    if(badge_id in badges_avancement) {
                        doc["avancement"] = badges_avancement[badge_id];
                    }
                });
            }
            doc["_msg"] = msg;
            documents.push(doc);
        }
    };

    // Tri
    documents.sort((a, b) => {
        if (a["_type"] == "œuvre" && b["_type"] != "œuvre")
            return false;
        else if (a["_type"] != "œuvre" && b["_type"] == "œuvre")
            return true;
        else {
            for (let critere in["nature", "genre", "date", "titre"])
                if (a[critere] && b[critere] && a[critere] != b[critere])
                    return (a[critere] < b[critere]);
        }
        return (a["item"] < b["item"]);
    });

    // Mise à jour de l'avancement des oeuvres à partir de celui des éditions
    documents.forEach(doc => {
        if (!doc["avancement"] && (doc["id"]in avancement_oeuvres))
            doc["avancement"] = avancement_oeuvres[doc["id"]]; // Avancement de l'édition la plus avancée
    });

    const LISTE_DEBUT = "{{liste documents début}}\n";
    const LISTE_FIN = "{{liste documents fin}}\n";
    let in_liste_docs = false;

    let count = 0;
    let nb_oeuvres = 0;
    let nb_editions = 0;
    if (documents.length > 0) {
        let cur_typ = null;
        let wikitxt = "";
        documents.forEach(doc => {
            let typ = doc["nature"] + (doc["genre"] ? ", " + doc["genre"] : "");
            if (typ != cur_typ) {
                if (in_liste_docs) {
                    wikitxt += LISTE_FIN;
                    in_liste_docs = false;
                }
                wikitxt += "'''" + typ + "'''\n";
                cur_typ = typ;
            }
            if (doc["_type"] == "édition")
                nb_editions += 1;
            else if (doc["_type"] == "œuvre")
                nb_oeuvres += 1;
            else
                throw new Error("Type de document inattendu : " + doc["_type"]);

            if (!doc["wsname"]) {
                if (in_liste_docs) {
                    wikitxt += LISTE_FIN;
                    in_liste_docs = false;
                }
                wikitxt += "* " + doc["titre"];
                if ("date" in doc)
                    wikitxt += " (" + doc["date"] + ")";
                wikitxt += " " + lienwiki_wd_edit(doc["id"]);
            } else {
                if (!in_liste_docs) {
                    wikitxt += LISTE_DEBUT;
                    in_liste_docs = true;
                }
                wikitxt += "* {{Document|";
                wikitxt += (doc["_type"] == "édition" ? "titre=" : "éditions=");
                if (doc["wsname"] && doc["titre"]) {
                    if (doc["wsname"] == doc["titre"])
                        wikitxt += "[[" + doc["titre"] + "]]";
                    else
                        wikitxt += "[[" + doc["wsname"] + "|" + doc["titre"] + "]]";
                } else if (doc["wsname"])
                    wikitxt += "[[" + doc["wsname"] + "]]";
                else
                    wikitxt += doc["titre"];
                if ("livre" in doc)
                    wikitxt += "|livre=" + doc["livre"];
                if ("date" in doc)
                    wikitxt += "|date=" + doc["date"];
                if ("avancement" in doc)
                    wikitxt += "|avancement=" + doc["avancement"];
                wikitxt += "}}";
            }
            wikitxt += " <!-- " + doc["id"];
            if (doc["_msg"].length > 0)
                wikitxt += " : " + doc["_msg"].join(", ");
            wikitxt += " -->";
            wikitxt += "\n";
            count += 1;
        });
        if (in_liste_docs) {
            wikitxt += LISTE_FIN;
            in_liste_docs = false;
        }
        editbox.val(wikitxt + editbox.val());
    }
    OO.ui.alert("Nombre de résultats insérés au début du champ de saisie : " + count + ". "
        + "Ne cliquez pas sur « Publier » ! Reclassez d'abord les documents à l'endroit approprié.");
}

function wdlink(id, display_id = true) {
    // Renvoit un lien vers une propriété ou une entité Wikidata
    // répertoriée dans wdproperties ou wdentities
    let txt = id;
    let complement = null;
    let url;
    if (wdentities[id]) {
        if(display_id)
            complement = "(" + wdentities[id] + ")";
        else
            txt = wdentities[id];
        url = "https://www.wikidata.org/wiki/" + id;
    } else if (wdproperties[id]) {
        if(display_id)
            complement = "(" + wdproperties[id] + ")";
        else
            txt = wdproperties[id];
        url = "https://www.wikidata.org/wiki/Property:" + id;
    } else if(id && id[0] == "P")
        url = "https://www.wikidata.org/wiki/Property:" + id;
    else
        url = "https://www.wikidata.org/wiki/" + id;
    const lien = "<a target=_blank href='" + url + "'>" + escapeHtml(txt) + "</a>";
    const res = (complement ? 
        "<span style='font-size: 90%'>" + lien + "</span>" + (complement ? " " + complement : "")
        : lien);
    return res;
}

function lienwiki_wd_edit(id) {
    // Renvoit un lien vers Wikidata sous forme de crayon
    return `[[Image:Blue pencil.svg|10px|link=d:${id}|Voir et modifier les données sur Wikidata]]`;
}

function lienhtml_wd_edit(id) {
    // Renvoit un lien vers Wikidata sous forme de crayon
    return `<span typeof="mw:File"><a href="https://www.wikidata.org/wiki/${id}" title="Voir et modifier les données sur Wikidata"><img alt="Voir et modifier les données sur Wikidata" src="//upload.wikimedia.org/wikipedia/commons/thumb/7/73/Blue_pencil.svg/10px-Blue_pencil.svg.png" decoding="async" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/7/73/Blue_pencil.svg/15px-Blue_pencil.svg.png 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/7/73/Blue_pencil.svg/20px-Blue_pencil.svg.png 2x" data-file-width="600" data-file-height="600" width="10" height="10"></a></span>`;
}

function lienhtml_ws(item) {
    // Renvoit un lien vers Wikisource le cas échéant
    // item peut être un nom de page ou un objet construit à partir d'une requête SPARQL
    if(typeof(item) == "string")
       return `<a target=_blanc href="${WIKIDATAFY_WIKISOURCE_URL}/wiki/${encodeURIComponent(item)}">${item}</a>`;
    else if(item["wsname"])
       return `<a target=_blanc href="${WIKIDATAFY_WIKISOURCE_URL}/wiki/${encodeURIComponent(item["wsname"])}">${item["itemLabel"]}</a>`;
    else // Pas de page
       return item["itemLabel"];
}

function get_centralauthtoken(data = null) {
    let promise = getAjax(WIKIDATAFY_WIKISOURCE_URL + "/w/api.php?action=centralauthtoken&format=json")
    .then(function(json) {
        if(json.centralauthtoken && json.centralauthtoken.centralauthtoken) {
            let centralauthtoken = json.centralauthtoken.centralauthtoken;
            console.log("central auth token=" + centralauthtoken );
            return [centralauthtoken, data];
        }
        else {
            console.log(json);
            throw "Token central non renvoyé";
        }
    }).fail(function(jqXHR, textStatus, errorThrown) {
        console.log("" + textStatus + ", " + errorThrown);
        alert("Erreur lors de la récupération du token centralisé : " + textStatus);
    });
    return promise;
}

function get_wikidata_token(centralauthtoken, test=false) {
    let url = (test ? "https://test.wikidata.org/w/api.php" : WD_API_URL);
    let promise = getAjax(url, {
        data: {
            "action": "query",
            "format": "json",
            "origin": WIKIDATAFY_WIKISOURCE_URL,
            "meta": "tokens",
            "centralauthtoken": centralauthtoken
        },
        headers: { 'Api-User-Agent': api_user_agent },
        xhrFields: {
            withCredentials: true
        },
        datatype: "json"
    }).then(function(json) {
        if(json.query && json.query.tokens && json.query.tokens.csrftoken) {
            let token = json.query.tokens.csrftoken;
            console.log("Récupéré de l'API le token : " + token);
            return token; 
        }
        else {
            console.log(json);
            throw "Token non renvoyé";
        }
    }).fail(function(jqXHR, textStatus, errorThrown) {
        console.log("" + textStatus + ", " + errorThrown);
        alert("Erreur lors de la récupération du token : " + textStatus);
    });
    return promise;
}

// Fontion éditant une propriété d'un item de Wikidata
function edit_wikidata(item_id, propriete, valeurs, btneditid) {
    if(![p_facsimile, p_langue, p_titre, "badge"].includes(propriete))
        throw "Propriété non éditable : " + propriete;
    let promise = 
        get_centralauthtoken()
        .then(function(args) {
            return get_wikidata_token(args[0]);
        })
        .then(function(token) {
            return get_centralauthtoken(token);
        })
        .then(function(args) {
            // Une fois qu'on a tous les tokens, on peut faire l'édition
            let centralauthtoken = args[0];
            let token = args[1];
            let querydata = null;
            let summary = "";
            if(propriete == p_facsimile) {
                // On accepte un fac-similé ou un array de fac-similés
                if(typeof(valeurs) == "string")
                    valeurs = [valeurs];
                if(!typeof(valeurs) == "object") {
                    console.error(valeurs);
                    throw "Erreur interne";
                }
                querydata = { "claims": [] };
                valeurs.forEach(val => {
                    querydata["claims"].push({
                        "mainsnak": {
                            "snaktype": "value",
                            "property": propriete,
                            "datatype": "commonsMedia",
                            "datavalue": {
                                "value": val,
                                "type":"string"
                            }
                        },
                        "rank":"normal",
                        "type":"statement"
                    });
                });
                summary = "Set [[Property:" + propriete + "]] (" + get_property_label(propriete) + ")";
            }
            else if(propriete == p_langue) {
                // On accepte une langue ou un array de langues
                if(typeof(valeurs) == "string")
                    valeurs = [valeurs];
                if(!typeof(valeurs) == "object") {
                    console.error(valeurs);
                    throw "Erreur interne";
                }
                querydata = { "claims": [] };
                valeurs.forEach(val => {
                    let numeric_val = parseInt(val.substr(1));
                    querydata["claims"].push({
                        "mainsnak": {
                            "snaktype": "value",
                            "property": propriete,
                            "datatype": "wikibase-item",
                            "datavalue": {
                                "value": {
                                    "entity-type": "item",
                                    "numeric-id": numeric_val,
                                    "id": val
                                },
                                "type":"wikibase-entityid"
                            }
                        },
                        "rank":"normal",
                        "type":"statement"
                    });
                });
                summary = "Set [[Property:" + propriete + "]] (" + get_property_label(propriete) + ")";
            }
            else if(propriete == p_titre) {
                if(!typeof(valeurs) == "string")
                    throw "Erreur interne";
                let valeur = valeurs; // Une seule
                querydata = { "claims": [] };
                querydata["claims"].push({
                    "mainsnak": {
                        "snaktype": "value",
                        "property": propriete,
                        "datatype": "monolingualtext",
                        "datavalue": {
                            "type":"monolingualtext",
                            "value": {
                                "language": "fr",
                                "text": valeur
                            }
                        }
                    },
                    "rank":"normal",
                    "type":"statement"
                });
                summary = "Set [[Property:" + propriete + "]] (" + get_property_label(propriete) + ")";
            }
            else if(propriete == "badge") {
                if(!typeof(valeurs) == "string")
                    throw "Erreur interne";
                let valeur = valeurs; // Une seule
                // Cette requête, en principe, écrase les badges sur frwikisource mais ne touche pas au reste
                querydata = { "sitelinks": { "frwikisource": {
                    "site": "frwikisource",
                    "badges": [ valeur ]
                }}};
                summary = "Set frwikisource badge ([[" + valeur + "]])";
            }
            else
                throw "Propriété non éditable : " + propriete;
        
            summary += " via [[:s:fr:User:Seudo/" + script_name + "]] " + version;
        
            $("#" + btneditid).replaceWith($("<span>", {
                id: btneditid, style: "height:" + g_btn_height
            }).text("En cours..."));

            let url = "https://www.wikidata.org/w/api.php"
                + "?centralauthtoken=" + encodeURIComponent(centralauthtoken)
                + "&origin=" + encodeURIComponent(WIKIDATAFY_WIKISOURCE_URL);
            console.log("Appel de l'API Wikidata (" + url + ")");
            console.log(querydata);
            
            return $.post({
                url: url,
                headers: { "Api-User-Agent": api_user_agent },
                data: {
                    action: "wbeditentity",
                    format: "json",
                    origin: WIKIDATAFY_WIKISOURCE_URL,
                    centralauthtoken: centralauthtoken,
                    data: JSON.stringify(querydata),
                    summary: summary,
                    id: item_id,
                    token: token
                }
            });
        }).then(function(json) {
            console.log("Réponse de l'API Wikidata : ")
            console.log(json);
            if(json.entity && json.entity.lastrevid) {
                let lastrevid = json.entity.lastrevid;
                let url_rev = `https://www.wikidata.org/w/index.php?title=${item_id}&diff=prev&oldid=${lastrevid}`;
                let img = '<span><img src="//upload.wikimedia.org/wikipedia/commons/thumb/f/f6/OOjs_UI_icon_check-constructive.svg/20px-OOjs_UI_icon_check-constructive.svg.png" decoding="async" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/f/f6/OOjs_UI_icon_check-constructive.svg/30px-OOjs_UI_icon_check-constructive.svg.png 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/f/f6/OOjs_UI_icon_check-constructive.svg/40px-OOjs_UI_icon_check-constructive.svg.png 2x" data-file-width="20" data-file-height="20" width="20" height="20"></span>';
                $("#" + btneditid).replaceWith($("<span>", {
                       id: btneditid,
                 style: "white-space: nowrap; height:" + g_btn_height
                }).html("Fait " + img + " (<a target=_blank href='" + url_rev + "'>révision</a>)"));
            }
            else if(json.error && json.error.info) {
                msg = "";
                if(json.error.messages && json.error.messages.length == 1
                    && json.error.messages[0].name == "wikibase-validator-no-such-media" 
                    && propriete == "P996")
                     msg = "Il semble que le fac-similé ait été versé sur Wikisource uniquement, "
                         + "de sorte qu'il ne peut pas être renseigné sur Wikidata. ";
                 msg += "La modification a échoué avec le message suivant :\n" + json.error.info;
                 alert(msg); // OO.ui.alert() ne fonctionne pas depuis une boîte de dialogue
                 $("#" + btneditid).replaceWith($("<span>", { style: "height: " + g_btn_height})
                                                .text("Échec de la modification."));
            }
            else {
                alert("Wikidata a renvoyé un contenu inattendu. Veuillez vérifier le "
                      + "contenu de vos dernières modifications sur Wikidata et "
                      + "prévenir l'auteur de ce gadget de l'erreur indiquée ci-dessous : \n"
                      + JSON.stringify(json));
                 $("#" + btneditid).replaceWith($("<span>", { style: "height: " + g_btn_height }) 
                                                .text("Échec de la modification."));
            }
        }).fail(function(jqXHR, textStatus, errorThrown) {
            console.log("" + textStatus + ", " + errorThrown);
            $("#" + btneditid).replaceWith($("<span>", { style: "height: " + g_btn_height})
                                           .text("Échec de la modification."));
            alert("Erreur lors de la mise à jour : " + textStatus);
        });
    return promise;
}

// Crée une oeuvre dans Wikidata
function creer_oeuvre_pour_edition(item_id, item, btnid) {
    $("#" + btnid).replaceWith($("<div>", { 
        id: btnid, style: "clear: right; margin-top: 1em;"
    }));
    let div = $("#" + btnid);

    div.html("Recherche d'éléments similaires sur Wikidata...");
    let url = "https://www.wikidata.org";
    let itemLabel = item["itemLabel"];
    if(!itemLabel) 
        itemLabel = "";

    getAjax("https://www.wikidata.org/w/api.php", {
        data: { action: "wbsearchentities", language: "fr", uselang: "fr", search: itemLabel, 
                format: "json", origin: WIKIDATAFY_WIKISOURCE_URL }
    }).done(function(data) {
        console.log(data);
        let btn_label = "Confirmer la création";
        if(data.search && data.search.length > 0) {
            div.html("");
            let htmls = [];
            data.search.forEach(elem => {
                if(elem.id == item_id)
                    return;
                let html = (elem.label ? elem.label : "<libellé non renseigné>")
                    + " " + lienhtml_wd_edit(elem.id);
                if(elem.description)
                    html += " (" + elem.description + ")";
                htmls.push(html);
            });
            if(htmls.length > 0) {
                div.append($("<p>").text("Éléments approchants trouvés sur Wikidata :"));
                let ul = div.append($("<ul>"));
                htmls.forEach(html => { ul.append($("<li>").html(html)); });
                div.append($("<p>").html("Si AUCUN de ces éléments ne correspond à l'œuvre recherchée, alors cliquez "
                    + "sur «&nbsp;" + btn_label + "&nbsp;»."));
            }
            else {
                div.html($("<p>").html("Aucun élément approchant n'a été trouvé dans Wikidata. "
                    + "Cliquez sur «&nbsp;" + btn_label + "&nbsp;» pour créer l'élément dans Wikidata."));
            }
        }
        else {
            div.html($("<p>").text("Aucun élément approchant n'a été trouvé dans Wikidata. "
                + "Cliquez sur «&nbsp;" + btn_label + "&nbsp;» pour créer l'élément dans Wikidata."));
        }
        let wdeditbtn_id = "wdeditbtn_" + item_id;
        let wdeditbtn = $("<input>", {
            type: "button", value: btn_label, id: wdeditbtn_id,
            style: "height:" + g_btn_height + "; float:right;"
        });
        wdeditbtn.on("click", function(e) {
            let USE_TEST = false;
            $("#" + wdeditbtn_id).text("En cours...");
                // Champs à inclure lors de la création de l'œuvre (idéal : reprendre l'interface de WEF) : 
                // - label : demander (par défaut : même que l'édition)
                // - nature : à demander (liste déroulante des plus fréquentes)
                // - description : à demander (facultatif
                // - auteur : demander (par défaut : même que l'édition)
                // - langue : demander (par défaut : même que l'édition)
                let wd_data = {
                    labels: { 
                        fr: { language: "fr", value: itemLabel}
                    },
                    claims: [{
                        "mainsnak": {
                            "snaktype": "value",
                            "property": "P31",
                            "datavalue": {
                                "value": {
                                    "entity-type": "item",
                                    "numeric-id": 7725634,
                                    "id": "Q7725634"
                                },
                                "type": "wikibase-entityid"
                            },
                            "datatype": "wikibase-item"
                        },
                        "type": "statement",
                        "rank": "normal"
                    }]
                };

            getAjax(WD_API_URL, {
                data: { action: "wbgetentities", ids: encodeURIComponent(item_id), 
                        format: "json", origin: WIKIDATAFY_WIKISOURCE_URL }
            })
            .then(function(json) {
                if(!json.entities || !json.entities[item_id] || !json.entities[item_id].sitelinks)
                    throw new Error("L'élément Wikidata n'a pas été créé");
                for(let sitelink in json.entities[item_id].sitelinks) {
                    let wikiidx = sitelink.indexOf("wiki");
                    if(wikiidx > 0 && wikiidx == sitelink.length - 4 && wikiidx <= 3)
                        throw new Error("L'élément Wikidata comprend un lien Wikipédia, "
                            + "ce qui est suspect pour un élément de type édition. "
                            + "En conséquence, je préfère ne pas créer automatiquement "
                            + "l'œuvre.");
                };
                return get_centralauthtoken();
            })
            .then(function(args) {
                return get_wikidata_token(args[0], USE_TEST);
            })
            .then(function(token) {
                return get_centralauthtoken(token);
            })
            .then(function(args) {
                // Une fois qu'on a tous les tokens, on peut faire l'édition
                let centralauthtoken = args[0];
                let token = args[1];
                let url = (USE_TEST ? "https://test.wikidata.org/w/api.php" : WD_API_URL)
                    + "?centralauthtoken=" + encodeURIComponent(centralauthtoken)
                    + "&origin=" + encodeURIComponent(WIKIDATAFY_WIKISOURCE_URL);
                let summary = " via [[:s:fr:User:Seudo/" + script_name + "]] " + version;
                return $.post({
                    url: url,
                    headers: { "Api-User-Agent": api_user_agent },
                    data: {
                        action: "wbeditentity",
                        new: "item",
                        format: "json",
                        origin: WIKIDATAFY_WIKISOURCE_URL,
                        centralauthtoken: centralauthtoken,
                        data: JSON.stringify(wd_data),
                        summary: summary,
                        token: token
                    }
                });
            })
            .then(function(args) {
                console.log(args);
                if(args.error) {
                    if(args.error.info) {
                        let msg = "";
                        if(args.error.code == "modification-failed")
                            msg = "La modification a échoué. ";
                        throw new Error(msg + args.error.info);
                    }
                    else
                        throw new Error(JSON.stringify(args.error));
                }
                let msg = "L'élément a été créé avec succès.";
                $("#" + wdeditbtn_id).replaceWith($("<div>", {"style": css_succesmsg}).text(msg));                
            })
            .catch(function(jqXHR, textStatus, errorThrown) {
                console.error(jqXHR);
                let msg = jqXHR.message;
                $("#" + wdeditbtn_id).replaceWith($("<div>", {"style": css_errormsg}).text(msg));
            });
        });
        div.append($("<div>").append(wdeditbtn));
    });
}

// Avancement :
// quality : paramètre de {{TextQuality}}, qui correspond au nom de la catégorie (00%, ..., 100%, Textes validés)
// avancement : 
var Avancement = {};
// Table de correspondance récupérée dans {{TextQuality}}
Avancement.info = [
    // qualité, image, avancement, badge, libellé badge
    ["Textes validés", "https://upload.wikimedia.org/wikipedia/commons/7/79/Mozilla.svg",
     "Texte validé", "Q20748093", "texte validé"],
    ["100%", "https://upload.wikimedia.org/wikipedia/commons/2/24/100_percent.svg",
     "À valider", "Q20748092", "texte relu et corrigé"],
    ["75%", "https://upload.wikimedia.org/wikipedia/commons/6/62/75_percent.svg",
     "À relire", "Q20748091", "texte non corrigé"],
    ["50%", "https://upload.wikimedia.org/wikipedia/commons/e/eb/50_percent.svg",
     "À formater", "Q20748091", "texte non corrigé"],
    ["25%", "https://upload.wikimedia.org/wikipedia/commons/c/ce/25_percent.svg",
     "À compléter", "Q20748094", "texte incomplet"],
    ["00%", "https://upload.wikimedia.org/wikipedia/commons/6/60/00_percent.svg",
     "À rechercher", "Q20748094", "texte incomplet"]
];
Avancement.qualityInfo = Object.fromEntries(Avancement.info.map(x => [x[0], x]));
Avancement.badgeInfo   = Object.fromEntries(Avancement.info.map(x => [x[3], x]));

Avancement.hasQuality = function(quality) {
    return (Avancement.qualityInfo[quality] ? true : false);
}

Avancement.hasBadge = function(badge) {
    return (Avancement.badgeInfo[badge] ? true : false);
}

Avancement.to_info = function(arr, cle, idx) {
    if(arr[cle]) 
        return arr[cle][idx];
    else
        throw "Valeur inconnue : " + cle;  
};
Avancement.quality2image      = function(quality) { return Avancement.to_info(Avancement.qualityInfo, quality, 1) };
Avancement.quality2avancement = function(quality) { return Avancement.to_info(Avancement.qualityInfo, quality, 2) };
Avancement.quality2badge      = function(quality) { return Avancement.to_info(Avancement.qualityInfo, quality, 3) };
Avancement.quality2badgelib   = function(quality) { return Avancement.to_info(Avancement.qualityInfo, quality, 4) };
Avancement.badge2quality      = function(badge)   { return Avancement.to_info(Avancement.badgeInfo, badge, 0) };
Avancement.badge2image        = function(badge)   { return Avancement.to_info(Avancement.badgeInfo, badge, 1) };
Avancement.badge2lib          = function(badge)   { return Avancement.to_info(Avancement.badgeInfo, badge, 4) };

SampleTabPanel = function DemoSampleTabPanel(name, config) {
    OO.ui.TabPanelLayout.call(this, name, config);
    if (this.$element.is(':empty')) {
        this.$element.text(this.label);
    }
};
OO.inheritClass(SampleTabPanel, OO.ui.TabPanelLayout);
IndexedDialog = function DemoIndexedDialog(args, config) {
    IndexedDialog.super.call(this, config);
    this.args = args;
};
OO.inheritClass(IndexedDialog, OO.ui.ProcessDialog);
IndexedDialog.static.title = 'Analyse des résultats';
IndexedDialog.static.name = 'Unnom';
IndexedDialog.static.actions = [{
        action: 'save',
        label: 'Fermer',
        flags: ['primary', 'progressive']
    }, {
        action: 'cancel',
        label: 'Cancel',
        flags: ['safe', 'close']
    }
];

IndexedDialog.prototype.getBodyHeight = function() {
    return 500;
};

IndexedDialog.prototype.getBodyWidth = function() {
    return 900;
};

IndexedDialog.prototype.initializeEditionsSansOeuvre = function(editions_sans_oeuvre) {
    if (Object.keys(editions_sans_oeuvre).length >= 1) { 
        let pl = (Object.keys(editions_sans_oeuvre).length >= 2);
        const entete = (pl ? "Les documents suivants sont" : "Le document suivant est") + " de nature <b>"
             + wdlink(q_edition) + " ou " + wdlink(q_traduction) + "</b>, donc "
             + (pl ? "ils devraient être <b>reliés" : "il devrait être <b>relié") + " à une œuvre</b>  "
             + "au moyen de la propriété " + wdlink("P747", true) + ".";
        let contenu = $("<div>").append($("<p>").html(entete));
        for (let item_id in editions_sans_oeuvre) {
            const item = editions_sans_oeuvre[item_id];
            let desc = $("<div>", { style: "clear:right; margin-top: 0.9em;" })
                       .append(lienhtml_ws(item) + " " + lienhtml_wd_edit(item_id) + " ");
            /* On ne rajoute pas ce bouton, trop complexe et risqué */
            /*
                let btneditid = item_id +"_editoeuvrebtn";
                let wdeditbtn = $("<input>", {
                    id: btneditid, type: "button", 
                    style: "height:" + g_btn_height + "; float:right;",
                    value: "Créer l'œuvre dans Wikidata"
                });
                wdeditbtn.on("click", function(e) { creer_oeuvre_pour_edition(item_id, item, btneditid) });
                desc.append(wdeditbtn);
            */
            contenu.append(desc);
        };

        this.tabPanels.push(new SampleTabPanel('tab' + this.tabPanels.length, {
            label: 'Éditions sans œuvre',
            content: [contenu]
        }));
    }
};

IndexedDialog.prototype.initializeEditionsIncompletes = function(editions_incompletes) {
    if (Object.keys(editions_incompletes).length >= 1) {
        let pl = (Object.keys(editions_incompletes).length >= 2);
        const entete = 
            "<p>Certains <b>champs relatifs aux éditions</b> devraient probablement être remplis dans "
             + (pl ? "les documents suivants" : "le document suivant") + ".</p>\n";
        let desc = "";
        for (let item_id in editions_incompletes) {
            const item = editions_incompletes[item_id][0];
            const manquants = editions_incompletes[item_id][1];
            desc += `<div>${lienhtml_ws(item)} ${lienhtml_wd_edit(item_id)} : `;
            manquants.forEach((x, idx) => {
                desc += (idx > 0 ? ", " : "") + wdlink(x[0], true); // `${x[0]} (${x[1]}), `;
            });
            desc += ".</div>\n";
        }
        this.tabPanels.push(new SampleTabPanel('tab' + this.tabPanels.length, {
            label: 'Champs manquants',
            content: [$(`${entete}${desc}`)]
        }));
    }
};

IndexedDialog.prototype.initializeFacsimilesManquants = function(facsimiles_manquants) {
    if(Object.keys(facsimiles_manquants).length >= 1) {
        let pl = (Object.keys(facsimiles_manquants).length >= 2);
        let entete = (pl ? "" + Object.keys(facsimiles_manquants).length + " documents, de type édition, sont reliés" : "Le document suivant est relié")
             + " à une page de Wikisource, "
             + `mais <b>la propriété ${wdlink(p_facsimile, true)} n'est pas renseignée</b> :`
             + " <b>attention</b> en cliquant sur « mettre à jour Wikidata », ce module est expérimental et il est indispensable "
             + "de vérifier sur Wikidata ce qui a été fait.";

        let desc = 
            $("<table>", {"style": "border-collapse: collapse"})
               .append($("<thead>").append($("<th>").text("Wikisource"))
               .append($("<th>").text("Fac-similé trouvé"))
               .append($("<th>").text("Action")));
        let tdstyle = "border-top: 1px solid #a2a9b1; border-bottom: 1px solid #a2a9b1";

        let tbody = $("<tbody>");
        desc.append(tbody);
        let countFS = 0;
        
        for(let item_id in facsimiles_manquants) {
            if(countFS >= maxCountFS) {
                entete += " Seules " + maxCountFS + " requêtes sont envoyées."
                break;
            }                    
            let item = facsimiles_manquants[item_id][0];
            let div_id = "fsmanquant_" + countFS;
            // desc += `<div style="margin-top: 0.5em;" id="${div_id}">${lienhtml_ws(item)} ${lienhtml_wd_edit(item_id)}</div>`;
            desc.append($("<tr>", {id: div_id})
                            .append($("<td>", {style: tdstyle}).html(lienhtml_ws(item) + " " + lienhtml_wd_edit(item_id))));
            countFS += 1;
        }

        this.tabPanels.push(new SampleTabPanel('tab' + this.tabPanels.length, {
            label: 'Fac-similé manquant',
            content: [$("<div>").append($("<p>").html(entete)).append(desc)]
        }));

        countFS = 0;
        let tab_fsids = {};
        // Pour ne pas trop charger le serveur je mets un intervalle entre deux requêtes
        const intervalle = 200;
        const fsmanquants_count = Object.keys(facsimiles_manquants).length;
        let fsmanquants_idx = 0;
        const intervalid = setInterval(function(x) {
            if(fsmanquants_idx >= fsmanquants_count) {
                clearInterval(intervalid);
                return;
            }
            let item_id = Object.keys(facsimiles_manquants)[fsmanquants_idx];
            fsmanquants_idx += 1;

            if(countFS >= maxCountFS) {
                clearInterval(intervalid);
                return;
            }

            let item = facsimiles_manquants[item_id][0];
            let div_id = "fsmanquant_" + countFS; // même définition que ci-dessus
            const wsname = item["wsname"];
            // desc += `<div style="margin-top: 0.5em;" id="${div_id}">${lienhtml_ws(item)} ${lienhtml_wd_edit(item_id)}</div>`;
            // On essaie de récupérer la valeur du fac-similé
            if(wsname) {
                tab_fsids[wsname] = div_id;
                let facsimiles = [];
// Peut-être plus simple : récupérer mw.config.values.prpSourceIndexPage 
                getAjax(`${WIKIDATAFY_WIKISOURCE_URL}/api/rest_v1/page/html/${encodeURIComponent(wsname)}`)
                .then(function(html) {
                    // On récupère le ou les fac-similés dans le code HTML de la page
                    let elem = document.createElement("html");
                    elem.innerHTML = html; 
                    let prpPagesOutput = elem.getElementsByClassName("prp-pages-output");
                    for(let elem of prpPagesOutput) {
                        data = JSON.parse(elem.getAttribute("data-mw"));
                        if(data.attrs && data.attrs.index && ! facsimiles.includes(data.attrs.index))
                            facsimiles.push(data.attrs.index);
                    };
                    if(facsimiles.length > 0)
                        return getAjax("https://www.wikidata.org/w/rest.php/wikibase/v0/entities/items/" + item_id);
                    else
                        return null;
                })
                .then(function(wd_info) {
                    let fsedit_fs = $("<td>", {style: tdstyle});
                    let fsedit_action = $("<td>", {style: tdstyle});
                    // On vérifie que le fac-similé n'est pas déjà renseigné dans Wikidata
                    // (au cas où il ait été modifié depuis l'appel SPARQL)
                    if(wd_info && wd_info.statements && wd_info.statements.P996 
                       && wd_info.statements.P996.length > 0) {
                        let wd_facsimiles = wd_info.statements.P996.map(x => x.value.content);
                        if(wd_facsimiles.sort().join("/") == facsimiles.sort().join("/"))
                            fsedit_fs.html("Le fac-similé est déjà renseigné sur Wikidata"); 
                        else 
                            fsedit_fs.html("Le fac-similé est déjà renseigné sur Wikidata avec une valeur différente.");
                    }
                    else {

                        if(facsimiles.length > 0) {
                            let btneditid = item_id +"_editbtn";
                            let wdeditbtn = $("<input>", { 
                                type: 'button', style: "height:" + g_btn_height,
                                id: btneditid, value: "Mettre à jour Wikidata"
                            });
                            wdeditbtn.on("click", function(e) { 
                                edit_wikidata(item_id, 'P996', facsimiles, btneditid);
                            });
                            let html = facsimiles.map(s => lienhtml_ws("Livre:" + s))
                                                 .join(", ");
                            fsedit_fs.html(html);
                            fsedit_action.append(wdeditbtn);
                        }
                        else 
                            fsedit_fs.html("Fac-similé non trouvé sur Wikisource");
                    }

                    $("#" + tab_fsids[wsname]).append(fsedit_fs)
                                              .append(fsedit_action);
                })
                .catch(function(error) {
                    console.error(error);
                    let fsedit_fs = $("<td>", {style: tdstyle}).text("Impossible de récupérer le fac-similé");
                    let fsedit_action = $("<td>", {style: tdstyle});                        
                    $("#" + tab_fsids[wsname]).append(fsedit_fs)
                                              .append(fsedit_action);
                });
            }
            countFS += 1;
        }, intervalle, 42);
        console.log("Interval id pour la recherche de fac-similés : " + intervalid);
    }
};

IndexedDialog.prototype.initializeAvancementTab = function(items) {
    /* Panel comparant l'avancement des éditions dans Wikidata et dans Wikisource */
    let tabPanelAvancement = new SampleTabPanel('tab' + this.tabPanels.length, {
        label: 'Avancement',
        content: [$("<div>Rien à indiquer sur l'état d'avancement pour l'instant.</div>")]
    });
    let avancement_load = function() {
        let first_in_tabavancement = true;
        let desc = 
            $("<table>", {"style": "border-collapse: collapse"})
               .append($("<thead>").append($("<th>").text("Wikisource"))
               .append($("<th>").text("Avancement trouvé"))
               .append($("<th>").text("Action")));
        let tdstyle = "border-top: 1px solid #a2a9b1; border-bottom: 1px solid #a2a9b1";

        let tbody = $("<tbody>");
        desc.append(tbody);

        let get_avancementpaneltr = function(item_id, item, badge) {
           let avancementitemid = "tabavancement_" + item_id + "_" + badge;
           let tr = $("<tr>", {id: avancementitemid})
                     .append($("<td>", {style: tdstyle}).html(lienhtml_ws(item) + " " + lienhtml_wd_edit(item_id)));
           tbody.append(tr);
           if(first_in_tabavancement) {
               tabPanelAvancement.$element.text("");
               tabPanelAvancement.$element.append(desc);
               first_in_tabavancement = false;
           }
           return tr;
        };
        let items_withwsname = Object.fromEntries(Object.entries(items).filter(([item_id, item]) => item["wsname"] ? true : false));
        let intervalle = 200;
        let item_idx = 0;
        let count_api_call = 0;
        let max_api_call = 100;
        const item_count = Object.keys(items_withwsname).length;
        const intervalid = setInterval(function(x) {
            if(item_idx >= item_count) {
                clearInterval(intervalid);
                return;
            }
            
            let item_id = Object.keys(items_withwsname)[item_idx];
            item_idx += 1;

            const item = items_withwsname[item_id];
            const wsname = item["wsname"];

            if(count_api_call >= max_api_call) {
// TODO : rien n'est affiché si l'avancement est correct sur maxCountFS.
                tabPanelAvancement.$element.prepend($("<div>").text("Seules "
                    + maxCountFS + " requêtes sont envoyées."));
                clearInterval(intervalid);
                return;
            }
            count_api_call += 1;
            
            // On essaie de récupérer l'avancement dans la page Wikisource
            let api = get_mw_api(); // new mw.Api();
            api.get( {
                action: "query",
                titles: wsname, 
                prop: "categories"
            }).done(function(data) {
                if(data.query && data.query.pages) {
                    let categories = data.query.pages[Object.keys(data.query.pages)[0]].categories;
                    console.log("Catégories pour la page " + wsname + " : " + categories);
                    if(!categories)
                        return;
                    let avancement = null;
                    categories.forEach(function(cat) {
                        if(!cat.title || !cat.title.startsWith("Catégorie:"))
                            throw "Catégorie inattendue : " + cat;
                        let categorie = cat.title.substring(10);
                        if(Avancement.hasQuality(categorie)) {
                            let badge = Avancement.quality2badge(categorie);
                            let badge_trouve = false;
                            let btneditid = null;
                            let wdeditbtn = null;
                            let item_autresbadges = [];
                            if(item["badge"]) {
                                item["badge"].forEach(function(b) {
                                    const badge_id = url_last_part(b);
                                    if(Avancement.hasBadge(badge_id)) {
                                        if(badge_id == badge)
                                            badge_trouve = true;
                                        else
                                            item_autresbadges.push(badge_id);
                                    }
                                });
                            }
                            if(!badge_trouve) {
                                const badge = Avancement.quality2badge(categorie);
                                btneditid = item_id +"_editavancementbtn";
                                wdeditbtn = $("<input>" , {
                                    type: "button", id: btneditid,
                                    value: "Mettre à jour Wikidata"
                                });
                                wdeditbtn.on("click", function(e) { 
                                    edit_wikidata(item_id, "badge", badge, btneditid);
                                });
                                let avancementtr = get_avancementpaneltr(item_id, item, badge);
                                let td = $("<td>", { style: tdstyle })
                                    .append("L'avancement est ")
                                    .append($("<img>", { 
                                        width: "10px", src: Avancement.quality2image(categorie)
                                    }))
                                    .append(" (")
                                    .append($("<i>").text(categorie))
                                    .append(") dans Wikisource");
                                if(item_autresbadges.length > 0) {
                                   td.append(" mais vaut ")
                                     .append(item_autresbadges.map(badge_id =>
                                       "<img width=10px src='" + encodeURI(Avancement.badge2image(badge_id)) + "'>"
                                     ).join(", "))
                                     .append(" dans Wikidata");
                                }
                                td.append($("<br>"))
                                  .append("→ il faudrait poser le badge «&nbsp;")
                                  .append($("<i>").text(Avancement.quality2badgelib(categorie)))
                                  .append("&nbsp;» dans Wikidata");
                                avancementtr.append(td);
                                let td2 = $("<td>", {style: tdstyle});
                                if(wdeditbtn)
                                    td2.append(wdeditbtn);
                                avancementtr.append(td2);
                            }                                                                      
                        }
                    });
                }
            });
        }, intervalle);
        console.log("Intervalle pour la recherche de l'état d'avancement : " + intervalid);
    }
    let avancement_load_btn = $("<input>", { 
        type: "button", 
        value: "Charger l'état d'avancement"
    });
    avancement_load_btn.on("click", avancement_load);
    tabPanelAvancement.$element.append(avancement_load_btn);
    this.tabPanels.push(tabPanelAvancement);
};

IndexedDialog.prototype.initializeDivers = function(items, edition2oeuvre, oeuvre2editions) {
    msg = "";
    for (oeuvre_id in oeuvre2editions) {
        const editions = oeuvre2editions[oeuvre_id];
        let malreliees = [];
        let editions_inconnues = [];
        editions.forEach(edition => {
            // On vérifie que l'édition a bien été renvoyée par la requête SPARQL,
            // car sinon c'est normal qu'on ne l'ait pas
            if(edition in items) {
                if(!edition in edition2oeuvre)
                    editions_inconnues.push(edition);
                else if(!edition2oeuvre[edition] || !edition2oeuvre[edition].includes(oeuvre_id))
                    malreliees.push(edition);
            }
        });
        if (editions_inconnues.length > 0) {
            const pluriel = editions_inconnues.length >= 2 ? "s" : "";
            msg += `<li>L'œuvre ${wdlink(oeuvre_id)} est reliée `
             + (pluriel ? "aux éditions " : "à l'édition ")
             + editions_inconnues.map(item => wdlink(item)).join(", ")
             + ` par la propriété ${wdlink("P747", true)}, mais `
             + (pluriel ? "ces dernières n'ont pas été récupérées"
                 : "cette dernière n'a pas été récupérée")
             + ".</li>";
        }
        if (malreliees.length > 0) {
            const pluriel = malreliees.length >= 2 ? "s" : "";
            msg += `<li>L'œuvre ${wdlink(oeuvre_id)} est reliée `
             + (pluriel ? "aux éditions " : "à l'édition ")
             + malreliees.map(item => wdlink(item)).join(", ")
             + ` par la propriété ${wdlink("P747", true)}, mais `
             + (pluriel ? "celles-ci ne sont pas reliées " : "cette dernière n'est pas reliée ")
             + `à l'œuvre par la propriété ${wdlink("P629", true)}.</li>`;
        }
    };
};
    
IndexedDialog.prototype.initialize = function() 
{
    let [items, editions_sans_oeuvre,
      oeuvre2editions, edition2oeuvre, editions_incompletes, facsimiles_manquants] = this.args;

    IndexedDialog.super.prototype.initialize.apply(this/*, arguments*/);

    this.indexLayout = new OO.ui.IndexLayout();
    this.tabPanels = [];

    // Message général
    let msg = "<p>Cette boîte de dialogue recense des insuffisances constatées dans les fiches de Wikidata "
        + "consacrées aux ouvrages de cet auteur. Si aucun onglet n'apparaît à droite de celui-ci, "
        + "aucune insuffisance n'a été constatée.</p>";
    this.tabPanels.push(new SampleTabPanel('tab' + this.tabPanels.length, {
        label: 'Résultats',
        content: [$(msg)]
    }));

    // Champs optionnels, selon les problèmes rencontrés dans les éléments
    // Wikidata
    this.initializeEditionsSansOeuvre(editions_sans_oeuvre);
    this.initializeEditionsIncompletes(editions_incompletes);
    this.initializeFacsimilesManquants(facsimiles_manquants);
    this.initializeAvancementTab(items);
    this.initializeDivers(items, edition2oeuvre, oeuvre2editions);

    if (msg.length > 0) {
        // this.content.$element.append("<p>Analyse du lien entre les œuvres et les éditions sur Wikidata :</p><ul>" + msg + "</ul></p>");
        this.tabPanels.push(new SampleTabPanel('tab' + this.tabPanels.length, {
            label: 'Analyse',
            content: [$(msg)]
        }));
    }

    this.indexLayout.addTabPanels(this.tabPanels);
    this.$body.append(this.indexLayout.$element);
};

IndexedDialog.prototype.getActionProcess = function (action) {
    if (action) {
        return new OO.ui.Process(function () {
            this.close({
                action: action
            });
        }, this);
    }
    return IndexedDialog.super.prototype.getActionProcess.call(this, action);
};
IndexedDialog.prototype.getTeardownProcess = function (data) {
    return IndexedDialog.super.prototype.getTeardownProcess.call(this, data)
    .next(function () {
        this.indexLayout.resetScroll();
    }, this);
};

function display_resultats(items, editions_sans_oeuvre,
    oeuvre2editions, edition2oeuvre, editions_incompletes, facsimiles_manquants) {

    let undialogue = new IndexedDialog([items, editions_sans_oeuvre,
        oeuvre2editions, edition2oeuvre, editions_incompletes, facsimiles_manquants],
        { size: 'medium' });

    // Create and append a window manager, which opens and closes the window.
    let idWM = "auteur-window-manager";
    if($("#" + idWM).length)
        $("#" + idWM).remove();
    let unwindowManager = new OO.ui.WindowManager({ id: idWM});
    $(document.body).append(unwindowManager.$element);
    unwindowManager.addWindows([undialogue]);

    // Open the window
    unwindowManager.openWindow(undialogue);
    return;
}

function get_oeuvres_auteur(itemAuteur, callback) {
    // Récupère la liste des oeuvres de Wikidata et appelle une callback avec le résultat
    const query = `
SELECT DISTINCT
  ?item ?itemLabel ?nature ?natureLabel ?formeDeLOeuvreLabel
  ?facsimile ?langue
  ?datePublication ?yearPublication 
  ?dateFondation ?yearFondation 
  ?datePremiereRepresentation ?yearPremiereRepresentation
  ?wsname ?badge ?revision
  ?editionDe (GROUP_CONCAT(?edition) AS ?editions)
 WHERE {
  { # Soit l'item est une édition en langue française
    # d'une œuvre qui a X pour auteur
    ?item wdt:${p_langue} wd:${q_francais};
          wdt:P629 ( wdt:P50 wd:Q535 ). }
  UNION
  { # Si ce n'est pas le cas, l'item doit avoir X directement pour auteur
    ?item wdt:${p_auteur} wd:${itemAuteur}. }

  OPTIONAL { # On récupère les éditions en français de l'item, le cas échéant
             ?item wdt:P747 ?edition.
             ?edition wdt:${p_langue} wd:${q_francais}. }

  OPTIONAL { # On récupère le lien vers Wikisource-fr, le cas échéant
             ?wsLien schema:isPartOf <${WIKIDATAFY_WIKISOURCE_URL}/>;
                     schema:inLanguage "fr";
                     schema:name ?wsname;
                     schema:about ?item.
                     OPTIONAL { ?wsLien wikibase:badge ?badge. }
           }
  # Et puis les autres propriétés intéressantes
  ?item wdt:P31 ?nature.  # nature de l'élément
  ?item schema:version ?revision .
  OPTIONAL { ?item wdt:${p_edition_de} ?editionDe. }
  OPTIONAL { ?item wdt:${p_langue} ?langue. }
  OPTIONAL { ?item wdt:P7937 ?formeDeLOeuvre. }
  OPTIONAL { ?item wdt:P996 ?facsimile. }
  OPTIONAL { ?item wdt:P577 ?datePublication. }
    BIND(YEAR(?datePublication) AS ?yearPublication)
  OPTIONAL { ?item wdt:P571 ?dateFondation. }
    BIND(YEAR(?dateFondation) AS ?yearFondation)
  OPTIONAL { ?item wdt:P1191 ?datePremiereRepresentation. }
    BIND(YEAR(?datePremiereRepresentation) AS ?yearPremiereRepresentation)

  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],fr,en". }
}
GROUP BY
  ?item ?itemLabel ?nature ?natureLabel ?formeDeLOeuvreLabel
  ?facsimile ?langue
  ?datePublication ?yearPublication 
  ?dateFondation ?yearFondation 
  ?datePremiereRepresentation ?yearPremiereRepresentation
  ?wsname ?badge ?revision
  ?editionDe
ORDER BY ?natureLabel ?editions ?editionDe ?formeDeLOeuvreLabel ?yearPublication ?yearFondation ?yearPremiereRepresentation
    `;

    send_sparql(query, callback);
}


/////////////////////////////////////
// Vérifie l'ensemble des oeuvres d'un auteur

function wdverif_auteur(e) {
    e.preventDefault();
    try {
        let wdid = get_wikidata_id(true);
        if(wdid) 
            get_oeuvres_auteur(wdid, verif_wd_oeuvres_auteur);
        else
            OO.ui.alert("Apparemment, cette page n'a pas encore été reliée à un élément Wikidata. Utilisez par exemple le gadget WEF: FRBR Edition.");

    } catch (exc) {
        OO.ui.alert("Erreur lors de la vérification d'un auteur : " + exc);
        return;
    }

}

function get_page_info() {
    // Renvoit les infos trouvées dans la page même, sans appel à Wikidata
    let info = {
        item_id: mw.config.get("wgWikibaseItemId"),
        categories: mw.config.get("wgCategories"),
        prpSourceIndexPage: mw.config.get("prpSourceIndexPage")
    };
    return info;
}

function get_page_ws_data() {
    // Renvoit les informations compris dans l'élément HTML div#ws_data
    let res = {
        "title":      $("#ws-data .ws-title").text(),
        "author":     $("#ws-data .ws-author").text(),
        "translator": $("#ws-data .ws-translator").text(),
        "publisher":  $("#ws-data .ws-publisher").text(),
        "year":       $("#ws-data .ws-year").text(),
        "progress":   $("#ws-data .ws-progress").text(),
        "scan":       $("#ws-data .ws-scan").text(),
        "cover":      $("#ws-data .ws-cover").text(),
        "pages":      $("#ws-data .ws-pages").text()
    };
    return res;
}

class ApiError {
    constructor(json_error) {
        this.json_error = json_error;
    }
    get_message() {
        let json_error = this.json_error;
        if(json_error.code == "no-such-entity")
            return("Entité inconnue : " + json_error.id);
        else if(json_error.info)
            return(json_error.info);
        else
            return("Erreur inconnue : " + JSON.stringify(json.error));
    }
}

function display_erreurs(erreurs_elem) {
    let err_style = "margin: 1em 1em 1em 1em; padding: 0em 0em 0.5em; border: 2px solid red; font-weight bold;";
    let div_id = "wd_erreur_msg";
    if($("#" + div_id).length > 0)
        $("#" + div_id).remove();
    let jsclose = "$('#" + div_id + "').hide()";
    $("#bodyContent").prepend($("<div>", { "id": div_id, style: err_style })
                              .append($("<a>", { "style": "float: right; border-left: 1px solid gray; border-bottom: 1px solid gray; margin: 0px 0px 5px 5px; padding: 3px 3px 3px 3px; font-size: smaller;",
                                                 "href": "#",
                                                 "onclick": jsclose })
                                      .text("Fermer ce message"))
                              .append(erreurs_elem));
}

class Item {
    constructor(item_id, data) {
        this.id = item_id;
        this.data = data;
    }
 
    get_label() {
        if(this.data.labels.fr) 
            return this.data.labels.fr.value;
        return null;
    }

    get(property_id) {
        return this.data.claims[property_id];
    }

    // Renvoit la valeur unique
    get_value(property_id) {
        const prop = this.data.claims[property_id];
        if(!prop)
            return null;
        else if(prop.length >= 2)
            throw new Error("Plusieurs valeurs pour " + property_id + " : " + JSON.stringify(prop));
        else
            return snak_value(prop[0]);
    }

    get_values(property_id) {
        const prop = this.data.claims[property_id];
        if(!prop)
            return null;
        else
            return prop.map(elem => snak_value(elem.mainsnak));
    }

    get_auteur()                { return this.get_values(p_auteur)  }
    get_traducteur()            { return this.get_values(p_traducteur) }
    get_editeur()               { return this.get_values(p_editeur) }
    get_facsimiles()            { return this.get_values(p_facsimile) }
    get_langues()               { return this.get_values(p_langue) }
    get_nature()                { return this.get_values(p_nature)  }
    get_editionOuTraductionDe() { return this.get_values(p_edition_de) }        
    get_editions()              { return this.get_values(p_edition) }
    get_titre()                 { return this.get_values(p_titre) }

    is_edition() {
        let natures = this.get_nature();
        if(!natures)
            return false;
        let res = false;
        natures.forEach(nature => {
            if(nature == q_edition)
                res = true;
        });
        return res;
    }
    
    /* Vérifier les données et émet des alertes en cas de problème */
    check_donnees(page_info, page_ws_data, dans_lancement=false) {
        let erreurs = [];
        if(this.get_nature() == null)
            erreurs.push("Il manque la nature (propriété P31).");
        if(this.is_edition()) {
            this.check_edition(page_info, page_ws_data, erreurs);    
        }
        let url = "https://www.wikidata.org/wiki/" + this.id;
        let divacceswd = ($("<div>", { style: "text-align: center" })
                          .append($("<a>", { "href": url, target: "_blank" })
                                 .text("⇒ " + i18n("Accédez à l'élément Wikidata"))));
        if(this.is_edition()) {
            if($("#p-wef").length >= 1)
                divacceswd.append(" ou utilisez le gadget WEF: FRBR Edition");
            else {
                let wefbtn = $("<a>", { href: "#" })
                             .text(i18n("chargez le gadget WEF"));
                wefbtn.on("click", function(e) { 
                    mw.loader.load('https://ru.wikipedia.org/w/index.php?title=MediaWiki:WEF AllEditors.js&action=raw&ctype=text/javascript');
                });
                divacceswd.append(" " + i18n("or") + " ") 
                          .append(wefbtn)
                          .append(" " + i18n("puis cliquez sur WEF: FRBR Edition pour éditer l'élément Wikidata sans sortir de Wikisource"));
            }
         }

        if(erreurs.length >= 1) {
            let msg = $("<ul>");
            erreurs.forEach(erreur => {
                if(typeof(erreur) == "string")
                    msg.append($("<li>").text(erreur));
                else
                    msg.append($("<li>").append(erreur));
            });
            msg.append(divacceswd);
            display_erreurs(msg);
        }
        else {
            if(!dans_lancement) {
                let msg = $("<div>").text("Aucun problème particulier n'a été détecté dans l'élément Wikidata. N'hésitez toutefois pas à le vérifier directement.");
                msg.append(divacceswd);
                display_erreurs(msg);
            }
        }
    }

    /* Vérifier les données pour une édition*/
    check_edition(page_info, page_ws_data, erreurs) {
        let oeuvre = this.get_values(p_edition_de);
        if(!oeuvre || oeuvre.length == 0)
            erreurs.push(i18n("Il manque une référence à l'œuvre correspondant à cette édition (propriété {1} : « {2} »)", 
                              [p_edition_de, i18n(wdproperties[p_edition_de])]));
//            erreurs.push("Il manque une référence à l'œuvre correspondant à cette édition (propriété " + p_edition_de 
//                         + " : « " + wdproperties[p_edition_de] + " »)");
        this.check_langue(erreurs);
        let cur_quality = get_quality_in_page();
        if(cur_quality) {
            if(! this.data.sitelinks || this.data.sitelinks.frwikisource == 0)
                erreurs.push("L'élément Wikidata n'a pas de liens vers des sites Wikimédia.");
            else {
                let badges = this.data.sitelinks.frwikisource.badges;
                if(badges == null || badges.length == 0) {
                   let msg = $("<p>").append("L'avancement n'est pas indiqué dans la fiche Wikidata. Ajoutez le badge ")
                                     .append($("<img>", { width: "10px", src: Avancement.quality2image(cur_quality) }))
                                     .append(" (")
                                     .append($("<i>").text(Avancement.quality2badgelib(cur_quality)))
                                     .append(").");
                    const badge = Avancement.quality2badge(cur_quality);
                    const wdeditbtn = this.make_wdedit_btn("badge", badge);
                    msg.append($("<span>", { style: "margin-left: 0.5em"})
                               .text(" "))
                       .append(wdeditbtn);

                    erreurs.push(msg);
                }
                else {
                    badges.forEach(badge => {
                        if(Avancement.hasBadge(badge) && badge != Avancement.quality2badge(cur_quality)) {
                            const badgenew = Avancement.quality2badge(cur_quality);
                            const wdeditbtn = this.make_wdedit_btn("badge", badgenew);
                            let msg = $("<p>").append("L'avancement indiqué dans la fiche Wikidata (« "
                                         + Avancement.badge2lib(badge) + " ») ne correspond pas "
                                         + " à la qualité indiquée ici (« " + cur_quality + " »). "                                              
                                         + "Corriger l'avancement dans Wikidata en ")
                                         .append($("<img>", { width: "10px", src: Avancement.quality2image(cur_quality) }))
                                         .append(" (")
                                         .append($("<i>").text(Avancement.badge2lib(badgenew)))
                                         .append(").");
                            msg.append($("<span>", { style: "margin-left: 0.5em"})
                                   .text(" "))
                               .append(wdeditbtn);
                           erreurs.push(msg);
                        }
                    });
                }
            }
        }
        this.check_facsimile(page_info, erreurs);
        // Titre
        let page_titre = page_ws_data["title"];
        let wd_titre = this.get_titre();
        if(!wd_titre || wd_titre.length == 0) {
            if(page_titre) {
                let msg = $("<p>").append("Le titre n'est pas renseigné dans Wikidata. "
                                          + "Rajouter le titre « " + page_titre + " » ? ");
                const wdeditbtn = this.make_wdedit_btn(p_titre, page_titre);
                msg.append($("<span>", { style: "margin-left: 0.5em"})
                           .text(" "))
                   .append(wdeditbtn);
                erreurs.push(msg);
            }
            else {
                let msg = "Le titre n'est pas renseigné dans Wikidata, "
                          + "mais je ne l'ai pas trouvé non plus dans cette page.";
                erreurs.push(msg);
           }
        }
        // cf. https://fr.wikisource.org/wiki/Wikisource:Scriptorium/Septembre_2023#Wikidata_et_Wikisource
        // else if(page_titre && canonique(page_titre) != canonique(wd_titre[0])) {
        //     let msg = "Le titre dans Wikidata est « " + wd_titre[0] + " », "
        //               + "mais j'ai trouvé dans cette page le titre suivant : « " + page_titre + " »";
        //     erreurs.push(msg);
        // }

        // Auteur, traducteur, éditeur
        let page_auteur = page_ws_data["author"];
        let wd_auteur = this.get_auteur();
        if(page_auteur && (!wd_auteur || wd_auteur.length == 0))
            erreurs.push("L'auteur « " + page_auteur + " » n'est pas mentionné dans Wikidata.");

        let page_traducteur = page_ws_data["translator"];
        let wd_traducteur = this.get_traducteur();
        if(page_traducteur && (!wd_traducteur || wd_traducteur.length == 0))
            erreurs.push("Le traducteur « " + page_traducteur + " » n'est pas mentionné dans Wikidata.");

// Pas forcément utile, surtout dans le cas où un item Wikidata est créé pour chaque élément d'un recueil
//        let page_editeur = page_ws_data["publisher"];
//        let wd_editeur = this.get_editeur();
//        if(page_editeur && (!wd_editeur || wd_editeur.length == 0))
//            erreurs.push("L'éditeur « " + page_editeur + " » n'est pas mentionné dans Wikidata.");
    }
    
    check_langue(erreurs) {
        // TODO : handle other languages?
        if(WIKIDATAFY_WIKISOURCE_URL != 'https://fr.wikisource.org')
            return;
        let langues = this.get_langues();
        if(!langues || langues.filter(function(n) { return langues_fr_wdids.indexOf(n) !== -1; }).length == 0) {  
            let msg = $("<p>").append("Rajouter le français comme langue du document ? ");
            const wdeditbtn = this.make_wdedit_btn(p_langue, q_francais);
            msg.append($("<span>", { style: "margin-left: 0.5em"})
                       .text(" "))
               .append(wdeditbtn);
            erreurs.push(msg);
        }
    }

    check_facsimile(page_info, erreurs) {
        const prpSourceIndexPage = page_info["prpSourceIndexPage"];
        if(prpSourceIndexPage) {
            let main_index = prpSourceIndexPage.substr("Livre:".length);
            let facsimiles = this.get_facsimiles();
            if(!facsimiles || facsimiles.length == 0) {
                let msg = $("<p>").append("Rajouter le fac-similé " 
                                          + prpSourceIndexPage
                                          + " (vérifier que la page transclut bien ce fac-similé et lui seul) ?");
                const wdeditbtn = this.make_wdedit_btn(p_facsimile, main_index);
                msg.append($("<span>", { style: "margin-left: 0.5em"})    
                       .text(" "))
                   .append(wdeditbtn);
                erreurs.push(msg);
            }
        }
    }

    // Crée un bouton d'édition de Wikidata pour une catégorie de donnée 
    // (qui doit être reconnue par edit_wikidata().
    make_wdedit_btn(typ, val) {
        const btneditid = ("editbtn_" + Math.random()).replace(".", "");
        const wdeditbtn = $("<input>" , {
            type: "button", id: btneditid,
            style: "margin-left: 1em",
            value: "Mettre à jour Wikidata"
        });
        const item_id = this.id;
        wdeditbtn.on("click", function(e) { 
            edit_wikidata(item_id, typ, val, btneditid);
        });
        return wdeditbtn;
    }
}

/////////////////////////////////////////
// Vérifie une page (transclusion)

function wdverif_page(e=null, dans_lancement=false) {
    if(e)
        e.preventDefault();
    try {
        let msgs = ["Résultat"];
        let page_info = get_page_info();
        let page_ws_data = get_page_ws_data();  
        let item_id = get_wikidata_id();
        if(!item_id) {
            display_erreurs([$("<p>").text("Apparemment, cette page n'a pas encore été reliée à un élément Wikidata. Utilisez par exemple le gadget WEF: FRBR Edition.")]);
            return;
        }
        getAjax(WD_API_URL, {
            data: { action: "wbgetentities", ids: encodeURIComponent(item_id), 
                    format: "json", origin: WIKIDATAFY_WIKISOURCE_URL }
        }).then(function(json) {
            if(json.error) 
                throw new ApiError(json.error);
            let item = new Item(item_id, json.entities[item_id]);
            item.check_donnees(page_info, page_ws_data, dans_lancement);
        }).fail(function(exc) {
            if(exc instanceof ApiError)
                OO.ui.alert(exc.get_message());
            else {
                msg = typeof(exc);
                OO.ui.alert("Erreur lors de l'appel de wbgetentities : " + msg);
            }
        })
    } catch (exc) {
        if(exc instanceof ApiError)
            OO.ui.alert(exc.get_message());
        else
            OO.ui.alert("Erreur lors de la vérification d'une page : " + exc);
        return;
    }

}

/////////////////////////////////////////
// Insère la liste des oeuvres d'un auteur
function auteur(e) {
    e.preventDefault();
    try {
        const editbox = $("#wpTextbox1");
        if (!editbox || !editbox.length) {
            OO.ui.alert("Cette page ne contient pas de champ d'édition");
            return;
        }
        const wdid = get_wikidata_id;
        if(wdid) {
            get_oeuvres_auteur(wdid, display_oeuvres);
        };
    } catch (exc) {
        OO.ui.alert("Erreur lors de l'insertion des œuvres d'un auteur : " + exc);
        return;
    }
}

function addCommande(libelle, id, desc, fn) {
    mw.util.addPortletLink("p-cactions",
        "#",
        libelle,
        id,
        desc);

    $('#' + id).on('click', function (e) {
        fn(e);
    });
}

MAIN_NS = 0;
AUTEUR_NS = 102;
// AUTEUR_NS_TALK = 103;
 
$(function ($) {
    WIKIDATAFY_WIKISOURCE_URL = `https://${mw.config.get('wgServerName')}`;
    const editbox = $("#wpTextbox1");
    const ns = mw.config.get('wgNamespaceNumber');
    const script_url = getScriptURL();
    // Désactivation de la vérification automatique pour une vérification depuis mobile
    // automatique = (script_url != null && script_url.match(/(fr\.m\.wikisource|[\&\?]auto=non(\&|\b))/) ? false : true);
    automatique = (location.href.match(/fr\.m\.wikisource/) 
                   || (script_url != null 
                       && script_url.match(/[\&\?]auto=non(\&|\b)/))) ? false : true;
    if(ns == AUTEUR_NS) {
// Pas sûr que ce soit vraiment utile
//            addCommande("(WDfy) Œuvres de l’auteur",
//                "sd-auteur",
//                "Œuvres de l’auteur",
//                auteur);
        addCommande("(WDfy) Cohérence des données",
            "sd-wdverif",
            "Vérifier la qualité des informations dans Wikidata",
            wdverif_auteur);
    }
    else if(ns == MAIN_NS) {
        addCommande("(WDfy) Vérifier l'élément Wikidata",
            "sd-coherence-page",
            "Élément Wikidata d'une transclusion",
            wdverif_page);
        if(automatique) {
            wdverif_page(null, true);
        }
    }
})

})