JVCode

Améliore l'affichage du code sur jeuxvideo.com : coloration syntaxique, numéros de ligne, bouton copier, design moderne.

À partir de 2025-12-20. Voir la dernière version.

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name         JVCode
// @namespace    http://tampermonkey.net/
// @version      1.0.0
// @description  Améliore l'affichage du code sur jeuxvideo.com : coloration syntaxique, numéros de ligne, bouton copier, design moderne.
// @author       FaceDePet
// @match        https://www.jeuxvideo.com/forums/*
// @match        https://www.jeuxvideo.com/messages-prives/*
// @icon         https://image.noelshack.com/fichiers/2017/04/1485268586-hackeur-v1.png
// @require      https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js
// @resource     HLJS_CSS https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css
// @license      MIT
// @grant        GM_addStyle
// @grant        GM_getResourceText
// @grant        GM_setClipboard
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    // (Highlight.js + Custom UI
    const hljsCss = GM_getResourceText("HLJS_CSS");
    GM_addStyle(hljsCss);

    const customCss = `
        /* Conteneur principal */
        .jv-enhanced-code {
            background-color: #282c34;
            border-radius: 8px;
            margin: 10px 0;
            overflow: hidden;
            font-family: 'Fira Code', 'Consolas', 'Monaco', 'Courier New', monospace;
            font-size: 13px;
            box-shadow: 0 4px 6px rgba(0,0,0,0.3);
            border: 1px solid #3e4451;
            position: relative;
            max-width: 100%;
            display: flex;
            flex-direction: column;
        }

        /* En-tête */
        .jv-code-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            background-color: #21252b;
            padding: 5px 15px;
            border-bottom: 1px solid #181a1f;
            color: #abb2bf;
            font-size: 11px;
            text-transform: uppercase;
            letter-spacing: 0.5px;
            user-select: none;
            flex-shrink: 0;
        }

        .jv-lang-badge {
            font-weight: bold;
            color: #61afef;
        }

        /* Bouton Copier */
        .jv-copy-btn {
            background: transparent;
            border: 1px solid #3e4451;
            color: #abb2bf;
            cursor: pointer;
            padding: 3px 8px;
            border-radius: 4px;
            font-size: 11px;
            transition: all 0.2s;
            display: flex;
            align-items: center;
            gap: 5px;
        }
        .jv-copy-btn:hover {
            background-color: #3e4451;
            color: white;
        }
        .jv-copy-btn.copied {
            border-color: #98c379;
            color: #98c379;
        }

        /* Corps du code */
        .jv-code-body {
            display: flex;
            width: 100%;
            min-width: 0;
        }

        /* Colonne des numéros de ligne */
        .jv-line-numbers {
            text-align: right;
            padding: 10px 10px 10px 15px;
            background-color: #282c34;
            color: #495162;
            border-right: 1px solid #3e4451;
            user-select: none;
            line-height: 1.5;
            flex-shrink: 0;
            z-index: 2;
        }

        /* Colonne du code */
        .jv-code-content {
            padding: 10px 15px;
            background-color: #282c34;
            color: #abb2bf;
            line-height: 1.5;
            tab-size: 4;
            flex-grow: 1;
            overflow-x: auto;
            min-width: 0;
        }

        .jv-code-content pre {
            margin: 0 !important;
            padding: 0 !important;
            border: none !important;
            background: none !important;
            display: inline-block;
            min-width: 100%;
        }

        .jv-code-content code.hljs {
            padding: 0;
            background: transparent;
            white-space: pre;
            overflow-x: visible;
        }

        .jv-code-content::-webkit-scrollbar {
            height: 8px;
            background-color: #282c34;
        }
        .jv-code-content::-webkit-scrollbar-thumb {
            background-color: #4b5363;
            border-radius: 4px;
        }
        .jv-code-content::-webkit-scrollbar-corner {
            background-color: #282c34;
        }
    `;
    GM_addStyle(customCss);

    function enhanceCodeBlocks(container = document) {
        const blocks = container.querySelectorAll('pre.pre-jv:not([data-processed="true"])');

        blocks.forEach(pre => {
            const codeElement = pre.querySelector('code.code-jv');
            if (!codeElement) return;

            pre.setAttribute('data-processed', 'true');

            let rawCode = codeElement.textContent;
            // enlève les retours chariots du tout début et toute fin, mais garde l'indentation
            rawCode = rawCode.replace(/^\s*\n/g, '').replace(/\n\s*$/g, '');

            if(rawCode === "") return;

            const highlightResult = hljs.highlightAuto(rawCode);
            const detectedLang = highlightResult.language || 'text';
            const highlightedCode = highlightResult.value;

            const lineCount = rawCode.split(/\r\n|\r|\n/).length;
            let lineNumbersHtml = '';
            for (let i = 1; i <= lineCount; i++) {
                lineNumbersHtml += `${i}\n`;
            }

            const wrapper = document.createElement('div');
            wrapper.className = 'jv-enhanced-code';

            wrapper.innerHTML = `
                <div class="jv-code-header">
                    <span class="jv-lang-badge">${detectedLang}</span>
                    <button class="jv-copy-btn" title="Copier le code">
                        <span>Copier</span>
                    </button>
                </div>
                <div class="jv-code-body">
                    <div class="jv-line-numbers"><pre>${lineNumbersHtml}</pre></div>
                    <div class="jv-code-content"><pre><code class="hljs ${detectedLang}">${highlightedCode}</code></pre></div>
                </div>
            `;

            const copyBtn = wrapper.querySelector('.jv-copy-btn');
            copyBtn.addEventListener('click', () => {
                navigator.clipboard.writeText(rawCode).then(() => {
                    const originalText = copyBtn.innerHTML;
                    copyBtn.innerHTML = '<span>Copié !</span>';
                    copyBtn.classList.add('copied');
                    setTimeout(() => {
                        copyBtn.innerHTML = '<span>Copier</span>';
                        copyBtn.classList.remove('copied');
                    }, 2000);
                }).catch(err => {
                    console.error('Erreur copie :', err);
                });
            });

            pre.style.display = 'none';
            pre.parentNode.insertBefore(wrapper, pre);
        });
    }

    enhanceCodeBlocks();

    // Observer
    const observer = new MutationObserver((mutations) => {
        let needsUpdate = false;
        mutations.forEach(mutation => {
            if (mutation.addedNodes.length > 0) {
                mutation.addedNodes.forEach(node => {
                    if (node.nodeType === 1) {
                        if (node.matches && node.matches('pre.pre-jv') || node.querySelector && node.querySelector('pre.pre-jv')) {
                            needsUpdate = true;
                        }
                        if (node.classList && (node.classList.contains('bloc-message-forum') || node.id === 'bloc-formulaire-forum')) {
                            needsUpdate = true;
                        }
                    }
                });
            }
        });
        if (needsUpdate) enhanceCodeBlocks();
    });

    const targetNode = document.getElementById('page-messages-forum') || document.body;
    observer.observe(targetNode, { childList: true, subtree: true });

})();