MediaWiki:Common.js

De WikiRuches
Version datée du 3 juillet 2026 à 18:12 par Admin (discussion | contributions) (Validation obligatoire de la justification lors du retrait de droits abeille)

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

  • Firefox / Safari : maintenez la touche Maj (Shift) en cliquant sur le bouton Actualiser ou appuyez sur Ctrl + F5 ou Ctrl + R (⌘ + R sur un Mac).
  • Google Chrome : appuyez sur Ctrl + Maj + R (⌘ + Shift + R sur un Mac).
  • Internet Explorer / Edge : maintenez la touche Ctrl en cliquant sur le bouton Actualiser ou pressez Ctrl + F5.
  • Opera : appuyez sur Ctrl + F5.

/* ============================================================
   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 () {
        /* En MW 1.41 (OOUI), les cases ont name="wpGroup-<groupe>"
           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 = {};
        getAbeilleBoxes().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;
            getAbeilleBoxes().each( function () {
                var n = $( this ).attr( 'name' );
                if ( initialChecked[ n ] === true && !this.checked ) {
                    retrait = true;
                }
            } );

            if ( !retrait ) { return; }

            /* Le champ raison peut être input ou textarea, identifié par name="wpReason" */
            var $reason = $( 'input[name="wpReason"], textarea[name="wpReason"]' );
            var reason  = $reason.val() ? $reason.val().trim() : '';

            if ( !reason ) {
                e.preventDefault();
                e.stopImmediatePropagation();
                $( '#justif-error' ).remove();
                $( '<div id="justif-error" style="' +
                   'color:#d33;font-weight:bold;padding:0.5em 0.8em;' +
                   'background:#fff0f0;border:1px solid #d33;border-radius:4px;margin:0.6em 0;">' +
                   '⚠ Vous devez indiquer une raison pour retirer les droits d\'abeille.' +
                   '</div>' ).insertBefore( $reason.closest( '.oo-ui-fieldLayout, tr, p' ).first() );
                $reason.trigger( 'focus' );
                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 === */