JVCode

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

2025-12-20 기준 버전입니다. 최신 버전을 확인하세요.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

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

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

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

})();