MediaWiki:Common.js

Un livre de Wikilivres.

Attention : Depuis MediaWiki 1.18 les pages se terminant avec l'extension .js ou .css sont interprétées comme des pages wiki ! En particulier les modèles (subst ou non) et les liens. Vous devez donc migrer le code source et effectuer vos changements en évitant ces éléments de syntaxe wiki (peu importe leurs emplacements dans le code source : commentaire, chaine) :

  • Double accolades ouvrantes (en particulier avec subst:) : séparer les deux accolades "{"+"{" du reste de la chaine
  • Double crochets ouvrants : même technique de séparation.
  • Signature (tildes ~ multiples) : même technique de séparation.

Note : après avoir enregistré vos préférences, vous devrez attendre que le serveur mette à jour la feuille de style globale avant de 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 ;
  • Konqueror : Cliquez sur Actualiser ou pressez F5 ;
  • Opera : Videz le cache dans Outils → Préférences.
/*jshint undef:true, eqnull:true */
wgfrwikibooks_CommonJsVersion = "20230825002";
function wgfrwikibooks_CommonJs_getVersion(){ return wgfrwikibooks_CommonJsVersion; }
/**
 * Les ECMAscripts présents sur cette page s'appliquent à tous les contributeurs, enregistrés ou non.
 *
 * - Si vous voulez utiliser des scripts personnels utilisez votre page de scripts personnelle :
 *      /Special:Mypage/common.js
 *      /Special:Mypage/vector.js
 *      /Special:Mypage/monobook.js
 *      ...
 * - Vous pouvez proposer en page de discussion des modifications à apporter à cette page.
 * - Si vous souhaitez désactiver une fonctionnalité, enlevez-la de la table onLoadFuncts
 *   en utilisant la fonction removeOnloadHook(func)
 * - cette page est structurée ainsi :
 *   * la première partie donne des fonctions qui étendent les fonctionnalités de javascript lui-même
 *     (fonctions sur les classes, les chaines de caractères...) ;
 *   * la deuxième apporte des améliorations à /w/skins/common/wikibits.js
 *     (fonctions facilitant la manipulation de mediawiki) ;
 *   * la troisième contient les applications en elles-mêmes, c'est à dire les fonctionnalités.
 *     Elles sont séparées entre les scripts actifs à la lecture (utiles à tous)
 *     et ceux utiles pour les contributeurs (qui s'activent principalement à l'édition)
 *     et qui ont vocation à être migrés vers les gadgets.
 *
 * Voir aussi
 * - /wiki/Mediawiki:Common.css
 * - /wiki/Mediawiki:Monobook.css
 *
 * NOTES aux programmeurs :
 * - Pour ajouter de nouvelles fonctionnalités, il est préférable de créer un script séparé,
 *   appelé depuis celui-ci, afin d'éviter le passage par le vérificateur buggué de Mediawiki qui peut
 *   transformer un code javascript en charabia de syntaxe incorrecte.
 * - Les scripts doivent être indépendants des fonctions de Mediawiki car celles-ci peuvent devenir
 *   indisponibles ou modifiées dans une version ultérieure de Mediawiki.
 * - Évitez les comparaisons directes avec className qui peut contenir plusieurs noms de classe CSS,
 *   mais utiliser la fonction hasClass(element, classe)
 * - CamelCase + K&R bracing + 4 espaces d'indentation + commentaires doxygen
 *
 */


/************************************************************/
/* Fonctions générales (pallient les limites de javascript) */
/************************************************************/


/**
 * Traitement des classes
 * Javascript ne fournit actuellement que .className qui est extrêmement limité
 * surveiller l'implémentation de .classList http://www.w3.org/TR/2008/WD-html5-diff-20080122/#htmlelement-extensions
 */

function whichClass(element, classes) {
    var s=" "+element.className+" ";
    for (var i=0; i<classes.length; i++)
        if (s.indexOf(" "+classes[i]+" ")>=0) return i;
    return -1;
}

function hasClass(node, classe) {
    var s=" "+node.className+" ";
    return s.indexOf(" "+classe+" ") >= 0;
}

function addClass(node, className) {
    if (hasClass(node, className)) return false;
    node.className += ' '+ className;
    return true;
}

function removeClass(node, className) {
    if (!hasClass(node, className)) return false;
    node.className = eregReplace('(^|\\s+)'+ className +'($|\\s+)', ' ', node.className);
    return true;
}

function eregReplace(search, replace, subject)
{ return subject.replace(new RegExp(search,'g'), replace); }

function getElementsByClassName(node,tag,searchClass)
{ return $.makeArray( $(node).find(tag+'.'+searchClass) ); }

/**
 * rechercher les éléments de la page dont le paramètre "class" est celui recherché
 * http://developer.mozilla.org/en/docs/DOM:document.getElementsByClassName
 */
function getElementsByClass(searchClass,node,tag) {
    if ( node == null ) node = document;
    if ( tag == null ) tag = '*';
    return getElementsByClassName(node, tag, searchClass);
}

/**
 * Fonctions de traitement de chaînes
 */
function equalsAa(str1, str2) {
    return str1.toUpperCase() == str2.toUpperCase();
}

function startsWithAa(string, prefix) {
    return equalsAa(string.substring(0, prefix.length), prefix);
}

function substractPrefixAa(string, prefix) {
    return startsWithAa(string, prefix) ? string.substring(prefix.length) : string;
}

function endsWithAa(string, prefix) {
    return (string.length>=prefix.length) &&
        equalsAa(string.substring(string.length-prefix.length), prefix);
}

function substractSuffixAa(string, prefix) {
    return endsWithAa(string, prefix) ? string.substring(0,string.length-prefix.length) : string;
}

function startsWith(string, prefix) {
    return string.substring(0, prefix.length) == prefix;
}

function substractPrefix(string, prefix) {
    return startsWith(string, prefix) ? string.substring(prefix.length) : string;
}

function endsWith(string, prefix) {
    return (string.length>=prefix.length) &&
        (string.substring(string.length-prefix.length) == prefix);
}

function substractSuffix(string, prefix) {
    return endsWith(string, prefix) ? string.substring(0,string.length-prefix.length) : string;
}

// Extension de la classe String :
String.prototype.trimLeft = function() { return this.replace(/^\s\s*/, ''); };
String.prototype.trimRight = function() {
    var str = this,
        ws = /\s/,
        i = str.length;
    while (ws.test(str.charAt(--i)));
    return str.slice(0, i + 1);
};
if (!String.prototype.trim) {
  String.prototype.trim = function() { return this.trimLeft().trimRight(); };
}
String.prototype.equalAa = function(other) { return equalsAa(this,other); };
String.prototype.startsAa = function(prefix) { return startsWithAa(this,prefix); };
String.prototype.endsAa = function(prefix) { return endsWithAa(this,prefix); };
String.prototype.popAa = function(prefix,suffix) {
    var s = this;
    if (prefix) s=substractPrefixAa(s,prefix);
    if (suffix) s=substractSuffixAa(s,suffix);
    return s;
};
String.prototype.starts = function(prefix) { return startsWith(this,prefix); };
String.prototype.ends = function(prefix) { return endsWith(this,prefix); };
String.prototype.pop = function(prefix,suffix) {
    var s = this;
    if (prefix) s=substractPrefix(s,prefix);
    if (suffix) s=substractSuffix(s,suffix);
    return s;
};



/*
-------------------------------------------------------------------
Fonctions DOM étendues

    Fonctions utiles pour générer les éléments en utilisant DOM
    plus rapides et sûres que d'utiliser innerHTML.
-------------------------------------------------------------------
*/

function style_name(name)
{ return name.replace(/([A-Z])/g,"-$1").toLowerCase(); }

function styleName(name)
{
    var name_parts=name.split(/(-[a-z])/g);
    for(var i=0;i<name_parts.length;i++)
        if (name_parts[i].starts('-'))
            name_parts[i] = name_parts[i].substring(1).toUpperCase();
    return name_parts.join('');
}

// Obtenir la classe d'un objet
function getObjectClassName(obj)
{
    if (obj===undefined) return undefined;
    if (obj===null) return null;
    try{
    if (obj && obj.constructor && obj.constructor.toString)
    {
        var p = obj.constructor.toString().match(/function\s*(\w+)/);
        if (p && p.length == 2) return p[1];
    }}catch(e){}
    var s = obj.toString();
    if (s.substring(0,8)=="[object ") return s.substring(8,s.length-1);
    return typeof obj;
}

/*
    Facilite la construction DOM (Document Object Model).
    def: Définition des nœuds à générer.

         DOM node -> DOM node
         "..."    -> array of DOM text node and <br/> elements
         [ "nom", { "N": "V" }, ... ]  -> <nom N="V">...</nom>
*/
function genDOM(def)
{
    while (getObjectClassName(def)=="Function") def=def();
    if (typeof def =="string")
    {
      // Remplacement des \n par des <br />
      var res=[];
      var lines=def.split("\n");
      for(var i=0;i<lines.length;i++)
      {
        if (i>0) res.push(document.createElement("br"));
        if (lines[i].length>0) res.push(document.createTextNode(lines[i]));
      }
        return res;
    }
    if (getObjectClassName(def)=="Array")
    {
        var e = document.createElement(def[0]);
        var j = 1;
        if ((def.length>1)&&(getObjectClassName(def[1])=="Object"))
        {
            var attrs = def[1];
            j++;
            for(var name in attrs)
            {
                if ((name=="style")&&(typeof attrs[name] =="object"))
                {
                    var styles = attrs[name];
                    for(var nom in styles)
                    {
                        e.style[nom] = styles[nom];
                        var autre = styleName(nom); if (autre!=nom) e.style[autre] = styles[nom];
                        autre = style_name(nom); if (autre!=nom) e.style[autre] = styles[nom];
                    }
                }
                else if ((name.substring(0,2)=="on")&&(typeof(attrs[name])=="function"))
                    e.addEventListener(name.substring(2), attrs[name]);
                else e.setAttribute(name,attrs[name]);
            }
        }
        for(;j<def.length;j++)
        {
            var n = genDOM(def[j]);
            if (getObjectClassName(n)=="Array")
            {
                for(k=0;k<n.length;k++)
                    e.appendChild( n[k] );
            }
            else e.appendChild( n );
        }
        return e;
    }
    else return def;
}

/*
    Syntaxe :
      node, def, def, ...
*/
function appendDOM(e)
{
    for(var i=1;i<arguments.length;i++)
    {
        var n = genDOM(arguments[i]);
        if (getObjectClassName(n)=="Array")
        {
            for(k=0;k<n.length;k++)
                e.appendChild( n[k] );
        }
        else e.appendChild( n );
    }
}

function setNodeAt(e, pos, node) {
    if (!e.hasChildNodes() || pos >= e.childNodes.length) e.appendChild(node);
    else e.insertBefore(node, e.childNodes[pos]);
}

/*
    Syntaxe :
      node, position, def, def, ...
*/
function setDomAt(e, pos)
{
    for(var i=2;i<arguments.length;i++)
    {
        var n = genDOM(arguments[i]);
        if (getObjectClassName(n)=="Array")
        {
            for(k=0;k<n.length;k++)
                setNodeAt(e, pos++, n[k]);
        }
        else setNodeAt(e, pos++, n);
    }
}

/**
 * retourne la valeur d'un attribut html, sinon une valeur par défaut
 */
function getAttr(node, name, defvalue) {
    if (!node.attributes) return defvalue;
    var v=node.attributes.getNamedItem(name);
    return (v&&v.value.length)?v.value:defvalue;
}

function removeAllSubnodes(node)
{
    while (node.hasChildNodes())
        node.removeChild(node.lastChild);
}

function setDOM(e)
{
    removeAllSubnodes(e);
    for(var i=1;i<arguments.length;i++)
    {
        var n = genDOM(arguments[i]);
        if (getObjectClassName(n)=="Array")
        {
            for(k=0;k<n.length;k++)
                e.appendChild( n[k] );
        }
        else e.appendChild( n );
    }
}


/**
 * Déterminer l'adresse du serveur à utiliser dans les liens quand fullurl:page n'est pas utilisable.
 */

var wgProtocol = '';
// Nouveau type d'URL relative gérée par MediaWiki
if (mw.config.get('wgServer').starts('//')) {
    var url = location.href;
    var i = url.indexOf('://'); // position of first ://
    wgProtocol = url.substring(0, i);
} else {
    var i = mw.config.get('wgServer').indexOf('://'); // position of first ://
    wgProtocol = mw.config.get('wgServer').substring(0, i);
}

/*
 * Détection des fausses pages d'utilisateur (pas de compte associé).
 */
function detectUserPage()
{
  if (!document.getElementById(mw.config.get("skin")=="minerva" ? "page-actions-contributions" : "t-contributions"))
  {
    var e = document.getElementById("ca-edit");
    if (e) e.parentNode.removeChild(e);
    e = document.getElementById("ca-edit-0");
    if (e) e.parentNode.removeChild(e);
    e = document.getElementById("ca-addsection");
    if (e) e.parentNode.removeChild(e);
    e = document.getElementById("ca-talk");
    if (e) e.parentNode.removeChild(e);
    e = document.getElementById("wpSave"); 
    if (e) e.parentNode.removeChild(e);
    alert("Fausse page d'utilisateur détectée.\nL'utilisateur \""+mw.config.get('wgTitle')+"\" n'existe pas.");
  }
}

if ([2,3].includes(mw.config.get('wgNamespaceNumber'))) // Utilisateur & Discussion Utilisateur
{
  $(detectUserPage);
}

/**
 * Recherche d'un élément contenu dans base
 * dont le nom est name
 * @param base Nœud contenant l'élément recherché
 * @param name Nom de l'élément recherché
 */
function getFirstElement(base,name)
{
    if (!base) return null;
    return getNextElement(base.firstChild, name, true);
}

/**
 * Recherche du prochain élément après base
 * dont le nom est name
 * @param base Nœud précédemment trouvé
 * @param name Nom de l'élément recherché
 * @param testbase (optionnel) true pour tester le nœud donné.
 */
function getNextElement(base,name,testbase)
{
    while (base!=null)
    {
        if (testbase && (base.nodeType==1) && equalsAa(base.nodeName,name)) return base;
        testbase=true;
        base=base.nextSibling;
    }
    return null;
}

function devdebug(msg) {
    if (typeof _devdebug =="function") _devdebug(msg);
}


/************************************************************/
/* URL des pages                                            */
/************************************************************/


/** Nom de page -> URL */
function urlOfWikiPage(pagename, server)
{
    if (typeof server == "undefined") server = ""; // relative URL
    else server=wgProtocol+"://"+server; // same protocol
    pagename = pagename.replace("?", "%3F");
    return server + mw.config.get('wgArticlePath').replace("$1", pagename);
}

function encodeParameter(parameter)
{
    var i = parameter.indexOf('=');
    return parameter.substring(0,i+1) + encodeURIComponent(parameter.substring(i+1));
}

// -2 Média, -1 Spécial, 0 (principal), 1 Discussion, 2 Utilisateur, 3 Discussion utilisateur
// 4 Wikilivres, 5 Discussion Wikilivres, 6 Fichier, 7 Discussion fichier,
// 8 MediaWiki, 9 Discussion MediaWiki, 10 Modèle, 11 Discussion modèle,
// 12 Aide, 13 Discussion aide, 14 Catégorie, 15 Discussion catégorie, 100 Transwiki, 101 Discussion Transwiki

// Pour des scripts internationaux :
function getpagename(ns_index, pagename)
{
    return mw.config.get('wgFormattedNamespaces')[""+ns_index]+":"+pagename;
}

function localurl(pagename /*, parameters... */)
{
    // If there is no more parameter than the pagename, use wiki/...
    if (arguments.length==1) return mw.config.get('wgArticlePath').replace("$1", pagename.replace("?", "%3F"));
    // Otherwise use /w/index.php?...
    var url = mw.config.get('wgScript') + '?title=' + encodeURIComponent(pagename);
    for( var i = 1; i < arguments.length; i++ )
        url = url + '&' + encodeParameter(arguments[i]);
    return url;
}


// Get the ISO lang code preceeding the pagename,
// otherwise might be short namespace name

var all_langs = [
// Généré automatiquement
//    voir [[MediaWiki:Gadget-DevTools/Code]]

"aa","ab","ace","af","ak","als","am","an","ang","ar","arc","arz","as","ast","av","ay","az","ba","bar","bat-smg","bcl","be","be-x-old","bg","bh","bi","bjn","bm","bn","bo","bpy","br","bs","bug","bxr","ca","cbk-zam","cdo","ce","ceb","ch","cho","chr","chy","ckb","co","cr","crh","cs","csb","cu","cv","cy","cz","da","de","diq","dk","dsb","dv","dz","ee","el","eml","en","eo","epo","es","et","eu","ext","fa","ff","fi","fiu-vro","fj","fo","fr","frp","frr","fur","fy","ga","gag","gan","gd","gl","glk","gn","got","gu","gv","ha","hak","haw","he","hi","hif","ho","hr","hsb","ht","hu","hy","hz","ia","id","ie","ig","ii","ik","ilo","io","is","it","iu","ja","jbo","jp","jv","ka","kaa","kab","kbd","kg","ki","kj","kk","kl","km","kn","ko","koi","kr","krc","ks","ksh","ku","kv","kw","ky","la","lad","lb","lbe","lez","lg","li","lij","lmo","ln","lo","lt","ltg","lv","map-bms","mdf","mg","mh","mhr","mi","min","minnan","mk","ml","mn","mo","mr","mrj","ms","mt","mus","mwl","my","myv","mzn","na","nah","nan","nap","nb","nds","nds-nl","ne","new","ng","nl","nn","no","nov","nrm","nso","nv","ny","oc","om","or","os","pa","pag","pam","pap","pcd","pdc","pfl","pi","pih","pl","pms","pnb","pnt","ps","pt","qu","rm","rmy","rn","ro","roa-rup","roa-tara","ru","rue","rw","sa","sah","sc","scn","sco","sd","se","sg","sh","si","simple","sk","sl","sm","sn","so","sq","sr","srn","ss","st","stq","su","sv","sw","szl","ta","te","tet","tg","th","ti","tk","tl","tn","to","tpi","tr","ts","tt","tum","tw","ty","udm","ug","uk","ur","uz","ve","vec","vep","vi","vls","vo","wa","war","wo","wuu","xal","xh","xmf","yi","yo","za","zea","zh","zh-cfr","zh-classical","zh-min-nan","zh-yue","zu"

];

// defcode (optionnal): generally wgContentLanguage
// return [newpagename, langcode]
function getLangCode(pagename, defcode)
{
    var i = pagename.indexOf(':');
    if (i<0) return [pagename, defcode];
    var code = pagename.substring(0,i);
    return (all_langs.indexOf(code)<0) ? [pagename, defcode] : [pagename.substring(i+1), code];
}

// All project configuration
var wm_projects_url_info =
{        // ..._p = prefix   ..._s = suffix
'http':  {'url_p':'http://', 'pagewiki_s':'/wiki/', 'pagescript_s':'/w/index.php?title=',
          'projects':{

              // [before language code, after language code]
              'w:' :       ['', '.wikipedia.org'],
              'b:' :       ['', '.wikibooks.org'],
              's:' :       ['', '.wikisource.org'],
              'q:' :       ['', '.wikiquote.org'],
              'v:' :       ['', '.wikiversity.org'],
              'wikt:' :    ['', '.wiktionary.org'],
              'n:' :       ['', '.wikinews.org'],

              // [without language code]
              'wmf:' :     ['wikimediafoundation.org'],
              'mw:' :      ['www.mediawiki.org'],
              'meta:' :    ['meta.wikimedia.org'],
              'm:' :       ['meta.wikimedia.org'],
              'commons:' : ['commons.wikimedia.org'],
              'species:' : ['species.wikimedia.org'],

              // fullurl specified
              'bugzilla:' : 'https://bugzilla.wikimedia.org/show_bug.cgi?id='
          }},

'https':  {'url_p':'https://', 'pagewiki_s':'/wiki/', 'pagescript_s':'/w/index.php?title=',
          'projects':{

              // [before language code, after language code]
              'w:' :       ['', '.wikipedia.org'],
              'b:' :       ['', '.wikibooks.org'],
              's:' :       ['', '.wikisource.org'],
              'q:' :       ['', '.wikiquote.org'],
              'v:' :       ['', '.wikiversity.org'],
              'wikt:' :    ['', '.wiktionary.org'],
              'n:' :       ['', '.wikinews.org'],

              // [without language code]
              'wmf:' :     ['wikimediafoundation.org'],
              'mw:' :      ['www.mediawiki.org'],
              'meta:' :    ['meta.wikimedia.org'],
              'm:' :       ['meta.wikimedia.org'],
              'commons:' : ['commons.wikimedia.org'],
              'species:' : ['species.wikimedia.org'],

              // fullurl specified
              'bugzilla:' : 'https://bugzilla.wikimedia.org/show_bug.cgi?id='
          }}
};

function fullurl(pagename /*, parameters... */) {
    var url;
    var i;
    if (pagename.starts('wm')) {
    // special case
        i = pagename.indexOf(':');
        if (i>=0) {
            try{
                var year = parseInt(pagename.substring(2,i));
                if (year==pagename.substring(2,i) && year>=2005) {
                    pagename = pagename.substring(i+1);
                    url = 'http://wikimania'+year+'.wikimedia.org';
                    // If there is no more parameter than the pagename, use wiki/...
                    if (arguments.length==1) {
                      return url + '/wiki/' + pagename.replace("?", "%3F");
                    }
                    // Otherwise use /w/index.php?...
                    url += '/w/index.php?title=' + encodeURIComponent(pagename);
                }
            }catch(e){}
        }
    }
    if (typeof url =='undefined') {
        var info = wm_projects_url_info[wgProtocol];
        var projects = info.projects;
        for(var prefix in projects)
            if (pagename.starts(prefix))
            {
                pagename = pagename.substring(prefix.length);
                var project = projects[prefix];
                if (typeof project =='string') // full url with GET form
                    url = project + encodeURIComponent(pagename);
                else
                {
                    url = info.url_p + project[0];
                    if (project.length==2)
                    {
                        var pagecode = getLangCode(pagename, mw.config.get('wgContentLanguage'));
                        url += pagecode[1] + project[1]; pagename = pagecode[0];
                    }
                    if (arguments.length==1) url += info.pagewiki_s + pagename.replace("?", "%3F");
                    else url += info.pagescript_s + encodeURIComponent(pagename);
                }
                break;
            }
        if (typeof url =='undefined')
        {
            // If there is no more parameter than the pagename, use wiki/...
            if (arguments.length==1) return mw.config.get('wgServer') + mw.config.get('wgArticlePath').replace("$1", pagename.replace("?", "%3F"));
            // Otherwise use /w/index.php?...
            url = mw.config.get('wgServer') + mw.config.get('wgScript') + '?title=' + encodeURIComponent(pagename);
        }
    }
    for( i = 1; i < arguments.length; i++ )
        url = url + '&' + encodeParameter(arguments[i]);
    return url;
}


/*******************************************************************************************************
  fr: Empêcher les erreurs durant l'appel à une fonction d'interrompre les autres scripts Javascript.
  en: Prevent errors in a called function from breaking remaining javascript code.
*******************************************************************************************************/

/*
  fr: Collecte les erreurs détectées.
  en: Collect detected errors.
*/
var collectedErrors=[];

function functionName(fn) {
  var name=/\W*function\s+([\w\$]+)\(/.exec(fn);
  if (!name) return '';
  return name[1];
}


/*
  fr: Empêcher les erreurs durant l'appel à une fonction d'interrompre les autres scripts Javascript.
  en: Prevent errors in a called function from breaking remaining javascript code.
*/
function protectedCall( hookFunct ) {
  try{ hookFunct(); }
  catch(e) {
    collectedErrors.push(e);
    devdebug("ERROR in function "+functionName(hookFunct)+": "+e);
    // Obtenir les détails de l'erreur.
    // Tous les navigateurs n'ont pas les mêmes informations :
    //   - IE : message, description(==message), number, name
    //   - FF : fileName, lineNumber
    for(var detail in e) devdebug("    - "+detail+" = "+e[detail]);
  }
}

var doneOnload = false;
var onloadProtectedFuncts = [];

/**
    fr: Lance les fonctions on load en mode protégé
    en: Run the on load functions in protected mode
*/
function protectedRunOnloadHooks() {
  doneOnload = true;
  var i;
  for ( i = 0; i < onloadProtectedFuncts.length; i++ )
    protectedCall(onloadProtectedFuncts[i]);
}

$(protectedRunOnloadHooks);

/**
    fr: Ajout de fonctions dans onloadProtectedFuncts
    en: Add functions in onloadProtectedFuncts
*/
addOnloadHook = function ( hookFunct ) {
  // Allows add-on scripts to add onload functions
  if( !doneOnload ) {
    onloadProtectedFuncts.push(hookFunct);
  } else {
    protectedCall(hookFunct);  // bug in MSIE script loading
  }
};

function removeOnloadHook(func) {
  var i;
  for(i in onloadProtectedFuncts)
    if (onloadProtectedFuncts[i]==func) {
      onloadProtectedFuncts.splice(i, 1);
      return;
    }
}

/******************************************************************************************************/


/************************************************************************/
/* Fonctions générales mediawiki (pallient les limitations du logiciel) */
/************************************************************************/

/**
 * Insérer un javascript d'une page particulière, idée de Mickachu
 */
loadJs = importScript;

function obtenir(nom) {
    loadJs('MediaWiki:JSScripts/' + nom);
}

/**
 * Insérer une feuille de style d'une page particulière, idée de DavidL
 */
loadCss = importStylesheet;

function obtenirCss(nom) {
    loadCss('MediaWiki:CSS/' + nom);
}


/**
 * Teste si la page fait partie d'un livre donné
 * Ne pas oublier d'ajouter le séparateur utilisé à la fin de title
 * (en général / , : ou (espace))
 */
function inBook(title) {
    return (mw.config.get('wgTitle')==title.substring(0,title.length-1)) ||
           mw.config.get('wgTitle').substr(0, title.length) == title;
}

/**
 * Renvoie le nom du livre
 */
function getBookName() {
    var i = mw.config.get('wgTitle').indexOf('/');
    return (i<0) ? mw.config.get('wgTitle') : mw.config.get('wgTitle').slice(0, i);
}


/************************************************************/
/* Utilisateurs                                             */
/************************************************************/

/**
 * Tester si une chaine est une adresse IPv4/v6.
 */
function isIP(s)
// Partial MediaWiki IPv6 format :
{ return /^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))(\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))){3}|(0|[1-9A-F][0-9A-F]{0,3})([:](0|[1-9A-F][0-9A-F]{0,3})){7})$/.test(s); }
// Full standard IPv6 format :
//{ return /^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))(\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))){3}|[0-9A-Fa-f]{1,4}([:][0-9A-Fa-f]{1,4}){7}|[:]([:]|([:][0-9A-Fa-f]{1,4}){1,7})|[0-9A-Fa-f]{1,4}[:]([:]|([:][0-9A-Fa-f]{1,4}){1,6})|([0-9A-Fa-f]{1,4}[:]){2}([:]|([:][0-9A-Fa-f]{1,4}){1,5})|([0-9A-Fa-f]{1,4}[:]){3}([:]|([:][0-9A-Fa-f]{1,4}){1,4})|([0-9A-Fa-f]{1,4}[:]){4}([:]|([:][0-9A-Fa-f]{1,4}){1,3})|([0-9A-Fa-f]{1,4}[:]){5}([:]|([:][0-9A-Fa-f]{1,4}){1,2})|([0-9A-Fa-f]{1,4}[:]){6}([:]|([:][0-9A-Fa-f]{1,4}))|([0-9A-Fa-f]{1,4}[:]){7}[:]|(([0-9A-Fa-f]{1,4}[:]){6}|[:][:]([0-9A-Fa-f]{1,4}[:]){0,5}|[0-9A-Fa-f]{1,4}[:][:]([0-9A-Fa-f]{1,4}[:]){0,4}|([0-9A-Fa-f]{1,4}[:]){2}[:]([0-9A-Fa-f]{1,4}[:]){0,3}|([0-9A-Fa-f]{1,4}[:]){3}[:]([0-9A-Fa-f]{1,4}[:]){0,2}|([0-9A-Fa-f]{1,4}[:]){4}[:]([0-9A-Fa-f]{1,4}[:]){0,1}|([0-9A-Fa-f]{1,4}[:]){5}[:])([0-9]|[1-9][0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))(\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))){3})$/.test(s); }


/**
 * Teste le groupe de l'utilisateur
 */
function usergroup(groupname) {
    if (!mw.config.get('wgUserGroups')) return false;
    for(var i in mw.config.get('wgUserGroups'))
        if (mw.config.get('wgUserGroups')[i]==groupname) return true;
    return false;
}

/**
 * Moyen efficace de connaitre tous les droits de l'utilisateur
 * en une variable (filtrage selon les droits plus simple).
 */
var wgUserLevel =
    (usergroup("user")?1:0) |
    (usergroup("autoconfirmed")?2:0) |
    (usergroup("sysop")?4:0) |
    (usergroup("bureaucrat")?8:0) ;

/**
 * Tester le niveau utilisateur
 * levelmask :
 *   bits 7-4 = masque de bits
 *   bits 3-0 = valeur de wgUserLevel à obtenir après masquage (et binaire).
 * ex: 0x40 pour les non sysops
 * Retourne true si la valeur correspond, false sinon.
 */
function userHasLevel(levelmask)
{ return (wgUserLevel & (levelmask>>>4)) == (levelmask & 0xF); }

/**
 * Indicateur de page de discussion ou considérée comme telle
 */
var wgIsTalkPage =
   (((mw.config.get('wgNamespaceNumber')>0)&&(mw.config.get('wgNamespaceNumber')&1)) // Page de discussion
   ||(mw.config.get('wgTitle').ends("Livre d'or")) // ou livre d'or
   ||((mw.config.get('wgNamespaceNumber')==4)&&(inBook("Le Bistro/") // ou bistro
       ||inBook("Requêtes aux ") // ou page de requêtes
       ||inBook("Candidature ") // ou candidature
       ||inBook("Patrouilleur/") // ou candidature patrouilleur
       ||inBook("Bureaucrate/") // ou candidature bureaucrate
       ||inBook("Administrateurs/") // ou candidature administrateur
       ||inBook("Patrouilleurs/") // ou candidature patrouilleur
       ||inBook("Bureaucrates/") // ou candidature bureaucrate
       ||inBook("Prise de décision/") // ou prise de décision
       ||inBook("Pages à ") // ou votes
       ||inBook("Livre d'or/") // ou livre d'or
   )));

/**
 * Indicateurs de page utilisateur (page ou discussion)
 */
var wgIsUser = (mw.config.get('wgNamespaceNumber')>>1)==1 ;
var wgIsUserIP = wgIsUser && isIP(mw.config.get('wgTitle'));
var wgIsUserNotIP = wgIsUser && !wgIsUserIP;

/**
 * Indicateur de page en cours d'édition
 */
var wgIsEditingPage = (mw.config.get('wgAction') == "edit") || (mw.config.get('wgAction') == "submit"); // modification ou aperçu
var wgIsViewingPage = (mw.config.get('wgAction') == "view") || (mw.config.get('wgAction') == "submit"); // vue ou aperçu

/** Indicateur de page de script (*.js, *.css) */
var wgIsScriptPage = !wgIsTalkPage && (mw.config.get('wgTitle').ends('.js') || mw.config.get('wgTitle').ends('.css'));


/************************************************************/
/* Applications : les fonctionnalités client en elles-mêmes */
/************************************************************/

/**
 * Boites déroulantes
 *
 * @todo : Réécrire
 * @author :
 */

// set up max count of Navigation Bars on page,
// if there are more, all will be hidden
// NavigationBarShowDefault = 0; // all bars will be hidden
// NavigationBarShowDefault = 1; // on pages with more than 1 bar all bars will be hidden
var NavigationBarShowDefault = 0;
var toggleNavText=['[ Masquer ]','[ Afficher ]'];

// shows and hides content and picture (if available) of navigation bars
function toggleNavigationBar(indexNavigationBar)
{
    var NavFrame = document.getElementById("NavFrame" + indexNavigationBar);
    var NavChild;

    if (!NavFrame) return false;

    for ( NavChild = NavFrame.firstChild;
          NavChild != null;
          NavChild = NavChild.nextSibling )
        if ( whichClass(NavChild,['NavPic','NavContent'])>=0 )
            NavChild.style.display = NavChild.style.display=='none' ? 'block' : 'none';
}

// adds show/hide-button to navigation bars
function createNavigationBarToggleButton()
{
    var indexNavigationBar = 0;
    // catch every "NavFrame" class
    var all_nav_frames = getElementsByClass("NavFrame");
    if (all_nav_frames)
    for(var i=0 ; i<all_nav_frames.length ; i++)
    {
        NavFrame = all_nav_frames[i];
        indexNavigationBar++;

        // Find the NavHead and attach the toggle link (Must be this complicated because Moz's firstChild handling is borked)
        for( var j=0; j < NavFrame.childNodes.length; j++)
            if (hasClass(NavFrame.childNodes[j],"NavHead"))
            {
                var navhead = NavFrame.childNodes[j];
                navhead.addEventListener('click',new Function('toggleNavigationBar(' + indexNavigationBar + ');'));
                break;
            }
        NavFrame.setAttribute('id', 'NavFrame' + indexNavigationBar);
    }
    // if more Navigation Bars found than Default: hide all
    if (NavigationBarShowDefault < indexNavigationBar)
        for(var i=1; i<=indexNavigationBar; i++)
            toggleNavigationBar(i);
}

if (wgIsViewingPage)
    $(createNavigationBarToggleButton);

// -------------------------------------------------------------------------

// Bouton de bascule d'affichage des chapitres dans la barre de navigation

function toggleChap(button, content)
{
	if (content.style.display == 'none')
	{
		content.style.display = '';
		button.classList.replace('nav-chap-open', 'nav-chap-close')
	}
	else
	{
		content.style.display = 'none';
		button.classList.replace('nav-chap-close', 'nav-chap-open')
	}
}
function createToggleChap()
{
	var buttons = document.querySelectorAll(".nav-chap-open");
	var chaps = document.querySelectorAll(".nav-chap");
	for(var i=0 ; i<buttons.length ; i++)
	(function () {
		var but = buttons[i];
		var chap = chaps[i];
		buttons[i].addEventListener('click', function(){toggleChap(but, chap)});
	})();
}

if (wgIsViewingPage)
    $(createToggleChap);

// -------------------------------------------------------------------------

/**
 * Masque les solutions des exercices et permet leur affichage par un clic
 * Remarque : pourrait hériter d'une classe commune aux boites déroulantes ?
 * @author : fr:Tavernier
 * @todo : compatibilité IE/Opera
 */
function solutionExercices() {
    if (navigator.userAgent.indexOf('MSIE') !=-1)
        return false; //en attendant IE 8 ?
    var exercices = getElementsByClass("exercice");
    for (var exercice=0; exercice<exercices.length; exercice++)
    {
        for (var exoElementIter=0; exoElementIter<exercices[exercice].childNodes.length; exoElementIter++)
        {
            var exoElement = exercices[exercice].childNodes[exoElementIter];
            if (exoElement.textContent == "Solution" || exoElement.innerText == "Solution")
            {
                setDOM(exoElement, ["strong","Solution"], " ", ["span",{"style":{"color":"grey"}}, "(cliquez)"]);
                exoElement.style.cursor = "pointer";
                solution = exoElement.nextSibling.nextSibling; // càd \n puis ol
                solution.style.display = "none";
                addClass(solution, "solution");
                exoElement.addEventListener( //valides DOM
                    "click",
                    function(event){this.nextSibling.nextSibling.style.display="block";},
                    false
                );
            }
        }
    }
}

if (wgIsViewingPage)
    $(solutionExercices);

// -------------------------------------------------------------------------

/**
    Déplacer tous les éléments de classe "content-footer" en fin de page.
*/
function createFooters() {
    var ends = getElementsByClass("printfooter");
    if ((!ends)||(ends.length<1)) return;
    var end = ends[0], content = end.parentNode;

    var footers = getElementsByClass("content-footer");
    for(var i=0;i<footers.length;i++)
    {
        content.insertBefore(footers[i], end);
        footers[i].style.display = "block";
    }
}
$(createFooters);

// -------------------------------------------------------------------------

/**
 * Ajoutez le menu pour choisir des sous-ensembles de caractères spéciaux
 * La liste est créée dynamiquement à partir des attributs id des paragraphes
 * de MediaWiki:Edittools.
 */
function addCharSubsetMenu() {
  var specialchars = document.getElementById('specialchars');

  if (specialchars) {
    var l = specialchars.getElementsByTagName('p');

    /* default subset : use a cookie */
    var n = parseInt(mw.cookie.get("default_charset") || "0");
    if (isNaN(n)||(n<0)||(n>=l.length)) n=0;

    // Définition de <select>
    var def_select = ["select",{"style": {"display":"inline"},"onchange":"chooseCharSubset(selectedIndex)"}];
    for (var i = 0; i < l.length ; i++)
    {
        var s=getAttr(l[i],"id","");
        if (s != "") def_select.push(["option", (i==n?{"selected":"1"}:{}), s]);
    }
    if (def_select.length>2)
        setDomAt(specialchars, 0, def_select);

    chooseCharSubset(n);
  }
}

/* select subsection of special characters */
function chooseCharSubset(s) {
  var l = document.getElementById('specialchars').getElementsByTagName('p');
  mw.cookie.set("default_charset", ""+s, { expires: 365 });
  for (var i = 0; i < l.length ; i++)
  {
    l[i].style.display = i == s ? 'inline' : 'none';
    l[i].style.visibility = i == s ? 'visible' : 'hidden';
  }
}

if (wgIsEditingPage) {
    $.when(mw.loader.using(['mediawiki.cookie']), $.ready).then(addCharSubsetMenu);
}


// -------------------------------------------------------------------------

/**
 * Insertion de nouveaux boutons dans la barre d'outils
 */
// Emplacements classiques des images :
url_wpcm = wgProtocol + '://upload.wikimedia.org/wikipedia/commons/';
url_wpfr = wgProtocol + '://upload.wikimedia.org/wikipedia/fr/';
url_wpen = wgProtocol + '://upload.wikimedia.org/wikipedia/en/';
url_wbfr = wgProtocol + '://upload.wikimedia.org/wikibooks/fr/';

var auto_custom_button_id = 0;

function addCustomButton(imageFile, speedTip, tagOpen, tagClose, sampleText) {
    if (mw.toolbar)
        mw.toolbar.addButton(imageFile, speedTip, tagOpen, tagClose, sampleText, 'mw-editbutton-custom-'+(auto_custom_button_id++));
}

// Même fonction que addCustomButton en supposant que l'image est sur Wikipedia Commons
// (//upload.wikimedia.org/wikipedia/commons/)
function addCustomButtonWpCm(img,desc,topen,tclose,text) {
    addCustomButton(url_wpcm+img,desc,topen,tclose,text);
}

if (wgIsEditingPage) {
    addCustomButtonWpCm('3/30/Btn_toolbar_rayer.png','Rayer',"<s>","</s>",'');
    addCustomButtonWpCm('8/88/Btn_toolbar_enum.png','Énumération',"\n# élément 1\n# élément 2\n# élément 3","",'');
    addCustomButtonWpCm('1/11/Btn_toolbar_liste.png','Liste',"\n* élément A\n* élément B\n* élément C","",'');
    addCustomButtonWpCm('9/9e/Btn_toolbar_gallery.png','Galerie d\'images',"\n<gallery>\n","</gallery>",'M63.jpg|['+'['+'M63]'+']\nMona Lisa.jpg|['+'[La Joconde]'+']\nTruite arc-en-ciel.jpg|Une ['+'[truite'+']'+']\n');
    addCustomButtonWpCm('3/37/Btn_toolbar_commentaire.png','Commentaire',"<!--","-->",'');
    addCustomButtonWpCm('c/c8/Button_redirect.png','Redirection',"#REDIRECT ["+"[","]"+"]",'nom de la destination');
    addCustomButtonWpCm('e/ea/Button_easy_cite.png','Références',"\n==Références==\n<references/>\n","",'');
    addCustomButtonWpCm('b/b4/Button_category03.png','Catégorie','['+'[Catégorie:',']'+']','nom de la catégorie');
    addCustomButtonWpCm('2/26/Button_latinas.png','Guillemets français',"« "," »",'');
}

// -------------------------------------------------------------------------

/**
 * Noms de page non autorisés pour les utilisateurs non inscrits acceptants le javascript
 */
var forbidden=[
    [ /(\/+)$/ , "" ] ,
    [ /^(\/+)/ , "" ] ,
    [ /(\/index.php)$/ ],
    [ /^(Image\:)/ ],
    [ /(Wikilivres\:Le_Bistro)$/, "Wikilivres:Le_Bistro" ]
    ];
var n_invalid = 4; // nombre de noms de page invalides au début du tableau forbidden

function redirect(newTitle)
{
    if (!newTitle) newTitle = "Accueil";
    document.location = mw.config.get('wgArticlePath').replace("$1", newTitle);
}

function nocreatepage(newTitle, invalid)
{
    if (usergroup("sysop"))
    {
        if (invalid) alert("ATTENTION : Le nom de cette page n'est pas correct !");
        return;
    }

    if (mw.config.get('wgAction')=="edit") redirect(newTitle);
    var e = document.getElementById("_createpage");
    if (e) { e=e.parentNode; if (e) e.parentNode.removeChild(e); }
    e = document.getElementById("ca-edit");
    if (e) e.parentNode.removeChild(e);
    e = document.getElementById("ca-addsection");
    if (e) e.parentNode.removeChild(e);
    e = document.getElementById("ca-talk");
    if (e) e.parentNode.removeChild(e);
}

function forbid(re, invalid)
{
    var newTitle=(re.length>1)?mw.config.get('wgPageName').replace(re[0],re[1]):null;
    nocreatepage(newTitle, invalid);
}

function forbidpage() {
    for(var i=0; i<forbidden.length; i++)
        if (forbidden[i][0].test(mw.config.get('wgPageName'))) return forbid(forbidden[i], i<n_invalid);
}

if (mw.config.get('wgNamespaceNumber')>=0)
    $(forbidpage);


/**
 * Transformer les pages du Bistro en page de discussion
 */
function DiscussionBistro() {
    addClass(document.body, "ns-1");
}
if ( (mw.config.get('wgNamespaceNumber')==4) && inBook("Le Bistro/") )
    $(DiscussionBistro);

/**
 * Transformer certaines pages en page de discussion avec le [[modèle:page de discussion]]
 */
function TransformeEnDiscussion() {
   var transformeEnPDD = document.getElementById("transformeEnPageDeDiscussion");
   if (transformeEnPDD) addClass(document.body, "ns-1");
}
$(TransformeEnDiscussion);


/**
 *  Éviter la modification accidentelle...
 *  (même par des administrateurs)
 */
if ((mw.config.get('wgAction') == "edit")&&(mw.config.get('wgNamespaceNumber')==4)&&(mw.config.get('wgTitle')=="Le Bistro"))
{
    if (!usergroup("sysop")) location.href = fullurl("Wikilivres:Le_Bistro/"+ new Date().getFullYear(),"action=edit","section=new");
    else if (! endsWith(window.location.href,"&jsconfirm=1") )
    {
        if (confirm("ATTENTION : La page \"Le Bistro\" ne doit pas contenir de messages !\n\nOK : Vous allez être redirigé vers la page d'ajout de nouveaux messages.\n\nAnnuler : Modifier la page tout de même."))
            location.href = fullurl("Wikilivres:Le_Bistro/"+ new Date().getFullYear(),"action=edit","section=new");
        else location.href = fullurl("Wikilivres:Le_Bistro","action=edit","jsconfirm=1");
    }
}

function urlForm(name, def_value) {
    var url = location.href;
    var i = url.indexOf('?'+name+'=');
    if (i<0) i = url.indexOf('&'+name+'=');
    if (i<0) return def_value;
    i += 2 + name.length;
    var j = url.indexOf('&', i);
    return decodeURIComponent( (j<0)?url.substring(i):url.substring(i,j) );
}


/**
 * Redirection automatique des page décrivant un fichier commons
 * vers la page de description sur commons pour éviter des erreurs
 * importé de Wikilivres
 */
$( function () {
    if (mw.config.get('wgNamespaceNumber')!=6) return;
    // Search for link to commons
    // - search div with sharedUploadNotice class
    var shared_link = getElementsByClass("sharedUploadNotice");
    if ((!shared_link)||(shared_link.length<1)) return; // No sharedUploadNotice div
    shared_link = shared_link[0];
    // - external link within the div
    var links = getElementsByClass("external", shared_link, "a");
    if ((!links)||(links.length<1)) links = getElementsByClass("external", shared_link, "A");
    if ((!links)||(links.length<1)) return; // No external link
    // Get URL and redirect
    var url = getAttr(links[0], "href", null);
    if (url != null && url != undefined) location.href = url;
} );


var nsb_try=20;
/*
Retrait du bouton de signature sur les espaces de noms concernés
*/
function noSignButton() {
  var btnSig = document.getElementById('mw-editbutton-signature');
  if (btnSig) btnSig.style.display = "none";
  else if (nsb_try-- > 0) setTimeout(noSignButton, 200);
}

if (wgIsEditingPage && (mw.config.get('wgNamespaceNumber') != 2) && (!wgIsTalkPage))
  $(noSignButton);


function changeSummary(sampleText)
{document.editform.wpSummary.value = sampleText;}

function mwInsertEditButton(parent, item) {
    if (item.labelText) // not a button but a label
    {
        var lbl = document.createTextNode(item.labelText);
        parent.appendChild(lbl);
        return;
    }
    if (item.toolbarNewLine) // not a button but a line break
    {
        var n_br = document.createElement("br");
        parent.appendChild(n_br);
        return;
    }
    if (item.domNode) // not a button but a DOM node
    {
        parent.appendChild(item.domNode);
        return;
    }
    var image = document.createElement("img");
    if (item.width) image.width = item.width;           // largeur en pixels
    else if (item.widthf) image.width = 23*item.widthf; // facteur de la largeur standard
    else image.width = 23;                              // largeur standard
    image.height = 22;
    image.src = item.imageFile;
    image.border = 0;
    image.style.cursor = "pointer";
    if (item.imageId) image.id = item.imageId;
    var ref = document.createElement("a");
    var href;
    if (item.jscode != null) href="javascript:"+item.jscode+";";
    else href="javascript:insertTags(\"" + item.tagOpen + "\",\"" + item.tagClose + "\",\"" + item.sampleText + "\");";
    if (item.summary != null)
        href+="javascript:changeSummary(\"" + item.summary + "\");";
    ref.setAttribute("href", href);
    ref.setAttribute("title", item.speedTip);
    ref.appendChild(image);

    parent.appendChild(ref);
}

/**
 * IconesDeTitre : fait en sorte que le modèle  Icône de titre  puisse être utilisé plusieurs fois
 */
function IconesDeTitre() {
    var h1 = document.getElementsByTagName("h1")[0];
    var icones = document.getElementsByTagName("div");
    var icones2=[];
    var i;var j=0;
    for(i=0;i<icones.length;i++)
        if (hasClass(icones[i], "icone_de_titre"))
            icones2[j++]=icones[i];
    while (j--> 0) {
        var icon=icones2[j];
        icon.style.display = "block"; /* annule display:none par défaut */
        h1.insertBefore(icon, h1.firstChild); /* déplacement de l'élément */
    }
}

$(IconesDeTitre);


// ******************************************
/**
 * Permet d'afficher les catégories cachées pour les contributeurs enregistrés, en ajoutant un (+) à la manière des boîtes déroulantes
 */
function hiddencat()
{
 if(location.href.indexOf("printable=yes")!=-1) return;
 var cl = document.getElementById('catlinks'); if(!cl) return;
 if( !(hc = document.getElementById('mw-hidden-catlinks')) ) return;
 if( hasClass(hc, 'mw-hidden-cats-user-shown') ) return;
 var nc = document.getElementById('mw-normal-catlinks');
 if( !nc ) {
  // Copie de MediaWiki:Pagecategorieslink
  var catpage = 'Spécial:Catégories'; // 'Catégorie:Accueil';

  var catline = document.createElement('div');
  catline.id = 'mw-normal-catlinks';
  var a = document.createElement('a');
  a.href = mw.config.get('wgArticlePath').replace("$1", catpage);
  a.title = catpage;
  a.appendChild(document.createTextNode('Catégories'));
  catline.appendChild(a);
  catline.appendChild(document.createTextNode(' : '));
  nc = cl.insertBefore(catline, cl.firstChild);
 }
 else nc.appendChild(document.createTextNode(' | '));
 var lnk = document.createElement('a');
 lnk.id = 'mw-hidden-cats-link';
 lnk.title = 'Cette page contient des catégories cachées';
 lnk.style.cursor = 'pointer';
 lnk.style.color = 'black';
 lnk.onclick = toggleHiddenCats;
 lnk.appendChild(document.createTextNode('[+]'));
 hclink = nc.appendChild(lnk);
}

function toggleHiddenCats()
{
 if( hasClass(hc, 'mw-hidden-cats-hidden') )
 {
  removeClass(hc, 'mw-hidden-cats-hidden');
  addClass(hc, 'mw-hidden-cat-user-shown');
  $(hclink).text('[–]');
 }
 else
 {
  removeClass(hc, 'mw-hidden-cat-user-shown');
  addClass(hc, 'mw-hidden-cats-hidden');
  $(hclink).text('[+]');
 }
}

$(hiddencat);

// ******************************************

/* ******************************************
    Modification des liens modifier :
      div class="no_edit_links"    --> suppression
      div class="page_edit_links"  --> remplacement par lien pour modifier la page
    Utile pour l'inclusion de modèles comportant des sections.
*/

var wxScriptUrl = mw.config.get('wgServer')+mw.config.get('wgScript')+"?";

function SpecialLiensModifierDans(replace, elem)
{
    if ((elem.nodeType==1) && equalsAa(elem.nodeName,"a"))
    {
        var link = String(elem.getAttribute("href")+"&");
        if (link.starts(wxScriptUrl) || link.starts(mw.config.get('wgScript')+"?"))
        {
             if (link.starts(wxScriptUrl)) link = link.substring(wxScriptUrl.length);
             else link = link.substring(mw.config.get('wgScript').length+1);
             var form = String("&"+link);
             if (form.indexOf("&action=edit&")>=0)
             {
                 if (replace) elem.setAttribute("href", fullurl(mw.config.get('wgPageName'),"action=edit"));
                 else elem.parentNode.parentNode.removeChild(elem.parentNode);
             }
        }
    }
    else
    {
        for(var i=0 ; i<elem.childNodes.length ; i++)
            SpecialLiensModifierDans(replace, elem.childNodes[i]);
    }
}

function SpecialLiensModifier() {
    var divs, i;
    divs = getElementsByClass("no_edit_links");
    for(i=0 ; i<divs.length ; i++)
        SpecialLiensModifierDans(false, divs[i]);
    divs = getElementsByClass("page_edit_links");
    for(i=0 ; i<divs.length ; i++)
        SpecialLiensModifierDans(true, divs[i]);
}

$(SpecialLiensModifier);

// *******************************************

/**
 * Changer le lien d'import en lien vers la page [[Aide:Importer un fichier]] (depuis fr.wikipedia)
 */
function LienUpload() {
    var a = document.getElementById("p-tb");
    if (a) {
        var b = a.getElementsByTagName("ul");
        if(b.length > 0) {
            var lien = fullurl("Aide:Importer_un_fichier");
            appendDOM(b[0],
                ["li",
                    ["span",{"id":"t-upload2","title":lien},
                        ["a",{"href":lien}, "Importer image ou son"]
                    ]
                ]);
        }
    }
}

$(LienUpload);

/**
 * English : Book-wide search using Google
 * from pl.wikibooks, maintainer [[b:pl:User:Piotr]]
 * /
function insertGoogleSearch() {
    var serverName = mw.config.get('wgServerName');
    var google = "http://www.google.com/custom?sa=Google+Search&domains="+serverName+mw.config.get('wgArticlePath')+
                 "&sitesearch="+serverName+mw.config.get('wgArticlePath');
    var tb = document.getElementById('p-tb').getElementsByTagName('ul')[0];
    var link = document.createElement('a');

    var book = getBookName();

    link.href = google.replace(/\$1/g, book);
    link.appendChild(document.createTextNode("Recherche dans livre"));

    var li = document.createElement('li');
    li.id = "google-trick-search";

    li.appendChild(link);
    tb.insertBefore(li, tb.firstChild);
}

if ( mw.config.get('wgNamespaceNumber') === 0 )
    $(insertGoogleSearch);
*/

/**
 * Moteurs de recherche externes
 */
var cookieCacheRechercheExterneName = "cacheRechercheExterne";
var urls = [];
function ExternalSearch() { //génère le contenu
    //if (mw.config.get('wgPageName') != "Accueil/Brouillon") return;
    var mainNode = document.getElementById("searchbox");
    if (!mainNode) return;

    var c = 0;

    function CreateButton(name,value,parent,url) {
        var button = document.createElement("input");
        button.type = "button";
        button.value = value;
        button.setAttribute('onclick','SearchRedir(' + c + ');');
        parent.appendChild(button);

        urls[c] = url;
        c++;
    }

    var span = document.createElement("span");
    span.style.fontSize = "smaller";

    var a = document.createElement("a");
    a.href="javascript:affRechercheExterne(true);";
    a.id = "AfficheRechercheExterne";
    a.title = "En raison des limitations du moteur de recherche interne, des boutons vers des moteurs externes vous sont temporairement proposés afin de vous aider dans vos recherches";
    a.style.marginLeft = "1ex";
    a.appendChild(document.createTextNode(">>"));
    span.appendChild(a);

    var a = document.createElement("a");
    a.href="javascript:affRechercheExterne(false);";
    a.id = "CacheRechercheExterne";
    a.style.marginLeft = "1ex";
    a.appendChild(document.createTextNode("<<"));
    span.appendChild(a);

    getElementsByClass("searchboxSearchButton")[0].parentNode.appendChild(span);

    var serverName = mw.config.get('wgServerName');
    var div = document.createElement("div");
    div.id = "BoutonsRechercheExterne";
    CreateButton('wikiwix','Wikiwix', div,
        "http://fr.wikiwix.com/index.php?lang=fr&art=true&disp=article&boolop=and&action=");
    CreateButton('exalead','Exalead', div,
        "http://www.exalead.fr/search/results?q=site%3Awikibooks.org+");
    CreateButton('google','Google', div,
        "http://www.google.fr/search?as_sitesearch="+serverName+"&q=");
    CreateButton('live','Bing', div,
        "http://www.bing.com/search?q1=site:"+serverName+"&q=");
    CreateButton('yahoo','Yahoo!', div,
        "http://fr.search.yahoo.com/search?vs="+serverName+"&p=");

    mainNode.appendChild(div);

    affRechercheExterne((getCookieVal(cookieCacheRechercheExterneName) == "1"));
}

function affRechercheExterne(visible) { //affiche / masque les boutons
    var date = new Date();
    var val = visible ? "=1" : "=0";
    date.setTime(date.getTime() + 30*86400*1000);
    document.cookie = cookieCacheRechercheExterneName + val + "; expires="+date.toGMTString() + "; path=/";
    document.getElementById('BoutonsRechercheExterne').style.display = visible ? 'block' : 'none';
    document.getElementById('CacheRechercheExterne').style.display = visible ? 'inline' : 'none';
    document.getElementById('AfficheRechercheExterne').style.display = visible ? 'none' : 'inline';
}

// lance recherche externe suite pression sur bouton
function SearchRedir(index) {
    location.href = urls[index] + encodeURIComponent(getElementsByClass("searchboxInput")[0].value);
}

$(ExternalSearch);


/************************************************************/

/**
 * Expressions régulières
 * Auteur: ThomasV, Pathoschild
 * Note : cet outil utilise la syntaxe javascript : on utilise $ (et pas \) pour appeler un groupe dans la chaîne de remplacement.
 * Tutoriel : http://www.regular-expressions.info/tutorial.html
 */

/* create form */
function custom() {
  /* if already open */
  if(document.getElementById('regexform')) customremove();
  else
  {
    var editbox = document.getElementById('wpTextbox1');
    /* container */
    var regexform = document.createElement('div');
    regexform.setAttribute('id','regexform');
    editbox.parentNode.insertBefore(regexform,editbox.parentNode.firstChild);

    /* form tag */
    var formform = document.createElement('form');
    formform.setAttribute('id','regexformform');
    formform.setAttribute('onSubmit','customgo(); return false;');
    regexform.appendChild(formform);

    // add input boxes
    var newinput = document.createElement('input');
    newinput.setAttribute('id','formsearch');
    var newlabel = document.createElement('label');
    newlabel.setAttribute('for','formsearch');
    newlabel.appendChild(document.createTextNode("Remplacer "));

    formform.appendChild(newlabel);
    formform.appendChild(newinput);

    var newinput = document.createElement('input');
    newinput.setAttribute('id','formreplace');
    var newlabel = document.createElement('label');
    newlabel.setAttribute('for','formreplace');
    newlabel.appendChild(document.createTextNode(' par '));

    formform.appendChild(newlabel);
    formform.appendChild(newinput);

    // go! link
    var go_button = document.createElement('input');
    go_button.setAttribute('type',"submit");
    go_button.setAttribute('title',"go!");
    go_button.setAttribute('value',">");
    formform.appendChild(go_button);
  }
}

/* run patterns */
function customgo() {
    /* get search and replace strings */

    search = document.getElementById('formsearch').value;
    search = search.replace(/\\n/g, '\n');

    replace = document.getElementById('formreplace').value;
    replace = replace.replace(/\\n/g, '\n');

    /* convert input to regex */

    // without delimiters
    if(!/^\s*\/[\s\S]*\/[a-z]*\s*$/i.test(search))
        search = new RegExp(search,'g');
    // with delimiters
    else {
        // break into parts
        var regpattern = search.replace(/^\s*\/([\s\S]*)\/[a-z]*\s*$/i,'$1');
        var regmodifiers = search.replace(/^\s*\/[\s\S]*\/([a-z]*)\s*$/,'$1');
        // filter invalid flags
        regmodifiers = regmodifiers.replace(/[^gim]/ig,'');
        search = new RegExp(regpattern, regmodifiers);
    }

    /* perform */
    var editbox = document.getElementById('wpTextbox1');
    editbox.value = editbox.value.replace(search,replace);
}

/* remove form */
function customremove() {
  var regexform = document.getElementById('regexform');
  regexform.parentNode.removeChild(regexform);
  patterncount = -1;
}

/* create button */
function add_regexp_button() {
  var toolbar = document.getElementById("toolbar");
  if (toolbar) {
    var image = document.createElement("img");
    image.width = 23;
    image.height = 22;
    image.border = 0;
    image.className = "mw-toolbar-editbutton";
    image.style.cursor = "pointer";
    image.alt = "regexp";
    image.title = "Expression régulière";
    image.src = url_wpcm+'a/a0/Button_references_alt.png';
    image.onclick = custom;
    toolbar.appendChild(image);
  }
}

$(add_regexp_button);

/************************************************************/

/*********************
*** Pour les statistiques : http://wikistics.falsikon.de/latest/wikibooks/fr/searchTerms.htm
Désactivé par JackPotte le 17/02 à cause d'un bug de complétion (comme sur le Wiktionnaire)
**********************/
// mw.loader.load("//de.wiktionary.org/w/index.php?title=MediaWiki:If-search.js&action=raw&ctype=text/javascript");


/************************************************************/

// CADRE À ONGLETS

//  CadreOngletIndex [index_cadre 0..9] = index onglet visible 1..9
var CadreOngletIndex = [];
//  CadreOngletOnglet [index_cadre 0..9][index onglet 1..9] = element onglet
var CadreOngletOnglet = [];
//  CadreOngletContenu [index_cadre 0..9][index onglet 1..9] = element contenu
var CadreOngletContenu = [];

function CadreOngletAffiche(elemong, elemcont, display) {
  elemcont.style.display = display ? "block" : "none";
  elemcont.style.visibility = display ? "visible" : "hidden";
  elemong.className = display ? "mbBoutonSel" : "mbBouton";
}

/*
    Fonctionnement du [[Modèle:Cadre à onglets]]
    Modèle implanté par User:Peleguer de //ca.wikipedia.org
    Actualisé par User:Joanjoc de //ca.wikipedia.org
    Traduction et adaptation User:Antaya de //fr.wikipedia.org
    Importation sur le Wiktionnaire puis Wikilivres par JackPotte
*/
function CadreOngletInit()
{
  for (var i=0;i<=9;i++)
  {
     var vMb = document.getElementById("mb"+i);
     if (!vMb) return;

     var elems_onglet =[{}]; // un élément bidon car j de 1 à 9 (éviter +1 ou -1)
     var elems_contenu=[{}];
     var j;
     for (j=1;j<=9;j++)
     {
        var vBt = document.getElementById("mb"+i+"bt"+j);
        if (!vBt) break;
        elems_onglet.push(vBt);
        elems_contenu.push(document.getElementById("mb"+i+"og"+j));
        vBt.onclick = CadreOngletVoirOnglet;
        if (vBt.className=="mbBoutonSel")
          CadreOngletIndex[i] = j; // onglet sélectionné
     }
     CadreOngletOnglet.push(elems_onglet);
     CadreOngletContenu.push(elems_contenu);

     if (i >= CadreOngletIndex.length) // Pas d'onglet sélectionné
     {
       j = 1+Math.floor((elems_contenu.length-1)*Math.random());
       CadreOngletIndex[i] = j; // onglet sélectionné
       CadreOngletAffiche(elems_onglet[j], elems_contenu[j], true);
     }
  }
}

function CadreOngletVoirOnglet() {
  var i = parseInt(this.id.substr(2,1));
  var j = parseInt(this.id.substr(5,1));
  var oj = CadreOngletIndex[i];
  if (j==oj) return; // inchangé
  CadreOngletIndex[i] = j;
  CadreOngletAffiche(CadreOngletOnglet[i][oj],CadreOngletContenu[i][oj],false);
  CadreOngletAffiche(CadreOngletOnglet[i][j],CadreOngletContenu[i][j],true);
  return false;
}
$(CadreOngletInit);

/************************************************************/


// Récupère proprement le contenu textuel d'un nœud et de ses nœuds descendants
// Copyright Harmen Christophe, http://openweb.eu.org/articles/validation_avancee, CC
function getTextContent(oNode) {
  if (typeof oNode.textContent !="undefined") return oNode.textContent;
  switch (oNode.nodeType) {
    case 3: // TEXT_NODE
    case 4: // CDATA_SECTION_NODE
      return oNode.value;
    case 7: // PROCESSING_INSTRUCTION_NODE
    case 8: // COMMENT_NODE
      if (getTextContent.caller!=getTextContent) return oNode.value;
      break;
    case 9: // DOCUMENT_NODE
    case 10: // DOCUMENT_TYPE_NODE
    case 12: // NOTATION_NODE
      return null;
  }
  var _textContent = "";
  oNode = oNode.firstChild;
  while (oNode) {
    _textContent += getTextContent(oNode);
    oNode = oNode.nextSibling;
  }
  return _textContent;
}


/**
 * insertAfter : insérer un élément dans une page
 */
function insertAfter(parent, node, referenceNode)
{ parent.insertBefore(node, referenceNode.nextSibling); }


function noArchive(el)
{
  var depth = 0;
  while ((depth < 30) && (el))
  {
    if (hasClass(el, "noarchive")) return true;
    if (hasClass(el, "archive")) return false;
    el= el.parentNode;
    depth++;
  }
  return false;
}


/**
 * Cacher les sections vides plutôt que laisser une indication de 'vide'.
 */
function hideEmptySections() {
  var div = document.getElementById("mf-newbooks");
  if (div && div.getElementsByTagName('a').length<=1) /* Lien Nouveautés seul */
    div.style.display = 'none';
}
$(hideEmptySections);



/************************************************************/


// Importé de [[wikt:MediaWiki:Onlyifsystem.js]] : ajoute des liens interwikis dans la barre de gauche, dans les namespaces ci-dessous
if (
       mw.config.get('wgNamespaceNumber') == -1 // Spécial
// ||  mw.config.get('wgNamespaceNumber') ==  8 // MediaWiki
) {
    if (typeof wgScriptSideboxVersion =='undefined') loadJs('MediaWiki:Gadget-ScriptSidebox.js');
}


/************************************************************/

/* Réparation du bug selon skin : autres langues avant autres projets */
$(function()
{
	var portal = document.querySelector("#p-wikibase-otherprojects")
	if (portal!==null)
	{
		var parent = portal.parentNode;
		parent.removeChild(portal);
		parent.appendChild(portal);
	}
});

// Importation des gadgets qui ne fonctionnent pas avec "resourceloader"
mw.loader.load("//fr.wikibooks.org/w/index.php?title=MediaWiki:Common-IntersectionCategorie.js\u0026action=raw\u0026ctype=text/javascript");
mw.loader.load("//fr.wikibooks.org/w/index.php?title=MediaWiki:Common-Wikiwix.js\u0026action=raw\u0026ctype=text/javascript");
if (wgIsEditingPage)
  mw.loader.load("//fr.wikibooks.org/w/index.php?title=MediaWiki:Common-OutilsEdition.js\u0026action=raw\u0026ctype=text/javascript");

/* Réparation des boites déroulantes */
$(protectedRunOnloadHooks);

/* Réparation de l'absence des sommaires dans les PDF */
if ( location.href.indexOf( 'printable=yes' ) != -1 ) {
    var $toc = $( '#toc' );
    $toc.removeAttr( 'id' );
    $toc.removeAttr( 'class' );
}