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

De WikiRuches
(Ajout bandeau avertissement pages IA)
(Validation obligatoire de la justification lors du retrait de droits abeille)
 
(7 versions intermédiaires par le même utilisateur non affichées)
Ligne 328 : Ligne 328 :
});
});
/* === Fin Sections réductibles === */
/* === Fin Sections réductibles === */
/* === Bandeau avertissement pages IA === */
/* === Bandeau avertissement pages IA === */
mw.hook('wikipage.content').add(function ($content) {
mw.hook('wikipage.content').add(function ($content) {
     var cats = mw.config.get('wgCategories') || [];
     var cats = mw.config.get('wgCategories') || [];
     if (cats.indexOf('Généré par IA') === -1) return;
     // 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({
     var $bandeau = $('<div>').css({
Ligne 344 : Ligne 350 :
         alignItems  : 'center',
         alignItems  : 'center',
         gap          : '10px'
         gap          : '10px'
     }).html(
     }).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>");
        "<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);
     $content.prepend($bandeau);
});
});
/* === Fin Bandeau IA === */
/* === 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 === */