Basculer le menu
Changer de menu des préférences
Basculer le menu personnel
Non connecté(e)
Votre adresse IP sera visible au public si vous faites des modifications.
Version datée du 11 mars 2026 à 19:24 par Hiob (discussion | contributions)
(diff) ← Version précédente | Version actuelle (diff) | Version suivante → (diff)

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).
  •  Edge : maintenez la touche Ctrl en cliquant sur le bouton Actualiser ou pressez Ctrl + F5.
/* Tout JavaScript présent ici sera exécuté par tous les utilisateurs à chaque chargement de page. */
$( function () {
    $( '.citizen-search-trigger' ).on( 'click', function () {
        $( '#searchInput' ).focus();
    } );

    // Raccourci Ctrl+K (ou Cmd+K sur Mac)
    $( document ).on( 'keydown', function ( e ) {
        if ( ( e.ctrlKey || e.metaKey ) && e.key === 'k' ) {
            e.preventDefault();
            $( '#searchInput' ).focus();
        }
    } );
} );

/**
 * MinecraftConnect - Boutons de copie d'adresse serveur Minecraft
 * Inspiré de l'extension PreToClip
 */
(function() {
    'use strict';
    
    function initMinecraftButtons($content) {
        $content.find('.minecraft-connect-wrapper').each(function() {
            var $wrapper = $(this);
            
            // Éviter la double initialisation
            if ($wrapper.data('mc-initialized')) {
                return;
            }
            $wrapper.data('mc-initialized', true);
            
            var serverAddress = $wrapper.data('mc-server');
            var buttonText = $wrapper.data('mc-text');
            
            // Créer le bouton
            var $button = $('<button>')
                .addClass('mw-ui-button mw-ui-progressive minecraft-connect-btn')
                .attr('type', 'button')
                .attr('title', 'Cliquer pour copier : ' + serverAddress)
                .text(buttonText + ' 📋');
            
            // Remplacer le span par le bouton
            $wrapper.replaceWith($button);
            
            // Gestion du clic
            $button.on('click', function() {
                copyToClipboard(serverAddress, $button, buttonText);
            });
        });
    }
    
    function copyToClipboard(text, $button, originalText) {
        // Méthode moderne (Clipboard API)
        if (navigator.clipboard && navigator.clipboard.writeText) {
            navigator.clipboard.writeText(text).then(
                function() {
                    showSuccess($button, originalText);
                },
                function() {
                    // Fallback si échec
                    fallbackCopy(text, $button, originalText);
                }
            );
        } else {
            // Fallback pour anciens navigateurs
            fallbackCopy(text, $button, originalText);
        }
    }
    
    function fallbackCopy(text, $button, originalText) {
        var $temp = $('<textarea>')
            .val(text)
            .css({
                position: 'fixed',
                top: 0,
                left: 0,
                width: '2em',
                height: '2em',
                padding: 0,
                border: 'none',
                outline: 'none',
                boxShadow: 'none',
                background: 'transparent'
            })
            .appendTo('body');
        
        $temp[0].select();
        $temp[0].setSelectionRange(0, 99999);
        
        try {
            var successful = document.execCommand('copy');
            if (successful) {
                showSuccess($button, originalText);
            } else {
                showError($button, originalText);
            }
        } catch (err) {
            showError($button, originalText);
        }
        
        $temp.remove();
    }
    
    function showSuccess($button, originalText) {
        mw.notify('Adresse copiée dans le presse-papier !', {
            type: 'success',
            autoHide: true,
            tag: 'minecraft-connect'
        });
        
        $button
            .text('✓ Copié !')
            .removeClass('mw-ui-progressive')
            .addClass('mw-ui-constructive')
            .prop('disabled', true);
        
        setTimeout(function() {
            $button
                .text(originalText + ' 📋')
                .prop('disabled', false)
                .removeClass('mw-ui-constructive')
                .addClass('mw-ui-progressive');
        }, 2000);
    }
    
    function showError($button, originalText) {
        mw.notify('Erreur lors de la copie', {
            type: 'error',
            autoHide: true,
            tag: 'minecraft-connect'
        });
        
        $button
            .text('✗ Erreur')
            .removeClass('mw-ui-progressive')
            .addClass('mw-ui-destructive');
        
        setTimeout(function() {
            $button
                .text(originalText + ' 📋')
                .removeClass('mw-ui-destructive')
                .addClass('mw-ui-progressive');
        }, 2000);
    }
    
    // Initialisation au chargement et pour VisualEditor
    mw.hook('wikipage.content').add(initMinecraftButtons);
    
}());


/* === Modèle:Règle — copie d'ancre === */
mw.hook('wikipage.content').add(function ($content) {

    /* Crée le toast une seule fois */
    var $toast = $('#regle-toast');
    if ($toast.length === 0) {
        $toast = $('<div>')
            .attr('id', 'regle-toast')
            .addClass('regle-toast')
            .appendTo('body');
    }

    var toastTimer;

    function showToast(message) {
        clearTimeout(toastTimer);
        $toast.text(message).addClass('regle-toast--visible');
        toastTimer = setTimeout(function () {
            $toast.removeClass('regle-toast--visible');
        }, 2000);
    }

    /* Cible tous les liens internes dont le href commence par #r- */
    $content.find('a[href^="#r-"]').off('click.regle').on('click.regle', function (e) {
        e.preventDefault();

        var ancre = $(this).attr('href').replace('#', '');
        var url   = window.location.origin
                  + window.location.pathname
                  + '#' + ancre;

        /* Défilement vers l'ancre */
        var cible = document.getElementById(ancre);
        if (cible) {
            cible.scrollIntoView({ behavior: 'smooth', block: 'start' });
        }

        /* Mise à jour de l'URL */
        history.replaceState(null, '', '#' + ancre);

        /* Copie dans le presse-papier */
        if (navigator.clipboard && window.isSecureContext) {
            navigator.clipboard.writeText(url)
                .then(function () { showToast('🔗 Lien copié !'); })
                .catch(function () { showToast('❌ Copie impossible'); });
        } else {
            /* Fallback navigateurs anciens */
            var $tmp = $('<textarea>')
                .val(url)
                .css({ position: 'fixed', opacity: 0 })
                .appendTo('body');
            $tmp[0].select();
            try {
                document.execCommand('copy');
                showToast('Lien copié !');
            } catch (err) {
                showToast('❌ Copie impossible');
            }
            $tmp.remove();
        }
    });

});
/* === Modèle:Règle — injection CSS du toast === */
(function () {
    if (document.getElementById('regle-toast-style')) return;
    var style = document.createElement('style');
    style.id = 'regle-toast-style';
style.textContent = [
    '.regle-toast {',
    '  position: fixed;',
    '  bottom: 2rem;',
    '  left: 50%;',
    '  transform: translateX(-50%) translateY(1rem);',
    '  background: var(--color-surface-2);',
    '  color: var(--color-base);',
    '  border: 1px solid var(--color-surface-4);',
    '  padding: 0.4rem 1rem;',
    '  border-radius: var(--border-radius-pill, 2rem);',
    '  font-size: 0.875rem;',
    '  font-weight: 500;',
    '  letter-spacing: 0.01em;',
    '  opacity: 0;',
    '  pointer-events: none;',
    '  transition: opacity 0.25s ease, transform 0.25s ease;',
    '  z-index: 200000;',
    '  white-space: nowrap;',
    '  box-shadow: var(--box-shadow-dialog, 0 4px 16px rgba(0,0,0,0.15));',
    '  display: flex;',
    '  align-items: center;',
    '  gap: 0.4em;',
    '}',
    '.regle-toast.regle-toast--visible {',
    '  opacity: 1;',
    '  transform: translateX(-50%) translateY(0);',
    '}'
].join('\n');

    document.head.appendChild(style);
}());
/* === fin injection CSS === */

/* === fin Modèle:Règle === */

/* =============================================================================
   EFFET D'APPARITION EN CASCADE POUR LES MINICARDS (Intersection Observer)
   ============================================================================= */
mw.hook('wikipage.content').add(function ($content) {
    // On cherche toutes les minicards de la page qui n'ont pas encore été animées
    const cards = $content[0].querySelectorAll('.minicard:not(.is-loaded)');
    if (!cards.length) return;

    // Vérification de la compatibilité du navigateur
    if ('IntersectionObserver' in window) {
        let delay = 0;
        let resetDelayTimeout;

        const observer = new IntersectionObserver((entries, obs) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    // Si la carte entre dans l'écran, on applique un délai pour l'effet "cascade"
                    setTimeout(() => {
                        entry.target.classList.add('is-loaded');
                    }, delay);
                    
                    delay += 75; // 75ms de décalage entre chaque carte
                    
                    // On réinitialise le délai si on a fini de traiter un groupe de cartes
                    clearTimeout(resetDelayTimeout);
                    resetDelayTimeout = setTimeout(() => { delay = 0; }, 100);
                    
                    // On arrête d'observer cette carte pour ne l'animer qu'une seule fois
                    obs.unobserve(entry.target);
                }
            });
        }, {
            rootMargin: '0px 0px -30px 0px', // Déclenche l'animation 30px AVANT que la carte n'arrive en bas de l'écran
            threshold: 0.1
        });

        // On observe chaque carte trouvée
        cards.forEach(card => observer.observe(card));
    } else {
        // Fallback pour les très vieux navigateurs : on affiche tout direct
        cards.forEach(card => card.classList.add('is-loaded'));
    }
});

/* ============================================================
 * Nefald Page Search — Recherche dans le contenu de la page
 * ============================================================ */

/* --- Interception globale de Ctrl+F / Cmd+F --- */
$( document ).on( 'keydown.nefaldPageSearch', function ( e ) {
    if ( ( e.ctrlKey || e.metaKey ) && e.key.toLowerCase() === 'f' ) {
        var $searchInput = $( '.nefald-page-search__input' );
        
        if ( $searchInput.length > 0 ) {
            e.preventDefault();
            
            /* Scrolle vers la barre si elle n'est pas sticky ou visible */
            $searchInput[0].scrollIntoView( { behavior: 'smooth', block: 'center' } );
            
            /* Donne le focus et sélectionne le texte pour une frappe rapide */
            $searchInput.focus().select();
        }
    }
} );

mw.hook( 'wikipage.content' ).add( function ( $content ) {
    $content.find( '.nefald-page-search-container' ).each( function () {
        var $container = $( this );
        if ( $container.children().length > 0 ) { return; }

        /* Mise à jour du placeholder par défaut pour indiquer le raccourci */
        var placeholder = $container.data( 'placeholder' ) || 'Rechercher dans la page (Ctrl+F)…';

        /* --- Injection du HTML --- */
        var html =
            '<div class="nefald-page-search">' +
                '<span class="nefald-page-search__icon">&#x2315;</span>' +
                '<input class="nefald-page-search__input" type="search"' +
                    ' placeholder="' + placeholder + '"' +
                    ' aria-label="Rechercher dans la page"' +
                    ' autocomplete="off" spellcheck="false" />' +
                '<span class="nefald-page-search__counter"></span>' +
                '<button class="nefald-page-search__prev" title="Résultat précédent">&#x2191;</button>' +
                '<button class="nefald-page-search__next" title="Résultat suivant">&#x2193;</button>' +
                '<button class="nefald-page-search__clear" title="Effacer">&#x2715;</button>' +
            '</div>';
        $container.append( html );

        /* --- Références DOM --- */
        var $input   = $container.find( '.nefald-page-search__input' );
        var $counter = $container.find( '.nefald-page-search__counter' );
        var $prev    = $container.find( '.nefald-page-search__prev' );
        var $next    = $container.find( '.nefald-page-search__next' );
        var $clear   = $container.find( '.nefald-page-search__clear' );

        /* Zone dans laquelle on cherche : le contenu principal */
        var $scope = $( '#mw-content-text .mw-parser-output' );

        var matches = [];
        var currentIndex = -1;
        var timer = null;
        var HIGHLIGHT_CLASS = 'nefald-search-highlight';
        var ACTIVE_CLASS    = 'nefald-search-highlight--active';

        /* --- Fonctions utilitaires --- */

        /**
         * Parcourt récursivement les nœuds texte d'un élément
         * en ignorant les scripts, styles et le widget lui-même.
         */
        function getTextNodes( root ) {
            var nodes = [];
            var walker = document.createTreeWalker(
                root,
                NodeFilter.SHOW_TEXT,
                {
                    acceptNode: function ( node ) {
                        var parent = node.parentNode;
                        if ( !parent ) { return NodeFilter.FILTER_REJECT; }
                        var tag = parent.nodeName.toLowerCase();
                        if ( tag === 'script' || tag === 'style' || tag === 'textarea' || tag === 'noscript' ) {
                            return NodeFilter.FILTER_REJECT;
                        }
                        /* Ignorer le widget de recherche lui-même */
                        if ( $( parent ).closest( '.nefald-page-search-container' ).length ) {
                            return NodeFilter.FILTER_REJECT;
                        }
                        /* Ignorer les nœuds vides */
                        if ( node.nodeValue.trim() === '' ) {
                            return NodeFilter.FILTER_REJECT;
                        }
                        return NodeFilter.FILTER_ACCEPT;
                    }
                }
            );
            while ( walker.nextNode() ) {
                nodes.push( walker.currentNode );
            }
            return nodes;
        }

        /**
         * Supprime tous les surlignages existants en restaurant le texte d'origine.
         */
        function clearHighlights() {
            $scope.find( '.' + HIGHLIGHT_CLASS ).each( function () {
                var parent = this.parentNode;
                parent.replaceChild( document.createTextNode( this.textContent ), this );
                parent.normalize();
            } );
            matches = [];
            currentIndex = -1;
            $counter.text( '' );
        }

        /**
         * Cherche `query` dans tous les nœuds texte et entoure
         * chaque occurrence d'un <mark>.
         */
        function doSearch( query ) {
            clearHighlights();
            if ( !query || query.length < 2 ) { return; }

            /* Échapper les caractères spéciaux pour la RegExp */
            var escaped = query.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
            var regex = new RegExp( '(' + escaped + ')', 'gi' );

            var textNodes = getTextNodes( $scope[ 0 ] );

            textNodes.forEach( function ( node ) {
                var text = node.nodeValue;
                if ( !regex.test( text ) ) { return; }
                regex.lastIndex = 0; /* reset après test */

                var frag = document.createDocumentFragment();
                var lastIndex = 0;
                var match;

                while ( ( match = regex.exec( text ) ) !== null ) {
                    /* Texte avant le match */
                    if ( match.index > lastIndex ) {
                        frag.appendChild( document.createTextNode( text.slice( lastIndex, match.index ) ) );
                    }
                    /* Le match lui-même dans un <mark> */
                    var mark = document.createElement( 'mark' );
                    mark.className = HIGHLIGHT_CLASS;
                    mark.textContent = match[ 1 ];
                    frag.appendChild( mark );
                    lastIndex = regex.lastIndex;
                }

                /* Texte restant après le dernier match */
                if ( lastIndex < text.length ) {
                    frag.appendChild( document.createTextNode( text.slice( lastIndex ) ) );
                }

                node.parentNode.replaceChild( frag, node );
            } );

            matches = $scope.find( '.' + HIGHLIGHT_CLASS ).toArray();

            if ( matches.length > 0 ) {
                currentIndex = 0;
                goTo( 0 );
            } else {
                $counter.text( '0 résultat' );
            }
        }

        /**
         * Navigue vers le match d'index donné.
         */
        function goTo( index ) {
            if ( matches.length === 0 ) { return; }

            /* Retirer la classe active du précédent */
            $( matches ).removeClass( ACTIVE_CLASS );

            /* Boucler */
            if ( index < 0 ) { index = matches.length - 1; }
            if ( index >= matches.length ) { index = 0; }
            currentIndex = index;

            var el = matches[ currentIndex ];
            $( el ).addClass( ACTIVE_CLASS );

            /* Scroller vers l'élément actif */
            el.scrollIntoView( { behavior: 'smooth', block: 'center' } );

            $counter.text( ( currentIndex + 1 ) + ' / ' + matches.length );
        }

        /* --- Événements --- */

        $input.on( 'input', function () {
            clearTimeout( timer );
            var query = $input.val();
            /* Debounce de 250 ms pour ne pas spammer sur chaque frappe */
            timer = setTimeout( function () {
                doSearch( query );
            }, 250 );
        } );

        $next.on( 'click', function () {
            goTo( currentIndex + 1 );
        } );

        $prev.on( 'click', function () {
            goTo( currentIndex - 1 );
        } );

        $clear.on( 'click', function () {
            $input.val( '' ).trigger( 'focus' );
            clearHighlights();
        } );

        /* Raccourcis clavier dans l'input */
        $input.on( 'keydown', function ( e ) {
            if ( e.key === 'Enter' ) {
                e.preventDefault();
                if ( e.shiftKey ) {
                    goTo( currentIndex - 1 );
                } else {
                    goTo( currentIndex + 1 );
                }
            }
            if ( e.key === 'Escape' ) {
                $input.val( '' );
                clearHighlights();
                $input.trigger( 'blur' );
            }
        } );
    } );
} );
Les témoins (''cookies'') nous aident à fournir nos services. En utilisant nos services, vous acceptez notre utilisation de témoins.