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 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 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 });

})();