From aaa953f38b8f33726c6cd7eda473ac232f7fcc87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pr=C3=A9nom=20Nom?= <adresse@mail.com> Date: Sat, 22 Feb 2025 02:21:01 +0100 Subject: [PATCH] fusion avec main + correction popup --- src/assets/lexicon_icon.js | 21 +- src/background/background.js | 225 ++++--- src/context_menu/browser_context_menu.js | 63 +- src/context_menu/custom_context_menu.js | 19 +- src/popup/popup.html | 132 ++-- src/popup/popup.js | 807 +++++++++++++---------- src/sidebar/sidebar.html | 45 +- src/sidebar/sidebar.js | 385 +++++++---- src/utils/api.js | 78 +-- src/utils/definitions.js | 148 ++--- src/utils/highlighting.js | 526 +++++++++++++++ src/utils/logger.js | 35 +- src/utils/stats.js | 88 +-- src/workers/pyodide_worker.js | 65 +- 14 files changed, 1750 insertions(+), 887 deletions(-) create mode 100644 src/utils/highlighting.js diff --git a/src/assets/lexicon_icon.js b/src/assets/lexicon_icon.js index 69833f8..1247bcf 100644 --- a/src/assets/lexicon_icon.js +++ b/src/assets/lexicon_icon.js @@ -19,7 +19,7 @@ async function getLexiconsColors(authToken) { log("✅ Couleurs des lexiques récupérées :", colors); return colors; } catch (error) { - console.error("⌠Erreur lors de la récupération des couleurs des lexiques :", error); + log("⌠Erreur lors de la récupération des couleurs des lexiques :", error); return {}; } } @@ -87,7 +87,7 @@ async function getOrCreateLexiconColor(lexiconId) { } } } catch (error) { - console.error("Erreur lors de la récupération des couleurs via l'API :", error); + log("Erreur lors de la récupération des couleurs via l'API :", error); } } // Si aucune couleur n'est associée, on la génère et on la sauvegarde @@ -133,7 +133,7 @@ async function updateLexiconColors(authToken) { await browser.storage.local.set({ lexiconColors: colorMapping }); return colorMapping; } catch (error) { - console.error("⌠Erreur lors de la mise à jour des couleurs :", error); + log("⌠Erreur lors de la mise à jour des couleurs :", error); return {}; } } @@ -147,6 +147,19 @@ async function getColorForLexicon(lexiconId) { const { lexiconColors } = await browser.storage.local.get("lexiconColors"); return (lexiconColors && lexiconColors[String(lexiconId)]) || "#cccccc"; } +/** + * Convertit une couleur hexadécimale en une couleur RGBA. + * @param {string} hex - La couleur en hexadécimal. + * @param {number} opacity - La transparence (0-1). + * @returns {string} La couleur RGBA. + */ +function hexToRgba(hex, opacity) { + const bigint = parseInt(hex.replace('#', ''), 16); + const r = (bigint >> 16) & 255; + const g = (bigint >> 8) & 255; + const b = bigint & 255; + return `rgba(${r}, ${g}, ${b}, ${opacity})`; +} window.updateLexiconColors = updateLexiconColors; window.getColorForLexicon = getColorForLexicon; @@ -155,4 +168,4 @@ window.convertColor = convertColor; window.getOrCreateLexiconColor = getOrCreateLexiconColor; window.createColorCircle = createColorCircle; window.getLexiconsColors = getLexiconsColors; - +window.hexToRgba = hexToRgba; diff --git a/src/background/background.js b/src/background/background.js index 66eb60a..b41da8b 100644 --- a/src/background/background.js +++ b/src/background/background.js @@ -10,7 +10,7 @@ const AUTH_LOGIN_URL = "https://prisms.lezinter.net/fr/login"; const AUTH_BALEX_URL = "https://prisms.lezinter.net/fr/headquarters/balex"; // ───────────────────────────────────────────────────────────────────────────── -// Logs de démarrage +// Logs de démarrage et initialisation // ───────────────────────────────────────────────────────────────────────────── log("🚀 ff2BaLex (background) chargé."); @@ -22,6 +22,11 @@ browser.runtime.onStartup.addListener(() => { log("🔄 Extension démarrée (onStartup)."); }); +browser.runtime.onInstalled.addListener(() => { + browser.storage.local.set({ extensionActive: false }); + log("🔔 Extension installée, état initialisé à désactivé."); +}); + // ───────────────────────────────────────────────────────────────────────────── // Suivi des changements dans le stockage // ───────────────────────────────────────────────────────────────────────────── @@ -45,7 +50,6 @@ browser.storage.onChanged.addListener((changes, area) => { if (!extensionActive) { log("Token ajouté, activation automatique de l'extension."); browser.storage.local.set({ extensionActive: true }); - enableExtensionFeatures(); browser.runtime.sendMessage({ action: "updateUI", extensionActive: true, @@ -68,7 +72,11 @@ async function isUserConnected() { async function refreshAllUI() { log("🔄 Rafraîchissement global de l'UI..."); - browser.runtime.sendMessage({ action: "refreshUI" }); + try { + await browser.runtime.sendMessage({ action: "refreshUI" }); + } catch (error) { + console.warn("Aucun récepteur pour 'refreshUI' :", error); + } } // ───────────────────────────────────────────────────────────────────────────── @@ -155,7 +163,6 @@ async function saveToken(token) { const { extensionActive } = await browser.storage.local.get("extensionActive"); if (!extensionActive) { await browser.storage.local.set({ extensionActive: true }); - enableExtensionFeatures(); browser.runtime.sendMessage({ action: "updateUI", extensionActive: true, @@ -167,7 +174,7 @@ async function saveToken(token) { } // ───────────────────────────────────────────────────────────────────────────── -// Gestion des messages reçus (depuis popup, etc.) +// Gestion des messages reçus // ───────────────────────────────────────────────────────────────────────────── browser.runtime.onMessage.addListener(async (message, sender, sendResponse) => { log("📩 Message reçu dans background.js :", message); @@ -217,6 +224,34 @@ browser.runtime.onMessage.addListener(async (message, sender, sendResponse) => { } break; } + case "toggleLexiconHighlight": { + const tabs = await browser.tabs.query({active: true, currentWindow: true}); + if (tabs[0]) { + try { + // S'assurer que le script est injecté + await browser.scripting.executeScript({ + target: { tabId: tabs[0].id }, + files: ["src/utils/highlighting.js"] + }); + + // Envoyer le message d'activation + await browser.tabs.sendMessage(tabs[0].id, { + command: message.isActive ? "activate-highlighting" : "deactivate-highlighting", + lexiconId: message.lexiconId + }); + log(`✅ Message de surlignage transmis à l'onglet ${tabs[0].id}`); + } catch (error) { + log("⌠Erreur lors de la gestion du surlignage:", error); + } + } + break; + } + + case "register-highlighting-script": { + log("📠Script de surlignage enregistré pour l'onglet", sender.tab.id); + break; + } + default: break; } @@ -249,14 +284,14 @@ browser.webNavigation.onCompleted.addListener(async (details) => { log("🔠Token détecté :", token); browser.runtime.sendMessage({ action: "saveToken", token }); } else { - console.error("⌠Token introuvable."); + log("⌠Token introuvable."); } return null; })(); ` }); } catch (error) { - console.error("⌠Erreur lors de la récupération du token :", error); + log("⌠Erreur lors de la récupération du token :", error); } } }, { url: [{ hostContains: "prisms.lezinter.net" }] }); @@ -290,25 +325,25 @@ function showInstructionPopup(details) { popup.style.transform = "translate(-50%, -50%)"; popup.style.backgroundColor = "#a08e9f"; popup.style.color = "#323046"; - popup.style.padding = "20px"; + popup.style.padding = "12px"; popup.style.borderRadius = "10px"; popup.style.boxShadow = "0 2px 10px rgba(0, 0, 0, 0.3)"; popup.style.zIndex = "10000"; popup.style.fontFamily = "Luciole"; - popup.style.fontSize = "14px"; + popup.style.fontSize = "16px"; popup.style.width = "300px"; popup.style.textAlign = "center"; popup.innerHTML = \` - <h5 style="color: #fff; font-weight: bold; margin-top: 0;">🔑 Connexion à l'extension</h5> - <p style="margin: 15px 0;"> - Après avoir renseigné vos identifiants, cliquez sur + <h5 style="color: #fff; font-weight: bold; margin-top: 2px;">Connexion à l'extension</h5> + <p style="margin: 8px 0;"> + Après avoir renseigné vos identifiants, veuillez cliquer sur <strong>"Se connecter avec BaLex"</strong>. </p> <button id="close-popup-btn" style=" width: 100%; margin-top: 15px; - padding: 10px; + padding: 8px; border: none; background-color: #8d5c70; color: #fbfcfc; @@ -334,44 +369,52 @@ let worker = null; function initWorker() { if (!worker) { - console.log("[Background] Initialisation du WebWorker..."); + log("[Background] Initialisation du WebWorker..."); try { worker = new Worker(browser.runtime.getURL("src/workers/pyodide_worker.js")); // Centralisation de l'écoute des messages et erreurs du Worker worker.addEventListener("message", handleWorkerMessage); worker.addEventListener("error", handleWorkerError); - console.log("[Background] WebWorker initialisé avec succès."); + log("[Background] WebWorker initialisé avec succès."); } catch (error) { - console.error("[Background] Échec de l'initialisation du WebWorker :", error); + log("[Background] Échec de l'initialisation du WebWorker :", error); } } } +function handleWorkerError(error) { + log("Erreur du WebWorker :", error.message); +} + function handleWorkerMessage(event) { const data = event.data; - console.log("[Background] Message du WebWorker :", data); + log("[Background] Message du WebWorker :", data); switch (data.type) { case "pyodide-simplemma": if (data.status === "success") { - console.log("[Background] Pyodide et Simplemma prêts. Mise à jour de l'état."); + log("[Background] Pyodide et Simplemma prêts. Mise à jour de l'état."); browser.storage.local.set({ pyodideSimplemmaReady: true }); checkAndUpdateTracking(); + } else if (data.status === "error") { + log("[Background] Erreur lors du chargement :", data.message); + } else if (data.status === "already_loaded") { + log("[Background] Pyodide et Simplemma déjà chargés."); } break; case "update-frequencies": - console.log("[Background] Mise à jour des fréquences :", data.frequencies); + log("[Background] Mise à jour des fréquences :", data.frequencies); notifyAllTabs({ command: "update-frequencies", frequencies: data.frequencies }); // Si un port stats est connecté, vous pouvez lui transmettre également : // port.postMessage({ command: "update-frequencies", frequencies: data.frequencies }); browser.storage.local.set({ lemmaFrequencies: data.frequencies }); break; case "threshold-exceeded": - console.log("[Background] Mots dépassant le seuil :", data.wordsAboveThreshold); + log("[Background] Mots dépassant le seuil :", data.wordsAboveThreshold); notifyAllTabs({ command: "threshold-exceeded", wordsAboveThreshold: data.wordsAboveThreshold }); break; case "word-added": - console.log(`[Background] Mot ajouté : '${data.word}' dans '${data.language}' (Lexiques: ${data.lexicons})`); + log(`[Background] Mot ajouté : '${data.word}' dans '${data.language}' (Lexiques: ${data.lexicons})`); break; default: console.warn("[Background] Message non traité du Worker :", data); @@ -379,10 +422,6 @@ function handleWorkerMessage(event) { } } -function handleWorkerError(error) { - console.error("[Background] Erreur dans le WebWorker :", error); -} - // Initialisation du worker dès le démarrage initWorker(); @@ -390,118 +429,112 @@ initWorker(); // Écoute des messages de la popup et transmission au WebWorker // ───────────────────────────────────────────────────────────────────────────── browser.runtime.onMessage.addListener(async (message, sender, sendResponse) => { - console.log("[Background] Message reçu :", message); + log("[Background] Message reçu :", message); if (!worker) { initWorker(); } if (message.command === "toggle-stats") { - console.log(`[Background] Statistiques ${message.isActive ? "activées" : "désactivées"}`); + log(`[Background] Statistiques ${message.isActive ? "activées" : "désactivées"}`); const { isActive } = message; await browser.storage.local.set({ isTrackingActive: isActive }); - if (!isActive) { - await browser.storage.local.set({ autoAdd: false }); - } + // if (!isActive) { + // await browser.storage.local.set({ autoAdd: false }); + // } checkAndUpdateTracking(); } if (message.command === "pyodide-simplemma") { - console.log("[Background] Demande d'initialisation de Pyodide et Simplemma..."); + log("[Background] Demande d'initialisation de Pyodide et Simplemma..."); worker.postMessage({ command: "pyodide-simplemma" }); } return true; }); +// ───────────────────────────────────────────────────────────────────────────── +// Fonction : Chargement des lexiques personnels dans le local storage +// ───────────────────────────────────────────────────────────────────────────── +async function saveUserLexicons() { + const { accessToken } = await browser.storage.local.get("accessToken"); + if (!accessToken) { + console.warn("Aucun token disponible, impossible de récupérer les lexiques."); + return; + } + log("Récupération des lexiques..."); + const lexicons = await getLexicons(accessToken); + const userLexicons = lexicons.filter(lexicon => lexicon.category === "User"); + if (userLexicons.length > 0) { + await browser.storage.local.set({ lexicons: userLexicons }); + log("Lexiques enregistrés dans le local storage :", userLexicons); + } else { + log("Aucun lexique utilisateur trouvé."); + } + +} // ───────────────────────────────────────────────────────────────────────────── -// Envoi des données au WebWorker : lexiques personnels +// Envoi des données au WebWorker : lexiques personnels et token // ───────────────────────────────────────────────────────────────────────────── // Charger et envoyer les lexiques au worker à la connexion + les stoplists associées browser.storage.onChanged.addListener(async (changes, area) => { if (area === "local" && changes.accessToken) { - console.log("Token mis à jour, récupération des lexiques..."); - sendLexiconsToWorker(); + log("Token mis à jour, récupération des lexiques..."); + const userLexicons = await saveUserLexicons(); // Récupérer les lexiques + sendLexiconsToWorker(userLexicons); // Envoyer les lexiques au Worker après une connexion sendAuthTokenToWorker(); } }); -async function sendLexiconsToWorker() { - const { accessToken } = await browser.storage.local.get("accessToken"); - if (!accessToken) { - console.warn("Aucun token disponible, impossible de récupérer les lexiques."); +// Envoyer les lexiques et stoplists au Worker +async function sendLexiconsToWorker(userLexicons = null) { + if (!userLexicons) { + const storedData = await browser.storage.local.get("lexicons"); + userLexicons = storedData.lexicons || []; + } + if (!Array.isArray(userLexicons) || userLexicons.length === 0) { + console.warn("[Background] Aucun lexique à envoyer au Worker."); return; } + log("[Background] Envoi des lexiques au Worker..."); + if (worker) { + worker.postMessage({ + command: "update-lexicons", + lexicons: JSON.stringify(userLexicons) + }); - console.log("Récupération des lexiques de l'utilisateur..."); - const lexicons = await getLexicons(accessToken); - - // Filtrage : Ne garder que les lexiques de catégorie "User" - const userLexicons = lexicons.filter(lexicon => lexicon.category === "User"); - - if (Array.isArray(userLexicons) && userLexicons.length > 0) { - await browser.storage.local.set({ lexicons: userLexicons }); - console.log("Lexiques utilisateur stockés :", userLexicons); - - console.log("[Background] Envoi des lexiques utilisateur au Worker..."); - if (worker) { - worker.postMessage({ command: "update-lexicons", lexicons: userLexicons }); - console.log("[Background] Envoi des lexiques utilisateur réussi !"); - } - // Charger et envoyer uniquement les stoplists des langues des lexiques utilisateur - const languages = [...new Set(userLexicons.map(lexicon => lexicon.language))]; - console.log("[Background] Langues détectées :", languages); - loadStoplistsForLanguages(languages); - - } else { - console.warn("Aucun lexique utilisateur trouvé."); + // Charger et envoyer uniquement les stoplists des langues des lexiques utilisateur + const languages = [...new Set(userLexicons.map(lexicon => lexicon.language))]; + log("[Background] Langues détectées :", languages); + loadStoplistsForLanguages(languages); + log("Lexiques envoyés au WebWorker !"); } } + // Charger et envoyer le token au worker à la connexion async function sendAuthTokenToWorker() { if (!worker) { console.warn("Worker non initialisé. Impossible d'envoyer le token."); return; } - const { accessToken } = await browser.storage.local.get("accessToken"); if (!accessToken) { console.warn("Aucun token disponible. Le worker ne pourra pas interagir avec l’API."); return; } - - console.log("🔑 Envoi du token au Worker..."); + log("Envoi du token au Worker..."); worker.postMessage({ command: "update-auth-token", accessToken }); } // ───────────────────────────────────────────────────────────────────────────── // Stoplists : Chargement et envoi au Worker // ───────────────────────────────────────────────────────────────────────────── -// let stoplistFr = []; - -// function loadStoplist() { -// fetch(browser.runtime.getURL("src/stoplists/stoplist_fr.txt")) -// .then(response => response.text()) -// .then(text => { -// stoplistFr = text.split("\n").map(word => word.trim()); -// console.log("[Background] Stoplist chargée :", stoplistFr); -// sendStoplistToWorker(); -// }) -// .catch(error => console.error("[Background] Erreur lors du chargement de la stoplist :", error)); -// } - -// function sendStoplistToWorker() { -// console.log("[Background] Envoi de la stoplist au Worker..."); -// worker.postMessage({ command: "update-stoplist", stoplist: stoplistFr }); -// } - async function loadStoplistsForLanguages(languages) { const stoplists = {}; - // Charger toutes les stoplists en parallèle await Promise.all( languages.map(async (lang) => { @@ -510,18 +543,17 @@ async function loadStoplistsForLanguages(languages) { const response = await fetch(browser.runtime.getURL(stoplistPath)); const text = await response.text(); stoplists[lang] = text.split("\n").map(word => word.trim()); - console.log(`[Background] ✅ Stoplist chargée pour '${lang}' : ${stoplists[lang].length} mots`); + log(`[Background] ✅ Stoplist chargée pour '${lang}' : ${stoplists[lang].length} mots`); } catch (error) { console.warn(`[Background] âš Stoplist introuvable pour '${lang}', aucun filtrage ne sera appliqué.`); } }) ); - sendStoplistsToWorker(stoplists); } function sendStoplistsToWorker(stoplists) { - console.log("[Background] Envoi des stoplists au Worker..."); + log("[Background] Envoi des stoplists au Worker..."); worker.postMessage({ command: "update-stoplist", stoplists }); } @@ -541,7 +573,7 @@ let storedFrequencies = {}; loadStoredFrequencies().then(frequencies => { storedFrequencies = frequencies; - console.log("[Background] Fréquences initialisées :", storedFrequencies); + log("[Background] Fréquences initialisées :", storedFrequencies); }); // ───────────────────────────────────────────────────────────────────────────── @@ -550,10 +582,10 @@ loadStoredFrequencies().then(frequencies => { async function checkAndUpdateTracking() { const { isTrackingActive, pyodideSimplemmaReady } = await browser.storage.local.get(["isTrackingActive", "pyodideSimplemmaReady"]); if (isTrackingActive && pyodideSimplemmaReady) { - console.log("[Background] Activation du tracking."); + log("[Background] Activation du tracking."); notifyAllTabs({ command: "activate-stats" }); } else { - console.log("[Background] Désactivation du tracking."); + log("[Background] Désactivation du tracking."); notifyAllTabs({ command: "deactivate-stats" }); } } @@ -571,20 +603,17 @@ async function notifyAllTabs(message) { } // ───────────────────────────────────────────────────────────────────────────── -// Écoute du changement des préférences et envoi des valeurs au Worker +// Statistiques : Écoute des modifications du stockage et mise à jour du tracking // ───────────────────────────────────────────────────────────────────────────── browser.storage.onChanged.addListener(async (changes, area) => { - if (area === "local" && (changes.isTrackingActive || changes.pyodideSimplemmaReady)) { checkAndUpdateTracking(); } - if (area === "local" && (changes.accessToken || changes.threshold || changes.trackedLanguages || changes.autoAdd)) { - console.log("[Background] Mise à jour des préférences détectée."); + log("[Background] Mise à jour des préférences détectée."); const { accessToken, trackedLanguages, threshold, autoAdd} = await browser.storage.local.get([ "accessToken", "trackedLanguages", "threshold", "autoAdd" ]); - const isAuthenticated = !!accessToken; worker.postMessage({ command: "update-preferences", @@ -597,14 +626,12 @@ browser.storage.onChanged.addListener(async (changes, area) => { if (area === "local" && changes.includeStopwords) { const includeStopwords = changes.includeStopwords.newValue; - console.log(`[Background] Inclusion des mots outils activé/désactivé: ${includeStopwords}`); + log(`[Background] Inclusion des mots outils activé/désactivé: ${includeStopwords}`); if (worker) { worker.postMessage({ command: "update-include-stopwords", includeStopwords }); } } - - }); // ───────────────────────────────────────────────────────────────────────────── @@ -612,7 +639,7 @@ browser.storage.onChanged.addListener(async (changes, area) => { // ───────────────────────────────────────────────────────────────────────────── browser.runtime.onMessage.addListener(async (message, sender) => { if (message.command === "register-stats-script") { - console.log("[Background] stats.js s'est enregistré."); + log("[Background] stats.js s'est enregistré."); const { isTrackingActive, pyodideSimplemmaReady } = await browser.storage.local.get(["isTrackingActive", "pyodideSimplemmaReady"]); if (isTrackingActive && pyodideSimplemmaReady) { browser.tabs.sendMessage(sender.tab.id, { command: "activate-stats" }) @@ -625,11 +652,11 @@ browser.runtime.onMessage.addListener(async (message, sender) => { // Connexion entre stats.js et le Worker via un port dédié // ───────────────────────────────────────────────────────────────────────────── browser.runtime.onConnect.addListener((port) => { - console.log("[Background] Connexion établie :", port.name); + log("[Background] Connexion établie :", port.name); if (port.name === "stats-worker-port") { // Redirige les messages de stats.js vers le Worker port.onMessage.addListener((message) => { - console.log("[Background] Message reçu de stats.js :", message); + log("[Background] Message reçu de stats.js :", message); worker.postMessage(message); }); diff --git a/src/context_menu/browser_context_menu.js b/src/context_menu/browser_context_menu.js index 9211233..cd98a6f 100644 --- a/src/context_menu/browser_context_menu.js +++ b/src/context_menu/browser_context_menu.js @@ -12,7 +12,7 @@ async function loadAuthToken() { authToken = result.accessToken; log("🔑 Token chargé au démarrage :", authToken); } catch (error) { - console.error("⌠Erreur lors de la récupération du token :", error); + log("⌠Erreur lors de la récupération du token :", error); } } @@ -25,8 +25,10 @@ async function loadAuthToken() { async function createContextMenu() { await browser.contextMenus.removeAll(); - if (authToken) { - // Item 1 : Recherche dans les lexiques de l’utilisateur + const { extensionActive } = await browser.storage.local.get("extensionActive"); + log("État de l'extension :", extensionActive); + + if (extensionActive) { browser.contextMenus.create({ id: "searchInLexicons", title: "Rechercher dans mes lexiques", @@ -34,37 +36,29 @@ async function createContextMenu() { icons: { "16": "src/assets/icons/quel_lexique.png" }, }); - // Item 2 : Ajouter le mot au(x) lexique(s) de l’utilisateur browser.contextMenus.create({ id: "addToLexicon", title: "Ajouter ce mot à mes lexiques", contexts: ["selection"], icons: { "16": "src/assets/icons/ajout_lexique.png" }, }); - } - // Séparateur - browser.contextMenus.create({ - id: "separatorExtension", - type: "separator", - contexts: ["all"], - }); + browser.contextMenus.create({ + id: "getDefinition", + title: "Obtenir une définition", + contexts: ["selection"], + icons: { "16": "src/assets/icons/definition.png" }, + }); - // Item 3 : Recherche globale de définition (Lexiques + Wiktionnaire) - browser.contextMenus.create({ - id: "getDefinition", - title: "Obtenir une définition", - contexts: ["selection"], - icons: { "16": "src/assets/icons/definition.png" }, - }); + browser.contextMenus.create({ + id: "separatorExtension", + type: "separator", + contexts: ["all"], + }); + } else { + log("âš ï¸ L'extension est désactivée, aucune option d'analyse ne sera affichée."); + } - browser.contextMenus.create({ - id: "separatorAfterExtension", - type: "separator", - contexts: ["all"], - }); - - // Item de connexion/déconnexion browser.contextMenus.create({ id: "login", title: authToken ? "Se déconnecter de BaLex" : "Se connecter à BaLex", @@ -72,10 +66,11 @@ async function createContextMenu() { }); } + loadAuthToken().then(createContextMenu); browser.runtime.onMessage.addListener((message) => { - if (message.action === "refreshUI") { + if (message.action === "refreshUI" || message.action === "updateUI") { log("🔄 refreshUI reçu dans browser_context_menu.js"); loadAuthToken().then(createContextMenu); } @@ -97,10 +92,14 @@ browser.contextMenus.onClicked.addListener(async (info, tab) => { // Action pour le bouton de connexion/déconnexion if (info.menuItemId === "login") { log("🔄 Action login/déconnexion demandée."); - if (typeof actuallyOpenLoginPage === "function") { - actuallyOpenLoginPage(); + if (authToken) { + await disconnectFromLexicalDB(); } else { - console.error("La fonction actuallyOpenLoginPage n'est pas accessible."); + if (typeof actuallyOpenLoginPage === "function") { + actuallyOpenLoginPage(); + } else { + log("La fonction actuallyOpenLoginPage n'est pas accessible."); + } } return; } @@ -146,7 +145,7 @@ browser.contextMenus.onClicked.addListener(async (info, tab) => { break; } - console.error(`⌠Action inconnue : ${info.menuItemId}`); + log(`⌠Action inconnue : ${info.menuItemId}`); }); // ───────────────────────────────────────────────────────────────────────────── @@ -167,7 +166,7 @@ async function getDefinition(selectedText) { definitions: allDefinitions, }); } catch (error) { - console.error("⌠Erreur lors de la recherche combinée des définitions :", error); + log("⌠Erreur lors de la recherche combinée des définitions :", error); } } @@ -201,7 +200,7 @@ async function searchInLexicons(selectedText) { selectedText, }); } catch (error) { - console.error("⌠Erreur lors de la recherche dans les lexiques :", error); + log("⌠Erreur lors de la recherche dans les lexiques :", error); browser.runtime.sendMessage({ action: "showLexiconResult", lexicons: [], diff --git a/src/context_menu/custom_context_menu.js b/src/context_menu/custom_context_menu.js index a4cb929..0c74f17 100644 --- a/src/context_menu/custom_context_menu.js +++ b/src/context_menu/custom_context_menu.js @@ -20,7 +20,7 @@ async function loadAuthToken() { authToken = result.accessToken || null; log("🔑 Token chargé :", authToken); } catch (error) { - console.error("⌠Erreur lors de la récupération du token :", error); + log("⌠Erreur lors de la récupération du token :", error); authToken = null; } } @@ -141,6 +141,7 @@ function updateMenuVisibility() { getDefinitionBtn.style.display = "inline-block"; loginBtn.style.display = "none"; } else { + hideWhiteBox(); addLexiconBtn.style.display = "none"; getDefinitionBtn.style.display = "inline-block"; loginBtn.style.display = "inline-block"; @@ -158,7 +159,12 @@ function getSelectedWord() { /** * Affiche le menu contextuel à la position du clic. */ -function showWhiteBox(event, selectedText) { +async function showWhiteBox(event, selectedText) { + const { extensionActive } = await browser.storage.local.get("extensionActive") || { extensionActive: false }; + if (!extensionActive || !authToken) { + hideWhiteBox(); + return; + } const whiteBox = getOrCreateWhiteBox(); const selectedWordElement = document.getElementById("selectedWord"); selectedWordElement.textContent = selectedText; @@ -297,7 +303,7 @@ async function showPicker(event, selectedText) { try { definitions = await fetchLexiconDefinitions(selectedText); } catch (error) { - console.error("Erreur lors de la récupération des définitions :", error); + log("Erreur lors de la récupération des définitions :", error); } const existingLexiconIds = new Set(); if (Array.isArray(definitions)) { @@ -308,11 +314,10 @@ async function showPicker(event, selectedText) { } } if (existingLexiconIds.size > 0) { - alert(`âš ï¸ Le mot "${selectedText}" existe déjà dans les lexiques suivants : ${Array.from(existingLexiconIds).join(", ")}`); + alert(`Le mot "${selectedText}" existe déjà dans les lexiques suivants : ${Array.from(existingLexiconIds).map(id => lexiconDescriptions[id]).join(", ")}`); } const lexiconsToAdd = [...selectedLexicons].filter(id => !existingLexiconIds.has(id)); if (lexiconsToAdd.length === 0) { - alert(`✅ Le mot "${selectedText}" est déjà présent dans tous les lexiques sélectionnés.`); return; } try { @@ -329,7 +334,7 @@ async function showPicker(event, selectedText) { lexicons: successMsg }); } catch (error) { - console.error("⌠Erreur lors de l'ajout du mot :", error); + log("⌠Erreur lors de l'ajout du mot :", error); const errorMsg = `⌠Erreur lors de l'ajout du mot : ${error.message}`; picker.innerHTML = `<p style="color: red;">${errorMsg}</p>`; setTimeout(() => picker.style.display = "none", 3000); @@ -351,7 +356,7 @@ async function showPicker(event, selectedText) { picker.style.top = event.pageY + "px"; picker.style.display = "flex"; } catch (error) { - console.error("⌠Erreur lors de la récupération des lexiques :", error); + log("⌠Erreur lors de la récupération des lexiques :", error); picker.innerHTML = "<p style='color:#333;'>Erreur lors du chargement des lexiques.</p>"; picker.style.display = "block"; } diff --git a/src/popup/popup.html b/src/popup/popup.html index b9c17fd..e4b7b95 100644 --- a/src/popup/popup.html +++ b/src/popup/popup.html @@ -28,10 +28,9 @@ max-width: 250px; z-index: 1000; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); - transition: opacity 0.3s ease, visibility 0.3s ease; + transition: visibility 0.3s ease; } #extension-notification.hidden { - opacity: 0; visibility: hidden; } @@ -45,7 +44,6 @@ border-radius: 5px; font-weight: bold; } - #close-notification:hover { background-color: #dddedd; color: #8d5c70; @@ -83,7 +81,7 @@ width: auto; display: inline-flex; padding: 6px 12px; - font-size: 18px; + font-size: 16px; font-family: Luciole; background: none; border: none; @@ -124,10 +122,8 @@ } .option-container { background: #444; - padding: 12px; + padding: 8px; border-radius: 10px; - margin-top: 4px; - margin-bottom: 4px; box-shadow: 0 2px 6px rgba(0,0,0,0.2); } .option-row { @@ -136,11 +132,9 @@ justify-content: space-between; flex-wrap: nowrap; padding: 12px; - margin-bottom: 4px; border-radius: 6px; background-color: #444; color: white; - font-size: 0.6rem; transition: transform 0.2s, box-shadow 0.2s; } .option-row:hover { @@ -151,10 +145,9 @@ font-weight: lighter; flex: 1; margin: 0; - font-size: 0.9rem; + font-size: 13px; align-items: center; } - .option-row input[type="checkbox"], .option-row input[type="number"] { appearance: none; @@ -166,12 +159,10 @@ cursor: pointer; transition: background-color 0.3s, border-color 0.3s; } - .option-row input[type="checkbox"]:checked { background-color: #8d5c70; border-color: #8d5c70; } - .toggle-switch { position: absolute; display: inline-block; @@ -191,7 +182,6 @@ transition: 0.4s; border-radius: 50%; } - .toggle-switch input { opacity: 0; width: 0; @@ -208,15 +198,12 @@ transition: 0.4s; border-radius: 24px; } - input:checked + .slider { background-color: #8d5c70; } - input:checked + .slider:before { transform: translateX(14px); } - .threshold-container input[type="number"] { width: 45px; height: 45px; @@ -224,19 +211,18 @@ text-align: center; border-radius: 50%; border: 2px solid #8d5c70; - font-size: 0.8rem; + font-size: 13px; box-sizing: border-box; background: #fff; color: #333; } - #save-options { border: none; background: #8d5c70; border-radius: 6px; color: white; padding: 8px 12px; - font-size: 0.9rem; + font-size: 14px; cursor: pointer; transition: background 0.3s, transform 0.2s; } @@ -270,13 +256,6 @@ color: white; border-color: #8d5c70; } - - /* #stats-options { - border: 1px solid #555; - border-radius: 8px; - align-items: center; - } */ - .option-row.auto-add-row { position: relative; display: flex; @@ -288,41 +267,24 @@ border-bottom: 1px solid #555; } .option-row.auto-add-row span { - font-size: 16px; + font-size: 14px; font-weight: lighter; } - .option-row.stopwords { position: relative; display: flex; align-items: center; justify-content: space-between; - font-size: 0.9rem; + font-size: 13px; font-weight: lighter; } - - /* .option-row.auto-add-row input[type="checkbox"] { - appearance: none; - width: 24px; - margin-left: 10px; - height: 24px; - border: 2px solid #8d5c70; - border-radius: 4px; - background: #fff; - cursor: pointer; - transition: background 0.3s; - } */ - - /* .option-row.auto-add-row input[type="checkbox"]:checked { - background: #8d5c70; - } */ - #open-stats { padding: 6px; font-weight: lighter; width: auto; display: block; margin: 0 auto; + margin-bottom: 10px; background-color: #525877; color: white; border: 2px solid #8d5c70; @@ -331,6 +293,65 @@ .hidden { display: none; } + #error-message { + font-size: 13px; + font-style: italic; + text-align: center; + color: white; + } + .tooltip-container { + position: relative; + display: inline-block; + pointer-events: auto !important; + } + .tooltip { + all: unset; + display: block; + box-sizing: border-box; + position: absolute; + left: 50%; + transform: translateX(-50%); + color: #fff !important; + font-size: 12px !important; + font-weight: lighter !important; + padding: 6px 10px; + border-radius: 5px; + white-space: normal; + overflow-wrap: break-word; + width: 200px; + text-align: center; + visibility: hidden; + transition: visibility 0.3s ease-in-out, transform 0.3s ease-in-out; + pointer-events: none; + z-index: 1000; + line-height: normal; + } + /* Règle générique pour les tooltips */ + .tooltip-container .tooltip { + bottom: 120%; + transform: translateX(-50%); + background-color: rgba(0,0,0,0.9) !important; + visibility: hidden; + pointer-events: auto !important; + } + .tooltip-container:hover .tooltip { + visibility: visible !important; + transform: translateX(-50%) translateY(-5px); + pointer-events: auto !important; + } + /* Style spécifique pour le bouton de connexion */ + #auth-button .tooltip { + top: 120%; + bottom: auto; + } + #auth-button.tooltip-container:hover .tooltip { + visibility: visible !important; + transform: translateX(-50%) translateY(5px); + } + /* Permettre l'interaction avec les tooltips même sur les boutons désactivés */ + button:disabled .tooltip { + pointer-events: auto !important; + } </style> </head> <body> @@ -348,16 +369,16 @@ <button id="toggleExtensionBtn">Activer/Désactiver</button> <button id="toggleStatsBtn">Statistiques</button> <button id="open-stats">Afficher les statistiques</button> - + <div id="pyodide-loading"></div> + <div id="stats-options" class="option-container"> - <div class="option-row auto-add-row"> + <div id="auto-add-container" class="option-row auto-add-row"> <span>Ajout automatique</span> <label class="toggle-switch"> <input type="checkbox" id="auto-add"> <span class="slider"></span> </label> </div> - <div id="auto-add-options" class="hidden"> <div class="option-row stopwords"> <span>Inclure mots outils</span> @@ -366,27 +387,22 @@ <span class="slider"></span> </label> </div> - - <div class="option-row threshold-container"> <label for="threshold">Seuil d'ajout d'un mot</label> <input type="number" id="threshold" value="10" min="1" /> </div> - <div class="option-row"> <label>Langues suivies</label> <div id="language-selection" class="language-selection"> <p id="loading-languages" style="color: gray;">Chargement...</p> </div> </div> - - <!-- Message d'erreur si on ne sélectionne pas une langue à suivre--> - <div id="error-message" class="hidden"> - <p>Veuillez sélectionner au moins une langue avant de sauvegarder.</p> + <!-- Message d'erreur si aucune langue sélectionnée --> + <div id="error-message" class="hidden"> + <p>Veuillez sélectionner une ou plusieurs langue(s).</p> </div> <button id="save-options" class="hidden">Valider</button> </div> - </div> <div id="extension-notification" class="hidden"> diff --git a/src/popup/popup.js b/src/popup/popup.js index 13cb644..6cc4015 100644 --- a/src/popup/popup.js +++ b/src/popup/popup.js @@ -1,443 +1,487 @@ log("✅ popup.js chargé avec succès !"); +// fetchExtensionState() : fonction qui se charge de récupérer les valeurs du local storage +// updateUI(): utilise les états récupérés par fetchExtensionState pour appeler d'autres fonctions +// ex. actualisation du bouton de connexion, bouton activer l'extension, les stats... +// setupEventListeners() : fonction qui regroupe les écouteurs d'évènement +// handleEvent() : une description de chaque évènement avec les changements de valeur du local storage -// ========================== + +// ==================================================================================== // Fonctions utilitaires -// ========================== +// ==================================================================================== + +//Obtenir le token async function getAccessToken() { const { accessToken } = await browser.storage.local.get("accessToken"); return accessToken; } -// ========================== -// Gestion de la connexion -// ========================== -async function updateConnectionButton() { - const accessToken = await getAccessToken(); - const button = document.getElementById("auth-button"); - - if (!button) { - console.error("⌠Le bouton de connexion n'a pas été trouvé."); - return; - } - - if (accessToken) { - button.textContent = "Se déconnecter"; - button.title = "En vous déconnectant, vous perdrez l'accès à vos lexiques personnels, ainsi que les fonctionnalités d'ajout automatique et de statistiques d'utilisation."; - } else { - button.textContent = "Se connecter"; - button.title = "En vous connectant, vous pourrez accéder à vos lexiques personnels, ainsi qu'aux fonctionnalités d'ajout automatique et de statistiques d'utilisation."; - } - - button.onclick = async () => { - const isConnected = !!accessToken; - - // Gestion de la déconnexion - if (isConnected) { - console.log("[Popup] Déconnexion détectée, réinitialisation des options..."); - - await browser.storage.local.set({ - accessToken: null, - autoAdd: false, - includeStopwords: false, - isTrackingActive: false - }); - - // Envoyer les mises à jour au worker - browser.runtime.sendMessage({ - command: "update-preferences", - autoAdd: false, - includeStopwords: false, - isTrackingActive: false - }); - - console.log("✅ Paramètres réinitialisés après déconnexion."); - - // Mettre à jour l'interface - await updateOptionsUI(); - await updateLanguageSelection(); - await updateExtensionToggleButton(); - } +// ========= +// Fonction d'actualisation générale de la popup : 1. Récupération des valeurs 2. Mà j UI +async function updateExtension() { + states = await fetchExtensionState(); //Récupérer les valeurs + updateUI(states); // Selon les valeurs, mettre à jour l'UI +} - // Envoyer le message pour basculer l'authentification - await browser.runtime.sendMessage({ action: "toggleAuth" }); +// ========= +// 1. Récupérer les valeurs du local storage +async function fetchExtensionState() { + const accessToken = await getAccessToken(); + const storedValues = await browser.storage.local.get([ + "extensionActive", + "isTrackingActive", + "autoAdd", + "threshold", + "pyodideSimplemmaReady", + "includeStopwords" + ]); + return { + isLoggedIn: !!accessToken, + extensionActive: storedValues.extensionActive ?? false, + isTrackingActive: storedValues.isTrackingActive ?? false, + autoAdd: storedValues.autoAdd ?? false, + threshold: storedValues.threshold ?? 10, + pyodideSimplemmaReady: storedValues.pyodideSimplemmaReady ?? false, + includeStopwords: storedValues.includeStopwords ?? false }; } -// ========================== -// Gestion de la sélection des langues -// ========================== -async function updateLanguageSelection() { - const languageSelection = document.getElementById("language-selection"); - languageSelection.innerHTML = "<p id='loading-languages' style='color: gray;'>Chargement...</p>"; - - const accessToken = await getAccessToken(); - if (!accessToken) { - languageSelection.innerHTML = "<p style='color: red;'>Veuillez vous connecter.</p>"; - return; - } +// ========= +// 2.Fonction de mise à jour de l'UI +async function updateUI(states) { + await updateConnectionButton(states.isLoggedIn); //Actualisation du bouton de connexion + await updateToggleExtensionButton(states.isLoggedIn, states.extensionActive, states.autoAdd, states.isTrackingActive, states.pyodideSimplemmaReady, states.includeStopwords); + // éventuellement ajouter des fonctions issues du toggleExtensionButton pour la lisibilité : + //await updateStatsButtons(states.isLoggedIn, states.extensionActive, states.isTrackingActive); + // await updateAutoAddOptions(states.isLoggedIn, states.extensionActive, states.autoAdd); + // await updatePyodideStatus(states.isLoggedIn, states.extensionActive, states.isTrackingActive, state.pyodideSimplemmaReady); + await updateLanguageSelection(); + await updateStopwordsOption(states.includeStopwords); + console.log("✅ Interface mise à jour :", states); +} - // Utilisation de la fonction de récupération de lexiques - // Choisissez ici entre getLexicons et getUserLexicons en fonction de votre implémentation - const lexicons = await getLexicons(accessToken); - const userLanguages = [...new Set(lexicons.map(lex => lex.language))]; - // Récupérer les langues suivies depuis le stockage - const { trackedLanguages } = await browser.storage.local.get("trackedLanguages") || { trackedLanguages: [] }; - languageSelection.innerHTML = ""; - if (userLanguages.length === 0) { - languageSelection.innerHTML = "<p style='color: red;'>Aucun lexique personnel trouvé.</p>"; - return; - } +// ==================================================================================== +// Fonction contenant les écouteurs d'évènements et gestion des valeurs du local storage +// ==================================================================================== +function setupEventListeners() { + // Bouton Connexion / Déconnexion + document.getElementById("auth-button")?.addEventListener("click", handleAuthToggle); - userLanguages.forEach(lang => { - const langButton = document.createElement("div"); - langButton.classList.add("lang-option"); - langButton.textContent = lang.toUpperCase(); - langButton.dataset.value = lang; - if (trackedLanguages && trackedLanguages.includes(lang)) { - langButton.classList.add("selected"); - } - langButton.addEventListener("click", () => { - langButton.classList.toggle("selected"); - }); - languageSelection.appendChild(langButton); - }); + // Bouton activer l'extension + document.getElementById("toggleExtensionBtn")?.addEventListener("click", handleToggleExtension); - log("✅ Sélection des langues mise à jour avec :", userLanguages); -} + // Bouton de gestion des statistiques + document.getElementById("toggleStatsBtn")?.addEventListener("click", handleStatsToggle); -// ========================== -// Gestion des options et des statistiques -// ========================== -async function updateOptionsUI() { - const accessToken = await getAccessToken(); - const isLoggedIn = !!accessToken; - const statsOptions = document.getElementById("stats-options"); - const autoAddContainer = document.getElementById("auto-add")?.parentElement; - const autoAddCheckbox = document.getElementById("auto-add"); - const autoAddOptions = document.getElementById("auto-add-options"); - const thresholdInput = document.getElementById("threshold"); - const saveOptionsBtn = document.getElementById("save-options"); - const toggleStatsBtn = document.getElementById("toggleStatsBtn"); - const openStats = document.getElementById("open-stats"); - const includeStopwordsCheckbox = document.getElementById("include-stopwords"); + // Gestion de l'ajout automatique + document.getElementById("auto-add")?.addEventListener("change", handleAutoAddToggle); - // Affichage de l'option "Ajout automatique" selon la connexion - if (autoAddContainer) { - autoAddContainer.style.display = isLoggedIn ? "block" : "none"; - } - - // Chargement des préférences - const { isTrackingActive, autoAdd, threshold, includeStopwords} = await browser.storage.local.get([ - "isTrackingActive", - "autoAdd", - "threshold", - "includeStopwords" - ]) || { isTrackingActive: false, includeStopwords: false }; - - // Gestion du bouton de statistiques - if (toggleStatsBtn) { - if (!isLoggedIn) { - toggleStatsBtn.style.opacity = "0.5"; - toggleStatsBtn.title = "Connectez-vous pour activer les statistiques"; - if (openStats) openStats.style.display = "none"; - } else { - toggleStatsBtn.style.opacity = "1"; - toggleStatsBtn.title = ""; - if (openStats) openStats.style.display = "block"; - } - toggleStatsBtn.textContent = isTrackingActive ? "Désactiver les statistiques" : "Activer les statistiques"; - } + //Activation/désactivation des stopwords + document.getElementById("include-stopwords")?.addEventListener("change", handleStopwordsToggle); + + // Sauvegarde des options + document.getElementById("save-options")?.addEventListener("click", handleSaveOptions); - // Gestion des boutons d'options de statistiques - if (statsOptions) { - statsOptions.classList.toggle("hidden", !isTrackingActive); - } - //Si l'utilsateur est connecté : afficher les options - if (isLoggedIn) { - if (autoAddCheckbox) { - autoAddCheckbox.checked = autoAdd || false; - } - if (includeStopwordsCheckbox) { - includeStopwordsCheckbox.checked = includeStopwords || false; - } - if (thresholdInput && threshold !== undefined) { - thresholdInput.value = threshold; - } - if (autoAddOptions) { - autoAddOptions.classList.toggle("hidden", !autoAdd); - } - if (saveOptionsBtn) { - saveOptionsBtn.classList.toggle("hidden", !autoAdd); - } - //Si l'utilsateur n'est connecté : masquer les options - } else { - if (autoAddCheckbox) { - autoAddCheckbox.checked = false; - } - if (includeStopwordsCheckbox) { - includeStopwordsCheckbox.checked = false; - } - if (autoAddOptions) { - autoAddOptions.classList.add("hidden"); - } - if (saveOptionsBtn) { - saveOptionsBtn.classList.add("hidden"); - } - } + // Ouverture de la page des statistiques + // TODO : ajouter l'évènement + document.getElementById("open-stats")?.addEventListener("click", () => { + window.open("stats.html", "_blank"); + }); } -// ========================== -// Gestion des stoplists -// ========================== -document.getElementById("include-stopwords")?.addEventListener("change", async () => { - const includeStopwords = document.getElementById("include-stopwords").checked; - - // Si on décoche, on met immédiatement à jour le stockage - if (!includeStopwords) { - await browser.storage.local.set({ includeStopwords: false }); - console.log("[Popup] Option 'Inclure mots outils' désactivée immédiatement."); - - browser.runtime.sendMessage({ command: "update-include-stopwords", includeStopwords: false }); - } -}); - - -// ========================== -// Gestion de l'activation/désactivation de l'extension -// ========================== -async function updateExtensionToggleButton() { - const { accessToken, extensionActive } = await browser.storage.local.get(["accessToken", "extensionActive"]); - const toggleButton = document.getElementById("toggleExtensionBtn"); - - if (!toggleButton) { - console.error("⌠Le bouton d'activation de l'analyse n'a pas été trouvé."); - return; - } +// =========== +//Description de chaque évènement +//Connexion / Déconnexion +async function handleAuthToggle() { + const accessToken = await getAccessToken(); if (!accessToken) { - toggleButton.textContent = "Activer l'analyse"; - toggleButton.style.opacity = "0.5"; - toggleButton.title = "Connectez-vous pour activer l'analyse"; + console.log("🔓 Connexion demandée..."); + await browser.runtime.sendMessage({ action: "toggleAuth" }); } else { - toggleButton.style.opacity = "1"; - toggleButton.textContent = extensionActive ? "Désactiver l'analyse" : "Activer l'analyse"; - toggleButton.title = ""; + console.log("🔒 Déconnexion demandée..."); + await browser.storage.local.set({ + accessToken: null, + autoAdd: false, + includeStopwords: false, + isTrackingActive: false + }); + browser.runtime.sendMessage({ + command: "update-preferences", + autoAdd: false, + includeStopwords: false, + isTrackingActive: false + }); + console.log("✅ Paramètres réinitialisés après déconnexion."); } + await updateExtension(); +} - // Ajout d'un écouteur unique - if (!toggleButton.dataset.listenerAdded) { - toggleButton.addEventListener("click", handleToggleExtension); - toggleButton.dataset.listenerAdded = "true"; +//Statistiques +async function handleStatsToggle() { + const accessToken = await getAccessToken(); + if (!accessToken) return; + // Récupérer l'état actuel des statistiques + const { isTrackingActive } = await browser.storage.local.get({ isTrackingActive: false }); + const newState = !isTrackingActive; + // Mise à jour uniquement de `isTrackingActive` + await browser.storage.local.set({ isTrackingActive: newState }); + console.log("📊 Nouvel état des statistiques :", newState); + // Envoi du message de mise à jour + browser.runtime.sendMessage({ command: "toggle-stats", isActive: newState }); + // Exécution de Pyodide si nécessaire + if (newState) { + browser.runtime.sendMessage({ command: "pyodide-simplemma" }); } -} -function handleToggleExtension() { - getAccessToken().then(accessToken => { - if (!accessToken) return; - proceedToggleExtension(); - }); + if (isUpdatingLexicons) return; + isUpdatingLexicons = true; + await updateLexiconsDisplay(); + isUpdatingLexicons = false; + await updateExtension(); } -async function proceedToggleExtension() { - const { extensionActive, isTrackingActive } = await browser.storage.local.get({ - extensionActive: false, - isTrackingActive: false - }); +// Activer l'extension +async function handleToggleExtension() { + const accessToken = await getAccessToken(); + if (!accessToken) return; + + const { extensionActive, isTrackingActive } = await browser.storage.local.get({ extensionActive: false, isTrackingActive: false }); const newState = !extensionActive; - await browser.storage.local.set({ extensionActive: newState }); - const toggleButton = document.getElementById("toggleExtensionBtn"); - if (toggleButton) { - toggleButton.textContent = newState ? "Désactiver l'analyse" : "Activer l'analyse"; - } + await browser.storage.local.set({ extensionActive: newState }); - browser.runtime.sendMessage({ action: "toggleExtension", isActive: newState }); - if (!newState) { await browser.storage.local.set({ isTrackingActive: false }); - // Si les statistiques étaient activées, ouvrir la page stats if (isTrackingActive) window.open("stats.html", "_blank"); browser.runtime.sendMessage({ action: "closeSidebarBlocks" }); } - await updateOptionsUI(); + browser.runtime.sendMessage({ action: "toggleExtension", isActive: newState }); + await updateExtension(); } -// ========================== -// Écouteurs d'événements -// ========================== -document.addEventListener("DOMContentLoaded", async () => { - await updateConnectionButton(); - await updateOptionsUI(); - await updateLanguageSelection(); - await updateExtensionToggleButton(); -}); -// Gestion des statistiques et options -document.getElementById("toggleStatsBtn")?.addEventListener("click", async () => { - const accessToken = await getAccessToken(); - if (!accessToken) return; +//Ajout automatique +function handleAutoAddToggle() { + const autoAddCheckbox = document.getElementById("auto-add"); + const autoAddOptions = document.getElementById("auto-add-options"); + const saveOptionsBtn = document.getElementById("save-options"); - const current = await browser.storage.local.get("isTrackingActive"); - const newState = !current.isTrackingActive; - await browser.storage.local.set({ isTrackingActive: newState }); + if (!autoAddCheckbox || !autoAddOptions || !saveOptionsBtn) return; - // Si désactivation, désactiver aussi l'ajout automatique - if (!newState) { - // Si désactivation, désactiver aussi l'ajout automatique et includeStopwords - await browser.storage.local.set({ - autoAdd: false, - includeStopwords: false - }); - document.getElementById("auto-add").checked = false; + const isAutoAddEnabled = autoAddCheckbox.checked; + + // Juste afficher ou cacher les options, mais ne pas sauvegarder dans le local storage + autoAddOptions.classList.toggle("hidden", !isAutoAddEnabled); + saveOptionsBtn.classList.toggle("hidden", !isAutoAddEnabled); + + // Si on décoche, désactiver immédiatement et forcer la sauvegarde + if (!isAutoAddEnabled) { + browser.storage.local.set({ autoAdd: false, includeStopwords: false }); document.getElementById("include-stopwords").checked = false; - document.getElementById("auto-add-options").classList.add("hidden"); - document.getElementById("save-options").classList.add("hidden"); + console.log("Ajout automatique désactivé → Stopwords désactivés immédiatement."); } +} - document.getElementById("toggleStatsBtn").textContent = newState ? "Désactiver les statistiques" : "Activer les statistiques"; - document.getElementById("stats-options").classList.toggle("hidden", !newState); - // Envoi du message au background pour le changement de tracking - browser.runtime.sendMessage({ command: "toggle-stats", isActive: newState }); - if (newState) { - log("[Popup] Demande d'initialisation de Pyodide et Simplemma"); - // Garder la commande de file 2 pour l'initialisation de Pyodide/Simplemma - browser.runtime.sendMessage({ command: "pyodide-simplemma" }); - } -}); +// Gestion de l'activation/désactivation des stopwords +function handleStopwordsToggle() { + const stopwordsCheckbox = document.getElementById("include-stopwords"); + if (!stopwordsCheckbox) return; -// Gestion du changement de l'option d'ajout automatique -document.getElementById("auto-add")?.addEventListener("change", async () => { - const isChecked = document.getElementById("auto-add").checked; - document.getElementById("auto-add-options").classList.toggle("hidden", !isChecked); - document.getElementById("save-options").classList.toggle("hidden", !isChecked); - if (!isChecked) { - log("[Popup] Désactivation de l'ajout automatique -> Désactivation des options"); - await browser.storage.local.set({ - autoAdd: false, //Désactiver l'ajout automatique - includeStopwords: false // Désactiver includeStopwords - }); - // Mettre à jour l'affichage du bouton - document.getElementById("include-stopwords").checked = false; + // Si décoché, forcer immédiatement la mise à jour du local storage + if (!stopwordsCheckbox.checked) { + browser.storage.local.set({ includeStopwords: false }); + console.log("Stopwords désactivés immédiatement."); } -}); +} + + // Sauvegarde des options utilisateur -document.getElementById("save-options")?.addEventListener("click", async () => { - const autoAdd = document.getElementById("auto-add").checked; +async function handleSaveOptions() { + const autoAddCheckbox = document.getElementById("auto-add"); + const stopwordsCheckbox = document.getElementById("include-stopwords"); const threshold = parseInt(document.getElementById("threshold").value, 10); const selectedLanguages = Array.from(document.querySelectorAll("#language-selection .lang-option.selected")) - .map(option => option.dataset.value); - const includeStopwords = document.getElementById("include-stopwords").checked; - - // Vérification : si auto-add est activé, au moins une langue doit être sélectionnée + .map(option => option.dataset.value); + const errorMessage = document.getElementById("error-message"); - if (autoAdd && selectedLanguages.length === 0) { + + if (autoAddCheckbox.checked && selectedLanguages.length === 0) { errorMessage?.classList.remove("hidden"); return; } errorMessage?.classList.add("hidden"); + // Seule la validation met à jour le stockage local await browser.storage.local.set({ - autoAdd, + autoAdd: autoAddCheckbox.checked, + includeStopwords: stopwordsCheckbox.checked, threshold, - trackedLanguages: selectedLanguages, - includeStopwords + trackedLanguages: selectedLanguages }); - log("Options sauvegardées :", { autoAdd, threshold, trackedLanguages: selectedLanguages, includeStopwords}); - // Envoyer les nouvelles préférences au Worker browser.runtime.sendMessage({ command: "update-preferences", - autoAdd, - threshold, - trackedLanguages: selectedLanguages, - includeStopwords + autoAdd: autoAddCheckbox.checked, + includeStopwords: stopwordsCheckbox.checked }); -}); + await updateExtension(); + console.log("✅ Options sauvegardées."); +} -// Bouton pour ouvrir la page des statistiques -document.getElementById("open-stats")?.addEventListener("click", async () => { - await displayStatsSummary();//résumé dans la console - window.open("stats.html", "_blank"); -}); -async function displayStatsSummary() { - console.log("[Popup] Préparation du résumé des statistiques..."); +//Ouverture de la page des statistiques - // Récupérer les données stockées - const { lemmaFrequencies, trackedLanguages, wordsAdded } = await browser.storage.local.get([ - "lemmaFrequencies", - "trackedLanguages", - "wordsAdded" - ]); +// ==================================================================================== +// Fonction actualisation UI des boutons +// ==================================================================================== - // Initialisation du résumé - let summary = { - totalWords: 0, // Nombre total de mots analysés - totalUniqueWords: 0, // Nombre total de mots uniques - languages: {}, // Nombre de mots analysés par langue - wordsAdded: wordsAdded || {} // Mots ajoutés classés par langue - }; +// Gestion de la connexion +async function updateConnectionButton() { + const accessToken = await getAccessToken(); + //Vérification du bouton de connexion + const button = document.getElementById("auth-button"); + if (!button) { + console.error("⌠Le bouton de connexion n'a pas été trouvé."); + return; + } + if (accessToken) { + button.textContent = "Se déconnecter"; + button.style.position = "relative"; + button.className = "tooltip-container"; + const tooltip = document.createElement("span"); + tooltip.className = "tooltip"; + tooltip.textContent = "En vous déconnectant, vous perdrez l'accès à vos lexiques personnels, ainsi qu'aux fonctionnalités d'ajout automatique et de statistiques d'utilisation."; + button.appendChild(tooltip); + } else { + button.textContent = "Se connecter"; + button.style.position = "relative"; + button.className = "tooltip-container"; + const tooltip = document.createElement("span"); + tooltip.className = "tooltip"; + tooltip.textContent = "En vous connectant, vous pourrez accéder à vos lexiques personnels, ainsi qu'aux fonctionnalités d'ajout automatique et de statistiques d'utilisation."; + button.appendChild(tooltip); + } +} - if (lemmaFrequencies) { - Object.entries(lemmaFrequencies).forEach(([lang, words]) => { - const wordCount = Object.values(words).reduce((sum, count) => sum + count, 0); - const uniqueWordCount = Object.keys(words).length; +// Gestion de la sélection des langues +async function updateLanguageSelection() { + const languageSelection = document.getElementById("language-selection"); + languageSelection.innerHTML = "<p id='loading-languages' style='color: gray;'>Chargement...</p>"; + + const storedData = await browser.storage.local.get("lexicons"); + const lexicons = storedData.lexicons || []; // Ne pas utiliser JSON.parse() - // Vérifier si la langue doit être suivie - if (!trackedLanguages || trackedLanguages.includes(lang)) { - summary.languages[lang] = { - totalWords: wordCount, - uniqueWords: uniqueWordCount - }; + if (!Array.isArray(lexicons) || lexicons.length === 0) { + log("Lexiques non trouvés, attente de la mise à jour..."); + languageSelection.innerHTML = "<p style='color: gray;'>En attente des lexiques...</p>"; - summary.totalWords += wordCount; - summary.totalUniqueWords += uniqueWordCount; + // Écouteur pour détecter quand les lexiques sont stockés + const listener = (changes, area) => { + if (area === "local" && changes.lexicons) { + log("Lexiques détectés dans le stockage, mise à jour de la sélection !"); + browser.storage.onChanged.removeListener(listener); + updateLanguageSelection(); // Recharger l'affichage des langues } + }; + browser.storage.onChanged.addListener(listener); + return; + } + + // Extraire les langues uniques + const userLanguages = [...new Set(lexicons.map(lex => lex.language))]; + + // Récupérer les langues suivies depuis le stockage + const { trackedLanguages } = (await browser.storage.local.get("trackedLanguages")) || { trackedLanguages: [] }; + + // Affichage des langues sous forme de boutons + languageSelection.innerHTML = ""; + userLanguages.forEach(lang => { + const langButton = document.createElement("div"); + langButton.classList.add("lang-option"); + langButton.textContent = lang.toUpperCase(); + langButton.dataset.value = lang; + + if (trackedLanguages && trackedLanguages.includes(lang)) { + langButton.classList.add("selected"); + } + + langButton.addEventListener("click", () => { + langButton.classList.toggle("selected"); }); + + languageSelection.appendChild(langButton); + }); + + log("Sélection des langues mise à jour avec :", userLanguages); +} + +// ========= +// Gestion bouton d'activation de l'extension +async function updateToggleExtensionButton(isLoggedIn, extensionActive, autoAdd, isTrackingActive, pyodideSimplemmaReady, includeStopwords) { + const toggleExtensionBtn = document.getElementById("toggleExtensionBtn"); + + if (toggleExtensionBtn) { + toggleExtensionBtn.textContent = extensionActive ? "Désactiver l'analyse" : "Activer l'analyse"; + toggleExtensionBtn.style.pointerEvents = isLoggedIn ? "auto" : "none"; + toggleExtensionBtn.disabled = !isLoggedIn; + toggleExtensionBtn.style.position = "relative"; + toggleExtensionBtn.className = "tooltip-container"; + + const existingTooltipExt = toggleExtensionBtn.querySelector('.tooltip'); + if (existingTooltipExt) { + existingTooltipExt.remove(); + } + const tooltipExt = document.createElement("span"); + tooltipExt.className = "tooltip"; + tooltipExt.style.opacity = "1 !important"; + if (!isLoggedIn) { + tooltipExt.textContent = "Connectez-vous pour activer l'analyse"; + tooltipExt.style.display = "block"; + } else if (!extensionActive) { + tooltipExt.textContent = "Activer les fonctionnalités de l'extension : affichage des mots et des définitions de vos lexiques, ajout de mots, etc."; + tooltipExt.style.display = "block"; + } else { + tooltipExt.style.display = "none"; + } + toggleExtensionBtn.appendChild(tooltipExt); + } + + // Mise à jour des options de statistiques + const statsOptions = document.getElementById("stats-options"); + const toggleStatsBtn = document.getElementById("toggleStatsBtn"); + const openStats = document.getElementById("open-stats"); + if (statsOptions) { + statsOptions.style.display = (isLoggedIn && extensionActive) ? "block" : "none"; + } + + // Mise à jour du bouton des statistiques + if (toggleStatsBtn) { + const isEnabled = isLoggedIn && extensionActive; + toggleStatsBtn.textContent = isEnabled && isTrackingActive ? "Désactiver les statistiques" : "Activer les statistiques"; + toggleStatsBtn.style.pointerEvents = isEnabled ? "auto" : "none"; + toggleStatsBtn.disabled = !isEnabled; + toggleStatsBtn.style.position = "relative"; + toggleStatsBtn.className = "tooltip-container"; + const existingTooltipStats = toggleStatsBtn.querySelector('.tooltip'); + if (existingTooltipStats) { existingTooltipStats.remove(); } + const tooltipStats = document.createElement("span"); + tooltipStats.className = "tooltip"; + tooltipStats.style.opacity = "1 !important"; + if (!isLoggedIn) { + tooltipStats.textContent = "Connectez-vous pour accéder aux statistiques"; + tooltipStats.style.display = "block"; + } else if (!extensionActive) { + tooltipStats.textContent = "Veuillez activer l'analyse pour utiliser les statistiques"; + tooltipStats.style.display = "block"; + } else { + tooltipStats.style.display = "none"; + } + toggleStatsBtn.appendChild(tooltipStats); + } + if (openStats) { + openStats.style.display = (isLoggedIn && extensionActive && isTrackingActive) ? "block" : "none"; + } + + // Mise à jour des options d'ajout automatique + const autoAddContainer = document.getElementById("auto-add")?.parentElement; + const autoAddCheckbox = document.getElementById("auto-add"); + const autoAddOptions = document.getElementById("auto-add-options"); + const saveOptionsBtn = document.getElementById("save-options"); + + if (autoAddContainer) { + autoAddContainer.style.display = (isLoggedIn && extensionActive) ? "block" : "none"; + } + if (autoAddCheckbox && isLoggedIn) { + autoAddCheckbox.checked = autoAdd; + } + if (autoAddOptions) { + autoAddOptions.classList.toggle("hidden", !autoAdd); + } + if (saveOptionsBtn) { + saveOptionsBtn.classList.toggle("hidden", !autoAdd); + } + + // Mise à jour du message de chargement Pyodide + const statusContainer = document.getElementById('pyodide-loading'); + if (statusContainer) { + if (!isLoggedIn) { + statusContainer.innerHTML = ""; + } else if (!pyodideSimplemmaReady && extensionActive && isTrackingActive) { + statusContainer.innerHTML = "<p style='color: black; text-align: center; font-size: 11px;'>Chargement de l'extension en cours, veuillez patienter...</p>"; + } else if (pyodideSimplemmaReady && extensionActive && isTrackingActive) { + statusContainer.innerHTML = "<p style='color: black; text-align: center; font-size: 11px;'>C'est prêt !</p>"; + setTimeout(() => { + statusContainer.innerHTML = ""; + }, 2000); + } else { + statusContainer.innerHTML = ""; + } } - console.log("[Popup] Résumé des statistiques mis à jour:", summary); + // Mise à jour de la sélection des langues + await updateLanguageSelection(); + + log("Interface mise à jour complètement", { + isLoggedIn, + extensionActive, + isTrackingActive, + autoAdd, + pyodideSimplemmaReady, + includeStopwords + }); +} - // Résumé dans le local storage - await browser.storage.local.set({ statsSummary: summary }); +//Activer/désactiver les stoplists +async function updateStopwordsOption(includeStopwords) { + const stopwordsCheckbox = document.getElementById("include-stopwords"); + if (stopwordsCheckbox) { + stopwordsCheckbox.checked = includeStopwords; + } } +// ✅ Actualisation de l'UI au chargement de la page +document.addEventListener("DOMContentLoaded", async () => { + await updateExtension(); // Mise à jour de l'extension selon les valeurs du local storage + setupEventListeners(); // Configuration des écouteurs d'événements +}); + +// !! boucle infinie ??? +// // Actualisation de l'UI en cas de changement dans le stockage local : ça c'est dans background qu'il faut le mettre xd +// browser.storage.onChanged.addListener((changes, area) => { +// if (area === "local" && changes.accessToken) { +// updateExtension(); +// } +// }); + // ========================== -// Réception des messages du background +// Gestion des messages et du stockage // ========================== browser.runtime.onMessage.addListener(async (message) => { log("📩 Message reçu dans popup.js :", message); - if (message.action === "updateUI") { - log("🔄 Mise à jour de l'UI du popup..."); - await updateConnectionButton(); - await updateOptionsUI(); - await updateLanguageSelection(); - // Possibilité de mettre à jour d'autres éléments via le message - } else if (message.action === "showNotification") { - // Gestion des notifications si besoin - showNotification(message.text); + await updateExtension(); + } else if (message.action === "notify") { + alert(message.message); } }); -// Actualisation de l'UI en cas de changement dans le stockage local browser.storage.onChanged.addListener((changes, area) => { - if (area === "local" && changes.accessToken) { - updateConnectionButton(); - updateExtensionToggleButton(); + if (area === "local") { + updateExtension(); } }); // ========================== -// Gestion de l'affichage de notifications (optionnelle) +// Gestion des notifications // ========================== function showNotification(message) { const notificationBox = document.getElementById("extension-notification"); @@ -451,6 +495,83 @@ function showNotification(message) { notificationBox.classList.add("hidden"); }, { once: true }); } else { - console.error("⌠Impossible d'afficher la notification : élément manquant."); + log("⌠Impossible d'afficher la notification : élément manquant."); } } + +function handleWorkerMessage(event) { + const data = event.data; + log("[Background] Message du WebWorker :", data); + + if (data.type === "process-text" && data.status === "error") { + browser.runtime.sendMessage({ + action: "notify", + message: data.message + }); + return; + }} + + + + + + + + + + + + + + + + + + +// // Bouton pour ouvrir la page des statistiques +// document.getElementById("open-stats")?.addEventListener("click", async () => { +// await displayStatsSummary();//résumé dans la console +// window.open("stats.html", "_blank"); +// }); + +// async function displayStatsSummary() { +// console.log("[Popup] Préparation du résumé des statistiques..."); + +// // Récupérer les données stockées +// const { lemmaFrequencies, trackedLanguages, wordsAdded } = await browser.storage.local.get([ +// "lemmaFrequencies", +// "trackedLanguages", +// "wordsAdded" +// ]); + +// // Initialisation du résumé +// let summary = { +// totalWords: 0, // Nombre total de mots analysés +// totalUniqueWords: 0, // Nombre total de mots uniques +// languages: {}, // Nombre de mots analysés par langue +// wordsAdded: wordsAdded || {} // Mots ajoutés classés par langue +// }; + +// if (lemmaFrequencies) { +// Object.entries(lemmaFrequencies).forEach(([lang, words]) => { +// const wordCount = Object.values(words).reduce((sum, count) => sum + count, 0); +// const uniqueWordCount = Object.keys(words).length; + +// // Vérifier si la langue doit être suivie +// if (!trackedLanguages || trackedLanguages.includes(lang)) { +// summary.languages[lang] = { +// totalWords: wordCount, +// uniqueWords: uniqueWordCount +// }; + +// summary.totalWords += wordCount; +// summary.totalUniqueWords += uniqueWordCount; +// } +// }); +// } + +// console.log("[Popup] Résumé des statistiques mis à jour:", summary); + +// // Résumé dans le local storage +// await browser.storage.local.set({ statsSummary: summary }); +// } diff --git a/src/sidebar/sidebar.html b/src/sidebar/sidebar.html index daea55d..99ef99e 100644 --- a/src/sidebar/sidebar.html +++ b/src/sidebar/sidebar.html @@ -54,7 +54,7 @@ width: auto; display: inline-flex; padding: 6px 12px; - font-size: 18px; + font-size: 16px; font-family: Luciole; background: none; border: none; @@ -150,7 +150,7 @@ font-weight: bold; color: #323046; flex-grow: 1; - font-size: 15px; + font-size: 12px; text-align: center; } @@ -183,7 +183,7 @@ transform: translateX(-50%); background-color: rgba(0,0,0,0.75); color: #fff; - font-size: 14px !important; + font-size: 12px !important; font-weight: lighter !important; padding: 6px 10px; border-radius: 5px; @@ -259,6 +259,8 @@ transition: filter 0.3s ease-in-out; } + /* Ajout de la classe active */ + .lexique-highlight-toggle.active .feutre-icon, .lexique-highlight-toggle[data-active="true"] .feutre-icon { filter: brightness(0) saturate(100%) invert(83%) sepia(89%) saturate(588%) hue-rotate(360deg); } @@ -293,6 +295,31 @@ transform: translateX(-50%) translateY(-5px); } + .lexicon-highlight { + 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 */ + } + + .color-bands div { + flex: 1; /* Chaque bande occupe une part égale */ + height: 100%; + } + + .lexicon-section { margin-bottom: 10px; } @@ -379,6 +406,11 @@ color: red !important; font-weight: bold; } + #messageContainer { + display: none; + text-align: center; + color: #323046; + } </style> </head> <body> @@ -452,7 +484,7 @@ <h4>🌠Wiktionnaire</h4> <ul id="wiktionnaireList"></ul> </div> - + <!-- Fenêtre modale cachée --> <div id="modalOverlay" class="modal-overlay"> <div class="modal-content"> @@ -463,5 +495,10 @@ </div> </div> + <!-- Bloc 4 : Message d'analyse désactivée --> + <div id="messageContainer"> + <p id="analysisMessage"></p> + </div> + </body> </html> diff --git a/src/sidebar/sidebar.js b/src/sidebar/sidebar.js index 48b3e37..fd75670 100644 --- a/src/sidebar/sidebar.js +++ b/src/sidebar/sidebar.js @@ -10,6 +10,7 @@ log( // ───────────────────────────────────────────────────────────────────────────── let authToken = window.authToken; window.authToken = authToken; +let isUpdatingLexicons = false; // ───────────────────────────────────────────────────────────────────────────── // â–Œ Fonctions liées au token @@ -37,17 +38,12 @@ function updateAuthButton(isLoggedIn) { const authButton = document.getElementById("auth-button"); if (authButton) { authButton.textContent = isLoggedIn ? "Se déconnecter" : "Se connecter"; - authButton.title = isLoggedIn - ? "En vous déconnectant, vous perdrez l'accès à vos lexiques personnels ainsi qu'aux fonctionnalités d'ajout automatique et de statistiques d'utilisation." - : "En vous connectant, vous pourrez accéder à vos lexiques personnels ainsi qu'aux fonctionnalités d'ajout automatique et de statistiques d'utilisation."; log("🔄 Bouton d'authentification mis à jour :", authButton.textContent); } else { console.warn("âš ï¸ Bouton d'authentification (#auth-button) introuvable."); } } - - function toggleElementsVisibility(isLoggedIn) { const elementsToShowOrHide = [ { id: "add-to-lexiques", shouldShow: isLoggedIn }, @@ -85,7 +81,11 @@ async function refreshSidebarState() { toggleElementsVisibility(isLoggedIn); toggleHighlightMessage(isLoggedIn); + const isAnalysisEnabled = await checkAnalysisStatus(); + if (isLoggedIn) { + hideBlocks(!isAnalysisEnabled); + if (!window.lexiconColorsUpdated) { await updateLexiconColors(authToken); window.lexiconColorsUpdated = true; @@ -93,18 +93,8 @@ async function refreshSidebarState() { await fetchLexicons(); } else { // Si l'utilisateur n'est pas connecté, on ferme tous les blocs - document.querySelectorAll('.block-content').forEach(block => { - block.classList.add('hidden'); - // Mise à jour des boutons de bascule associés pour afficher le symbole "+" - const header = block.previousElementSibling; - if (header) { - const toggleBtn = header.querySelector(".toggle-btn"); - if (toggleBtn) { - toggleBtn.textContent = '+'; - } - } - }); - + hideBlocks(true); // Masquer tous les blocs + const lexiquesContainer = document.getElementById("lexiques"); if (lexiquesContainer) { lexiquesContainer.textContent = "Veuillez vous connecter pour voir vos lexiques."; @@ -117,12 +107,30 @@ async function refreshSidebarState() { } } + // Afficher un message d'activation de l'analyse selon le statut de connexion + const messageContainer = document.getElementById("messageContainer"); + if (!isLoggedIn) { + if (messageContainer) { + messageContainer.style.display = "block"; + messageContainer.innerHTML = "Veuillez vous connecter pour utiliser l'extension."; + } + } else if (!isAnalysisEnabled) { + if (messageContainer) { + messageContainer.style.display = "block"; + messageContainer.innerHTML = "L'analyse est <strong>désactivée</strong>.<br>Pour utiliser l'extension, cliquez sur le bouton <strong>Activer l'analyse</strong> dans le menu d'extension."; + } + } else { + if (messageContainer) { + messageContainer.style.display = "none"; + } + } + log("✅ Barre latérale actualisée. Utilisateur connecté :", isLoggedIn); } /** - * Bascule l’affichage d’un bloc. - * @param {string} blockId - L’ID du conteneur à basculer. + * Bascule l'affichage d'un bloc. + * @param {string} blockId - L'ID du conteneur à basculer. * @param {HTMLElement} btn - Le bouton qui déclenche la bascule, pour mettre à jour son texte. */ function toggleBlock(blockId, btn) { @@ -139,8 +147,8 @@ function toggleBlock(blockId, btn) { } /** - * Ouvre un bloc s’il est fermé et met à jour le bouton de bascule. - * @param {string} blockId - L’ID du conteneur à ouvrir. + * Ouvre un bloc s'il est fermé et met à jour le bouton de bascule. + * @param {string} blockId - L'ID du conteneur à ouvrir. * @param {HTMLElement} [btn] - (Optionnel) Le bouton de bascule à mettre à jour. */ function openBlock(blockId, btn) { @@ -181,6 +189,19 @@ function closeBlock(blockId, btn) { } } +function hideBlocks(shouldHide) { + const blockIds = ["menu", "etat", "definitionContainer"]; + blockIds.forEach(blockId => { + const block = document.getElementById(blockId); + if (block) { + if (shouldHide) { + block.classList.add("hidden"); // Masquer le bloc + } else { + block.classList.remove("hidden"); // Afficher le bloc + } + } + }); +} // ───────────────────────────────────────────────────────────────────────────── // â–Œ Gestion des lexiques (Affichage) @@ -222,7 +243,7 @@ async function fetchLexicons() { lexiconMap.set(lex.id, lexiconName); }); - displayLexiconsWithCheckbox(lexicons.map((lex) => ({ + await displayLexiconsWithCheckbox(lexicons.map((lex) => ({ lexiconName: lex.category === "User" ? DEBUG @@ -234,8 +255,11 @@ async function fetchLexicons() { lexiconId: lex.id, active: lex.active || false, }))); + + // Restaurer l'état des boutons après l'affichage + await restoreHighlightingState(); } catch (error) { - console.error("⌠Erreur lors du chargement des lexiques :", error.message); + log("⌠Erreur lors du chargement des lexiques :", error.message); const lexiquesContainer = document.getElementById("lexiques"); if (lexiquesContainer) { lexiquesContainer.textContent = error.message || "Erreur lors du chargement des lexiques."; @@ -261,6 +285,10 @@ async function displayLexiconsWithCheckbox(lexicons) { } for (const { lexiconName, lexiconId, active } of lexicons) { + if (lexiquesContainer.querySelector(`div[data-lexicon-id="${lexiconId}"]`)) { + continue; // Si oui, passez à l'itération suivante + } + const lexiqueDiv = document.createElement("div"); lexiqueDiv.className = "lexique-item"; @@ -291,6 +319,9 @@ async function displayLexiconsWithCheckbox(lexicons) { // Conteneur pour le bouton de surlignage (avec tooltip) const highlightButton = document.createElement("button"); highlightButton.className = "tooltip-container lexique-highlight-toggle"; + if (active) { + highlightButton.classList.add('active'); + } highlightButton.dataset.lexiconId = lexiconId; highlightButton.dataset.active = active ? "true" : "false"; const feutreIcon = document.createElement("img"); @@ -301,19 +332,21 @@ async function displayLexiconsWithCheckbox(lexicons) { highlightTooltip.className = "tooltip"; highlightTooltip.textContent = "Activer/Désactiver le surlignage des mots du lexique"; - // Gestion du clic pour activer/désactiver le surlignage + // Mise à jour du gestionnaire d'événements highlightButton.addEventListener("click", async () => { let currentState = highlightButton.dataset.active === "true"; let newState = !currentState; try { - await browser.runtime.sendMessage({ - action: "toggleLexiconHighlight", - lexiconId, - isActive: newState, - }); + await toggleLexiconHighlight(lexiconId, newState); highlightButton.dataset.active = newState ? "true" : "false"; + highlightButton.classList.toggle("active", newState); + + // Sauvegarder l'état dans le storage local + const activeLexicons = Array.from(document.querySelectorAll('.lexique-highlight-toggle[data-active="true"]')) + .map(btn => parseInt(btn.dataset.lexiconId)); + await browser.storage.local.set({ activeLexicons }); } catch (error) { - console.error("Erreur lors du toggle de surlignage pour le lexique", lexiconId, ":", error); + log("Erreur lors du toggle de surlignage pour le lexique", lexiconId, ":", error); } }); @@ -323,7 +356,6 @@ async function displayLexiconsWithCheckbox(lexicons) { lexiqueDiv.appendChild(labelSpan); lexiqueDiv.appendChild(checkboxContainer); lexiqueDiv.appendChild(highlightButton); - lexiquesContainer.appendChild(lexiqueDiv); } @@ -351,6 +383,35 @@ async function displayLexiconsWithCheckbox(lexicons) { }, 100); } +async function updateLexiconsDisplay() { + const token = await getAuthTokenFromStorage(); // Récupérer le token d'authentification + if (!token) { + console.warn("âš ï¸ Aucun token d'authentification disponible."); + return; + } + + const lexicons = await getLexicons(token); // Récupérer les lexiques + await displayLexiconsWithCheckbox(lexicons); // Mettre à jour l'affichage des lexiques +} + +// Ajouter une fonction pour restaurer l'état des boutons au chargement +async function restoreHighlightingState() { + try { + const { activeLexicons } = await browser.storage.local.get("activeLexicons"); + if (activeLexicons && Array.isArray(activeLexicons)) { + const buttons = document.querySelectorAll('.lexique-highlight-toggle'); + buttons.forEach(button => { + const lexiconId = parseInt(button.dataset.lexiconId); + const isActive = activeLexicons.includes(lexiconId); + button.dataset.active = isActive ? "true" : "false"; + button.classList.toggle("active", isActive); + }); + } + } catch (error) { + log("Erreur lors de la restauration de l'état des boutons:", error); + } +} + function initModal() { log("initModal appelé"); const modalOverlay = document.getElementById("modalOverlay"); @@ -360,7 +421,7 @@ function initModal() { log("closeModalBtn =", closeModalBtn); if (!modalOverlay || !modalFullText || !closeModalBtn) { - console.error("Les éléments modaux ne sont pas trouvés !"); + log("Les éléments modaux ne sont pas trouvés !"); return; } @@ -381,6 +442,11 @@ document.addEventListener("DOMContentLoaded", initModal); async function handleAuthButtonClick() { await browser.runtime.sendMessage({ action: "toggleAuth" }); await refreshSidebarState(); + const messageContainer = document.getElementById("messageContainer"); + if (messageContainer) { + messageContainer.style.display = "block"; + messageContainer.innerHTML = "Veuillez vous connecter pour utiliser l'extension."; + } } // ───────────────────────────────────────────────────────────────────────────── @@ -418,12 +484,12 @@ async function handleAddWordClick() { return; } - // 3) Vérifier si le mot existe déjà dans l’un des lexiques sélectionnés + // 3) Vérifier si le mot existe déjà dans l'un des lexiques sélectionnés let definitions = []; try { definitions = await fetchLexiconDefinitions(selectedWord); } catch (error) { - console.error("Erreur lors de la récupération des définitions pour vérification :", error); + log("Erreur lors de la récupération des définitions pour vérification :", error); } const existingLexiconIds = new Set(); @@ -435,22 +501,31 @@ async function handleAddWordClick() { } } - // 4) Si le mot existe déjà dans certains lexiques, on les affiche + // 4) Déterminer les lexiques où ajouter le mot (ceux qui n'ont pas déjà le mot) + const lexiconsToAdd = selectedLexiconIds.filter(id => !existingLexiconIds.has(id)); + + // Si le mot existe déjà dans certains lexiques, on affiche le message if (existingLexiconIds.size > 0) { + const existingLexiconsNames = Array.from(existingLexiconIds) + .map(id => lexiconMap.get(id) || id) + .join(", "); if (lexiconResultElement) { lexiconResultElement.innerHTML = "Le mot <strong>" + selectedWord + "</strong> existe déjà dans le(s) lexique(s) : " + - Array.from(existingLexiconIds).join(", ") + "."; + "<strong>" + existingLexiconsNames + "</strong>."; } } - // 5) Déterminer les lexiques où ajouter le mot - const lexiconsToAdd = selectedLexiconIds.filter(id => !existingLexiconIds.has(id)); if (lexiconsToAdd.length === 0) { return; } - // 6) Envoi d’une seule requête pour tous les lexiques restants + // Construction du message de succès avec les noms des lexiques où le mot va être ajouté + const lexiconsNames = lexiconsToAdd + .map(id => lexiconMap.get(id) || id) + .join(", "); + + // 5) Envoi d'une seule requête pour tous les lexiques restants try { log(`📡 Envoi de l'ajout du mot "${selectedWord}" dans les lexiques :`, lexiconsToAdd); const result = await window.AddWord(authToken, selectedWord, lexiconsToAdd, false); @@ -461,115 +536,158 @@ async function handleAddWordClick() { await new Promise(resolve => setTimeout(resolve, 300)); browser.runtime.sendMessage({ action: "refreshUI" }); - // 7) Affichage du message de succès + // 6) Affichage du message de succès if (lexiconResultElement) { lexiconResultElement.innerHTML += "<br>✅ Mot <strong>" + selectedWord + "</strong> ajouté avec succès dans : " + - lexiconsToAdd.join(", ") + "."; + lexiconsNames + "."; } } catch (error) { - console.error("Erreur lors de l’ajout du mot :", error); + log("Erreur lors de l'ajout du mot :", error); if (lexiconResultElement) { - lexiconResultElement.textContent = "Erreur lors de l’ajout : " + error.message; + lexiconResultElement.textContent = "Erreur lors de l'ajout : " + error.message; } } } + // ───────────────────────────────────────────────────────────────────────────── // â–Œ Réception des messages // ───────────────────────────────────────────────────────────────────────────── browser.runtime.onMessage.addListener(async (message) => { log("📩 Message reçu dans sidebar.js :", message); - switch (message.action) { - case "refreshUI": - log("🔄 Demande de rafraîchissement de la barre latérale."); - await refreshSidebarState(); - break; - - case "mot_selectionne": - if (message.selectedText) { - log("ðŸ–‹ï¸ Mot sélectionné :", message.selectedText); - const selectedWordElement = document.getElementById("motSelectionne"); - if (selectedWordElement) { - selectedWordElement.textContent = message.selectedText; - } else { - console.warn("âš ï¸ Ã‰lément #motSelectionne introuvable."); - } - const lexiconResultElement = document.getElementById("lexiconResult"); - if (lexiconResultElement) { - lexiconResultElement.innerHTML = ""; + + if (message.action) + switch (message.action) { + case "refreshUI": + log("🔄 Demande de rafraîchissement de la barre latérale."); + await refreshSidebarState(); + break; + + case "mot_selectionne": + if (message.selectedText) { + log("ðŸ–‹ï¸ Mot sélectionné :", message.selectedText); + const selectedWordElement = document.getElementById("motSelectionne"); + if (selectedWordElement) { + selectedWordElement.textContent = message.selectedText; + } else { + console.warn("âš ï¸ Ã‰lément #motSelectionne introuvable."); + } + const lexiconResultElement = document.getElementById("lexiconResult"); + if (lexiconResultElement) { + lexiconResultElement.innerHTML = ""; + } + openBlock("etatContent"); } - openBlock("etatContent"); - } - break; + break; - case "getDefinition": - if (message.selectedText) { - log("📖 Recherche des définitions pour :", message.selectedText); - openBlock("definitionContent"); - await window.showDefinitions(message.selectedText); - } - break; + case "getDefinition": + if (message.selectedText) { + log("📖 Recherche des définitions pour :", message.selectedText); + openBlock("definitionContent"); + await window.showDefinitions(message.selectedText); + } + break; - case "showDefinitions": - if (Array.isArray(message.definitions)) { - window.displayDefinitions(message.definitions); - } - break; + case "showDefinitions": + if (Array.isArray(message.definitions)) { + window.displayDefinitions(message.definitions); + } + break; - case "fetchWiktionaryDefinitionResponse": - if (message.selectedText) { - log(`📖 Réception de la définition du Wiktionnaire pour '${message.selectedText}'`); - window.displayDefinitions(message.definitions); - } - break; + case "fetchWiktionaryDefinitionResponse": + if (message.selectedText) { + log(`📖 Réception de la définition du Wiktionnaire pour '${message.selectedText}'`); + window.displayDefinitions(message.definitions); + } + break; - case "showLexiconResult": - log("📚 Résultat des lexiques reçus :", message.lexicons); - window.displayLexiconResults(message.lexicons); - break; - - case "addWordResult": - const lexiconResultElement = document.getElementById("lexiconResult"); - if (lexiconResultElement) { - lexiconResultElement.innerHTML = message.lexicons; - } - break; + case "showLexiconResult": + log("📚 Résultat des lexiques reçus :", message.lexicons); + window.displayLexiconResults(message.lexicons); + break; + + case "addWordResult": + const lexiconResultElement = document.getElementById("lexiconResult"); + if (lexiconResultElement) { + lexiconResultElement.innerHTML = message.lexicons; + } + break; - case "addToLexicon": - handleAddWordClick(); - break; - - case "openLexiconBlock": - openBlock("menuContent"); - break; + case "addToLexicon": + handleAddWordClick(); + break; - case "toggleAuth": - break; + case "openLexiconBlock": + openBlock("menuContent"); + break; + + case "toggleAuth": + break; + + case "authStatusChanged": + break; + + case "updateUI": + if (!message.extensionActive) { + // Fermer tous les blocs + hideBlocks(true); + } else { + hideBlocks(false); + await refreshSidebarState(); + } + break; - case "authStatusChanged": - break; + case "pyodide-simplemma-ready": + return; - case "updateUI": - await refreshSidebarState(); - break; + case "saveToken": + authToken = message.token; + break; - case "pyodide-simplemma-ready": - return; + case "closeSidebarBlocks": + hideBlocks(true); + break; + } - case "saveToken": - authToken = message.token; - break; + if (message.command) { + switch (message.command) { + case "activate-highlighting": + const highlightButton = document.querySelector(`button[data-lexicon-id="${message.lexiconId}"]`); + if (highlightButton) { + highlightButton.dataset.active = "true"; + highlightButton.classList.add("active"); + } + break; + + case "deactivate-highlighting": + const highlightButtonOff = document.querySelector(`button[data-lexicon-id="${message.lexiconId}"]`); + if (highlightButtonOff) { + highlightButtonOff.dataset.active = "false"; + highlightButtonOff.classList.remove("active"); + } + break; - case "closeSidebarBlocks": - closeBlock("menuContent"); - closeBlock("etatContent"); - closeBlock("definitionContent") - break; + case "register-stats-script": + break; - default: - console.warn("âš ï¸ Action inconnue reçue :", message.action); + case "register-highlighting-script": + log("✅ Script de surlignage enregistré"); + browser.runtime.sendMessage({ + command: "updateAuthToken", + token: authToken + }); + break; + + case "updateAuthToken": + authToken = message.token; + window.authToken = message.token; + break; + + default: + console.warn("âš ï¸ Action inconnue reçue :", message.command); + } } }); @@ -578,7 +696,6 @@ browser.runtime.onMessage.addListener(async (message) => { // ───────────────────────────────────────────────────────────────────────────── document.addEventListener("DOMContentLoaded", async () => { log("📦 DOM entièrement chargé. Initialisation de la sidebar."); - authToken = await getAuthTokenFromStorage(); window.authToken = authToken; await refreshSidebarState(); @@ -606,7 +723,7 @@ document.addEventListener("DOMContentLoaded", async () => { chercherDefButton.style.visibility = "visible"; chercherDefButton.disabled = false; } else { - console.error("⌠ERREUR : Bouton #chercherDef introuvable."); + log("⌠ERREUR : Bouton #chercherDef introuvable."); } // Écouteur pour la case à cocher "toggle-definitions" @@ -683,3 +800,29 @@ document.addEventListener("DOMContentLoaded", async () => { } }); }); + +async function toggleLexiconHighlight(lexiconId, isActive) { + try { + const button = document.querySelector(`button[data-lexicon-id="${lexiconId}"]`); + if (button) { + button.dataset.active = isActive.toString(); + button.classList.toggle('active', isActive); + } + + await browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => { + browser.tabs.sendMessage(tabs[0].id, { + command: isActive ? "activate-highlighting" : "deactivate-highlighting", + lexiconId: lexiconId + }); + }); + + log(`✅ Surlignage ${isActive ? 'activé' : 'désactivé'} pour le lexique ${lexiconId}`); + } catch (error) { + log(`⌠Erreur lors du toggle du surlignage pour le lexique ${lexiconId}:`, error); + } +} + +async function checkAnalysisStatus() { + const { extensionActive } = await browser.storage.local.get("extensionActive"); + return extensionActive; // Retourne true si l'analyse est activée, sinon false +} diff --git a/src/utils/api.js b/src/utils/api.js index e32d858..5869525 100644 --- a/src/utils/api.js +++ b/src/utils/api.js @@ -15,7 +15,7 @@ document.addEventListener("mouseup", () => { }); // ───────────────────────────────────────────────────────────────────────────── -// â–Œ Fonction utilitaire pour appeler l’API +// â–Œ Fonction utilitaire pour appeler l'API // ───────────────────────────────────────────────────────────────────────────── /** * Effectue une requête API (GET, POST, etc.) avec ou sans body JSON @@ -44,7 +44,7 @@ async function callApi(url, authToken = null, method = 'GET', data = null) { } return await response.json(); } catch (error) { - console.error(`🚨 Erreur lors de l'appel API [${url}]:`, error); + log(`🚨 Erreur lors de l'appel API [${url}]:`, error); throw error; } } @@ -54,69 +54,17 @@ async function callApi(url, authToken = null, method = 'GET', data = null) { // ───────────────────────────────────────────────────────────────────────────── /** - * Récupère les lexiques de l’utilisateur, - * en langue par défaut "fr". + * Récupère les lexiques de l'utilisateur * * @param {string} authToken - Le token d'authentification. * @returns {Promise<any[]>} - Liste des lexiques trouvés. */ async function getLexicons(authToken) { - // Vérifier si les lexiques sont déjà en cache - const { lexicons } = await browser.storage.local.get("lexicons"); - if (Array.isArray(lexicons) && lexicons.length > 0) { - //console.log("Lexiques récupérés depuis le cache local :", lexicons); todo: ajouter condition car elle renvoie plusieurs fois la réponse dans la console - return lexicons; // Retourne les lexiques en cache pour éviter une requête API inutile - } - const url = "https://babalex.lezinter.net/api/lexicon/search"; - try { - const response = await callApi(url, authToken); - console.log("✅ Réponse de getLexicons :", response); - - if (Array.isArray(response) && response.length > 0) { - await browser.storage.local.set({ lexicons: response }); // Stocker en cache - } - - return response; - } catch (error) { - return []; - } -} - - - -/** - * Récupère tous les lexiques (catégories) de l’utilisateur - */ -async function getAllCategoriesLexicons(authToken) { - const categories = ["User", "Group", "Zero", "New words"]; - - const promises = categories.map(async (category) => { - const baseUrl = "https://babalex.lezinter.net/api/lexicon/search"; - const url = `${baseUrl}?&category=${encodeURIComponent(category)}&language=fr`; - - try { - return await callApi(url, authToken); - } catch (error) { - console.warn(`âš ï¸ Aucune donnée trouvée (ou accès refusé) pour la catégorie "${category}" :`, error.message); - return []; - } - }); - - try { - const resultsByCategory = await Promise.all(promises); - const combined = resultsByCategory.flat(); - - log("✅ Lexiques récupérés (toutes catégories confondues) :", combined); - return combined; - } catch (error) { - console.error("⌠Erreur lors de la récupération multi-catégories :", error); - return []; - } + return callApi(url, authToken); } - // ───────────────────────────────────────────────────────────────────────────── // â–Œ Récupération des entrées d'un lexique // ───────────────────────────────────────────────────────────────────────────── @@ -138,7 +86,7 @@ async function getAllLexiconWords(authToken) { const lexicons = await getLexicons(authToken); if (!Array.isArray(lexicons) || lexicons.length === 0) { - console.warn("âš ï¸ Aucun lexique retourné par l’API pour ces paramètres."); + console.warn("âš ï¸ Aucun lexique retourné par l'API pour ces paramètres."); return {}; } @@ -147,6 +95,11 @@ async function getAllLexiconWords(authToken) { for (const lexicon of lexicons) { const entries = await getLexiconEntries(authToken, lexicon.id); + // Vérification que entries est bien un tableau + if (!Array.isArray(entries)) { + console.warn(`âš ï¸ Format invalide pour les entrées du lexique ${lexicon.id}:`, entries); + continue; + } const allGraphies = entries.map(entry => entry.graphy); // Création d'un libellé unique pour le lexique @@ -161,7 +114,7 @@ async function getAllLexiconWords(authToken) { log("✅ Toutes les graphies récupérées :", allGraphiesByLexicon); return allGraphiesByLexicon; } catch (error) { - console.error("⌠Erreur lors de la récupération des graphies des lexiques :", error); + log("⌠Erreur lors de la récupération des graphies des lexiques :", error); return {}; } } @@ -189,7 +142,7 @@ async function getWiktionaryDefinition(word) { log(`📖 Définition trouvée pour '${word}':`, definition); return [definition]; } catch (error) { - console.error("Erreur lors de la récupération du Wiktionnaire :", error); + log("Erreur lors de la récupération du Wiktionnaire :", error); return ["Erreur : " + error.message]; } } @@ -207,13 +160,13 @@ async function getWiktionaryDefinition(word) { */ async function AddWord(authToken, selectedWord, lexiconIds, force = false) { if (!authToken) { - throw new Error("Aucun token d’authentification fourni."); + throw new Error("Aucun token d'authentification fourni."); } if (!selectedWord) { - throw new Error("Aucun mot n’a été spécifié pour l’ajout."); + throw new Error("Aucun mot n'a été spécifié pour l'ajout."); } if (!Array.isArray(lexiconIds) || lexiconIds.length === 0) { - throw new Error("Aucun lexique sélectionné pour l’ajout."); + throw new Error("Aucun lexique sélectionné pour l'ajout."); } const url = "https://babalex.lezinter.net/api/entry/create"; @@ -234,7 +187,6 @@ async function AddWord(authToken, selectedWord, lexiconIds, force = false) { window.callApi = callApi; window.getLexicons = getLexicons; -window.getAllCategoriesLexicons = getAllCategoriesLexicons; window.getLexiconEntries = getLexiconEntries; window.getAllLexiconWords = getAllLexiconWords; window.getWiktionaryDefinition = getWiktionaryDefinition; diff --git a/src/utils/definitions.js b/src/utils/definitions.js index fd57303..ca0a070 100644 --- a/src/utils/definitions.js +++ b/src/utils/definitions.js @@ -116,7 +116,7 @@ async function fetchLexiconDefinitions(word) { log("Résultat final filtré :", allDefinitions); return allDefinitions; } catch (error) { - console.error("⌠Erreur générale lors de la récupération des définitions :", error); + log("⌠Erreur générale lors de la récupération des définitions :", error); return []; } } @@ -125,45 +125,67 @@ async function fetchLexiconDefinitions(word) { * Récupère la définition d'un mot depuis le Wiktionnaire (fr). * Retourne un tableau d'objets : [{ source: 'Wiktionnaire', text: '...' }] */ -// async function fetchWiktionaryDefinition(word) { -// try { -// log(`🔠Requête Wiktionnaire pour "${word}"...`); -// if (!word || word.trim() === "") { -// throw new Error("âš ï¸ Mot vide, impossible d'envoyer la requête."); -// } -// const wiktionaryURL = `https://fr.wiktionary.org/w/api.php?action=query&format=json&origin=*&prop=extracts&explaintext=true&redirects=1&titles=${encodeURIComponent(word)}`; -// const response = await fetch(wiktionaryURL); -// if (!response.ok) { -// throw new Error(`⌠Erreur API Wiktionnaire: ${response.statusText}`); -// } -// const data = await response.json(); -// log("📖 Réponse API (Wiktionnaire) :", data); - -// const pages = data.query?.pages; -// const page = pages ? Object.values(pages)[0] : null; - -// const definitionText = page && page.extract -// ? page.extract.trim() -// : "âš ï¸ Aucune définition trouvée sur le Wiktionnaire."; - -// log("🌠Définition Wiktionnaire extraite :", definitionText); - -// return [ -// { -// source: "Wiktionnaire", -// text: definitionText -// } -// ]; -// } catch (error) { -// console.error("⌠Erreur Wiktionnaire :", error); -// return [ -// { -// source: "Wiktionnaire", -// text: "âš ï¸ Erreur lors de la récupération sur le Wiktionnaire." -// } -// ]; -// } -// } +async function fetchWiktionaryDefinition(word) { + try { + const result = await browser.storage.local.get("accessToken"); + authToken = result.accessToken; + + if (!authToken) { + log(`🔠Requête Wiktionnaire pour "${word}"...`); + if (!word || word.trim() === "") { + throw new Error("âš ï¸ Mot vide, impossible d'envoyer la requête."); + } + const wiktionaryURL = `https://fr.wiktionary.org/w/api.php?action=query&format=json&origin=*&prop=extracts&explaintext=true&redirects=1&titles=${encodeURIComponent(word)}`; + const response = await fetch(wiktionaryURL); + if (!response.ok) { + throw new Error(`⌠Erreur API Wiktionnaire: ${response.statusText}`); + } + const data = await response.json(); + log("📖 Réponse API (Wiktionnaire) :", data); + + const pages = data.query?.pages; + const page = pages ? Object.values(pages)[0] : null; + + const definitionText = page && page.extract + ? page.extract.trim() + : "âš ï¸ Aucune définition trouvée sur le Wiktionnaire."; + + log("🌠Définition Wiktionnaire extraite :", definitionText); + + return [ + { + source: "Wiktionnaire", + text: definitionText + } + ]; + } else { + log(` Recherche de la définition pour : ${word}`); + + // Récupération des données depuis l'API + const apiResponse = await wikiApiResponse(word); + log("Réponse brute de l'API :", apiResponse); + + if (!Array.isArray(apiResponse) || apiResponse.length === 0) { + console.warn(`Aucune définition trouvée pour "${word}"`); + return []; // Retourne un tableau vide si aucune définition + } + + // Formatage des données + const formattedData = formatDefinitionData(apiResponse); + log("Données formatées :", formattedData); + + return [ + { + source: "Wiktionnaire", + text: formattedData.definitions.length > 0 ? formattedData.definitions.join(" | ") : "âš ï¸ Aucune définition disponible." + } + ]; + } + } catch (error) { + log("⌠Erreur lors de la récupération de la définition :", error); + return [{ source: "Wiktionnaire", text: "Erreur lors de la récupération sur le Wiktionnaire." }]; + } +} async function wikiApiResponse(word) { const result = await browser.storage.local.get("accessToken"); @@ -186,10 +208,10 @@ async function wikiApiResponse(word) { const data = await response.json(); - console.log(`Résultats du Wiktionnaire pour le mot "${word}" :`, data); + log(`Résultats du Wiktionnaire pour le mot "${word}" :`, data); return data; } catch (error) { - console.error('Erreur lors de la récupération de la définition depuis le Wiktionnaire :', error); + log('Erreur lors de la récupération de la définition depuis le Wiktionnaire :', error); throw error; } } @@ -238,36 +260,6 @@ function formatDefinitionData(apiResponse) { return formattedData; } -async function fetchWiktionaryDefinition(word) { - try { - console.log(` Recherche de la définition pour : ${word}`); - - // Récupération des données depuis l'API - const apiResponse = await wikiApiResponse(word); - console.log("Réponse brute de l'API :", apiResponse); - - if (!Array.isArray(apiResponse) || apiResponse.length === 0) { - console.warn(`Aucune définition trouvée pour "${word}"`); - return []; // Retourne un tableau vide si aucune définition - } - - // Formatage des données - const formattedData = formatDefinitionData(apiResponse); - console.log("Données formatées :", formattedData); - - return [ - { - source: "Wiktionnaire", - text: formattedData.definitions.length > 0 ? formattedData.definitions.join(" | ") : "âš ï¸ Aucune définition disponible." - } - ]; - } catch (error) { - console.error("Erreur lors de la récupération de la définition :", error); - return [{ source: "Wiktionnaire", text: "Erreur lors de la récupération sur le Wiktionnaire." }]; - } -} - - // ───────────────────────────────────────────────────────────────────────────── // â–Œ Affichage des définitions dans la barre latérale // ───────────────────────────────────────────────────────────────────────────── @@ -387,7 +379,7 @@ function openDefinitionPopup(fullText) { const modalOverlay = document.getElementById("modalOverlay"); const modalFullText = document.getElementById("modalFullText"); if (!modalOverlay || !modalFullText) { - console.error("Modal elements not found!"); + log("Modal elements not found!"); return; } modalFullText.innerHTML = "<p>" + fullText.replace(/\n/g, "<br>") + "</p>"; @@ -467,7 +459,7 @@ async function showDefinitions(word) { return allDefinitions; } catch (error) { - console.error("⌠[showDefinitions] Erreur : ", error); + log("⌠[showDefinitions] Erreur : ", error); if (noDefinitionsContainer) { noDefinitionsContainer.textContent = @@ -479,15 +471,15 @@ async function showDefinitions(word) { } /** -* Appel direct pour récupérer les définitions d’un mot uniquement via l’API -* (sans Wiktionnaire), puis gérer l’affichage d’erreur ou non. +* Appel direct pour récupérer les définitions d'un mot uniquement via l'API +* (sans Wiktionnaire), puis gérer l'affichage d'erreur ou non. */ async function fetchDefinition(word) { log(`🔠Recherche de la définition pour '${word}'...`); const noDefinitionsContainer = document.getElementById("noDefinitionsContainer"); if (!noDefinitionsContainer) { - console.error("⌠Élément #noDefinitionsContainer introuvable."); + log("⌠Élément #noDefinitionsContainer introuvable."); return; } @@ -503,7 +495,7 @@ async function fetchDefinition(word) { noDefinitionsContainer.style.display = "none"; } catch (error) { - console.error("⌠Erreur lors de la récupération de la définition :", error); + log("⌠Erreur lors de la récupération de la définition :", error); noDefinitionsContainer.style.display = "block"; } } diff --git a/src/utils/highlighting.js b/src/utils/highlighting.js new file mode 100644 index 0000000..4a673e9 --- /dev/null +++ b/src/utils/highlighting.js @@ -0,0 +1,526 @@ +// Variables globales +window.activeLexiconIds = window.activeLexiconIds || new Set(); + +// Logs immédiats +log("🔵 DÉBUT DU FICHIER highlighting.js"); +try { + log("✅ highlighting.js chargé"); +} catch (e) { + log("⌠Erreur avec la fonction log:", e); +} + +// Vérification de l'environnement +log("🔠Vérification de l'environnement:", { + hasBrowser: typeof browser !== 'undefined', + windowLocation: window.location.href +}); + +// Gestion globale des erreurs +window.onerror = function(msg, url, line, col, error) { + log("🔴 Erreur globale:", { message: msg, url: url, line: line, col: col, error: error }); + return false; +}; + +// Fonction d'initialisation du token depuis le stockage local +async function initAuthToken() { + try { + const { accessToken } = await browser.storage.local.get("accessToken"); + if (accessToken) { + window.authToken = accessToken; + authToken = accessToken; + log("🔑 Token récupéré depuis le stockage local"); + return true; + } else { + log("âš ï¸ Aucun token trouvé dans le stockage local"); + return false; + } + } catch (error) { + log("⌠Erreur lors de la récupération du token:", error); + return false; + } +} +initAuthToken(); + +// Écoute de mise à jour du token +browser.runtime.onMessage.addListener((message) => { + if (message.command === "updateAuthToken" && message.token) { + window.authToken = message.token; + authToken = message.token; + log("🔑 Token mis à jour via message:", !!message.token); + } +}); + +(function () { + try { + if (window.hasRun) { + log("âš ï¸ highlighting.js déjà chargé"); + return; + } + window.hasRun = true; + + // 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"); + removeAllHighlights(); + await updateLexiconCache(); + highlightVisibleContent(); + attachMutationObserver(); + } + }); + window.addEventListener('pageshow', async () => { + if (window.highlightingActive && activeLexiconIds.size > 0) { + log("📄 Page affichée (pageshow), réinitialisation du surlignage"); + removeAllHighlights(); + await updateLexiconCache(); + highlightVisibleContent(); + attachMutationObserver(); + } + }); + + // Gestion des messages reçus du background + browser.runtime.onMessage.addListener((message, sender, sendResponse) => { + log("📨 Message reçu:", message, "Context:", { + highlightingActive, + activeLexiconIds: Array.from(activeLexiconIds), + hasAuthToken: !!window.authToken, + hasGetAllLexiconWords: !!window.getAllLexiconWords + }); + + if (message.command === "activate-highlighting") { + log(`🎯 Activation du surlignage pour le lexique ${message.lexiconId}`); + startHighlighting(message.lexiconId) + .then(() => { + window.highlightingActive = true; + sendResponse(true); + }) + .catch(error => { + log("⌠Erreur lors de l'activation:", error); + sendResponse(false); + }); + return true; + } + + if (message.command === "deactivate-highlighting") { + log(`🚫 Désactivation du surlignage pour le lexique ${message.lexiconId}`); + stopHighlighting(message.lexiconId) + .then(() => { + if (activeLexiconIds.size === 0) { + window.highlightingActive = false; + } + sendResponse(true); + }) + .catch(error => { + log("⌠Erreur lors de la désactivation:", error); + sendResponse(false); + }); + return true; + } + + return false; + }); + + log("📡 Enregistrement du script auprès du background"); + browser.runtime.sendMessage({ command: "register-highlighting-script" }); + + log("🚀 Initialisation de highlighting.js"); + + // 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.3)"; + span.style.backgroundImage = ""; + return; + } + if (lexIds.length === 1) { + const hexColor = await getColorForLexicon(lexIds[0]); + const rgbaColor = hexToRgba(hexColor, 0.3); + 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.3)); + 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) { + 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; + highlightVisibleContent(); + attachMutationObserver(); + return true; + } catch (error) { + log("⌠Erreur lors du démarrage du surlignage:", error); + window.highlightingActive = false; + highlightingActive = false; + 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; + removeAllHighlights(); + detachMutationObserver(); + } else { + removeAllHighlights(); + await updateLexiconCache(); + highlightVisibleContent(); + } + } else { + window.highlightingActive = false; + highlightingActive = false; + activeLexiconIds.clear(); + removeAllHighlights(); + detachMutationObserver(); + } + return true; + } catch (error) { + log("⌠Erreur lors de l'arrêt du surlignage:", error); + throw error; + } + } + + // Mise à jour du cache des lexiques + async function updateLexiconCache() { + log("📥 updateLexiconCache - Début avec context:", { + authToken: !!window.authToken, + getAllLexiconWords: !!window.getAllLexiconWords, + activeLexiconIds: Array.from(activeLexiconIds) + }); + let allWords; + try { + if (!window.authToken) { + throw new Error("Pas de token d'authentification"); + } + 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(); + 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:", + Object.fromEntries([...lexiconWordsCache.entries()].map(([id, words]) => [id, [...words]]))); + return true; + } catch (error) { + log("⌠Erreur dans updateLexiconCache:", { + message: error.message, + stack: error.stack, + authTokenExists: !!window.authToken, + getAllLexiconWordsType: typeof window.getAllLexiconWords, + response: allWords + }); + throw error; + } + } + + // Surlignage du contenu visible + function highlightVisibleContent() { + if (!highlightingActive) { + log("â¸ï¸ Surlignage inactif, sortie"); + return; + } + log("🔠Début du surlignage du contenu visible"); + const BATCH_SIZE = 100; + const BATCH_DELAY = 10; // ms + const textNodes = []; + const walker = document.createTreeWalker( + document.body, + NodeFilter.SHOW_TEXT, + { + acceptNode: (node) => { + const parent = node.parentElement; + 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`); + 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()) { + 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"; + 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.3)"; + } else { + 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.textContent.trim()) { + shouldHighlight = true; + break; + } + } + } + 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"); + observer.disconnect(); + observer = null; + } + } + + 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; + } + + log("🔠Vérification des dépendances au chargement:", { + authToken: !!window.authToken, + getAllLexiconWords: typeof window.getAllLexiconWords + }); + + async function checkAndRestoreHighlightingState() { + try { + const { activeLexicons } = await browser.storage.local.get("activeLexicons"); + if (!activeLexicons || !Array.isArray(activeLexicons) || activeLexicons.length === 0) { + window.highlightingActive = false; + highlightingActive = false; + return; + } + log("🔄 État des lexiques trouvé:", activeLexicons); + for (const lexiconId of activeLexicons) { + await startHighlighting(lexiconId); + } + } catch (error) { + log("⌠Erreur lors de la restauration de l'état:", error); + window.highlightingActive = false; + highlightingActive = false; + } + } + + checkAndRestoreHighlightingState(); + + } catch (error) { + log("🔴 Erreur critique dans l'IIFE:", error); + } +})(); diff --git a/src/utils/logger.js b/src/utils/logger.js index 8b3f55e..3d3891c 100644 --- a/src/utils/logger.js +++ b/src/utils/logger.js @@ -1,12 +1,31 @@ - /** * Mode debug : affiche les logs dans la console - * Mode prod : masque les logs + * Mode prod : masque les logs ainsi que les identifiants des lexiques */ -const DEBUG = false; // false en prod - -function log(...args) { - if (DEBUG) { - console.log(...args); +(function () { + if (typeof window !== 'undefined') { + if (typeof window.DEBUG === 'undefined') { + window.DEBUG = true; // true en debug + } + if (!window.log) { + function log(...args) { + if (window.DEBUG) { + console.log(...args); + } + } + window.log = log; + } + } else if (typeof self !== 'undefined') { + if (typeof self.DEBUG === 'undefined') { + self.DEBUG = true; // true en debug + } + if (!self.log) { + function log(...args) { + if (self.DEBUG) { + console.log(...args); + } + } + self.log = log; + } } -} +})(); diff --git a/src/utils/stats.js b/src/utils/stats.js index 1f7b799..3ee90b9 100644 --- a/src/utils/stats.js +++ b/src/utils/stats.js @@ -10,15 +10,15 @@ //Connexion au port function connectToWorker() { if (!workerPort) { - // console.log("[Stats] Connexion au WebWorker..."); + // log("[Stats] Connexion au WebWorker..."); workerPort = browser.runtime.connect({ name: "stats-worker-port" }); workerPort.onMessage.addListener((message) => { - console.log("[Stats] Message reçu du Worker :", message); + log("[Stats] Message reçu du Worker :", message); if (message.command === "update-frequencies") { - console.log("[Stats] Fréquences mises à jour :", message.frequencies); + log("[Stats] Fréquences mises à jour :", message.frequencies); } if (message.command === "threshold-exceeded") { - console.log("[Stats] Mots dépassant le seuil :", message.wordsAboveThreshold); + log("[Stats] Mots dépassant le seuil :", message.wordsAboveThreshold); let alertMessage = "Nouveaux mots dépassant le seuil :\n"; if (typeof message.wordsAboveThreshold !== "object" || message.wordsAboveThreshold === null) { return; @@ -35,7 +35,7 @@ } }); workerPort.onDisconnect.addListener(() => { - // console.log("[Stats] Déconnexion du WebWorker."); + // log("[Stats] Déconnexion du WebWorker."); workerPort = null; }); } @@ -46,7 +46,7 @@ connectToWorker(); } if (workerPort) { - console.log("[Stats] Envoi du texte au Worker :", text); + log("[Stats] Envoi du texte au Worker :", text); workerPort.postMessage({ command: "process-text", text: text }); } } @@ -55,7 +55,7 @@ // Gestion des messages envoyés depuis le background // ───────────────────────────────────────────────────────────────────────────── browser.runtime.onMessage.addListener((message) => { - console.log("[Stats] Message reçu :", message); + log("[Stats] Message reçu :", message); if (message.command === "activate-stats") { startTracking(); } @@ -85,7 +85,7 @@ // Arrête tous les chronomètres lorsque l'utilisateur change d'onglet document.addEventListener("visibilitychange", () => { if (document.hidden) { - // console.log("[Stats] Changement d'onglet détecté"); + // log("[Stats] Changement d'onglet détecté"); resetAllTimers(); } }); @@ -119,25 +119,25 @@ if (!readContent.has(text)) { startReadingTimer(element, text, minReadTime); } - highlightElement(element, true); + // highlightElement(element, true); } else { stopReadingTimer(element, text); - highlightElement(element, false); + // highlightElement(element, false); } }); } - /** - * Ajoute une bordure rouge autour de l'élément - */ - function highlightElement(element, shouldHighlight) { - if (shouldHighlight) { - element.style.outline = "3px solid red"; - } else { - element.style.outline = ""; - removeReadingIndicator(element); - } - } + // /** + // * Ajoute une bordure rouge autour de l'élément + // */ + // function highlightElement(element, shouldHighlight) { + // if (shouldHighlight) { + // element.style.outline = "3px solid red"; + // } else { + // element.style.outline = ""; + // removeReadingIndicator(element); + // } + // } /** * Démarre un chronomètre pour vérifier si l'élément est lu @@ -145,7 +145,11 @@ function startReadingTimer(element, text, minReadTime) { if (!readingTimers.has(element)) { let elapsedTime = 0; - let counter = document.createElement("div"); + let counter = null; + + // Créer l'indicateur uniquement si on est en mode debug + if (DEBUG) { + counter = document.createElement("div"); counter.classList.add("reading-counter"); counter.style.position = "absolute"; counter.style.background = "black"; @@ -154,31 +158,33 @@ counter.style.borderRadius = "5px"; counter.style.fontSize = "12px"; counter.style.zIndex = "9999"; - document.body.appendChild(counter); - - let interval = setInterval(() => { + } + + let interval = setInterval(() => { elapsedTime += 1000; // Vérifie si l'utilisateur est actif et si le temps minimum est atteint if (userIsActive && elapsedTime >= minReadTime) { - console.log(`[Stats] Élément lu : ${text}`); + log(`[Stats] Élément lu : ${text}`); readContent.add(text); sendTextToWorker(text); stopReadingTimer(element, text); } - // Mise à jour de la position du compteur + // Mise à jour de la position et du contenu du compteur si en debug + if (DEBUG && counter) { let rect = element.getBoundingClientRect(); counter.style.top = `${rect.top + window.scrollY - 20}px`; counter.style.left = `${rect.left + window.scrollX + rect.width + 10}px`; counter.innerText = `â³ ${Math.floor(elapsedTime / 1000)}s`; - + } }, 1000); - + readingTimers.set(element, { interval, counter, elapsedTime }); + } } - } + /** * Arrête le chronomètre et supprime l'affichage du temps de lecture @@ -187,7 +193,9 @@ if (readingTimers.has(element)) { let { interval, counter } = readingTimers.get(element); clearInterval(interval); + if (counter) { counter.remove(); + } readingTimers.delete(element); } } @@ -197,7 +205,9 @@ function resetAllTimers() { for (let [element, { interval, counter }] of readingTimers) { clearInterval(interval); - counter.remove(); + if (counter) { + counter.remove(); + } } readingTimers.clear(); } @@ -207,8 +217,10 @@ */ function removeReadingIndicator(element) { if (readingTimers.has(element)) { - let { counter } = readingTimers.get(element); - counter.remove(); + let { counter } = readingTimers.get(element); + if (counter) { + counter.remove(); + } } } /** @@ -228,13 +240,13 @@ // Gestion de l'activation/désactivation des statistiques // ───────────────────────────────────────────────────────────────────────────── function startTracking() { - console.log("[Stats] Suivi des statistiques activé."); + log("[Stats] Suivi des statistiques activé."); addViewportBorder(); attachScrollListener(); } function stopTracking() { - console.log("[Stats] Suivi des statistiques désactivé."); + log("[Stats] Suivi des statistiques désactivé."); removeViewportBorder(); detachScrollListener(); } @@ -243,7 +255,7 @@ if (!scrollListenerAttached) { window.addEventListener("scroll", trackVisibleContent); scrollListenerAttached = true; - console.log("[Stats] Écouteur de défilement attaché."); + log("[Stats] Écouteur de défilement attaché."); } } @@ -251,7 +263,7 @@ if (scrollListenerAttached) { window.removeEventListener("scroll", trackVisibleContent); scrollListenerAttached = false; - console.log("[Stats] Écouteur de défilement détaché."); + log("[Stats] Écouteur de défilement détaché."); } } @@ -319,7 +331,7 @@ rect.setAttribute("stroke-width", "0.5"); rect.setAttribute("stroke-dasharray", "200 200"); rect.setAttribute("stroke-dashoffset", "400"); - rect.style.animation = "dashAnimation 8s ease-in-out infinite"; + rect.style.animation = "dashAnimation 6s ease-in-out infinite"; svg.appendChild(rect); document.body.appendChild(svg); diff --git a/src/workers/pyodide_worker.js b/src/workers/pyodide_worker.js index e5f7872..ebd2b0b 100644 --- a/src/workers/pyodide_worker.js +++ b/src/workers/pyodide_worker.js @@ -1,4 +1,5 @@ -console.log("pyodide_worker.js chargé avec succès !"); +importScripts("../utils/logger.js"); +log("pyodide_worker.js chargé avec succès !"); // URL de la version Pyodide la plus récente const LATEST_BASE_URL = "https://cdn.jsdelivr.net/pyodide/v0.27.2/full/"; @@ -24,19 +25,19 @@ let stoplistsReady = new Promise((resolve) => resolve()); // Écouteur des messages reçus du background script self.onmessage = async (event) => { const { command, ...data } = event.data; - console.log("[WebWorker] Message reçu du Background:", command, data); + log("[WebWorker] Message reçu du Background:", command, data); switch (command) { case "pyodide-simplemma": if (pyodideLoaded && simplemmaLoaded) { - console.log("[Worker] Pyodide et Simplemma déjà chargés."); + log("[Worker] Pyodide et Simplemma déjà chargés."); self.postMessage({ type: "pyodide-simplemma", status: "already_loaded", message: "Pyodide et Simplemma déjà en mémoire" }); return; } try { if (!pyodideLoaded) { - console.log("[Worker] Chargement de Pyodide..."); + log("[Worker] Chargement de Pyodide..."); try { importScripts(`${LATEST_BASE_URL}pyodide.js`); } catch (err) { @@ -48,11 +49,11 @@ self.onmessage = async (event) => { await pyodide.loadPackage("lzma"); await pyodide.loadPackage("micropip"); pyodideLoaded = true; - console.log("[Worker] Pyodide chargé avec succès !"); + log("[Worker] Pyodide chargé avec succès !"); } if (!simplemmaLoaded) { - console.log("[Worker] Installation de simplemma..."); + log("[Worker] Installation de simplemma..."); await pyodide.runPythonAsync(` import micropip import asyncio @@ -77,7 +78,7 @@ self.onmessage = async (event) => { await main() `); simplemmaLoaded = true; - console.log("[Worker] Simplemma installé avec succès !"); + log("[Worker] Simplemma installé avec succès !"); } // Envoyer confirmation au background script self.postMessage({ type: "pyodide-simplemma", status: "success", message: "Pyodide et Simplemma chargés" }); @@ -89,12 +90,12 @@ self.onmessage = async (event) => { case "process-text": if (!pyodideLoaded) { - console.log("[Worker] Pyodide non chargé."); + log("[Worker] Pyodide non chargé."); self.postMessage({ type: "process-text", status: "error", message: "Pyodide pas encore chargé" }); return; } - console.log("[Worker] Texte reçu pour analyse :", data.text); + log("[Worker] Texte reçu pour analyse :", data.text); try { const result = await pyodide.runPythonAsync(` import json @@ -145,17 +146,17 @@ self.onmessage = async (event) => { trackedLanguages = data.trackedLanguages; autoAddEnabled = data.autoAdd; isAuthenticated = data.isAuthenticated; - console.log("[Worker] Mise à jour des préférences :", { userThreshold, trackedLanguages, autoAddEnabled, isAuthenticated }); + log("[Worker] Mise à jour des préférences :", { userThreshold, trackedLanguages, autoAddEnabled, isAuthenticated }); break; case "update-lexicons": - userLexicons = data.lexicons; - console.log("[Worker] Lexiques mis à jour :", userLexicons); + userLexicons = JSON.parse(data.lexicons) + log("[Worker] Lexiques mis à jour :", userLexicons); break; case "update-auth-token": authToken = data.accessToken; - console.log("[Worker] Token mis à jour :", authToken ? "Disponible" : "Aucun token reçu"); + log("[Worker] Token mis à jour :", authToken ? "Disponible" : "Aucun token reçu"); break; case "update-stoplist": @@ -164,10 +165,10 @@ self.onmessage = async (event) => { stoplistsByLang = {}; for (const [lang, words] of Object.entries(data.stoplists)) { stoplistsByLang[lang] = new Set(words.map(word => word.toLowerCase().trim())); - console.log(`[Worker] Stoplist mise à jour pour '${lang}' : ${stoplistsByLang[lang].size} mots.`); + log(`[Worker] Stoplist mise à jour pour '${lang}' : ${stoplistsByLang[lang].size} mots.`); } } else { - console.warn("[Worker] âš Stoplists reçues incorrectes ou vides."); + log("[Worker] âš Stoplists reçues incorrectes ou vides."); } resolve(); // Stoplist prête }); @@ -175,7 +176,7 @@ self.onmessage = async (event) => { case "update-include-stopwords": includeStopwords = data.includeStopwords; - console.log(`[Worker] Mise à jour de includeStopwords : ${includeStopwords}`); + log(`[Worker] Mise à jour de includeStopwords : ${includeStopwords}`); break; } }; @@ -189,16 +190,16 @@ async function checkThreshold(lang) { await stoplistsReady; // Attendre que les stoplists soient chargées if (!autoAddEnabled || !isAuthenticated) { - console.log("[Worker] âš Auto-Add désactivé ou utilisateur non connecté."); + log("[Worker] âš Auto-Add désactivé ou utilisateur non connecté."); } else if (!trackedLanguages.includes(lang)) { - console.log(`[Worker] âš La langue '${lang}' n'est pas suivie.`); + log(`[Worker] âš La langue '${lang}' n'est pas suivie.`); } else { - console.log(`[Worker] Vérification des fréquences pour la langue '${lang}'...`); + log(`[Worker] Vérification des fréquences pour la langue '${lang}'...`); const stoplist = stoplistsByLang[lang] || new Set(); const shouldFilterStopwords = stoplist.size > 0 && includeStopwords; - console.log(`[Worker] 📠Stoplist pour '${lang}' : ${shouldFilterStopwords ? "Appliquée" : "Non appliquée"}`); + log(`[Worker] 📠Stoplist pour '${lang}' : ${shouldFilterStopwords ? "Appliquée" : "Non appliquée"}`); const wordsFrequencies = storedFrequencies[lang] || {}; const notifiedSet = new Set(notifiedWords[lang] || []); @@ -209,25 +210,25 @@ async function checkThreshold(lang) { .map(([word]) => word); if (exceededWords.length === 0) { - console.log(`[Worker] Aucun mot dépassant le seuil pour '${lang}'.`); + log(`[Worker] Aucun mot dépassant le seuil pour '${lang}'.`); } else { // Filtrer selon la stoplist si nécessaire const finalWords = shouldFilterStopwords ? exceededWords.filter(word => { const isInStoplist = stoplist.has(word); - if (isInStoplist) console.log(`[Worker] Mot "${word}" exclu (stoplist)`); + if (isInStoplist) log(`[Worker] Mot "${word}" exclu (stoplist)`); return !isInStoplist; }) : exceededWords; if (finalWords.length === 0) { - console.log(`[Worker] Tous les mots dépassant le seuil pour '${lang}' sont dans la stoplist.`); + log(`[Worker] Tous les mots dépassant le seuil pour '${lang}' sont dans la stoplist.`); } else { // Ajouter les mots aux sets et logs notifiedWords[lang] = notifiedSet; finalWords.forEach(word => notifiedSet.add(word)); - console.log("Mots dépassant le seuil :", finalWords); + log("Mots dépassant le seuil :", finalWords); self.postMessage({ type: "threshold-exceeded", wordsAboveThreshold: finalWords }); // Ajout aux mots en attente pour un envoi groupé @@ -245,7 +246,7 @@ async function checkThreshold(lang) { //Traiter les ajouts groupés async function processPendingWords() { - console.log("Traitement des mots à ajouter en lot..."); + log("Traitement des mots à ajouter en lot..."); for (const lang in pendingWords) { const words = pendingWords[lang]; @@ -261,11 +262,11 @@ async function processPendingWords() { async function addWordToLexicon(lang, word) { if (!authToken) { - console.warn("Impossible d'ajouter le mot : Aucun token d’authentification."); + log("Impossible d'ajouter le mot : Aucun token d’authentification."); return; } - console.log(`Tentative d'ajout du mot '${word}' pour la langue '${lang}'`); + log(`Tentative d'ajout du mot '${word}' pour la langue '${lang}'`); const stoplist = stoplistsByLang[lang] || new Set(); // Vérifier si on a une stoplist et si l'utilisateur veut exclure les stopwords @@ -273,7 +274,7 @@ async function addWordToLexicon(lang, word) { // Si le filtrage est activé et que le mot est un stopword, on ne l'ajoute pas if (shouldFilterStopwords && stoplist.has(word)) { - console.log(`Mot '${word}' ignoré car présent dans la stoplist.`); + log(`Mot '${word}' ignoré car présent dans la stoplist.`); return; } @@ -283,12 +284,12 @@ async function addWordToLexicon(lang, word) { .map(lexicon => lexicon.id); if (targetLexicons.length === 0) { - console.warn(`Aucun lexique trouvé pour la langue '${lang}'. Impossible d'ajouter '${word}'.`); + log(`Aucun lexique trouvé pour la langue '${lang}'. Impossible d'ajouter '${word}'.`); return; } try { - console.log(`Envoi de '${word}' aux lexiques ${targetLexicons}...`); + log(`Envoi de '${word}' aux lexiques ${targetLexicons}...`); await AddWord(authToken, word, targetLexicons); // Notifier le background de l'ajout réussi self.postMessage({ type: "word-added", word, language: lang, lexicons: targetLexicons }); @@ -319,7 +320,7 @@ async function AddWord(authToken, selectedWord, lexiconIds, force = false) { target_lex: lexiconIds }; - console.log("Envoi de la requête API AddWord :", body); + log("Envoi de la requête API AddWord :", body); try { const response = await fetch(url, { @@ -335,7 +336,7 @@ async function AddWord(authToken, selectedWord, lexiconIds, force = false) { throw new Error(`Erreur API (${response.status}): ${response.statusText}`); } - console.log(`Mot '${selectedWord}' ajouté avec succès aux lexiques ${lexiconIds}`); + log(`Mot '${selectedWord}' ajouté avec succès aux lexiques ${lexiconIds}`); return await response.json(); } catch (error) { console.error(`Erreur lors de l'ajout du mot '${selectedWord}':`, error); -- GitLab