JVCode

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

À data de 20/12/2025. Ver a versão mais recente.

Terá de instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

Terá de instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Terá de instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Terá de instalar uma extensão como Tampermonkey para instalar este script.

Terá de instalar uma extensão de gestão de scripts de utilizador para instalar este script.

(Já tenho um gestor de scripts de utilizador, deixe-me instalá-lo!)

Terá de instalar uma extensão como Stylus para instalar este estilo.

Terá de instalar uma extensão como Stylus para instalar este estilo.

Terá de instalar uma extensão como Stylus para instalar este estilo.

Terá de instalar uma extensão de gestão de estilos de utilizador para instalar este estilo.

Terá de instalar uma extensão de gestão de estilos de utilizador para instalar este estilo.

Terá de instalar uma extensão de gestão de estilos de utilizador para instalar este estilo.

(Já tenho um gestor de estilos de utilizador, deixe-me instalá-lo!)

// ==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 });

})();