Améliore l'affichage du code sur jeuxvideo.com : coloration syntaxique, numéros de ligne, bouton copier, design moderne.
اعتبارا من
// ==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 });
})();