diff --git a/src/sidebar/sidebar.html b/src/sidebar/sidebar.html index 6c776708d7f458031d54a3599df5bd903be3884f..7345d94fcdf41aa9bb1f9e7f3cb025aef193fc0d 100644 --- a/src/sidebar/sidebar.html +++ b/src/sidebar/sidebar.html @@ -296,14 +296,30 @@ } .lexicon-highlight { - background-color: var(--highlight-color, rgba(255, 255, 0, 0.3)); - border-bottom: 1px dashed #666; + position: relative; /* Pour positionner le conteneur de bandes en absolu */ + display: inline-block; /* Pour que le span prenne en compte les dimensions */ + padding-bottom: 4px; /* Laisser de l'espace pour les bandes */ + border-bottom: 1px dashed #666; /* Vous pouvez conserver votre bordure si besoin */ transition: background-color 0.3s; + background-color: rgba(255, 255, 0, 0.15); + + } + + .color-bands { + position: absolute; + left: 0; + right: 0; + bottom: 0; + height: 3px; /* Ajustez la hauteur des bandes */ + display: flex; /* Pour répartir équitablement les bandes */ } - .lexicon-highlight:hover { - background-color: var(--highlight-color-hover, rgba(255, 255, 0, 0.5)); + + .color-bands div { + flex: 1; /* Chaque bande occupe une part égale */ + height: 100%; } + .lexicon-section { margin-bottom: 10px; } diff --git a/src/utils/highlighting.js b/src/utils/highlighting.js index ac318cda93d9d57480bbf05ff0d9d63a2d148c4f..3a9829850c6793efaed40504cf5c789f6616adb6 100644 --- a/src/utils/highlighting.js +++ b/src/utils/highlighting.js @@ -1,7 +1,7 @@ +// Variables globales window.activeLexiconIds = window.activeLexiconIds || new Set(); - -// Logs immédiats pour vérifier l'injection +// Logs immédiats console.log("🔵 DÉBUT DU FICHIER highlighting.js"); try { log("✅ highlighting.js chargé"); @@ -9,25 +9,19 @@ try { console.error("⌠Erreur avec la fonction log:", e); } -// Vérification de l'existence de l'objet browser +// Vérification de l'environnement console.log("🔠Vérification de l'environnement:", { hasBrowser: typeof browser !== 'undefined', windowLocation: window.location.href }); -// Ajout d'un log global pour capturer les erreurs non gérées +// Gestion globale des erreurs window.onerror = function(msg, url, line, col, error) { - console.error("🔴 Erreur globale:", { - message: msg, - url: url, - line: line, - col: col, - error: error - }); + console.error("🔴 Erreur globale:", { message: msg, url: url, line: line, col: col, error: error }); return false; }; -// Au début du fichier, récupérons le token du storage local +// Fonction d'initialisation du token depuis le stockage local async function initAuthToken() { try { const { accessToken } = await browser.storage.local.get("accessToken"); @@ -45,11 +39,9 @@ async function initAuthToken() { return false; } } - -// Appel immédiat de l'initialisation initAuthToken(); -// Gardons aussi l'écoute des mises à jour du token +// Écoute de mise à jour du token browser.runtime.onMessage.addListener((message) => { if (message.command === "updateAuthToken" && message.token) { window.authToken = message.token; @@ -58,7 +50,6 @@ browser.runtime.onMessage.addListener((message) => { } }); -// Déplacer checkAndRestoreHighlightingState après la déclaration de startHighlighting (function () { try { if (window.hasRun) { @@ -67,12 +58,13 @@ browser.runtime.onMessage.addListener((message) => { } window.hasRun = true; - // Variables globales pour le script + // Variables internes let lexiconWordsCache = new Map(); let highlightingActive = false; window.highlightingActive = false; let observer = null; - + + // Gestion de la visibilité/page show pour réappliquer le surlignage document.addEventListener('visibilitychange', async () => { if (document.visibilityState === 'visible' && window.highlightingActive && activeLexiconIds.size > 0) { log("📄 Page redevenue visible, réinitialisation du surlignage"); @@ -91,11 +83,8 @@ browser.runtime.onMessage.addListener((message) => { attachMutationObserver(); } }); - - // ───────────────────────────────────────────────────────────────────────────── - // Gestion des messages du background - // ───────────────────────────────────────────────────────────────────────────── - log("📡 Enregistrement du listener de messages"); + + // Gestion des messages reçus du background browser.runtime.onMessage.addListener((message, sender, sendResponse) => { console.log("📨 Message reçu:", message, "Context:", { highlightingActive, @@ -136,34 +125,88 @@ browser.runtime.onMessage.addListener((message) => { return false; }); - + log("📡 Enregistrement du script auprès du background"); browser.runtime.sendMessage({ command: "register-highlighting-script" }); - + log("🚀 Initialisation de highlighting.js"); - - // ───────────────────────────────────────────────────────────────────────────── - // Fonctions principales - // ───────────────────────────────────────────────────────────────────────────── + + // Fonction asynchrone pour mettre à jour le style d'un élément surligné en fonction des lexiques qui le concernent + async function updateHighlightStyle(span, lexIds) { + if (!lexIds || lexIds.length === 0) { + span.style.backgroundColor = "rgba(255, 255, 0, 0.25)"; + span.style.backgroundImage = ""; + return; + } + if (lexIds.length === 1) { + const hexColor = await getColorForLexicon(lexIds[0]); + const rgbaColor = hexToRgba(hexColor, 0.25); + span.style.backgroundColor = rgbaColor; + span.style.backgroundImage = ""; + } else { + const hexColors = await Promise.all(lexIds.map(id => getColorForLexicon(id))); + const colors = hexColors.map(hex => hexToRgba(hex, 0.25)); + const n = colors.length; + let stops = []; + for (let i = 0; i < n; i++) { + const start = ((100 * i) / n).toFixed(2) + '%'; + const end = ((100 * (i + 1)) / n).toFixed(2) + '%'; + stops.push(`${colors[i]} ${start}, ${colors[i]} ${end}`); + } + const gradient = `linear-gradient(90deg, ${stops.join(', ')})`; + span.style.backgroundImage = gradient; + + } + } + + // Vérifier si un mot appartient à un lexique (à l'aide de la cache) + function wordIsInLexicon(lexiconId, word) { + const wordsSet = lexiconWordsCache.get(String(lexiconId)); + return wordsSet ? wordsSet.has(word.toLowerCase()) : false; + } + + // Mise à jour incrémentale des éléments déjà surlignés lorsqu'un nouveau lexique est ajouté + async function updateHighlightsForNewLexicon(newLexiconId) { + const spans = document.querySelectorAll('.lexicon-highlight'); + for (let span of spans) { + const word = span.textContent; + if (wordIsInLexicon(newLexiconId, word)) { + let lexIds = []; + try { + lexIds = JSON.parse(span.getAttribute('data-lexicons')); + } catch (e) { + lexIds = []; + } + if (!lexIds.includes(newLexiconId)) { + lexIds.push(newLexiconId); + span.setAttribute('data-lexicons', JSON.stringify(lexIds)); + await updateHighlightStyle(span, lexIds); + } + } + } + } + + // Fonction startHighlighting modifiée pour intégrer la mise à jour incrémentale async function startHighlighting(lexiconId) { try { await initAuthToken(); - if (!window.authToken) { throw new Error("⌠Pas de token d'authentification disponible"); } - if (lexiconId) { - activeLexiconIds.add(lexiconId); - const activeLexicons = Array.from(activeLexiconIds); - await browser.storage.local.set({ activeLexicons }); - log("📊 Lexiques actifs sauvegardés:", activeLexicons); + if (!activeLexiconIds.has(lexiconId)) { + activeLexiconIds.add(lexiconId); + const activeLexicons = Array.from(activeLexiconIds); + await browser.storage.local.set({ activeLexicons }); + log("📊 Lexiques actifs sauvegardés:", activeLexicons); + // Mise à jour de la cache pour inclure le nouveau lexique + await updateLexiconCache(); + // Mise à jour immédiate des éléments surlignés pour intégrer le nouveau lexique + await updateHighlightsForNewLexicon(lexiconId); + } } - window.highlightingActive = true; highlightingActive = true; - - await updateLexiconCache(); highlightVisibleContent(); attachMutationObserver(); return true; @@ -174,14 +217,13 @@ browser.runtime.onMessage.addListener((message) => { throw error; } } - + async function stopHighlighting(lexiconId) { try { if (lexiconId) { activeLexiconIds.delete(lexiconId); const activeLexicons = Array.from(activeLexiconIds); await browser.storage.local.set({ activeLexicons }); - if (activeLexiconIds.size === 0) { window.highlightingActive = false; highlightingActive = false; @@ -205,7 +247,8 @@ browser.runtime.onMessage.addListener((message) => { throw error; } } - + + // Mise à jour du cache des lexiques async function updateLexiconCache() { console.log("📥 updateLexiconCache - Début avec context:", { authToken: !!window.authToken, @@ -217,52 +260,40 @@ browser.runtime.onMessage.addListener((message) => { if (!window.authToken) { throw new Error("Pas de token d'authentification"); } - - // Vérification explicite de getAllLexiconWords if (typeof window.getAllLexiconWords !== 'function') { log("âš ï¸ getAllLexiconWords n'est pas une fonction"); log("Type de getAllLexiconWords:", typeof window.getAllLexiconWords); log("Contenu de window.getAllLexiconWords:", window.getAllLexiconWords); throw new Error("getAllLexiconWords n'est pas disponible"); } - log("📥 Appel de getAllLexiconWords..."); allWords = await window.getAllLexiconWords(window.authToken); log("📠Réponse de getAllLexiconWords:", allWords); - if (!allWords || typeof allWords !== 'object') { throw new Error(`Format de données invalide: ${JSON.stringify(allWords)}`); } - lexiconWordsCache.clear(); - - // Vérification de la structure des données if (Object.keys(allWords).length === 0) { log("âš ï¸ Aucun lexique reçu de getAllLexiconWords"); return false; } - for (const [lexiconName, words] of Object.entries(allWords)) { if (!Array.isArray(words)) { console.warn(`âš ï¸ Format invalide pour le lexique ${lexiconName}:`, words); continue; } - const lexiconId = lexiconName.match(/\[(\d+)\]$/)?.[1]; if (!lexiconId) { console.warn(`âš ï¸ Impossible d'extraire l'ID du lexique depuis: ${lexiconName}`); continue; } - log(`📎 Traitement du lexique ${lexiconName} (ID: ${lexiconId})`); - if (activeLexiconIds.has(Number(lexiconId))) { lexiconWordsCache.set(lexiconId, new Set(words)); log(`📖 Lexique ${lexiconId} chargé avec ${words.length} mots`); } } - - log("✅ Cache des lexiques mis à jour:", + log("✅ Cache des lexiques mis à jour:", Object.fromEntries([...lexiconWordsCache.entries()].map(([id, words]) => [id, [...words]]))); return true; } catch (error) { @@ -276,194 +307,155 @@ browser.runtime.onMessage.addListener((message) => { throw error; } } - - // ───────────────────────────────────────────────────────────────────────────── - // Fonctions de surlignage - // ───────────────────────────────────────────────────────────────────────────── + + // Surlignage du contenu visible function highlightVisibleContent() { if (!highlightingActive) { log("â¸ï¸ Surlignage inactif, sortie"); return; } log("🔠Début du surlignage du contenu visible"); - - // Ajout d'une limite de traitement par lot const BATCH_SIZE = 100; - const BATCH_DELAY = 10; // ms entre chaque lot - + const BATCH_DELAY = 10; // ms const textNodes = []; const walker = document.createTreeWalker( document.body, NodeFilter.SHOW_TEXT, { acceptNode: (node) => { - // Optimisation des filtres const parent = node.parentElement; - if (!parent || - parent.closest('script, style, .lexicon-highlight') || - !node.textContent.trim() || - getComputedStyle(parent).display === 'none') { + if (!parent || parent.closest('script, style, .lexicon-highlight') || !node.textContent.trim() || getComputedStyle(parent).display === 'none') { return NodeFilter.FILTER_REJECT; } return NodeFilter.FILTER_ACCEPT; } } ); - let node; while (node = walker.nextNode()) { textNodes.push(node); } - log(`🔤 ${textNodes.length} nÅ“uds de texte trouvés à traiter`); - - // Traitement par lots const processNextBatch = (startIndex) => { if (!highlightingActive || startIndex >= textNodes.length) { return; } - const endIndex = Math.min(startIndex + BATCH_SIZE, textNodes.length); const batch = textNodes.slice(startIndex, endIndex); - batch.forEach(processTextNode); - if (endIndex < textNodes.length) { setTimeout(() => processNextBatch(endIndex), BATCH_DELAY); } }; - processNextBatch(0); } - + + // Traitement d'un nÅ“ud de texte function processTextNode(textNode) { if (activeLexiconIds.size === 0) { log("âš ï¸ Aucun lexique actif, sortie du processTextNode"); return; } - const text = textNode.textContent; log(`🔠Traitement du texte: "${text.substring(0, 50)}..."`); - let lastIndex = 0; let fragments = []; - const allWords = new Set(); + const matchedLexiconIdsMap = new Map(); for (const [lexiconId, words] of lexiconWordsCache.entries()) { - // Convertir directement en nombre car lexiconId est déjà l'ID numérique const numericId = parseInt(lexiconId); log(`🔄 Vérification du lexique ${lexiconId} (ID: ${numericId})`); if (activeLexiconIds.has(numericId)) { log(`✅ Lexique ${lexiconId} actif, ajout de ${words.size} mots`); words.forEach(word => allWords.add(word)); + words.forEach(word => { + const lowerCaseWord = word.toLowerCase(); + if (!matchedLexiconIdsMap.has(lowerCaseWord)) { // Correction ici ! + matchedLexiconIdsMap.set(lowerCaseWord, []); + } + matchedLexiconIdsMap.get(lowerCaseWord).push(lexiconId); + }); } } - log(`🔤 Nombre total de mots à rechercher: ${allWords.size}`); if (allWords.size === 0) { log("âš ï¸ Aucun mot à rechercher dans les lexiques actifs"); return; } - const wordsPattern = Array.from(allWords) .sort((a, b) => b.length - a.length) .map(escapeRegExp) .join("|"); - if (!wordsPattern) { log("âš ï¸ Aucun mot à rechercher, sortie"); return; } - const regex = new RegExp(`\\b(${wordsPattern})\\b`, "gi"); let match; let matchCount = 0; - while ((match = regex.exec(text)) !== null) { matchCount++; if (match.index > lastIndex) { fragments.push(document.createTextNode(text.slice(lastIndex, match.index))); } - const span = document.createElement("span"); span.textContent = match[0]; span.className = "lexicon-highlight"; - - let currentLexiconId = null; - for (const [id, words] of lexiconWordsCache.entries()) { - if (activeLexiconIds.has(Number(id)) && words.has(match[0])) { - currentLexiconId = id; - break; - } - } - - if (currentLexiconId) { - getColorForLexicon(currentLexiconId).then(hexColor => { - const rgbaColor = hexToRgba(hexColor, 0.3); - const rgbaHoverColor = hexToRgba(hexColor, 0.5); - span.style.setProperty('--highlight-color', rgbaColor); - span.style.setProperty('--highlight-color-hover', rgbaHoverColor); - span.style.backgroundColor = rgbaColor; - }); + span.style.display = "inline-block"; + + const matchedLexiconIds = matchedLexiconIdsMap.get(match[0].toLowerCase()) || []; + span.setAttribute('data-lexicons', JSON.stringify(matchedLexiconIds)); + + if (matchedLexiconIds.length === 0) { + span.style.backgroundColor = "rgba(255, 255, 0, 0.25)"; } else { - // Optionnel : couleur par défaut - span.style.backgroundColor = "rgba(255, 255, 0, 0.3)"; + updateHighlightStyle(span, matchedLexiconIds); } - fragments.push(span); lastIndex = regex.lastIndex; } - if (matchCount > 0) { log(`✨ ${matchCount} correspondances trouvées dans le nÅ“ud`); } - if (lastIndex < text.length) { fragments.push(document.createTextNode(text.slice(lastIndex))); } - if (fragments.length > 0) { const parent = textNode.parentNode; fragments.forEach(fragment => parent.insertBefore(fragment, textNode)); parent.removeChild(textNode); } } - + + // Suppression de tous les surlignages function removeAllHighlights() { log("🧹 Suppression de tous les surlignages"); const highlights = document.querySelectorAll('.lexicon-highlight'); log(`📊 ${highlights.length} surlignages à supprimer`); - highlights.forEach(highlight => { const text = highlight.textContent; const textNode = document.createTextNode(text); highlight.parentNode.replaceChild(textNode, highlight); }); } - - // ───────────────────────────────────────────────────────────────────────────── + // Gestion des mutations DOM - // ───────────────────────────────────────────────────────────────────────────── function attachMutationObserver() { log("👀 Attachement de l'observateur de mutations"); - let debounceTimer = null; const DEBOUNCE_DELAY = 250; // ms - observer = new MutationObserver((mutations) => { if (debounceTimer) { clearTimeout(debounceTimer); } - debounceTimer = setTimeout(() => { log(`🔄 Traitement groupé de ${mutations.length} mutations DOM`); let shouldHighlight = false; - for (const mutation of mutations) { if (mutation.type === 'childList') { for (const node of mutation.addedNodes) { if (node.nodeType === Node.ELEMENT_NODE && - !node.closest('.lexicon-highlight') && + !node.closest('.lexicon-highlight') && node.textContent.trim()) { shouldHighlight = true; break; @@ -472,19 +464,17 @@ browser.runtime.onMessage.addListener((message) => { } if (shouldHighlight) break; } - if (shouldHighlight) { highlightVisibleContent(); } }, DEBOUNCE_DELAY); }); - observer.observe(document.body, { childList: true, subtree: true }); } - + function detachMutationObserver() { if (observer) { log("👋 Détachement de l'observateur de mutations"); @@ -492,40 +482,32 @@ browser.runtime.onMessage.addListener((message) => { observer = null; } } - - // ───────────────────────────────────────────────────────────────────────────── - // Utilitaires - // ───────────────────────────────────────────────────────────────────────────── + function escapeRegExp(string) { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } - + function getLexiconIdFromName(lexiconName) { const match = lexiconName.match(/\[(\d+)\]$/); const id = match ? parseInt(match[1]) : null; log(`ðŸ·ï¸ Extraction de l'ID depuis '${lexiconName}': ${id}`); return id; } - - // Ajout d'une vérification au démarrage + log("🔠Vérification des dépendances au chargement:", { authToken: !!window.authToken, getAllLexiconWords: typeof window.getAllLexiconWords }); - - // Puis déclarer checkAndRestoreHighlightingState + async function checkAndRestoreHighlightingState() { try { const { activeLexicons } = await browser.storage.local.get("activeLexicons"); - // Ne rien faire si pas de lexiques actifs if (!activeLexicons || !Array.isArray(activeLexicons) || activeLexicons.length === 0) { window.highlightingActive = false; highlightingActive = false; return; } log("🔄 État des lexiques trouvé:", activeLexicons); - - // Restauration de l'état de surlignage for (const lexiconId of activeLexicons) { await startHighlighting(lexiconId); } @@ -535,10 +517,9 @@ browser.runtime.onMessage.addListener((message) => { highlightingActive = false; } } - - // Ne pas démarrer automatiquement le surlignage + checkAndRestoreHighlightingState(); - + } catch (error) { console.error("🔴 Erreur critique dans l'IIFE:", error); }