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

De WikiRuches
(Validation obligatoire de la justification lors du retrait de droits abeille)
(Validation obligatoire de la justification lors du retrait de droits abeille)
 
(4 versions intermédiaires par le même utilisateur non affichées)
Ligne 359 : Ligne 359 :
if ( mw.config.get( 'wgCanonicalSpecialPageName' ) === 'Userrights' ) {
if ( mw.config.get( 'wgCanonicalSpecialPageName' ) === 'Userrights' ) {
     $( function () {
     $( function () {
         /* En MW 1.41 (OOUI), les cases ont name="wpGroup-<groupe>"
         /* Mémorise l'état initial de toutes les cases de groupes (name="wpGroup-*") */
          On détecte celles dont le name contient "abeille". */
        function getAbeilleBoxes() {
            return $( 'input[type="checkbox"]' ).filter( function () {
                return ( $( this ).attr( 'name' ) || '' ).toLowerCase().indexOf( 'abeille' ) !== -1;
            } );
        }
 
        /* Mémorise l'état initial */
         var initialChecked = {};
         var initialChecked = {};
         getAbeilleBoxes().each( function () {
         $( 'input[type="checkbox"][name^="wpGroup-"]' ).each( function () {
             initialChecked[ $( this ).attr( 'name' ) ] = this.checked;
             initialChecked[ $( this ).attr( 'name' ) ] = this.checked;
         } );
         } );
Ligne 376 : Ligne 368 :
         $( document ).on( 'submit', 'form', function ( e ) {
         $( document ).on( 'submit', 'form', function ( e ) {
             var retrait = false;
             var retrait = false;
             getAbeilleBoxes().each( function () {
             $( 'input[type="checkbox"][name^="wpGroup-"]' ).each( function () {
                 var n = $( this ).attr( 'name' );
                 var n = $( this ).attr( 'name' );
                 if ( initialChecked[ n ] === true && !this.checked ) {
                 if ( initialChecked[ n ] === true && !this.checked ) {
Ligne 385 : Ligne 377 :
             if ( !retrait ) { return; }
             if ( !retrait ) { return; }


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


             if ( !reason ) {
             if ( !reason ) {
Ligne 393 : Ligne 384 :
                 e.stopImmediatePropagation();
                 e.stopImmediatePropagation();
                 $( '#justif-error' ).remove();
                 $( '#justif-error' ).remove();
                 $( '<div id="justif-error" style="' +
                 var $err = $( '<div id="justif-error" style="' +
                  'color:#d33;font-weight:bold;padding:0.5em 0.8em;' +
                    'color:#d33;font-weight:bold;padding:0.6em 1em;' +
                  'background:#fff0f0;border:1px solid #d33;border-radius:4px;margin:0.6em 0;">' +
                    'background:#fff0f0;border:2px solid #d33;border-radius:4px;' +
                  '⚠ Vous devez indiquer une raison pour retirer les droits d\'abeille.' +
                    'margin:0.8em 0;font-size:1em;">' +
                  '</div>' ).insertBefore( $reason.closest( '.oo-ui-fieldLayout, tr, p' ).first() );
                    '⚠ Rentrez un motif pour pouvoir retirer des droits !' +
                 $reason.trigger( 'focus' );
                    '</div>' );
                /* Insertion en tête du formulaire (toujours visible) */
                $( this ).prepend( $err );
                 $( 'html, body' ).animate( { scrollTop: $err.offset().top - 80 }, 250 );
                 return false;
                 return false;
             }
             }

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 === */