« MediaWiki:Common.js » : différence entre les versions

De WikiRuches
(Ajout widget évènements dynamique)
(Validation obligatoire de la justification lors du retrait de droits abeille)
 
(16 versions intermédiaires par le même utilisateur non affichées)
Ligne 87 : Ligne 87 :
     }
     }
});
});
/* === Évènements à venir — Agrégation dynamique des ruches === */
/* === Évènements à venir — Agrégation dynamique des ruches === */
mw.loader.using(['mediawiki.api', 'mediawiki.util', 'jquery']).then(function () {
mw.loader.using(['mediawiki.api', 'mediawiki.util', 'jquery']).then(function () {
Ligne 145 : Ligne 144 :
     function wikitextToHtml(text) {
     function wikitextToHtml(text) {
         var html = $('<div>').text(text).html(); // échapper le HTML
         var html = $('<div>').text(text).html(); // échapper le HTML
        // Titres de sous-section (du plus profond au plus superficiel)
        html = html.replace(/^======\s*(.+?)\s*======\s*$/gm, '<h6>$1</h6>');
        html = html.replace(/^=====\s*(.+?)\s*=====\s*$/gm,  '<h5>$1</h5>');
        html = html.replace(/^====\s*(.+?)\s*====\s*$/gm,    '<h4>$1</h4>');
        html = html.replace(/^===\s*(.+?)\s*===\s*$/gm,      '<h3>$1</h3>');
         // Liens wiki : [[Cible|Texte]] ou [[Cible]]
         // Liens wiki : [[Cible|Texte]] ou [[Cible]]
         html = html.replace(/\[\[([^\]|]+)\|([^\]]+)\]\]/g, function(_, target, label) {
         html = html.replace(/\[\[([^\]|]+)\|([^\]]+)\]\]/g, function(_, target, label) {
Ligne 155 : Ligne 159 :
         html = html.replace(/^(\* .+)$/gm, '<li>$1</li>').replace(/^(\* )/gm, '');
         html = html.replace(/^(\* .+)$/gm, '<li>$1</li>').replace(/^(\* )/gm, '');
         html = html.replace(/(<li>[\s\S]*?<\/li>)/g, '<ul>$1</ul>');
         html = html.replace(/(<li>[\s\S]*?<\/li>)/g, '<ul>$1</ul>');
         // Sauts de ligne
         // Sauts de ligne (hors balises bloc)
         html = html.replace(/\n/g, '<br>');
         html = html.replace(/\n/g, '<br>');
         return html;
         return html;
Ligne 162 : Ligne 166 :
     /* Extrait la section Évènements à venir du wikitext */
     /* Extrait la section Évènements à venir du wikitext */
     function extractEvents(wikitext) {
     function extractEvents(wikitext) {
         var m = wikitext.match(/==\s*Évènements à venir\s*==\s*\n([\s\S]*?)(?=\n==|$)/);
        // Stoppe dès qu'une ligne commence par == (titre de section suivant)
         var m = wikitext.match(/==\s*Évènements à venir\s*==[^\n]*\n((?:(?!==(?!=))[^\n]*\n?)*)/);
        // (?!==(?!=)) : stoppe sur == (niveau 2) mais pas === ou plus profond
         if (!m) return '';
         if (!m) return '';
         var content = m[1].trim();
         var content = m[1].trim();
        // Ignorer les placeholders
         if (!content) return '';
         if (!content || /^[\s*\-]*$/.test(content)) return '';
        var placeholders = ['à compléter', '{{à compléter}}', '*', '-', '* à compléter'];
         var lines = content.split('\n').filter(function(l) {
         var lines = content.split('\n').filter(function(l) {
             var t = l.trim().toLowerCase();
             var t = l.trim().toLowerCase();
             return t && t !== 'à compléter' && t !== '{{à compléter}}' && t !== '*' && t !== '-';
             return t && placeholders.indexOf(t) === -1;
         });
         });
         return lines.length ? lines.join('\n') : '';
         return lines.length ? lines.join('\n') : '';
     }
     }


     /* Crée un bloc réductible */
     /* Crée un bloc réductible avec <details>/<summary> (natif, pas de JS) */
     function makeCollapsible(title, contentHtml, collapsed) {
     function makeCollapsible(title, contentHtml, collapsed) {
         var cls = 'wikitable mw-collapsible' + (collapsed !== false ? ' mw-collapsed' : '');
         var $d = $('<details>').css({
         return $('<table>').addClass(cls).css('width', '100%').css('margin', '3px 0')
            'border': '1px solid #a2a9b1',
             .append($('<tr>').append($('<th>').html(title)))
            'border-radius': '2px',
            .append($('<tr>').append($('<td>').html(contentHtml)));
            'margin': '3px 0',
            'background': '#fff',
            'width': '100%'
        });
        if (!collapsed) $d.attr('open', '');
         $('<summary>').html(title).css({
            'padding': '4px 8px',
            'cursor': 'pointer',
            'background': '#eaecf0',
            'user-select': 'none',
             'list-style': 'revert'
        }).appendTo($d);
        $('<div>').html(contentHtml).css('padding', '4px 8px').appendTo($d);
        return $d;
     }
     }


Ligne 220 : Ligne 239 :
             $wrap.append(makeCollapsible(
             $wrap.append(makeCollapsible(
                 '<strong>Ruche 00 — National</strong>',
                 '<strong>Ruche 00 — National</strong>',
                 wikitextToHtml(events['Ruche 00']), false
                 wikitextToHtml(events['Ruche 00']), true
             ));
             ));
         }
         }
Ligne 250 : Ligne 269 :
         if ($wrap.children().length) {
         if ($wrap.children().length) {
             $(container).append($wrap.children());
             $(container).append($wrap.children());
            mw.hook('wikipage.content').fire($(container));
         }
         }
     }).catch(function() {
     }).catch(function() {
Ligne 257 : Ligne 275 :
});
});
/* === Fin Évènements à venir === */
/* === Fin Évènements à venir === */
/* === Sections réductibles (h2) === */
mw.hook('wikipage.content').add(function ($content) {
    $content.find('h2').each(function () {
        var $h2    = $(this);
        var $section = $h2.closest('section');
        // MW 1.35+ : les sections sont wrappées dans <section>
        // Fallback : on parcourt les siblings jusqu'au prochain h2
        var $body;
        if ($section.length) {
            $body = $section.children().not('h2');
        } else {
            $body = $();
            var $cur = $h2.next();
            while ($cur.length && !$cur.is('h1, h2')) {
                $body = $body.add($cur);
                $cur  = $cur.next();
            }
        }
        if (!$body.length) return;
        var $btn = $('<span>')
            .addClass('section-toggle-btn')
            .attr('title', 'Replier/déplier cette section')
            .css({
                cursor      : 'pointer',
                marginLeft  : '6px',
                fontSize    : '0.75em',
                fontWeight  : 'normal',
                color      : '#888',
                userSelect  : 'none',
                verticalAlign: 'middle'
            })
            .text('[masquer]');
        $btn.on('click', function () {
            if ($body.is(':visible')) {
                $body.hide();
                $btn.text('[afficher]');
            } else {
                $body.show();
                $btn.text('[masquer]');
            }
        });
        // Insérer le bouton dans .mw-headline (le span du titre)
        var $headline = $h2.find('.mw-headline');
        ($headline.length ? $headline : $h2).append($btn);
    });
});
/* === Fin Sections réductibles === */
/* === Bandeau avertissement pages IA === */
mw.hook('wikipage.content').add(function ($content) {
    var cats = mw.config.get('wgCategories') || [];
    // Compatibilité : MW peut renvoyer espaces ou underscores dans wgCategories
    var cible = 'Généré par IA';
    var present = cats.some(function (c) {
        return c.replace(/_/g, ' ') === cible;
    });
    if (!present) return;
    var $bandeau = $('<div>').css({
        background  : '#fff3cd',
        border      : '1px solid #ffc107',
        borderLeft  : '4px solid #ffc107',
        borderRadius : '4px',
        padding      : '10px 14px',
        margin      : '0 0 16px 0',
        fontSize    : '0.95em',
        display      : 'flex',
        alignItems  : 'center',
        gap          : '10px'
    }).html("<span style=\"font-size:1.4em\">⚠️</span><span><strong>Contenu généré par Intelligence Artificielle.</strong> Cette page n'a pas été validée par les membres des Ruches. Méfiez-vous de son contenu.</span>");
    $content.prepend($bandeau);
});
/* === Fin Bandeau IA === */
/* === Justification retrait droits abeille === */
if ( mw.config.get( 'wgCanonicalSpecialPageName' ) === 'Userrights' ) {
    $( function () {
        /* Mémorise l'état initial de toutes les cases de groupes (name="wpGroup-*") */
        var initialChecked = {};
        $( 'input[type="checkbox"][name^="wpGroup-"]' ).each( function () {
            initialChecked[ $( this ).attr( 'name' ) ] = this.checked;
        } );
        /* Intercepte n'importe quel formulaire de la page */
        $( document ).on( 'submit', 'form', function ( e ) {
            var retrait = false;
            $( 'input[type="checkbox"][name^="wpGroup-"]' ).each( function () {
                var n = $( this ).attr( 'name' );
                if ( initialChecked[ n ] === true && !this.checked ) {
                    retrait = true;
                }
            } );
            if ( !retrait ) { return; }
            /* Lire wpReason via sélecteur direct (le champ n'est pas dans le form sérialisé) */
            var reason = ( $( 'input[name="wpReason"], textarea[name="wpReason"], #mw-input-wpreason, #wpReason' ).val() || '' ).trim();
            if ( !reason ) {
                e.preventDefault();
                e.stopImmediatePropagation();
                $( '#justif-error' ).remove();
                var $err = $( '<div id="justif-error" style="' +
                    'color:#d33;font-weight:bold;padding:0.6em 1em;' +
                    'background:#fff0f0;border:2px solid #d33;border-radius:4px;' +
                    'margin:0.8em 0;font-size:1em;">' +
                    '⚠ Rentrez un motif pour pouvoir retirer des droits !' +
                    '</div>' );
                /* Insertion en tête du formulaire (toujours visible) */
                $( this ).prepend( $err );
                $( 'html, body' ).animate( { scrollTop: $err.offset().top - 80 }, 250 );
                return false;
            }
        } );
        /* Efface l'erreur dès qu'on tape dans le champ raison */
        $( document ).on( 'input', 'input[name="wpReason"], textarea[name="wpReason"]', function () {
            $( '#justif-error' ).remove();
        } );
    } );
}
/* === Fin Justification retrait === */

Dernière version du 3 juillet 2026 à 18:34


/* ============================================================
   Tooltips pour les exposants <ref> — Ruches wiki v2
   ============================================================ */
( function () {
    'use strict';

    function init() {
        var refs = document.querySelectorAll( 'sup.reference a' );
        if ( !refs.length ) return;

        var tip = document.createElement( 'div' );
        tip.style.cssText = [
            'position:fixed',
            'z-index:9999',
            'max-width:380px',
            'background:#fefefe',
            'border:1px solid #a2a9b1',
            'border-radius:4px',
            'box-shadow:0 2px 8px rgba(0,0,0,.25)',
            'padding:10px 14px',
            'font-size:.875rem',
            'line-height:1.5',
            'color:#202122',
            'pointer-events:none',
            'display:none'
        ].join( ';' );
        document.body.appendChild( tip );

        refs.forEach( function ( a ) {
            a.addEventListener( 'mouseenter', function ( e ) {
                var href = a.getAttribute( 'href' );
                if ( !href ) return;
                var id = href.replace( /^#/, '' );
                var li = document.getElementById( id );
                if ( !li ) return;
                var clone = li.cloneNode( true );
                clone.querySelectorAll( '.mw-cite-backlink' ).forEach( function ( el ) {
                    el.remove();
                } );
                tip.textContent = clone.textContent.trim();
                tip.style.display = 'block';
                move( e );
            } );
            a.addEventListener( 'mousemove', move );
            a.addEventListener( 'mouseleave', function () {
                tip.style.display = 'none';
            } );
        } );

        function move( e ) {
            var x = e.clientX + 16;
            var y = e.clientY + 16;
            if ( x + tip.offsetWidth  > window.innerWidth  - 10 ) {
                x = e.clientX - tip.offsetWidth  - 16;
            }
            if ( y + tip.offsetHeight > window.innerHeight - 10 ) {
                y = e.clientY - tip.offsetHeight - 16;
            }
            tip.style.left = x + 'px';
            tip.style.top  = y + 'px';
        }
    }

    if ( document.readyState === 'loading' ) {
        document.addEventListener( 'DOMContentLoaded', init );
    } else {
        init();
    }

}() );

/* === Lien "Coopter une abeille" pour les abeilles et sysops === */
mw.loader.using(['mediawiki.util']).then(function () {
    var groups = mw.config.get('wgUserGroups') || [];
    var isAbeille = groups.some(function (g) {
        return g.indexOf('abeille') === 0 || g === 'sysop';
    });
    if (isAbeille) {
        mw.util.addPortletLink(
            'p-tb',
            mw.util.getUrl('Special:UserRights'),
            'Coopter une abeille',
            't-coopter',
            'Attribuer des droits à un utilisateur'
        );
    }
});
/* === Évènements à venir — Agrégation dynamique des ruches === */
mw.loader.using(['mediawiki.api', 'mediawiki.util', 'jquery']).then(function () {
    if (mw.config.get('wgPageName') !== 'Accueil') return;
    var container = document.getElementById('evenements-ruches');
    if (!container) return;

    var DEPARTMENTS = {
        '01':'Ain','02':'Aisne','03':'Allier','04':'Alpes-de-Haute-Provence',
        '05':'Hautes-Alpes','06':'Alpes-Maritimes','07':'Ardèche','08':'Ardennes',
        '09':'Ariège','10':'Aube','11':'Aude','12':'Aveyron',
        '13':'Bouches-du-Rhône','14':'Calvados','15':'Cantal','16':'Charente',
        '17':'Charente-Maritime','18':'Cher','19':'Corrèze',
        '2A':'Corse-du-Sud','2B':'Haute-Corse',
        '21':"Côte-d'Or",'22':"Côtes-d'Armor",'23':'Creuse','24':'Dordogne',
        '25':'Doubs','26':'Drôme','27':'Eure','28':'Eure-et-Loir','29':'Finistère',
        '30':'Gard','31':'Haute-Garonne','32':'Gers','33':'Gironde','34':'Hérault',
        '35':'Ille-et-Vilaine','36':'Indre','37':'Indre-et-Loire','38':'Isère',
        '39':'Jura','40':'Landes','41':'Loir-et-Cher','42':'Loire',
        '43':'Haute-Loire','44':'Loire-Atlantique','45':'Loiret','46':'Lot',
        '47':'Lot-et-Garonne','48':'Lozère','49':'Maine-et-Loire','50':'Manche',
        '51':'Marne','52':'Haute-Marne','53':'Mayenne','54':'Meurthe-et-Moselle',
        '55':'Meuse','56':'Morbihan','57':'Moselle','58':'Nièvre','59':'Nord',
        '60':'Oise','61':'Orne','62':'Pas-de-Calais','63':'Puy-de-Dôme',
        '64':'Pyrénées-Atlantiques','65':'Hautes-Pyrénées','66':'Pyrénées-Orientales',
        '67':'Bas-Rhin','68':'Haut-Rhin','69':'Rhône','70':'Haute-Saône',
        '71':'Saône-et-Loire','72':'Sarthe','73':'Savoie','74':'Haute-Savoie',
        '75':'Paris','76':'Seine-Maritime','77':'Seine-et-Marne','78':'Yvelines',
        '79':'Deux-Sèvres','80':'Somme','81':'Tarn','82':'Tarn-et-Garonne',
        '83':'Var','84':'Vaucluse','85':'Vendée','86':'Vienne','87':'Haute-Vienne',
        '88':'Vosges','89':'Yonne','90':'Territoire de Belfort','91':'Essonne',
        '92':'Hauts-de-Seine','93':'Seine-Saint-Denis','94':'Val-de-Marne',
        '95':"Val-d'Oise",
        '971':'Guadeloupe','972':'Martinique','973':'Guyane',
        '974':'La Réunion','976':'Mayotte','FE':'Monde'
    };

    var REGIONS = [
        ["Auvergne-Rhône-Alpes",        ['01','03','07','15','26','38','42','43','63','69','73','74']],
        ["Bourgogne-Franche-Comté",     ['21','25','39','58','70','71','89','90']],
        ["Bretagne",                    ['22','29','35','56']],
        ["Centre-Val de Loire",         ['18','28','36','37','41','45']],
        ["Corse",                       ['2A','2B']],
        ["Grand Est",                   ['08','10','51','52','54','55','57','67','68','88']],
        ["Hauts-de-France",             ['02','59','60','62','80']],
        ["Île-de-France",               ['75','77','78','91','92','93','94','95']],
        ["Normandie",                   ['14','27','50','61','76']],
        ["Nouvelle-Aquitaine",          ['16','17','19','23','24','33','40','47','64','79','86','87']],
        ["Occitanie",                   ['09','11','12','30','31','32','34','46','48','65','66','81','82']],
        ["Pays de la Loire",            ['44','49','53','72','85']],
        ["Provence-Alpes-Côte d'Azur",  ['04','05','06','13','83','84']],
        ["Outre-mer",                   ['971','972','973','974','976']],
        ["Français de l'Étranger",      ['FE']]
    ];

    /* Convertit du wikitext simplifié en HTML */
    function wikitextToHtml(text) {
        var html = $('<div>').text(text).html(); // échapper le HTML
        // Titres de sous-section (du plus profond au plus superficiel)
        html = html.replace(/^======\s*(.+?)\s*======\s*$/gm, '<h6>$1</h6>');
        html = html.replace(/^=====\s*(.+?)\s*=====\s*$/gm,  '<h5>$1</h5>');
        html = html.replace(/^====\s*(.+?)\s*====\s*$/gm,    '<h4>$1</h4>');
        html = html.replace(/^===\s*(.+?)\s*===\s*$/gm,      '<h3>$1</h3>');
        // Liens wiki : [[Cible|Texte]] ou [[Cible]]
        html = html.replace(/\[\[([^\]|]+)\|([^\]]+)\]\]/g, function(_, target, label) {
            return '<a href="' + mw.util.getUrl(target) + '">' + label + '</a>';
        });
        html = html.replace(/\[\[([^\]]+)\]\]/g, function(_, target) {
            return '<a href="' + mw.util.getUrl(target) + '">' + target + '</a>';
        });
        // Listes à puces
        html = html.replace(/^(\* .+)$/gm, '<li>$1</li>').replace(/^(\* )/gm, '');
        html = html.replace(/(<li>[\s\S]*?<\/li>)/g, '<ul>$1</ul>');
        // Sauts de ligne (hors balises bloc)
        html = html.replace(/\n/g, '<br>');
        return html;
    }

    /* Extrait la section Évènements à venir du wikitext */
    function extractEvents(wikitext) {
        // Stoppe dès qu'une ligne commence par == (titre de section suivant)
        var m = wikitext.match(/==\s*Évènements à venir\s*==[^\n]*\n((?:(?!==(?!=))[^\n]*\n?)*)/);
        // (?!==(?!=)) : stoppe sur == (niveau 2) mais pas === ou plus profond
        if (!m) return '';
        var content = m[1].trim();
        if (!content) return '';
        var placeholders = ['à compléter', '{{à compléter}}', '*', '-', '* à compléter'];
        var lines = content.split('\n').filter(function(l) {
            var t = l.trim().toLowerCase();
            return t && placeholders.indexOf(t) === -1;
        });
        return lines.length ? lines.join('\n') : '';
    }

    /* Crée un bloc réductible avec <details>/<summary> (natif, pas de JS) */
    function makeCollapsible(title, contentHtml, collapsed) {
        var $d = $('<details>').css({
            'border': '1px solid #a2a9b1',
            'border-radius': '2px',
            'margin': '3px 0',
            'background': '#fff',
            'width': '100%'
        });
        if (!collapsed) $d.attr('open', '');
        $('<summary>').html(title).css({
            'padding': '4px 8px',
            'cursor': 'pointer',
            'background': '#eaecf0',
            'user-select': 'none',
            'list-style': 'revert'
        }).appendTo($d);
        $('<div>').html(contentHtml).css('padding', '4px 8px').appendTo($d);
        return $d;
    }

    /* Charge les pages en batch de 50 */
    var api = new mw.Api();
    var allTitles = ['Ruche 00'];
    Object.keys(DEPARTMENTS).forEach(function(code) {
        allTitles.push('Ruches/' + DEPARTMENTS[code]);
    });

    var batches = [];
    for (var i = 0; i < allTitles.length; i += 50) {
        batches.push(allTitles.slice(i, i + 50));
    }

    container.innerHTML = '<em>Chargement des évènements...</em>';

    Promise.all(batches.map(function(batch) {
        return api.get({
            action: 'query', titles: batch.join('|'),
            prop: 'revisions', rvprop: 'content', rvslots: 'main', format: 'json'
        });
    })).then(function(results) {
        var events = {};
        results.forEach(function(result) {
            Object.values(result.query.pages).forEach(function(page) {
                if (!page.revisions) return;
                var wt = (page.revisions[0].slots || {}).main
                    ? page.revisions[0].slots.main['*']
                    : page.revisions[0]['*'];
                var ev = extractEvents(wt || '');
                if (ev) events[page.title] = ev;
            });
        });

        var $wrap = $('<div>');

        // Ruche 00 en tête
        if (events['Ruche 00']) {
            $wrap.append(makeCollapsible(
                '<strong>Ruche 00 — National</strong>',
                wikitextToHtml(events['Ruche 00']), true
            ));
        }

        // Régions
        REGIONS.forEach(function(reg) {
            var regionName = reg[0], codes = reg[1];
            var $regionContent = $('<div>');
            codes.forEach(function(code) {
                var nom = DEPARTMENTS[code];
                var title = 'Ruches/' + nom;
                if (events[title]) {
                    var link = '<a href="' + mw.util.getUrl(title) + '">' + nom + ' (' + code + ')</a>';
                    $regionContent.append(makeCollapsible(link, wikitextToHtml(events[title]), true));
                }
            });
            if ($regionContent.children().length) {
                $wrap.append(makeCollapsible(
                    '<strong>' + regionName + '</strong>',
                    $regionContent[0].outerHTML, true
                ));
            }
        });

        container.innerHTML = $wrap.children().length
            ? ''
            : '<em>Aucun évènement à venir pour le moment.</em>';

        if ($wrap.children().length) {
            $(container).append($wrap.children());
        }
    }).catch(function() {
        container.innerHTML = '<em>Erreur lors du chargement des évènements.</em>';
    });
});
/* === Fin Évènements à venir === */

/* === Sections réductibles (h2) === */
mw.hook('wikipage.content').add(function ($content) {
    $content.find('h2').each(function () {
        var $h2     = $(this);
        var $section = $h2.closest('section');

        // MW 1.35+ : les sections sont wrappées dans <section>
        // Fallback : on parcourt les siblings jusqu'au prochain h2
        var $body;
        if ($section.length) {
            $body = $section.children().not('h2');
        } else {
            $body = $();
            var $cur = $h2.next();
            while ($cur.length && !$cur.is('h1, h2')) {
                $body = $body.add($cur);
                $cur  = $cur.next();
            }
        }

        if (!$body.length) return;

        var $btn = $('<span>')
            .addClass('section-toggle-btn')
            .attr('title', 'Replier/déplier cette section')
            .css({
                cursor      : 'pointer',
                marginLeft  : '6px',
                fontSize    : '0.75em',
                fontWeight  : 'normal',
                color       : '#888',
                userSelect  : 'none',
                verticalAlign: 'middle'
            })
            .text('[masquer]');

        $btn.on('click', function () {
            if ($body.is(':visible')) {
                $body.hide();
                $btn.text('[afficher]');
            } else {
                $body.show();
                $btn.text('[masquer]');
            }
        });

        // Insérer le bouton dans .mw-headline (le span du titre)
        var $headline = $h2.find('.mw-headline');
        ($headline.length ? $headline : $h2).append($btn);
    });
});
/* === Fin Sections réductibles === */

/* === Bandeau avertissement pages IA === */
mw.hook('wikipage.content').add(function ($content) {
    var cats = mw.config.get('wgCategories') || [];
    // Compatibilité : MW peut renvoyer espaces ou underscores dans wgCategories
    var cible = 'Généré par IA';
    var present = cats.some(function (c) {
        return c.replace(/_/g, ' ') === cible;
    });
    if (!present) return;

    var $bandeau = $('<div>').css({
        background   : '#fff3cd',
        border       : '1px solid #ffc107',
        borderLeft   : '4px solid #ffc107',
        borderRadius : '4px',
        padding      : '10px 14px',
        margin       : '0 0 16px 0',
        fontSize     : '0.95em',
        display      : 'flex',
        alignItems   : 'center',
        gap          : '10px'
    }).html("<span style=\"font-size:1.4em\">⚠️</span><span><strong>Contenu généré par Intelligence Artificielle.</strong> Cette page n'a pas été validée par les membres des Ruches. Méfiez-vous de son contenu.</span>");

    $content.prepend($bandeau);
});
/* === Fin Bandeau IA === */

/* === Justification retrait droits abeille === */
if ( mw.config.get( 'wgCanonicalSpecialPageName' ) === 'Userrights' ) {
    $( function () {
        /* Mémorise l'état initial de toutes les cases de groupes (name="wpGroup-*") */
        var initialChecked = {};
        $( 'input[type="checkbox"][name^="wpGroup-"]' ).each( function () {
            initialChecked[ $( this ).attr( 'name' ) ] = this.checked;
        } );

        /* Intercepte n'importe quel formulaire de la page */
        $( document ).on( 'submit', 'form', function ( e ) {
            var retrait = false;
            $( 'input[type="checkbox"][name^="wpGroup-"]' ).each( function () {
                var n = $( this ).attr( 'name' );
                if ( initialChecked[ n ] === true && !this.checked ) {
                    retrait = true;
                }
            } );

            if ( !retrait ) { return; }

            /* Lire wpReason via sélecteur direct (le champ n'est pas dans le form sérialisé) */
            var reason = ( $( 'input[name="wpReason"], textarea[name="wpReason"], #mw-input-wpreason, #wpReason' ).val() || '' ).trim();

            if ( !reason ) {
                e.preventDefault();
                e.stopImmediatePropagation();
                $( '#justif-error' ).remove();
                var $err = $( '<div id="justif-error" style="' +
                    'color:#d33;font-weight:bold;padding:0.6em 1em;' +
                    'background:#fff0f0;border:2px solid #d33;border-radius:4px;' +
                    'margin:0.8em 0;font-size:1em;">' +
                    '⚠ Rentrez un motif pour pouvoir retirer des droits !' +
                    '</div>' );
                /* Insertion en tête du formulaire (toujours visible) */
                $( this ).prepend( $err );
                $( 'html, body' ).animate( { scrollTop: $err.offset().top - 80 }, 250 );
                return false;
            }
        } );

        /* Efface l'erreur dès qu'on tape dans le champ raison */
        $( document ).on( 'input', 'input[name="wpReason"], textarea[name="wpReason"]', function () {
            $( '#justif-error' ).remove();
        } );
    } );
}
/* === Fin Justification retrait === */